Custom Rules & Meta-Loop¶
The meta-loop (src/synthorg/meta/) is SynthOrg's reflective layer: it aggregates
company-wide signals into an OrgSignalSnapshot, evaluates rules over that snapshot,
and proposes adaptations through the evolution pipeline. Rules let operators encode
local invariants (e.g. "react when the org success rate drops below a threshold")
without forking the core. Rules only PROPOSE; every adaptation still passes through
the proposal guards and human approval.
Off by default, and nothing here runs unattended
self_improvement.enabled ships false, so no snapshot is aggregated and no
rule is evaluated until an operator turns the loop on. Every strategy toggle
beneath it is off too, apart from config tuning, which has no effect while the
master switch is off. The approval gate is not a phase the loop graduates from:
a proposal reaches a human whether or not a rule fired, and no live end-to-end
run of this loop has happened.
Concepts¶
SignalRule: a class implementing theSignalRuleprotocol. It inspects anOrgSignalSnapshotand returns aRuleMatchwhen its pattern is detected, orNoneotherwise.RuleMatch: carriesrule_name, aRuleSeverity(info/warning/critical), a human-readabledescription, asignal_contextdict, and thesuggested_altitudes(which improvement strategies should generate proposals).- Meta-loop step: one signal-aggregation -> rule fan-out -> match collection -> optional evolution proposal.
SignalRule contract¶
from synthorg.core.types import NotBlankStr
from synthorg.meta.models import (
OrgSignalSnapshot,
ProposalAltitude,
RuleMatch,
RuleSeverity,
)
from synthorg.meta.protocol import SignalRule
class HighRetryRule:
"""Fires when the org-wide error count exceeds a threshold."""
def __init__(self, *, threshold: int = 10) -> None:
self._threshold = threshold
@property
def name(self) -> NotBlankStr:
return NotBlankStr("high_retry")
@property
def target_altitudes(self) -> tuple[ProposalAltitude, ...]:
return (ProposalAltitude.CONFIG_TUNING,)
def evaluate(self, snapshot: OrgSignalSnapshot) -> RuleMatch | None:
errors = snapshot.errors
if errors.total_findings <= self._threshold:
return None
return RuleMatch(
rule_name=self.name,
severity=RuleSeverity.WARNING,
description=f"{errors.total_findings} errors exceed threshold {self._threshold}",
signal_context={"total_findings": errors.total_findings},
suggested_altitudes=self.target_altitudes,
)
evaluate is synchronous and pure: it reads the snapshot and returns a match or
None. A structural check (isinstance(rule, SignalRule)) holds because the protocol
is @runtime_checkable.
Built-in rules¶
default_rules() in src/synthorg/meta/rules/builtin.py wires the shipped set:
QualityDecliningRule, SuccessRateDropRule, BudgetOverrunRule,
CoordinationCostRatioRule, CoordinationOverheadRule, StragglerBottleneckRule,
RedundancyRule, ErrorSpikeRule, and BenchmarkRegressionRule (the last defined
in the sibling benchmark_rule.py). Add a new code-level rule by implementing the
protocol and including it in default_rules().
Custom declarative rules (dashboard)¶
Operators author rules at runtime without code through the dashboard rather than via
YAML. A CustomRuleDefinition is stored through the CustomRuleController at
/api/v1/meta/custom-rules (list, get, create, update, delete, toggle, plus
/metrics for the available metric paths and /preview for a dry run) and compiled
into a DeclarativeRule (src/synthorg/meta/rules/custom.py) that implements the
same SignalRule protocol. Each definition carries a name, a metric_path (a
dot-notation path into OrgSignalSnapshot, validated against METRIC_REGISTRY), a
Comparator, a numeric threshold, a RuleSeverity, and the target_altitudes:
from synthorg.meta.rules.custom import DeclarativeRule
# definition: a CustomRuleDefinition loaded from the custom-rule store
rule: DeclarativeRule = DeclarativeRule(definition)
match = rule.evaluate(snapshot) # RuleMatch | None, same protocol as built-in rules
Severity orders the fan-out and nothing else: RuleEngine returns matched rules
sorted critical-first, so the most serious signal leads the context handed to the
improvement strategies. It is not a veto. A critical match does not block a
proposal, because every proposal is already blocked on the approval gate.
Worked example: unit-test a rule¶
import pytest
from synthorg.meta.models import RuleSeverity
@pytest.mark.unit
def test_high_retry_fires_above_threshold(org_signal_snapshot_factory) -> None:
snapshot = org_signal_snapshot_factory(total_findings=25)
match = HighRetryRule(threshold=10).evaluate(snapshot)
assert match is not None
assert match.severity is RuleSeverity.WARNING
assert match.signal_context["total_findings"] == 25
@pytest.mark.unit
def test_high_retry_silent_below_threshold(org_signal_snapshot_factory) -> None:
snapshot = org_signal_snapshot_factory(total_findings=3)
assert HighRetryRule(threshold=10).evaluate(snapshot) is None
Where this fits¶
A firing rule does NOT itself mutate the system: it returns a RuleMatch that the
meta-loop aggregates and feeds to the improvement strategies, proposal guards, and
human approval. For the broader meta-loop and self-improvement architecture, see
docs/design/self-improvement.md.