Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,35 @@ keepalive / orchestrator_local / orchestrator_remote), so run history says a cap
OVERALL and cannot say which surface passed it over. That is why signal 1 exists and why a promotion
is never derived from run history alone.

### Where layer 2's evidence comes from

Layer 2 needs resolved trials, and until 2026-08-22 nothing produced any: `advise()` recorded the
`match` edge, and the `invocation`/`outcome` edges had no production caller at all, so every
propensity was the prior and the cadence step said so on every run. **The tick is now that
producer** (`capability_propensity.py tick-evidence`, every tick, below the heartbeat export and
below the four steps it grades) — chosen because it is the highest-volume unattended surface, so
coverage accrues hourly with no further attention.

An observer's verdict is **not** a delivery verdict — a cadence report can never merge a PR, and
demanding one is the category error that parked eight capabilities in a measurement gap they could
not leave. For the tick-bound capabilities that `capabilities.is_observer()` confirms, *helped* means
**its report's finding set changed since its own previous run**: a defect newly reported, a
regression flagged, a switch verdict that moved, a finding resolved. Re-emitting an identical finding
set is *not* useful, and an empty set that stays empty is explicitly not useful — silence is not
usefulness. Capabilities the observer test does not confirm record that they ran and get no verdict,
because averaging an output-change question with a delivery question would violate the never-average
rule two paragraphs up.

The bounding is a correctness requirement, not a nicety: 24 ticks a day over four bound capabilities
is 96 potential data points, and a verdict written on every run would make the ranking measure the
cadence. Two independent bounds — the experiment id is scoped to the UTC day, so the ledger's
idempotency keys admit at most one verdict per capability per day whatever happens; and a verdict
additionally requires that capability's own cadence artifact to have been regenerated since the last
evaluation, which ties one verdict to one production and bounds the graded rate to ~1.3/day. The
finding projection keeps identity and verdict fields only, because `overdue`'s `silent_days` rises
daily on its own and hashing a row whole would score the monitor useful on every run it will ever
make.

**Demotion is the drain.** Bindings that could only grow end with every surface holding all 43 —
the exact condition binding prevents. A capability bound to a surface that never triggers it across
`DEMOTION_MIN_TRIALS` resolved experiments is proposed for removal.
Expand Down
21 changes: 20 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,23 @@ safety switch, not dead code.
into ledger outcome heartbeats, so a capability records not just that it RAN but how the work
turned out. Its `run_tagged` resolver was dead code until 2026-08-21 (it read a column that does
not exist), and its edge repairs now run before the heartbeat pass rather than a cycle behind it.
- **The tick consults the front door, and records whether a capability helped**
(`capability_propensity.py tick-evidence`, every tick, below `ORCH-ANCHOR: heartbeat-export` and
below the four steps it grades). `capability_advisor.advise()` and the `invocation`/`outcome`
recording edges both existed and had no production caller, which is why the propensity report
printed PRIOR-ONLY on every run: a measurement with no producer. For the four capabilities bound
to the `tick` surface it now consults the advisor and, for the ones
`capabilities.is_observer()` confirms, records an **output-change verdict**: an observer HELPED
when its report's finding set changed since its own previous run (a defect newly reported, a
regression flagged, a switch verdict that moved, a finding resolved) and did NOT help when it
re-emitted an identical set — silence is not usefulness. Never a delivery verdict, which a report
can never earn. Two independent bounds stop 24 runs/day becoming 96 unearned data points: the
experiment id is scoped to the UTC day (so the ledger idempotency keys admit at most one verdict
per capability per day), and a verdict additionally requires that capability's own cadence
artifact to have been regenerated, which bounds the graded rate to ~1.3/day. The finding
projection keeps identity and verdict fields only — `overdue`'s `silent_days` rises daily on its
own, and hashing a row whole would score the monitor "useful" on every run it will ever make.
Kill switch: `ORCH_TICK_EVIDENCE_DISABLED=1`, or `ORCH_DISABLE_STEPS=tick-capability-evidence`.
- **`gate_blocks_execution`** — an opt-in capability declaration for the case where a switch blocks
the code path that would produce an outcome (Thompson never chooses while the mode is
epsilon-greedy; range-lane's heartbeats sit on the live-apply branch; issue-readiness's label
Expand Down Expand Up @@ -264,7 +281,9 @@ safety switch, not dead code.
typo cannot leave a step running while you believe it is off. Unset/empty disables nothing.
- **Other kill switches** — `ORCH_OFFLOAD_DISABLED=1` refuses at the top of `dispatcher.offload`
before any spend; `ORCH_REPO_PLAYBOOK=0` stops playbook injection into delegation prompts on the
next dispatch without editing the registry.
next dispatch without editing the registry; `ORCH_TICK_EVIDENCE_DISABLED=1` makes the tick's
capability consult/verdict step inert from any caller (no consult, no ledger event, no state
file), which is the module-side twin of `ORCH_DISABLE_STEPS=tick-capability-evidence`.
- **Daily compiler cadence** — the active tick atomically publishes completion-event JSONL plus
pattern-miner status/inventory artifacts. Empty output is a healthy “no eligible history yet”
result, not a reason to seed synthetic data.
Expand Down
18 changes: 18 additions & 0 deletions cadence_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,24 @@
"measurement when callers record trigger/usefulness against the "
"advice:<digest> they were given",
},
{
# EVERY TICK, and deliberately stampless (like capability-lifecycle): the step is a cheap
# advisory consult plus an idempotent verdict, and its own bounding comes from the artifact
# freshness of the capabilities it grades, not from a cadence stamp. A stamp here would only
# add a second, drifting notion of "due".
"key": "tick-capability-evidence",
"success_stamp": None,
"cadence_days": 0,
"artifact": "tick-capability-evidence.json",
"log": "tick-capability-evidence.log",
"gate": "ORCH_TICK_EVIDENCE_DISABLED=1 or ORCH_DISABLE_STEPS=tick-capability-evidence "
"makes it inert; a verdict additionally requires the graded capability's own "
"cadence artifact to have been regenerated since the last evaluation",
"next_transition": "records at most one verdict per bound capability per UTC day; while "
"`gradable` is non-zero and `verdicts_recorded` is 0 the step is waiting "
"on those capabilities' own cadences, and a `gradable` of 0 is a "
"deadlock rather than patience",
},
{
"key": "capability-firing-monitor",
"success_stamp": ".last-capability-firing-monitor",
Expand Down
42 changes: 41 additions & 1 deletion capability_advisor.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ def advise(text: str, *, repository: str = "", lane: str = "opener", skill: str
entries = capability_propensity.rank(entries, path=path)
except Exception: # noqa: BLE001
pass
return {
result = {
"task": text, "experiment_id": experiment_id(text),
"useful": True, "confidence": "binding_only", "skill": skill or None,
"surface": (surface or skill) or None, "repository": repository,
Expand All @@ -271,6 +271,19 @@ def advise(text: str, *, repository: str = "", lane: str = "opener", skill: str
f"DECLARED for surface {surface or skill!r} and apply regardless of "
f"classification"),
}
if record and entries:
# A BINDING-ONLY ANSWER IS STILL AN OBSERVATION. This branch used to return real
# capabilities with `useful: true` and record NOTHING, so the fix that made a
# declared binding survive a classification miss covered the ANSWER and not the
# EVIDENCE. The consequence is a latched gate one layer down: a surface whose words
# never hit the keyword vocabulary — the tick's cadence pass is exactly that — could
# never accumulate a candidate set, so `capability_propensity.experiments()` saw
# triggers and outcomes belonging to trials with zero candidates, no control arm and
# no attributable skill. Found 2026-08-22 while wiring the tick: the trial existed
# and was unreadable. Same call as the classified branch, same idempotency, so a
# repeated identical question still does not inflate anything.
result["recorded_matches"] = _record_matches(result, skill=skill, path=path)
return result
return {
"task": text, "experiment_id": experiment_id(text),
"useful": False, "confidence": "none", "skill": skill or None,
Expand Down Expand Up @@ -1093,6 +1106,33 @@ def _selftest_bindings() -> None:
bare = advise("xyzzy plugh frobnicate", path=ledger, record=False)
assert bare["capabilities"] == [], bare

# 1b. AND IT MUST RECORD. A binding-only answer that records nothing is a latched gate
# one layer down: the surface gets its capabilities, but `capability_propensity` sees a
# trial with no candidates, so it has no control arm, no attributable skill, and no
# denominator for a trigger rate. Measured 2026-08-22 while wiring the tick — whose
# cadence text classifies as nothing, so it takes this branch on EVERY consult.
# Asserted through the LEDGER, which is what a downstream reader actually sees.
rec = advise("plugh xyzzy nothing classifies here", surface="t-surface", skill="t-surface",
path=ledger)
assert rec["confidence"] == "binding_only", rec["confidence"]
assert rec.get("recorded_matches") == 2, rec.get("recorded_matches")
import capability_propensity as _prop
trial = next(t for t in _prop.experiments(path=ledger)
if t["experiment_id"] == rec["experiment_id"])
assert sorted(trial["candidates"]) == ["bound-a", "bound-b"], trial
assert trial["skills"] == ["t-surface"], trial
# ...and the same question again must not inflate the count.
again = advise("plugh xyzzy nothing classifies here", surface="t-surface",
skill="t-surface", path=ledger)
assert again.get("recorded_matches") == 0, again.get("recorded_matches")
# record=False stays a pure query on this branch too.
pure = advise("plugh xyzzy nothing classifies here either", surface="t-surface",
path=ledger, record=False)
assert "recorded_matches" not in pure, pure
assert not any(t["experiment_id"] == pure["experiment_id"]
for t in _prop.experiments(path=ledger)), \
"record=False wrote a trial on the binding-only branch"

# 2. NEVER CONCEAL. A classifying task must still return the unbound match, ranked after
# the bound ones -- a hidden capability can never earn the evidence that would bind it.
hit = advise("add unit tests for the retry helper", surface="t-surface", path=ledger,
Expand Down
Loading