LoopX has accumulated many user / agent / state interaction lessons. The state interaction model explains the architecture; this catalog records the repeatable situations we want every controller, heartbeat, dashboard, and benchmark runner to handle the same way.
Use this document when a good case, bad case, incident, or product insight reveals a reusable interaction shape. Each pattern should be specific enough to drive implementation, tests, and dashboard copy without requiring future agents to mine chat history.
For the short product map that keeps the catalog, state definitions, and state
machine aligned, see
docs/product/core-control-plane/. The catalog
below remains the detailed IP registry; the core map is the graph lens that
connects those IPs to runtime states and legal transitions.
Each pattern should answer:
- Trigger: which status, quota, todo, run-history, or boundary signals make the pattern active;
- Importance:
P0for hot-path behaviors that can block or misroute a controller turn,P1for durable operational behaviors, andP2for specialized or experiment-specific behaviors; - User channel: whether the user must be interrupted, only notified, or not contacted;
- Agent channel: what Codex must do, may do, or must not do;
- State contract: durable fields that prove the pattern is represented;
- Bad smell: how the system usually fails when the pattern is missing;
- Visual model: one Mermaid diagram, state table, or decision tree that can be used in product explanation;
- Validation: smoke, fixture, or doc check that protects the behavior.
Keep examples public-safe. Do not copy raw benchmark tasks, raw trajectories, private logs, verifier output tails, credentials, internal URLs, or local machine paths.
The catalog is for reusable user / agent / state interaction shapes. Do not add a new IP merely because a maintainer needs a validation technique, smoke group, release checklist, dashboard card, or rollout procedure. Those are uses of the catalog, not catalog patterns by themselves.
When a new repo behavior appears, update the catalog at the lowest durable level:
- if the behavior is already covered by an IP, add the new smoke, fixture, protocol doc, or visual explanation to that IP's validation or details;
- if several existing IPs must be checked together, document the validation bundle in the relevant release/readiness or workflow doc and point back to those existing IPs;
- only allocate a new IP when the behavior itself is a repeatable interaction with its own trigger, user channel, agent channel, state contract, bad smell, and validation.
Canary and readiness groups should therefore be catalog-informed rather than catalog-expanding by default. A canary may sample Work Routing, State And Boundary, Evidence Lifecycle, Human Decision, and Planning Governance patterns, but it should not become a standalone IP unless the canary behavior itself is a runtime/state interaction that future controllers must route.
Catalog patterns are the vocabulary for designing many canary profiles, not one fixed canary. Start from the changed surface, identify the pattern families it can regress, then choose the smallest profile that exercises those patterns with public-safe fixtures. A good canary explains why each check is present and what kind of failure it diagnoses.
Use these archetypes as the reusable selection layer:
| Canary Archetype | Primary Question | Typical Trigger Surface | Fixture Depth | Cost Tier | Failure Usually Means |
|---|---|---|---|---|---|
| Hot-path route canary | Will the next agent turn route, fallback, monitor, or recover correctly? | quota should-run, status, review packet, heartbeat prompt, scheduler hint |
synthetic active state plus compact run-history fixtures | cheap | a controller may spend, stop, notify, or select a todo incorrectly |
| Scoped decision canary | Are user gates, reward, approvals, and deferred resumes concrete and scoped? | user todo projection, operator gate, reward, decision-scope schema | fixture state plus dry-run decision append/preview | cheap to medium | the user may see a vague ask, or an agent may treat a scoped gate as global |
| Projection and boundary canary | Does compact state match todos, claims, authority, scopes, leases, and public/private boundaries? | active-state parser, todo lifecycle, task graph, connector policy, loopx check |
fixture state plus boundary scan; no private source bodies | cheap to medium | dashboard/status may explain the wrong blocker or grant the wrong write/read scope |
| Evidence lifecycle canary | Can external evidence become countable without raw logs, task text, trajectories, or verifier tails? | benchmark adapter, CI handle, public PR metadata, compact evidence reducer | compact synthetic or public-handle fixture; no real benchmark launch by default | medium | evidence may be invisible, over-counted, or leak private/raw material |
| Planning governance canary | Does replan, repair, cadence, and plan-to-todo writeback change the machine-visible frontier? | autonomous replan, repair delta, dreaming, cadence hints, slash-command planning | synthetic stalled history plus todo/writeback fixtures | cheap | the system may loop on advisory prose without changing executable state |
| Product/readiness canary | Can a promoted product surface explain and render the relevant projections? | dashboard/frontstage/release/readiness surface | route or render fixture; browser only when visual behavior is promoted | medium to deep | the operator UI may look healthy while hiding broken route, gate, or evidence semantics |
Map P0/P1 catalog rows to canary archetypes before picking commands:
| Family | P0/P1 Pattern Coverage | Default Canary Archetypes | Trigger Surfaces | Minimum Useful Fixture | Failure Meaning |
|---|---|---|---|---|---|
| Work Routing | IP-001, IP-002, IP-003, IP-007, IP-008, IP-021, IP-029 | Hot-path route canary; Planning governance canary when cadence or repair is involved | quota should-run, interaction_contract, work_lane_contract, scheduler hint, handoff todo state |
one eligible delivery fixture, one blocked/fallback fixture, one quiet or monitor fixture | agent turn routing is unsafe: it may spend, wait, notify, or choose fallback incorrectly |
| Human Decision | IP-004, IP-014, IP-017, IP-027 | Scoped decision canary; Product/readiness canary when first-screen human copy changes | user todos, decision scope, operator-gate/reward preview, deferred resume candidates | one concrete user ask, one scoped non-blocking gate, one preview-or-append dry run | humans may be asked the wrong question, or an agent may continue without the needed decision |
| State And Boundary | IP-005, IP-006, IP-011, IP-016, IP-019, IP-020, IP-022, IP-023, IP-025, IP-026, IP-028 | Projection and boundary canary; Hot-path route canary when the projection feeds quota/status | active state, todo metadata, task graph, authority source, claim lease, connector runtime policy, public/private scan | fixture state plus structured projection check; boundary scan for touched public files | compact state and executable truth diverge, so dashboards and agents may trust stale or unsafe authority |
| Evidence Lifecycle | IP-012, IP-015 | Evidence lifecycle canary; Product/readiness canary when evidence is rendered | external handle observation, benchmark lifecycle reducer, compact result projection | compact public-safe evidence fixture with raw-material exclusion assertions | progress evidence may be missing, double-counted, or represented with unsafe raw material |
| Planning Governance | IP-010, IP-013, IP-018, IP-024 | Planning governance canary; Hot-path route canary when cadence changes affect execution | stalled run history, autonomous replan obligation, repair delta, cadence hint, plan-to-todo writeback | two-turn stalled fixture plus repair/writeback delta assertion | the agent may keep planning in prose while the machine-visible frontier stays unchanged |
P2 patterns may still have canaries, but they should be selected by an explicit domain profile instead of being pulled into every default profile. If a P2 pattern becomes hot-path for a shipped surface, promote the behavior or add a small profile-specific canary with an explicit owner and non-applicability note for other profiles.
When a PR touches several families, compose the smallest set of archetypes that covers the touched surfaces. Do not build a giant "everything canary" by default. Prefer a cheap route/projection profile for ordinary runtime changes, add evidence lifecycle checks for benchmark or external-handle changes, and add browser or deep integration only when a visual or end-to-end surface is being promoted.
Use this selection order for ordinary PR, release, and refactor review:
- Start from changed files and touched surfaces, not from the PR title.
- Map those surfaces to catalog families and choose the cheapest archetype that can catch the likely regression.
- Add one current-repo domain profile only when the surface has a known product route, such as PR review, release promotion, monitor scheduling, control-plane refactor, state-write correctness, frontstage rollout, or benchmark adapter readiness.
- Keep default profiles on fixture-level or dry-run checks. Pull in deep, browser, external, or writeback checks only when the PR promotes that exact surface or the owner explicitly asks for promotion readiness.
- When hot-path and cold-path surfaces both changed, keep the hot-path canary small enough to prove route/spend/notify behavior, then add a cold-path detail check for the expanded inspector or review surface.
Concrete examples:
| Review Situation | Selector Shape | Default Profile Choice | Add Deep Checks Only When |
|---|---|---|---|
| PR review or self-merge workflow | changed files under loopx/pr_review.py, skills/loopx-pr-review/, GitHub public probe, or PR merge policy docs |
PR review / merge domain profile plus Human Decision or Evidence Lifecycle when public-handle evidence changed | the PR changes posting, approval, merge, or external GitHub write behavior |
| Release or install promotion | release-readiness docs, installer/update/wrapper code, loopx doctor, loopx update, or canary wrapper behavior |
release-promotion domain profile plus Work Routing and State And Boundary checks | promoting a real release snapshot, app wrapper, dashboard, or writeback evidence |
| Control-plane refactor | loopx/quota.py, loopx/status.py, scheduler policy, todo projection, or review-packet route changes |
control-plane-refactor domain profile plus Work Routing and State And Boundary checks | moving a broad policy seam, changing public JSON fields, or touching monitor/scheduler writeback |
Existing-contract-first rule: canary planning should consume current public
runtime/status surfaces before proposing a new runtime contract. Prefer
quota should-run, status, review-packet, loopx check, current smoke
fixtures, loopx canary plan output, and fixture-level loopx canary run
checks as the first evidence layer. loopx canary run must stay no-write by
default: it may execute selected repository-local fixture checks, but it should
not write promotion evidence, create runtime contracts, poll external targets,
or run deep/browser checks unless that deeper profile is explicitly selected. A
new runtime contract is justified only when these surfaces cannot represent a
repeatable interaction that future controllers must route. In that case, stop at
a review packet first: name the minimal missing behavior, list the existing
contract alternatives rejected, state compatibility and migration risk, and
propose focused smokes for owner review before implementation.
User gates are not global booleans. The first-class model is a scoped decision:
the machine-facing schema is
decision_scope_v0.
Compatibility todo metadata follows the same rule. In a multi-agent goal, an
open user_gate todo must carry blocks_agent=<registered-agent> when only
one lane is waiting, or global_gate=true when the decision intentionally
blocks every registered agent. An unscoped multi-agent user_gate is a
projection bug, not a safe fallback to a global operator gate.
- a decision/gate names the authority still needed, such as a private material read, resource spend, write boundary, production action, public submission, or product-direction choice;
- an agent action names the authority it depends on and the effect it will produce, such as read-only analysis, local code edit, external run, private source sync, or dashboard write;
- the controller compares the two as a scope relation:
gate covers action,gate does not cover action, orscope is ambiguous.
This keeps the product behavior simple:
| Relation | User Channel | Agent Channel |
|---|---|---|
| gate covers selected action and no independent fallback exists | ask concrete user todo | stop gated delivery; no spend |
| gate covers selected action but an independent fallback exists | notify concrete user todo | execute fallback, validate, write back, spend once |
| gate does not cover selected action | keep gate visible if useful | execute selected action normally |
| scope is ambiguous | ask/repair projection | do not infer permission from prose |
The durable target schema should make this relation explicit rather than relying on prompt memory or text matching:
user_todo.decision_scope = {
kind: private_read | write_scope | resource | production | public_claim | direction,
granularity: action | lane | goal | project | global,
scope_key: "...",
expires_at?: "..."
}
agent_todo.required_decision_scopes[] = [...]
agent_todo.required_write_scopes[] = [...]
agent_todo.safety_class = read_only | local_write | external_run | protected_write
Compatibility inference from action_kind, title, or text is allowed only as a
transition layer. If explicit scope is missing and inference is not confident,
the correct behavior is projection repair or a user/controller gate, not a
silent fallback.
Markdown text inference is a lint, not the gate truth. In the hot path,
quota should-run should prefer structured fields such as task_class,
decision_scope, required_decision_scopes, safety_class,
user_todo_summary, and interaction_contract. Free-text parsing of
Next Action exists only to catch legacy states where a human-readable wait or
executable action was never projected into a todo. It must not override a
current interaction_contract.user_channel.action_required=false plus an open
agent todo. LLM-assisted interpretation belongs in a cold-path proposal or
authoring helper; it may suggest converting prose into a structured todo, but
must not decide delivery gates, spend policy, or write permission directly.
The IP IDs below are stable interaction patterns. Do not create a new IP only because a dashboard, operator-management workflow, or human-in-the-loop review needs extra labels. Prefer optional overlays that attach to an existing quota, status, review-packet, run, or catalog pattern.
These overlays are descriptive and analytic. They must not replace
interaction_contract, todo metadata, goal_boundary, or run-bound reward
events as the execution source of truth.
Use this overlay when a pattern needs to say who decides, who acts, and who checks the result without changing the underlying IP.
human_ai_role_contract_v0 = {
applies_to: quota_payload | status_card | review_packet | catalog_pattern | run,
human_role: owner | reviewer | operator | evaluator | none,
ai_role: executor | observer | drafter | verifier | router,
decision_owner: human | ai | shared | external,
validation_owner: human | ai | tool | external,
handoff_contract?: "what must be true before the other party acts",
forbidden_substitution?: ["things the AI must not decide for the human"]
}
Use this overlay to measure operating load and quality around an interaction without changing routing semantics.
ops_metric_overlay_v0 = {
operator_interruptions: integer,
concrete_user_todo_count: integer,
agent_delivery_attempts: integer,
quiet_noop_count: integer,
repair_delta_count: integer,
blocked_minutes?: number,
evidence_quality?: missing | surface | compact | verifier_backed,
notes?: "public-safe compact summary"
}
Use this overlay when a bad case should classify why a handoff or escalation failed. It is an incident-analysis label, not a permission check.
escalation_failure_type_v0 =
missing_concrete_user_todo
| wrong_agent_blocked
| stale_next_action
| hidden_blocked_priority
| no_repair_delta
| scope_projection_gap
| capability_bridge_missing
| private_boundary_ambiguous
| external_evidence_missing
The overlays should stay optional until a UI or controller path consumes them. When a runtime starts depending on one, add a focused smoke and update the relevant IP's validation list instead of renumbering the catalog.
Use families as the first routing layer. They keep the catalog readable as the number of patterns grows, and they make it easier for skills, dashboards, and benchmark reviews to ask for "the relevant interaction family" instead of scanning every IP.
| Family | Purpose | Start Here When |
|---|---|---|
| Work Routing | Decide whether the agent should deliver, fallback, recover, or stay quiet. | quota/status says work may run, but the next action is blocked, monitor-only, or outcome-thin |
| Human Decision | Represent user, owner, simulator, reward, approval, and deferred-resume moments without hiding the exact ask. | a user action, correction, approval, deferred gate, or run-bound judgment changes what the agent should do |
| State And Boundary | Keep compact control-plane truth aligned with todos, scopes, leases, authority, and write boundaries. | state says one thing but todos, permissions, or authority sources imply another |
| Evidence Lifecycle | Make external evidence and benchmark work countable without copying raw logs or task data. | a benchmark, CI, model run, or external handle must advance through observable lifecycle states |
| Planning Governance | Control replanning, dreaming, cadence, and future-work writeback without turning chat into state. | the agent is planning, widening work, proposing future routes, or publishing top todos |
Catalog rows are grouped by family and sorted by importance inside each family.
P0 means hot-path behavior that can block or misroute a controller turn;
P1 means durable operational behavior that should be preserved once the hot
path is healthy; P2 means specialized or experiment-specific behavior.
IP IDs remain stable even when display order changes.
Hot-path execution decisions: deliver, fallback, recover, or stay quiet.
| Importance | ID | Name | Primary Owner | User Channel | Agent Channel |
|---|---|---|---|---|---|
| P0 | IP-001 | Bounded Delivery | Agent | no interruption | implement, validate, write back, spend once |
| P0 | IP-002 | Blocked Priority With Safe Fallback | Agent plus user-visible notification | notify without requiring an answer | continue safe fallback after exposing blocked higher-priority work |
| P0 | IP-003 | Scoped Gate With Safe Fallback | User plus agent | notify concrete scoped gate | execute non-dependent fallback; no gated action |
| P0 | IP-029 | Handoff Todo Gate State | Status/quota | no interruption unless the handoff itself is user-held | map blocks_agent todo lifecycle into wait, successor replan, or concrete successor routing |
| P0 | IP-021 | Per-Todo Capability Gate | CLI projects, agent decides | ask only when missing capability is owner-held | expose runnable executable candidates; agent chooses one, otherwise repair bridge or skip |
| P0 | IP-007 | Outcome Floor Recovery | Agent | usually no interruption | produce missing outcome-scale evidence or blocker only |
| P1 | IP-008 | Monitor Quiet Skip | CLI/controller | no notification | append at most one no-spend poll, then stay quiet |
Human asks, approvals, interventions, and reward-derived lessons.
| Importance | ID | Name | Primary Owner | User Channel | Agent Channel |
|---|---|---|---|---|---|
| P0 | IP-004 | Concrete User Todo Projection | User | ask or notify with concrete todo/question | do not hide behind generic "owner gate" text |
| P0 | IP-027 | Deferred Gate Resume | Status/quota plus controller | notify only when the resume gate is still user-held | keep deferred work visible after open lanes; when ready, require lifecycle replan instead of no-candidate wait |
| P0 | IP-014 | Decision Write Preview And Append | User/operator | explicit preview/apply decision | append only exact run-bound reward or gate decision event |
| P1 | IP-017 | User Reward Lesson Promotion | User plus LoopX | acknowledge only when lesson changes route/priority/boundary | promote correction into durable lesson, todo, or projection before continuing |
| P2 | IP-009 | Active User Assistance | User simulator / operator | bounded intervention | inject audited user help without leaking reward/oracle signals |
Projection, authority, write scope, and lease integrity.
| Importance | ID | Name | Primary Owner | User Channel | Agent Channel |
|---|---|---|---|---|---|
| P0 | IP-005 | State Projection Gap | Agent | no user ask unless a user todo is missing | repair todo/state projection before ordinary delivery |
| P0 | IP-006 | Checkpointed Scope Mismatch | CLI/controller | ask or repair boundary projection | do not execute action whose write scope is not projected |
| P0 | IP-026 | Agent-Scoped No-Candidate Gap | Status/quota | no interruption | project scope exhaustion or agent-scope wait instead of forcing delivery |
| P1 | IP-011 | Authority Material Intake | Agent plus registry | notify only on gate/conflict | register redacted source contract before relying on material |
| P1 | IP-016 | Task Lease Claim | Controller/agent | no interruption unless conflict requires decision | claim bounded work with TTL, write scope, and conflict policy |
| P1 | IP-019 | Side-Agent Scoped Continuation | Primary plus side agent | no interruption unless scope/review is ambiguous | side agent claims scoped todo, uses independent worktree, then self-merges small validated work or hands review to primary |
| P1 | IP-020 | Todo Claim / Supersede / Successor Lifecycle | Agent plus controller | no interruption unless successor is a user todo or conflict needs decision | claim before delivery; supersede stale work; complete slices with successor or no-follow-up rationale |
| P1 | IP-022 | Claimed Todo Visibility And Agent-Lane Next Action | Status/quota/frontstage | no interruption | keep scheduler candidates separate from claimed-work visibility lanes and expose the current agent's slice |
| P1 | IP-023 | Status Neutral Run Window | Status/quota/history | no interruption | ignore neutral run noise for state authority while retaining it as stall evidence |
| P1 | IP-025 | Experimental Diagnostic Sidecar Boundary | Runtime/protocol owners | no interruption unless an opt-in proof asks for user action | keep proof/debug verdicts as sidecar diagnostics until a product-general schema is validated |
| P1 | IP-028 | Connector Runtime Boundary | Connector/runtime owners | notify only if the required owner decision is missing | enforce runtime allow/deny policy before browser or API connector reads can autoload raw material |
External handles, benchmark transitions, and countable proof.
| Importance | ID | Name | Primary Owner | User Channel | Agent Channel |
|---|---|---|---|---|---|
| P0 | IP-012 | External Evidence Observation | Agent/controller | no interruption unless handle missing needs owner input | observe compact handles/results; do not launch benchmark/model work |
| P1 | IP-015 | Benchmark Lifecycle Countability | Benchmark adapter/controller | no interruption by default | advance only through compact countable lifecycle gates |
Replanning, dreaming, cadence, and future-work writeback.
| Importance | ID | Name | Primary Owner | User Channel | Agent Channel |
|---|---|---|---|---|---|
| P0 | IP-013 | Autonomous Replan Vs Advisory Dreaming | Agent/controller plus user when promoted | ask only for promotion/decision | repair stalled delivery; keep dreaming proposal non-executable |
| P1 | IP-024 | Repair Delta Contract | Agent/controller | no interruption unless repair creates a user todo | self-repair/replan must change the machine-visible frontier or record a no-op/blocker |
| P1 | IP-010 | Cadence Hint | Agent/controller | no interruption by default | surface a low-confidence hint when turns look too thin |
| P1 | IP-018 | Plan To Todo Writeback | Agent plus LoopX | no interruption unless a user todo is created | write user-facing plans into todos, Next Action, or refresh-state |
The catalog should support partner and user-facing explanation, not only implementation. Keep diagrams public-safe and generic. Prefer diagrams that show actor boundaries, decision ownership, and fallback behavior without raw project or benchmark evidence.
The runtime state machine is the most compact way to explain why LoopX may run, wait for a user, wait for evidence, repair a stale projection, or stay quiet without spending quota.
stateDiagram-v2
[*] --> Registered
Registered --> Ready: registry + active_state loaded
Ready --> QuotaCheck: heartbeat / manual tick
QuotaCheck --> UserGate: requires human decision
QuotaCheck --> AwaitEvidence: external handle not terminal
QuotaCheck --> Running: eligible + runnable todo
QuotaCheck --> QuietNoop: no runnable scoped candidate after audit
QuotaCheck --> Repair: stale projection / boundary drift
Running --> Writeback: artifact + validation
Running --> Repair: contract drift / failed invariant
AwaitEvidence --> Ready: terminal evidence or blocker written
UserGate --> Ready: owner decision recorded
Writeback --> Ready: refresh-state + spend
Writeback --> Done: objective terminal
Repair --> Ready: projection repaired or blocker written
QuietNoop --> Ready: no spend
Done --> [*]
The smallest reusable routing diagram then shows how the quota guard projects
those lifecycle states into an interaction_contract for the current tick:
flowchart LR
Q["quota should-run"] --> C{"interaction_contract.mode"}
C -->|"bounded_delivery"| A["Agent implements, validates, writes back"]
C -->|"user_gate"| U["User answers concrete gate"]
C -->|"monitor_quiet_skip"| M["No-spend liveness poll"]
C -->|"outcome_floor_recovery"| R["Agent produces missing outcome evidence or blocker"]
A --> S["refresh-state / history event"]
U --> S
R --> S
S --> Q
The blocked-priority fallback pattern deserves its own public demo because it captures the product taste: do not idle on a gate, and do not hide the gate while doing fallback work.
sequenceDiagram
participant GH as LoopX
participant Agent as Agent
participant User as User
GH->>Agent: P0 blocked, P1 fallback safe
Agent->>User: Notify concrete P0 blocker
Agent->>Agent: Execute safe P1/P2 fallback
Agent->>GH: Validate, write back, spend once
GH->>User: P0 blocker remains visible
Future public surfaces can include:
- static SVG or Mermaid diagrams embedded in README/docs;
- a fake-data dashboard walkthrough for the first three patterns;
- a short animated video showing "P0 gate + safe fallback" with no private benchmark artifacts;
- a public demo script that can be narrated to potential collaborators.
Pattern details use the same family order as the catalog. Within each family, P0 patterns come first, followed by P1 and P2 patterns. The IP number is stable identity, not display priority.
Trigger
quota should-run.should_run=true;interaction_contract.mode=bounded_delivery;interaction_contract.agent_channel.must_attempt=true;- no private, credential, production, destructive, or unprojected write-scope blocker applies.
Expected behavior
The agent chooses one bounded segment, performs the work, runs focused validation, writes durable state or history, and spends exactly once after the validated delivery.
Visual Model
flowchart LR
Q["eligible quota"] --> A["choose bounded segment"]
A --> D["deliver artifact or blocker"]
D --> V["focused validation"]
V --> W["durable writeback"]
W --> S["spend once"]
Bad smell
The agent sends a status update after reading only one file, or spends quota without a validated artifact or blocker.
Validation
examples/control_plane/work-lane-contract-smoke.pyexamples/control_plane/heartbeat-quota-flow-smoke.pyloopx check
Trigger
- a higher-priority agent todo is blocked;
- a lower-priority todo is executable and safe;
blocked_priority_fallback.notify_user=trueor equivalent quota/status projection is present;- user action may be useful, but the selected fallback does not require the user answer before proceeding.
Expected behavior
The user-facing message must preserve the blocked higher-priority item and the reason the fallback is being used. The agent may continue the safe fallback, but must not let the fallback become the main story silently.
Example shape:
Core lane is blocked on <decision/resource>. I will continue <safe fallback>
now, and the pending user todo remains <concrete ask>.
Visual Model
sequenceDiagram
participant GH as LoopX
participant Agent as Agent
participant User as User
GH->>Agent: Higher P0 blocked, fallback executable
Agent->>User: Notify concrete blocked P0
Agent->>Agent: Execute safe fallback
Agent->>GH: Write result and keep P0 visible
Bad smell
The agent either freezes completely on a gate even though other safe work is available, or silently works on lower-priority items while the user loses sight of the P0 blocker.
Validation
examples/control_plane/todo-first-open-summary-smoke.pydocs/heartbeat-automation-prompt.md
Trigger
- an open user todo is a real gate, not just advisory context;
- the gate can be scoped to one selected agent action, lane, resource, or boundary;
- another executable agent todo is independent of that gate;
- the fallback remains inside public/private, write-scope, resource, and quota boundaries.
Expected behavior
The user channel must still notify the concrete gate. The agent channel must not execute the gated action, but should continue the independent fallback when quota and safety allow it. This is a dual-channel state, not a contradiction:
user_action_required=true
agent_action_required=true
agent_action=<independent fallback>
The controller should expose a durable field such as
scoped_user_gate_fallback with:
- the blocked user gate;
- the gated agent item(s);
- the selected fallback;
- a spend policy that permits spending only after validated fallback writeback.
The best long-term implementation is explicit scope metadata:
user_todo.decision_scope and agent_todo.required_decision_scopes. Runtime
text or action_kind inference is only a compatibility bridge for older goal
states.
When a user gate carries blocks_agent=<agent-id>, that metadata is already a
hard scope boundary. The target agent must still stop and surface the concrete
user decision. Other agents should keep the gate diagnostically visible but
must not count it as their own open_count, gate_open_items, or
user_channel.action_required if they have an independent executable todo.
For those agents, an operator_gate state derived only from the other-agent
gate should become an agent-scoped eligible lane, not a global owner gate.
Visual Model
flowchart TD
G["open user gate"] --> S{"which action scope?"}
S -->|"covers selected action"| F{"independent fallback exists?"}
S -->|"ambiguous"| R["repair projection or ask user/controller"]
F -->|"no"| U["notify user; stop gated delivery"]
F -->|"yes"| D["notify user and run fallback"]
D --> V["validate fallback"]
V --> W["write back; spend once"]
Bad smell
The payload says must_attempt_work=true and do_not_cancel_on_block=true,
but interaction_contract.agent_channel.delivery_allowed=false only because
requires_user_action=true. The agent then repeats the gate forever even
though another safe todo is available.
The opposite bad smell is also dangerous: the agent silently continues work without naming the blocked user decision, so the fallback becomes the main story and the human loses the critical gate.
A third bad smell is an agent-scoped user gate overreach. A user todo says it
blocks one registered product or side agent, but quota treats it as a global
operator gate for every --agent-id call. The non-target agent then stops even
though it has its own runnable todo. This is not IP-026 scope exhaustion; it is
IP-003 scope metadata being ignored by the user-todo blocking summary.
Validation
regression/scoped-user-gate-fallback-contract.pyexamples/protocol/protocol-action-packet-smoke.pyexamples/control_plane/work-lane-contract-smoke.pyexamples/control_plane/quota-agent-scoped-user-gate-smoke.pyforblocks_agentscoped user gates that block only the target agent while preserving other-agent delivery.docs/archive/incidents/agent-scoped-user-gate-overreach-incident-20260624.md
Trigger
- visible executable agent todos declare
required_capabilities, such asshell,filesystem_write,benchmark_runner,external_evidence_poll,network, orcredentials; quota should-runhas quota to spend, but the current launcher may not have every capability required by the highest-priority todo;- more than one executable todo may be visible, including multiple P0 or multiple P1 candidates.
Expected behavior
Capability is a per-todo execution preflight, not a global agent profile and
not a permission grant. status should project each todo's
required_capabilities; quota should-run should derive a read-only
capability_gate over the visible executable queue.
Do not declare a capability as required merely because the todo is meant to
develop, repair, or parity-check that capability. Use target_capabilities for
that output side of the work. For example, a product-path parity todo can
declare required_capabilities=shell and target_capabilities=benchmark_runner:
the gate may project it as runnable repair work even while the target bridge
capability is absent.
The controller scans executable candidates in projection order and classifies
them, but it does not make the final todo choice. If the first P0 requires
benchmark_runner but the second P0 only needs shell/filesystem capability,
both the runnable P0 and any later runnable fallback are projected in
capability_gate.runnable_candidates; blocked higher-priority items remain
visible in capability_gate.blocked_candidates. recommended_action remains
routing context, not a chosen runnable todo.
If the first P0 is not trying to run the benchmark but to repair or materialize
the benchmark bridge itself, it should appear in runnable_candidates with
target_capabilities, capability_repair_mode=true, and
capability_action=repair_bridge instead of being hidden behind a lower-value
fallback.
When capability_gate.action=run, the decision contract is:
decision_owner=agent;selection_policy=agent_steering_audit_over_runnable_candidates;runnable_candidatesis the allowed candidate set for this turn;- for a primary agent, same-priority candidates that unblock another agent via
blocks_agentplusunblocks_todo_idare ordered before ordinary backlog, so the candidate list andagent_lane_next_actionexpose the same handoff priority; - candidates with
capability_repair_mode=trueare allowed repair/development work for a missingtarget_capabilitiesbridge, not direct execution through that missing bridge; blocked_candidatesis the visible set of higher- or same-priority work that cannot currently run;- the agent must choose the actual todo from
runnable_candidates, then validate and write back that chosen work.
If no visible executable todo can run, the gate chooses:
repair_bridgefor local bridge gaps such asbenchmark_runner,external_evidence_poll,worker_bridge, orcli_bridge;ask_ownerfor owner-held capabilities such asnetwork,credentials, orproduction_access;skipwhen the missing capability is unsupported and no safe repair or owner action is known.
Launchers that really have an extra capability should pass it to both
quota should-run and quota spend-slot with --available-capability, so the
preflight and accounting phases agree.
Visual Model
flowchart TD
Q["quota should-run"] --> E["visible executable todo queue"]
E --> C{"candidate required_capabilities satisfied?"}
C -->|"yes"| R["add to runnable_candidates"]
C -->|"no, more candidates"| B["add to blocked_candidates"]
B --> E
C -->|"no candidates runnable"| M{"missing capability class"}
M -->|"bridge"| P["repair_bridge"]
M -->|"owner-held"| U["ask_owner with concrete capability ask"]
M -->|"unsupported"| S["skip without spend"]
R --> A["agent steering audit chooses one runnable todo"]
A --> V["validate chosen work"]
V --> W["write back and spend-slot with same available capabilities"]
Bad smell
The system treats quota eligibility as proof that the nearest todo can run, then repeatedly fails on missing benchmark/network/tooling capability. The opposite bad smell is over-blocking: one P0 needs a missing runner, but another P0 or P1 is runnable and safe; the controller freezes instead of projecting the runnable set for the agent to choose from.
Validation
docs/project-agent-todo-contract.mddocs/quota-allocation.mdexamples/capability-gate-smoke.pyexamples/control_plane/todo-cli-smoke.py
Trigger
- repeated surface-only work has crossed the outcome floor;
safe_bypass_kind=outcome_floor_recoveryorheartbeat_recommendation.recommended_mode=outcome_floor_recovery;- quota exposes a concrete
must_advancetarget.
Expected behavior
The agent may do only the bounded recovery: produce the missing evidence named
by must_advance, or write the blocker explaining why that evidence cannot be
produced. Ordinary docs/status propagation should wait.
Visual Model
flowchart LR
F["outcome floor crossed"] --> T["read must_advance"]
T --> E{"can produce outcome evidence?"}
E -->|"yes"| P["produce evidence"]
E -->|"no"| B["write concrete blocker"]
P --> V["validate and spend once"]
B --> V
Bad smell
The system keeps improving wrappers, summaries, or queues while never producing the evidence needed to decide whether the goal is working.
Validation
docs/archive/incidents/outcome-floor-safe-bypass-incident-20260606.mdexamples/control_plane/quota-plan-smoke.pyexamples/upgrade-plan-smoke.py
Trigger
should_run=false;effective_action=monitor_quiet_skip;- no user gate, user todo blocker, external handle observation, or self-repair obligation is active.
Expected behavior
The agent may append at most one no-spend monitor poll, rerun the guard, and
then stay quiet. The automation remains alive; monitor-only quiet skips are not
completion or deletion signals. The poll records quota_monitor_target_v0 so
the next guard can distinguish a harmless unchanged watch from the same target
repeating.
Status and diagnose should display unchanged monitor-only work as
waiting_on=monitor_signal with severity=watch, while retaining the quota
decision effective_action=monitor_quiet_skip. This keeps the monitor visible
without making it look like immediate Codex work or a user/controller gate.
Six consecutive no-spend polls for the same target should project a
dead_monitor_repeat autonomous replan obligation; the agent must write a
watch-lane expiry, concrete blocker, todo supersede, or successor runnable todo
before another quiet poll.
Visual Model
flowchart TD
N["should_run=false"] --> M{"monitor_quiet_skip and no gate?"}
M -->|"no"| C["follow concrete contract"]
M -->|"yes"| P["append no-spend poll"]
P --> R["rerun quota guard"]
R --> D{"same target repeated?"}
D -->|"no"| Q["quiet; keep automation active"]
D -->|"yes"| A["dead-monitor repair required"]
Bad smell
The heartbeat stops itself because nothing changed, or spends quota on a no-op status repetition. Another failure mode is presenting monitor-only work as an immediate Codex action; the agent then keeps trying to deliver from a watch lane instead of staying quiet or writing a concrete blocker.
Public-safe bad case
The 2026-06-21 monitor-only replan stall is the canonical public-safe bad case for this pattern. Its reusable shape is not tied to any private project:
effective_action=monitor_quiet_skip
user_channel.action_required=false
user_todo_summary.open_count=0
agent todo lane contains only monitor-style work
recent history repeats monitor-poll / replan-adjacent rows
no runnable todo, blocker, successor, supersede, or watch-lane expiry changed
Catalog and dashboard copy should name that as a watch state. A watch lane may remain visible, and it may append one no-spend liveness poll, but it must not render as immediate Codex delivery or as a user/controller approval gate. If the same watch target repeats past the stale threshold, IP-024 owns the repair delta: write a blocker, successor, supersede, or explicit watch-lane continuation rather than spending another delivery turn on prose.
Validation
examples/control_plane/heartbeat-quota-flow-smoke.pyexamples/control_plane/quota-plan-smoke.pydocs/heartbeat-automation-prompt.mddocs/archive/incidents/monitor-only-replan-stall-incident-20260621.md
Trigger
interaction_contract.user_channel.action_required=true; oruser_todo_summary.open_count > 0.
Expected behavior
The heartbeat, status, dashboard, or review packet must name the concrete user todo/question. It must not say only "owner gate" or "waiting on user". If the payload says user action is required but no concrete todo/question is projected, the correct message is a state projection bug:
specific user todo is not projected; repair LoopX state projection
When action_required=false and user_todo_summary.open_count=0, the system
may say there is no user todo and should not imply a projection fault.
Visual Model
flowchart TD
U{"user action required or user_open > 0?"}
U -->|"yes"| C{"concrete todo/question projected?"}
C -->|"yes"| A["ask or notify with concrete item"]
C -->|"no"| R["report projection repair needed"]
U -->|"no"| N["no user todo / no notification"]
Bad smell
The user sees repeated vague gate messages and cannot tell what decision is needed.
Validation
docs/heartbeat-automation-prompt.mdexamples/control_plane/quota-plan-smoke.pyexamples/control_plane/heartbeat-quota-flow-smoke.py
Trigger
- a todo has
status=deferredand a machine-readable resume condition such asresume_when=todo_done:<todo_id>or an unblock link such asunblocks_todo_id; - status can evaluate the condition into
resume_conditionandresume_ready; - the deferred item is current-agent claimed or unclaimed for the scoped agent that is awake; and
- no ordinary open current-agent or unclaimed advancement todo should outrank the resume handoff.
Expected behavior
Deferred todos represent parked work behind a resume gate. The gate may be a user todo, primary-review handoff, prerequisite implementation todo, resource decision, or another bounded control-plane condition. That makes this a Human Decision / gate-resume pattern, not a no-todo pattern.
Status and quota should keep deferred work visible after sorted open todo lanes:
deferred_items: bounded visibility for deferred todos;deferred_resume_candidates: bounded visibility for ready deferred todos;current_agent_deferred_resume_candidates,unclaimed_deferred_resume_candidates, andother_agent_deferred_resume_candidatesfor agent-scoped payloads.
Ready deferred work is not a no-candidate state. If a current-agent or
unclaimed deferred item is ready and no open current-agent/unclaimed
advancement todo outranks it, quota should return the existing
successor_replan_required / deferred_resume_projection contract:
effective_action=successor_replan_required
normal_delivery_allowed=false
execution_obligation.contract=deferred_resume_projection
interaction_contract.agent_channel.must_attempt=true
interaction_contract.agent_channel.quiet_noop_allowed=false
The bounded action is lifecycle repair: reopen the deferred todo, supersede it
with a current successor, or record a public-safe no-follow-up rationale. Only
after that writeback may normal delivery resume. If the resume condition is
still user-held or ambiguous, IP-004 / IP-003 owns the user-facing ask. If no
ready deferred item exists, IP-026 may classify the scoped frontier as
scope_exhausted, agent_scope_wait, or reassignment_required.
Visual Model
flowchart TD
D["deferred todo"] --> G{"resume gate satisfied?"}
G -->|"no, user-held"| U["IP-004 / IP-003 concrete user gate"]
G -->|"no, system prerequisite"| W["wait; keep deferred visible after open lanes"]
G -->|"yes"| C{"current-agent or unclaimed?"}
C -->|"other-agent claimed"| O["diagnostic visibility only"]
C -->|"current or unclaimed"| R["successor_replan_required"]
R --> L["reopen / supersede / no-follow-up rationale"]
L --> Q["rerun quota; normal delivery may resume"]
Bad smell
A ready deferred successor is rendered only inside an agent-scoped no-candidate payload. The side agent reports "nothing runnable" even though the system knows a previous gate is now satisfied. The opposite failure is also bad: deferred items are mixed into ordinary open backlog before the lifecycle step, so stale or future work outranks live open tasks.
Validation
examples/control_plane/work-lane-contract-smoke.pycovers a ready deferred successor returningsuccessor_replan_requiredinstead of a quiet no-op.examples/control_plane/todo-durability-fixture-smoke.pycovers parsingresume_when=todo_done:<todo_id>and projecting ready deferred candidates after open items.docs/project-agent-todo-contract.mddocs/quota-allocation.mddocs/status-data-contract.mdskills/loopx-self-repair/references/repair-patterns.mdrecordsdeferred_gate_resume_misclassifiedfor incident triage.
Trigger
- a todo carries
blocks_agent=<agent-id>and represents review, handoff, unblock, or owner work for that agent; - the todo status changes among open/blocked, done, deferred, or superseded;
- the todo may name a follow-up via
unblocks_todo_id,resume_when=todo_done:<todo_id>, orsuperseded_by; and quota should-run --agent-id <agent-id>needs to decide whether the agent should wait, replan, or run a concrete successor.
Expected behavior
blocks_agent todos are not only backlog rows. They are inter-agent gate
states. Status should project agent_todos.handoff_gates[] from the complete
todo list, not only from open lanes, using todo_handoff_gate_v0.
| gate_state | Todo condition | Quota effect |
|---|---|---|
blocking |
non-terminal handoff todo for the scoped agent | return agent_scope_wait; name the owning reviewer/agent rather than waking the blocked agent for delivery |
cleared_without_successor |
done handoff with no stable successor or supersede link | return successor_replan_required; reopen, supersede, or record no-follow-up rationale |
cleared_with_successor |
done handoff linked to a successor via unblocks_todo_id, resume_when, or superseded_by |
route to the concrete successor through normal todo selection |
cleared_no_followup |
done handoff carries no_followup=true with a compact rationale |
keep as terminal history; do not wake the blocked agent for successor replan |
superseded |
handoff todo carries superseded_by |
keep as history; do not wake the blocked agent from the stale gate |
deferred |
handoff todo is parked behind an unsatisfied resume condition | keep diagnostic visibility; IP-027 owns the ready-deferred resume path |
Quota ordering matters. Current-agent ordinary advancement still wins normal
delivery. If no ordinary current-agent successor is ready, a current-agent
blocking handoff wins over stale done handoffs. A
cleared_without_successor handoff wins over generic IP-026 no-candidate wait
because it means the handoff state changed but no replayable successor exists.
Only after those checks may IP-026 classify scope_exhausted or
agent_scope_wait.
Visual Model
flowchart TD
T["blocks_agent todo"] --> S{"todo lifecycle"}
S -->|"open / blocked"| B["handoff gate: blocking"]
B --> W["agent_scope_wait for blocked agent"]
S -->|"done + successor"| C["handoff gate: cleared_with_successor"]
C --> N["run concrete successor normally"]
S -->|"done + no successor"| R["handoff gate: cleared_without_successor"]
R --> L["successor_replan_required"]
L --> F["reopen / supersede / no-follow-up rationale"]
S -->|"open + stale closeout"| X["route continuation replan required"]
X --> L
S -->|"superseded_by"| H["handoff gate: superseded"]
H --> I["historical only"]
S -->|"deferred"| D["handoff gate: deferred"]
D --> P["IP-027 resume rules"]
Bad smell
A done review/handoff todo disappears because only open lanes feed quota, so
the blocked agent falls into a vague agent_scope_wait. The opposite bad smell
is also harmful: a stale done handoff outranks a live open review blocker, so
the agent replans while a real reviewer-owned gate is still open. Both are
state-machine bugs, not prompt wording bugs.
A third bad smell is an open handoff gate whose action is already a stale
handoff closeout. That gate is no longer a live reviewer decision. It should
project route_continuation_replan_required so quota wakes successor replan to
reopen, supersede, or close the stale route with a no-follow-up rationale.
Validation
loopx/control_plane/todos/handoff_gate.pyowns thetodo_handoff_gate_v0projection.examples/control_plane/quota-cleared-blocker-successor-gate-smoke.pycoversblocking,cleared_without_successor,cleared_with_successor, andsupersededgate states.docs/quota-allocation.mddocs/status-data-contract.mdskills/loopx-self-repair/references/repair-patterns.mdrecordshandoff_gate_state_projection_gapfor incident triage.
Trigger
- the operator is recording a run-bound
human_reward; or - the operator/controller is recording an
operator_gatedecision; - a dashboard or loopback server wants to write a decision event rather than only render status.
Expected behavior
Decision writes must be exact-target, compact, previewed when browser-originated,
and append-only. human_reward attaches to one selected run row. operator_gate
records a decision run and, for approvals, a resume contract that forces the
receiving agent to re-read current registry, active state, quota, repo status,
policy, and run state before executing.
Browser reward append requires an explicit local capability, loopback origin,
matching preview_id, unchanged selected run, unchanged payload, unchanged raw
index count, public-safe text, and exactly one overlay append. Dashboard gate
append remains disabled until a separate equivalent handshake exists.
Visual Model
sequenceDiagram
participant User as Operator
participant UI as Dashboard/CLI
participant GH as LoopX
User->>UI: Select exact run or gate
UI->>GH: Preview compact decision
GH->>UI: public-safe preview_id / dry-run result
User->>UI: Confirm append
UI->>GH: Apply exact preview
GH->>GH: Reject stale/private/mismatched writes
GH->>GH: Append one decision event
GH->>UI: Refresh compact status
Bad smell
The dashboard makes local reward/gate writes feel like ordinary form submission, or an approved gate is treated as durable write authority without a fresh decision-point re-read.
Validation
docs/reward-gate-direct-write-contract.mddocs/dashboard-reward-write-boundary.mdexamples/reward-gate-direct-write-contract-smoke.pyexamples/reward-append-api-smoke.pyexamples/dashboard-reward-append-browser-smoke.mjsexamples/project/operator-gate-resume-contract-smoke.py
Trigger
- the user explicitly corrects a product route, priority, benchmark protocol, safety boundary, or operating rule;
- the correction supersedes a current todo,
recommended_action, route assumption, or benchmark adapter plan; - future agents would be likely to repeat the old assumption if the correction remains only in chat.
Expected behavior
The agent must pause ordinary delivery selection and promote the correction into durable state before continuing. The minimal durable promotion is one of:
- update active
Next Actionand the relevant openAgent Todo; - append or prepare a run-bound
human_reward/ operating-lesson event; - add a concrete successor todo for the product/runtime change;
- update this catalog or the self-repair pattern table if the situation is reusable.
The correction should record:
- corrected rule;
- scope, such as goal, project, benchmark family, route, or adapter;
- superseded assumption;
- owner of the next implementation step;
- validation that future
quota should-runor posthoc parity checks can see the rule.
This is not the same as hidden model memory. The model may remember the conversation, but LoopX must expose a replayable hook for future agents.
Visual Model
flowchart TD
U["user correction / reward"] --> S{"does it change route, priority, or policy?"}
S -->|"no"| C["ordinary chat acknowledgement"]
S -->|"yes"| P["promote compact lesson"]
P --> T["update todo / Next Action / reward overlay"]
T --> Q["refresh-state and rerun quota"]
Q --> A["continue corrected bounded delivery"]
Bad smell
The user says "the three benchmarks should run on the remote development machine, but Codex stays local and the remote host is only the execution environment"; a later agent turn treats missing remote Codex/Codex-ACP as the main blocker or keeps following a stale local-only benchmark staging todo.
Validation
skills/loopx-self-repair/references/repair-patterns.mddocs/state-interaction-model.md- future
user_reward_lesson_projection_gapstatus/quota smoke that checks explicit operating lessons are projected intorecommended_action, activeAgent Todo, or a state-projection repair warning.
Trigger
- the experiment or product lane explicitly enables active user assistance;
- an intervention budget/frequency policy exists;
- hidden tests, reward/pass/fail, expected solutions, and credentials remain hidden from the worker.
Expected behavior
The assistant or user simulator may provide bounded help through an audited channel. Results must be labeled as assisted and must not be merged into official autonomous score claims.
Visual Model
sequenceDiagram
participant Sim as User simulator
participant GH as LoopX
participant Agent as Agent
Sim->>GH: Public-safe intervention within budget
GH->>Agent: Audited assistance channel
Agent->>GH: Assisted result evidence
GH->>GH: Label assisted and keep official score separate
Bad smell
The system calls a run "LoopX uplift" when the treatment secretly saw reward signals, oracle information, or unbounded human hints.
Validation
examples/worker-bridge-active-user-after-start-observation-smoke.pyexamples/worker-bridge-install-contract-smoke.py- benchmark active-user protocol docs.
Trigger
- quota says work is eligible or
must_attempt=true; agent_todo_summary.open_count=0anduser_todo_summary.open_count=0;Next Action, handoff prose, or recent run history still contains actionable work.- compatibility lint sees explicit user-wait prose in
Next Action, but no structuredUser Todoorinteraction_contract.user_channelgate exists.
Expected behavior
The next step should become replan / todo expansion / blocker writeback rather than normal delivery. Machine projection must be repaired before the controller pretends there is no work.
When structured fields are present, they are authoritative over Markdown lint.
If interaction_contract.user_channel.action_required=false,
user_todo_summary.open_count=0, and an executable agent todo exists, the
controller should continue bounded agent work instead of asking the agent to
report "具体 user todo 未投影". Conversely, if a real owner/user gate exists, it
must be represented as a concrete user todo or scoped decision rather than only
as prose in Next Action.
Visual Model
flowchart TD
E["eligible or must_attempt"] --> O{"open user/agent todos?"}
O -->|"yes"| D["normal lane selection"]
O -->|"no"| P{"actionable Next Action or handoff prose?"}
P -->|"yes"| R["replan / todo expansion / blocker writeback"]
P -->|"no"| M["monitor or quiet no-op"]
Bad smell
Humans can see a next action in prose, but the machine projection sees no open todo and the automation drifts into monitor-only no-ops.
Validation
examples/state-projection-gap-smoke.pydocs/project-agent-todo-contract.md
Trigger
- the selected todo or
recommended_actionrequires writing a scope; goal_boundary.write_scopedoes not include that scope;- a historical owner decision may exist, but it is not projected into the current boundary contract.
Expected behavior
LoopX should return boundary projection repair or a concrete user/controller gate. The agent should not execute the write, and should not spend turns on repo-only handoff if the real blocker is missing scope projection.
Only structured checkpointed authority can extend the runtime boundary. A
historical approval written in prose is not enough. The registry may carry
coordination.checkpointed_boundary_authority[] entries with:
schema_version=checkpointed_boundary_authority_v0;write_scope;sourceor equivalent public-safe provenance;recorded_at;- optional
expires_at; decision=approveand active status.
Fresh approved entries are compiled into goal_boundary.write_scope and
exposed under goal_boundary.checkpointed_boundary_authority. Expired,
rejected, missing-provenance, or missing-timestamp entries remain visible only
as diagnostics; they do not authorize writes.
Visual Model
flowchart TD
A["selected action"] --> S{"requires write scope?"}
S -->|"no"| E["execute if otherwise safe"]
S -->|"yes"| C{"fresh checkpointed authority?"}
C -->|"yes"| P["compile authority into goal_boundary.write_scope"]
C -->|"no"| B{"scope already in explicit write_scope?"}
P --> B
B -->|"yes"| E
B -->|"no"| R["boundary projection repair or user/controller gate"]
Bad smell
The control plane remembers that a user once approved a path, but the current quota boundary blocks it, so agents loop on small handoffs instead of repairing the checkpointed decision.
Validation
examples/control_plane/quota-action-scope-guard-smoke.py;examples/project/configure-goal-smoke.py;docs/state-interaction-model.mdcheckpointed decision sections.
Trigger
- a worker discovers or receives a durable design doc, research memo, owner packet, migration report, benchmark paper, external registry, or other source that future agents may need;
- the target project and
goal_idare known; - the material can be represented as public-safe metadata without storing raw URLs, document ids, local paths, source bodies, comments, credentials, or private logs.
Expected behavior
The agent should first identify the owning project, then register a compact
source contract in that project's authority surface. If the project has a
tracked docs/meta/DOC_REGISTRY.yaml, update that authority map first. If it
does not, use the project-local .loopx/registry.json through
authority_registry.topic_authority and authority_registry.project_materials.
This distinction is a storage/publication boundary, not two competing authority
systems: tracked DOC_REGISTRY files are project assets for review, while the
ignored authority_registry fallback is LoopX control-plane state.
The stored material should answer what it is, how fresh it is, which topic it governs, whether owner review or read access is needed, and how conflicts are resolved. It should not read or summarize the material body as part of the registration step.
Visual Model
flowchart TD
M["durable material discovered"] --> P{"target project and goal known?"}
P -->|"no"| B["write blocker or ask owner"]
P -->|"yes"| R{"public-safe source contract possible?"}
R -->|"no"| B
R -->|"yes"| D{"tracked DOC_REGISTRY exists?"}
D -->|"yes"| Y["update tracked project DOC_REGISTRY topic/source"]
D -->|"no"| L["write ignored project-local authority_registry fallback"]
Y --> C["register redacted authority source for harness/status sync"]
L --> C
C --> S["sync compact summary / refresh status"]
Bad smell
An agent remembers an important article or design only in chat, or registers it into the meta controller because that is the current repo, even though the material belongs to another connected project.
Validation
docs/authority-source-registration.mdexamples/register-authority-source-smoke.pyexamples/import-doc-registry-authority-smoke.pyexamples/platform-migration-material-registry-smoke.py
Trigger
- multiple agents, heartbeats, child workers, or frontstage channel views may act on the same todo;
- the selected work has a bounded
todo_id, owner, TTL, write scope, and idempotency key; - the system needs to prevent duplicate work, duplicate spend, or overlapping writes without moving truth into chat.
Expected behavior
A task claim should become task_lease_v0: an explicit, expiring claim over
one bounded todo. The pending key is (goal_id, todo_id): goal_id names the
control-plane lane, while todo_id names the work item inside it. Different
todos inside the same goal do not conflict merely because they share a goal;
only competing pending leases for the same todo_id or overlapping write
scopes should conflict. Status and future channel projections may render the
claim, but the lease remains a projection over the LoopX ledger and does
not override goal_boundary, user gates, quota, or write-scope checks.
When a lease is active and the selected action is inside its scope, the owner may proceed. When a competing worker sees an active overlapping lease, it must choose a non-overlapping fallback, wait, or surface a conflict. Expired leases need cleanup or renewal before they authorize continued work.
Visual Model
flowchart TD
T["selected todo"] --> A{"active lease for task?"}
A -->|"no"| C["create lease with TTL, owner, scope, idempotency key"]
A -->|"yes same owner/scope"| R["renew or continue bounded work"]
A -->|"yes different owner or overlapping scope"| K["conflict: fallback, wait, or ask controller"]
C --> W{"write scope still allowed?"}
R --> W
W -->|"yes"| D["deliver, validate, append event"]
W -->|"no"| G["boundary repair or user/controller gate"]
D --> X["release/expire lease through ledger"]
Bad smell
Two workers repeat the same task, double-spend quota, or write overlapping files because the only ownership signal was a chat message or dashboard label.
Validation
docs/frontstage-channel-lease-roadmap.mddocs/architecture.mdlocal server / daemon roadmap- future
task_lease_v0status and conflict smoke.
Trigger
- a shared-control-plane goal declares
coordination.registered_agentsand onecoordination.primary_agent; - a side agent has an automation prompt or handoff scope, such as product docs, showcase work, validation, or another low-conflict side lane;
- an in-scope agent todo can be claimed with
claimed_by, while the primary agent remains responsible for high-risk review, publication, reassignment, and merge decisions outside self-merge policy.
Expected behavior
The control plane should keep identity and ownership visible without turning scope into todo metadata. The side agent learns its scope from the automation prompt or handoff, then claims only a concrete in-scope todo:
loopx todo claim \
--goal-id <goal-id> \
--todo-id <todo_id> \
--claimed-by <side-agent-id>Repository edits happen in an independent worktree/branch. When the slice is small, validated, public-safe, and allowed by repository policy, the side agent may self-merge and complete the todo with evidence:
loopx todo complete \
--goal-id <goal-id> \
--todo-id <todo_id> \
--claimed-by <side-agent-id> \
--side-agent-self-merged \
--evidence "<commit, validation, and self-merge summary>"If the self-merged lane has an obvious same-scope continuation, the completion
may atomically add a successor and claim it back to the same side agent. If the
work is broad, risky, unclear, or outside the side scope, completion must create
a successor handoff todo claimed by the configured
coordination.side_agent_handoff_agent when set, otherwise by the primary
agent. LoopX does not need a separate kernel-level "review" object here: the
machine-readable contract is the successor todo plus claimed_by,
blocks_agent, and unblocks_todo_id. Same-agent broad handoff is rejected;
use --side-agent-self-merged --evidence for same-agent delivery. claimed_by
remains a soft owner and not a permission grant: quota, user gates,
public/private boundary checks, write scopes, and repository rules still apply.
Because prompt text alone is not a reliable guard, quota should-run --agent-id <side-agent-id> should also project workspace_guard when the side agent is
running from the registered primary checkout, a non-git directory, or an
unrelated git worktree. In that state normal_delivery_allowed=false and
interaction_contract.mode=side_agent_workspace_repair: the only allowed action
is to create or switch to an independent worktree/branch and rerun the guard
before editing repository files. Moving workspaces is a preflight repair and
does not get quota spend.
The same scoped identity must be carried through the whole successful turn.
If quota should-run was evaluated with --agent-id <side-agent-id>, follow-up
commands that interpret the same turn's control-plane state, especially
refresh-state and quota spend-slot, should use that same registered
--agent-id when the subcommand supports it. Otherwise the spend/accounting
preview can be evaluated as an unscoped automation and report
automation_prompt_upgrade_required even though the delivery decision was
made under a valid side-agent scope. The fix is not to ignore that warning; the
fix is to preserve the identity envelope across guard, writeback, accounting,
and rollout evidence.
Visual Model
flowchart TD
S["registered side agent wakes with scope"] --> T{"in-scope open todo?"}
T -->|"no"| Q["quiet no-op or add public-safe candidate todo"]
T -->|"yes"| C["claim todo with claimed_by side-agent"]
C --> W["work in independent worktree / branch"]
W --> V{"validated and AGENTS self-merge eligible?"}
V -->|"yes"| M["self-merge small change with evidence"]
M --> I["refresh/spend with same --agent-id"]
I --> K{"same-scope continuation?"}
K -->|"yes"| N["complete + add successor claimed_by same side agent"]
K -->|"no"| X["complete with no successor or no-follow-up rationale"]
V -->|"no"| R["complete with successor handoff todo"]
R --> P["successor claimed_by handoff owner, else primary_agent"]
Bad smell
A side agent edits the primary checkout, chooses work from chat memory instead
of the shared todo list, encodes scope into todo metadata, self-merges broad or
runtime-sensitive work, or creates a review successor and claims it back to
itself without the explicit self-merge path. A related bad smell is treating
"the prompt said use a worktree" as sufficient product protection; the guard
must be machine-visible before the first file edit. Another bad smell is a
scoped side-agent run that passes quota should-run --agent-id ... but later
spends without --agent-id, producing an unscoped accounting snapshot that
looks like a stale automation prompt instead of the completed scoped turn.
Validation
docs/project-agent-todo-contract.mddocs/codex-subagent-orchestration.mddocs/heartbeat-automation-prompt.mdexamples/control_plane/todo-lifecycle-cli-smoke.pyexamples/control_plane/todo-cli-smoke.pyexamples/control_plane/todo-concurrent-write-lock-smoke.pyexamples/control_plane/heartbeat-prompt-smoke.pyexamples/side-agent-workspace-guard-smoke.py
Trigger
- a selected agent todo has a stable
todo_idand the current agent is about to spend a delivery turn on it; - a todo has become stale because the user changed the route, new evidence made the old wording wrong, or a narrower replacement should become the first executable item;
- a non-trivial slice is complete but the feature still needs rollout, product-path proof, docs, benchmark evidence, telemetry, or review;
- multiple registered agents can see the same checklist and need ownership to be visible without moving scope into todo metadata.
Expected behavior
The agent claims concrete work before delivery:
loopx todo claim \
--goal-id <goal-id> \
--todo-id <todo_id> \
--claimed-by <agent-id>claimed_by is a soft owner, not permission. It must be checked against
registered agent ids and must not bypass quota, user gates, write boundaries,
repository policy, validation, or public/private scans.
When an open todo is wrong rather than merely incomplete, the agent should supersede it instead of editing its text in place or marking it done:
loopx todo supersede \
--goal-id <goal-id> \
--todo-id <todo_id> \
--reason "<public-safe reason>" \
--next-agent-todo "<replacement executable action>"Supersede preserves the old work item as history, records superseded_by, and
makes the replacement the durable current route. Use it for route changes,
stale benchmark lanes, narrowed blockers, or user-corrected priorities.
When a non-trivial slice is completed, the completion must either create a successor todo or record why no successor is needed:
loopx todo complete \
--goal-id <goal-id> \
--todo-id <todo_id> \
--evidence "<public-safe validation or artifact>" \
--next-agent-todo "<next rollout/proof/docs/review step>"Successor todos are the lightweight lifecycle model. LoopX should not grow many feature states such as slice_done, rolled_out, or proven_in_product unless a UI/runtime need appears. A done todo means the current slice is done; the successor expresses the next slice. If there is truly no follow-up, the completion note must include a compact no-follow-up rationale.
Visual Model
flowchart TD
S["selected open todo"] --> C{"claimed by this agent?"}
C -->|"no"| L["claim with claimed_by"]
C -->|"yes"| W["deliver bounded slice"]
L --> W
W --> V{"validated?"}
V -->|"no"| B["write blocker or keep todo open"]
V -->|"yes"| R{"old todo still describes the route?"}
R -->|"no"| U["supersede with replacement todo"]
R -->|"yes"| F{"follow-up needed?"}
F -->|"yes"| N["complete with successor todo"]
F -->|"no"| X["complete with no-follow-up rationale"]
U --> Q["refresh-state / quota projects successor"]
N --> Q
X --> Q
Bad smell
The agent starts work without claiming the todo, rewrites an open todo after a
route correction so history is lost, marks a broad feature done after one PR
without a successor, creates a successor only in chat, or treats claimed_by as
permission to ignore gates and boundaries.
Validation
docs/project-agent-todo-contract.mdexamples/control_plane/todo-lifecycle-cli-smoke.pyexamples/control_plane/todo-cli-smoke.pyexamples/control_plane/todo-concurrent-write-lock-smoke.py- future status/quota smoke that verifies first executable successor projection
after
todo supersedeandtodo complete --next-agent-todo.
Trigger
- status or quota summarizes a todo set with more open work than the small scheduler top-N can show;
- registered agents use
claimed_by, especially side agents whose scoped work may sit behind higher-priority primary or benchmark todos; - a dashboard, review packet, or heartbeat prompt needs to show ownership, current-agent work, and monitor responsibilities without changing which executable todo the scheduler selects.
Expected behavior
Todo projection has two jobs that should not collapse into one list:
- Scheduling: choose a narrow set of runnable candidates for the current guard, capability check, and steering audit.
- Visibility: keep ownership, claimed work, and monitor lanes observable for humans, dashboards, and scoped agents.
Status and quota may still expose first_open_items,
first_executable_items, and executable_backlog_items as compact scheduler
surfaces. They should also project bounded visibility lanes:
unclaimed_priority_open_items: priority-ranked unclaimed work that an agent may consider claiming;claimed_open_items: claimed work that may be outside the scheduler top-N;claimed_advancement_open_items: claimed executable delivery work;claimed_monitor_open_items: claimed continuous-monitor work;- for agent-scoped quota payloads,
current_agent_claimed_open_items,current_agent_claimed_advancement_items,current_agent_claimed_monitor_items, andclaimed_by_others_items.
The default agent-facing lane cap should remain modest, currently 16 items per lane, with count fields showing when more work exists. Rich frontstage views that need more than this should use a paged or filtered projection rather than inflating every heartbeat/quota payload. Monitor lanes remain visibility context unless they record a material transition or blocker; they should not steal the advancement slot simply because they are claimed.
When a claimed visibility lane has more items than the lane cap, truncation
should be claimant-balanced rather than raw top-N. First sort claimed items by
priority and source position, then group them by claimed_by, take a fair
per-claimant slice within the cap, and fill any remaining slots from the
priority-ordered remainder. The goal is not strict round-robin display order;
it is to keep one agent's long queue from hiding another agent's claimed work.
Agent-scoped quota then applies focus ordering after visibility balancing:
current-agent claimed items first, unclaimed items second, and other-agent
claimed items last with lower weight. Other-agent claims remain visible and may
be inspected when nothing better is available, but they should not crowd out
the current agent's own claimed advancement or genuinely unclaimed work.
claimed_by remains a soft ownership signal, not a lock, lease, capability
grant, or gate bypass.
For agent-scoped execution payloads, quota may also expose a narrow
agent_lane_next_action object with
schema_version=agent_lane_next_action_v0. It is derived from the same scoped
runnable queue: prefer capability_gate.runnable_candidates, then
agent_todo_summary.first_executable_items, then
agent_todo_summary.executable_backlog_items; filter out other-agent claimed
todos; select current-agent claimed todos before unclaimed fallback, and within
that claim bucket prefer capability_repair_mode=true before ordinary runnable
work of the same priority. This object is an agent-lane pointer, not a goal-level route rewrite,
so it must include preserves_goal_next_action=true. Status may attach the same
object for --agent-id observation, but it must not replace the item
recommended_action, project_asset.next_action, owner, or waiting lane.
Visual Model
flowchart TD
T["parsed todo set"] --> S["scheduler lanes: first/open/executable candidates"]
T --> B["claimed lane claimant-balanced truncation"]
B --> V["visibility lanes: claimed, unclaimed, monitor"]
S --> Q["quota/capability guard chooses runnable candidate set"]
Q --> A["agent steering audit chooses actual todo"]
Q --> N["agent_lane_next_action for --agent-id scoped turns"]
V --> F["dashboard/frontstage/review packet shows ownership"]
V --> C{"agent identity present?"}
C -->|"yes"| M["current-agent claimed > unclaimed > other-agent claimed"]
C -->|"no"| G["global claimed/unclaimed ownership view"]
F --> H["human sees who owns what without changing scheduler result"]
Bad smell
A side agent claims a productization todo, but status/quota only expose the first few priority-ranked benchmark todos. The agent then appears idle or unowned work appears available even though the control plane already knows its owner. The opposite bad smell is also harmful: a large claimed-work list is fed directly into the scheduler or heartbeat prompt, causing noisy routing and monitor work to crowd out the selected advancement lane.
Another concrete bad case: an operator asks the agent to monitor one PR until
merge, but the agent edits a generic heartbeat automation prompt to include
that PR-specific polling logic. That couples one monitor target to the
scheduler, makes the automation harder to regenerate from heartbeat-prompt,
and risks deleting or mutating the whole heartbeat when the PR closes. The
correct representation is a continuous_monitor todo, optionally with
unblocks_todo_id, while the heartbeat remains generic and discovers the
monitor through status/quota/todo projection.
Validation
docs/status-data-contract.mdexamples/control_plane/todo-first-open-summary-smoke.pyexamples/control_plane/work-lane-contract-smoke.pyforagent_lane_next_action_v0preserving the primary/globalNext Actionwhile surfacing the side-agent TUI slice.examples/control_plane/status-markdown-smoke.pyforstatus --agent-idrendering the same agent-lane pointer without replacing the project route.- PR #262 / commit
292a2c8: additive status/quota visibility lanes with a 16-item agent-facing cap.
Trigger
quota should-run --agent-id <agent>returnsshould_run=trueorinteraction_contract.agent_channel.must_attempt=true;- the same payload has no
agent_lane_next_action; current_agent_claimed_advancement_itemsis empty;- no runnable candidate is projected for that agent;
- no current-agent or unclaimed deferred resume candidate is ready; and
- the recommended action points at another agent's lane, an out-of-scope lane, or a goal-level route the current agent cannot safely advance.
Expected behavior
Agent-scoped quota must distinguish "the goal has runnable work" from "this
agent has runnable work." When the current agent has no in-scope candidate,
quota should not force a delivery turn. This pattern applies only after the
guard has also checked IP-027 and found no ready current-agent or unclaimed
deferred resume candidate, and after IP-029 has found no current-agent handoff
gate state that should wait or replan. If a deferred resume candidate is ready,
IP-027 owns the successor_replan_required path; if a handoff review todo has
changed state, IP-029 owns the handoff wait or successor-replan path. IP-026
must not swallow either case as "nothing runnable."
When the scoped frontier is truly empty, quota should project one of these machine states:
scope_exhausted: no current-agent or unclaimed candidate matches the registered agent profile and boundary;agent_scope_wait: the remaining useful step is progress, merge, reassignment, or decision by the agent/controller that owns the blocking work; this may be the primary agent, another side agent, or an explicitly claimed reviewer that blocks the current agent's handoff;reassignment_required: useful work exists, but ownership must be changed before this agent may treat it as its lane.
The interaction contract should then set:
agent_channel.must_attempt=false
agent_channel.delivery_allowed=false
agent_channel.quiet_noop_allowed=true
The user channel remains quiet unless a concrete user todo exists. The recommended action should name the scoped condition, not borrow the global goal-level route. A side agent should be allowed to no-op without spend, or claim a newly exposed in-scope todo before delivery becomes allowed again.
This pattern is the runtime counterpart of IP-022. IP-022 makes claimed,
deferred, handoff, and agent-lane work visible; IP-026 says what to do when the
scoped open frontier is empty, IP-027 has not found a ready deferred gate
resume, and IP-029 has not found a handoff todo gate state for the scoped
agent.
If the only apparent blocker is a user todo with blocks_agent pointing at a
different agent, IP-003 owns the case before IP-026: filter that other-agent
gate out of the current agent's blocking user summary, then decide whether the
current agent still has runnable work. If it does, delivery may continue; if it
does not, IP-026 can classify the remaining empty frontier.
Visual Model
flowchart TD
Q["quota should-run --agent-id side"] --> F{"current-agent frontier?"}
F -->|"current-agent candidate"| D["bounded delivery allowed"]
F -->|"unclaimed in-scope candidate"| C["agent may claim before delivery"]
F -->|"no open candidate"| R{"IP-027 ready deferred resume?"}
R -->|"yes"| P["defer to IP-027"]
R -->|"no"| G{"IP-029 handoff gate state?"}
G -->|"blocking or cleared_without_successor"| K["defer to IP-029"]
G -->|"none"| X["scope_exhausted / agent_scope_wait"]
F -->|"only other-agent or out-of-scope work"| X
X --> N["quiet no-op, no spend"]
X --> H["owning agent may advance, merge, or reassign"]
H --> Q
Bad smell
A side-agent heartbeat receives should_run=true,
delivery_allowed=true, and quiet_noop_allowed=false even though
agent_lane_next_action=None, current_agent_claimed_advancement_items=[],
and the only recommendation is another agent's benchmark or runtime lane. The
agent either churns through repeated empty heartbeats or risks working outside
its registered scope. A related failure is treating a ready deferred successor
as part of this no-candidate pattern instead of routing it through IP-027's
gate-resume lifecycle, or treating a handoff todo lifecycle change as generic
agent wait instead of routing it through IP-029. The opposite bad smell is also
harmful: deferred or handoff items are mixed into the open todo list, so stale
or future work outranks live open tasks.
Validation
- future quota/status regression with two registered agents where all runnable
work is claimed by the primary and the side-agent
--agent-idcall returnsscope_exhaustedoragent_scope_wait; examples/control_plane/work-lane-contract-smoke.pyshould cover that an empty current-agent frontier cannot producedelivery_allowed=true;docs/project-agent-todo-contract.mddocs/quota-allocation.mddocs/status-data-contract.mdexamples/control_plane/quota-agent-scoped-user-gate-smoke.pyfor the nearby case where a user gate is real but scoped to a different agent and therefore must not create current-agent scope exhaustion.examples/control_plane/quota-cleared-blocker-successor-gate-smoke.pyfor the nearby case where ablocks_agenthandoff todo directly controls the scoped gate.skills/loopx-self-repair/references/repair-patterns.mdrecordsagent_scoped_no_candidate_gapandhandoff_gate_state_projection_gapfor incident triage.
Trigger
quota should-runreports no user action or quiet monitor behavior, butstatus/diagnose --limit Nfalls back to a stale registry state, controller gate, or older connected-without-run state;- recent history is dominated by status-neutral entries such as quota monitor polls, slot-spend records, readiness pings, or display-only refreshes;
- a short UI/history limit hides the latest meaningful state transition just behind the visible window.
Expected behavior
Status, diagnose, quota, and history should share the same neutral-run classification contract. Neutral runs are real evidence for cadence and stall analysis, but they are not authoritative state transitions by themselves. When computing the current control-plane state, the implementation should reason over an internal window wide enough to skip neutral noise and find the latest meaningful state run before trimming the run list for UI display.
UI display limit must not become the control-plane reasoning window. A user can
ask for --limit 5 to keep output compact, but state selection should still
look far enough back to avoid projecting a fake controller/user gate. If no
meaningful state run exists inside the internal reasoning window, status should
say that explicitly instead of inventing a gate.
Visual Model
flowchart LR
H["recent run history"] --> N["classify neutral vs meaningful runs"]
N --> W["reason over internal state window"]
W --> M{"meaningful state run found?"}
M -->|"yes"| S["project current status from that run"]
M -->|"no"| U["report unknown/no signal without fake gate"]
S --> D["trim displayed rows to UI limit"]
N --> E["retain neutral rows as stall/cadence evidence"]
Bad smell
A monitor-only loop fills the most recent five history rows, so status --limit 5 claims the agent needs a controller connection or user decision even though
quota says monitor_quiet_skip and no user todo is open. The bad state is not
that monitor rows exist; it is that a presentation limit changed the meaning of
the control plane.
Validation
docs/status-data-contract.mdskills/loopx-self-repair/references/repair-patterns.md- future regression where the latest N runs are neutral and the N+1 run is the authoritative state transition.
Trigger
- a route-specific proof or debug tool emits verdict fields, such as Codex CLI visible attach decisions, runtime-idle blockers, continuation outcomes, or fallback contracts;
- an implementation proposes copying those verdicts into a stable hot-path
agent packet, status schema, dashboard contract, or
protocol_action_packet_v0; - the verdict semantics still depend on one experimental surface, a human observation, a fixture-only proof, or a temporary product question.
Expected behavior
Experimental proof/debug verdicts are sidecar diagnostics first. They may be public-safe, structured, versioned, and useful, but they do not become stable agent-facing packet fields until the abstraction is product-general and validated across the surfaces that will consume it.
The stable hot path should keep expressing generic control-plane obligations:
user action, agent action, work lane, gate state, quiet-noop allowance, spend
policy, and compact action label. Route-specific proof fields stay in the
sidecar that owns their evidence. For the current Codex CLI/TUI path, verdicts
such as visible_session_proof_required,
runtime_idle_evidence_required, same_tui_visible_attach_accepted,
accepted_for_same_tui_automation, continuation_outcome, and
fallback_contract belong in the visible attach/proof or observation packet,
not in protocol_action_packet_v0 or the routine quota/status packet shape.
Promotion from sidecar to stable schema needs an explicit schema decision:
- the field name is not tied to one route's debug wording;
- at least one non-Codex-CLI or future-runtime consumer can use the same abstraction without reinterpretation;
- public/private boundaries and no-transcript/no-session-file constraints are documented;
- failure modes are represented as generic obligations or blockers, not as product-spike labels;
- smoke tests prove both the sidecar verdict and the stable packet remain compatible.
The Codex CLI/TUI consequence is narrow but important: a manual same-open-TUI observation can prove that the first bootstrap continuation stayed visible, while scheduled same-TUI attach remains blocked by proof/idle requirements. That distinction is valid evidence, but it should not force every agent heartbeat or dashboard row to learn Codex-specific verdict fields.
Visual Model
flowchart TD
P["experimental proof or debug packet"] --> V["route-specific verdict fields"]
V --> S{"product-general abstraction validated?"}
S -->|"no"| D["keep as sidecar diagnostic; link from docs/status when useful"]
S -->|"yes"| R["write schema decision and compatibility smoke"]
R --> H["promote generic field into stable hot-path packet"]
D --> C["stable packet keeps generic user/agent/gate/spend obligations"]
H --> C
Bad smell
A proof spike adds same_tui_visible_attach_accepted or
visible_session_proof_required directly to a generic runtime packet, and
future agents start treating a Codex CLI debug verdict as a universal routing
field. The opposite bad smell is losing the proof entirely: the diagnostic
sidecar is public-safe and useful, but no doc or smoke says why it stayed out of
the stable schema.
Validation
docs/reference/protocols/protocol-action-packet-decision-v0.mddocs/product/codex-cli-same-open-tui-continuation-observation.mdexamples/interaction-pattern-catalog-smoke.pyexamples/codex-cli-visible-attach-acceptance-smoke.py
Trigger
- a connector todo or packet proposes running a browser, chat, platform, or document connector against a real service;
- the source status is
private_needs_review,metadata_only, or otherwise constrained by an owner gate; - a live connector route can autoload source bodies, message lists, derived reports, media, or engagement data before the agent can stop it.
Expected behavior
Connector packets must carry a machine-readable runtime policy before the first browser/API run. The policy says which probes are allowed before approval, which URLs or path prefixes are forbidden, whether browser open is allowed, and what owner decision unblocks the next stage. For private connectors, the safe default is gate projection only. For public metadata connectors, the safe default is a bounded metadata probe rather than opening a page that may autoload timelines, media, or analytics.
This pattern complements IP-004. IP-004 makes the owner ask concrete; IP-028 prevents the connector runtime from accidentally consuming the gated material while the ask is still pending.
State contract
connector_runtime_policy = {
schema_version: content_ops_connector_runtime_policy_v0,
access_mode: public_metadata_only | private_metadata_only | synthetic_fixture_only,
safe_default: head_only_metadata_probe | gate_projection_only | fixture_only,
browser_open_allowed_before_gate: false,
allowed_probe_methods: [...],
forbidden_url_path_prefixes_before_approval: [...],
forbidden_before_approval: [...]
}
Bad smell
An agent opens a private connector's default web route because the page looks like a convenient UI, but that route automatically calls message-list or message-detail APIs. Another bad smell is treating a public social profile as "metadata only" while the browser auto-downloads timelines, post bodies, media, or engagement streams.
Validation
docs/reference/protocols/content-ops-surface-v0.md;examples/content-ops-public-handle-observation-smoke.py;examples/content-ops-private-connector-gate-smoke.py;examples/interaction-pattern-catalog-smoke.py.
Trigger
waiting_on=external_evidence, a launched external worker is being polled, orinteraction_contract.mode=external_evidence_observation;- the selected action is evidence observation, compact result ingest, or compact blocker writeback;
- benchmark/model/Docker/cloud execution is not explicitly authorized by the current guard.
Expected behavior
The agent must distinguish observing an external handle from launching new external work. If a compact handle exists, it may poll or ingest compact public-safe result files. If the required handle is missing, the correct action is a compact blocker or projection repair, not a quiet no-op. Benchmark execution, model calls, Docker, cloud jobs, uploads, and leaderboard paths stay blocked unless the guard explicitly selects that work.
Visual Model
flowchart TD
E["external evidence mode"] --> H{"observable handle present?"}
H -->|"no"| B["write compact blocker / repair projection"]
H -->|"yes"| P{"compact result or failure marker present?"}
P -->|"no"| O["bounded poll; no spend if unchanged"]
P -->|"yes"| I["ingest compact result or blocker"]
I --> V["validate boundary and write event"]
V --> N["next guard decision"]
Bad smell
The heartbeat treats external-evidence waiting as a harmless quiet skip even though the guard requires an observable handle, or it launches a benchmark run from a meta/controller poll that was only authorized to observe.
Validation
regression/external-evidence-observation-real-codex.pyexamples/benchmark-lifecycle-state-smoke.pydocs/state-interaction-model.md
Trigger
- a benchmark adapter, runner wrapper, or reducer observes preflight, launch, materialization, compact result, comparison, claim review, or learning-ledger evidence;
- a controller is deciding whether a process launch, case attempt, score, budget spend, rerun, or public claim is countable.
Expected behavior
Benchmark work should advance through compact lifecycle gates instead of raw
runner narratives. process_started alone is not case entry. Case entry starts
at job_root_materialized or later. Budget/counting and candidate selection
require compact result ingestion, claim boundary review, and learning-ledger
state where applicable. Terminal failure markers can close out a launched
attempt without making it a case attempt or benchmark-budget event.
Visual Model
flowchart LR
P["preflight ready"] --> L["process started"]
L --> M{"job root / trial materialized?"}
M -->|"no"| B["not countable; materialization blocker"]
M -->|"failure marker"| F["terminal compact failure closeout"]
M -->|"yes"| R["compact result ingest"]
R --> C["claim / attribution review"]
C --> G{"learning ledger ready?"}
G -->|"no"| W["block budget count / candidate switch"]
G -->|"yes"| K["budget count allowed"]
Bad smell
A runner PID, detached process, stale active job, or raw log tail is treated as evidence of a benchmark case attempt or score claim before the compact lifecycle state says it is countable.
Validation
docs/research/long-horizon-agent-benchmarks/benchmark-core-adapter-contract-v0.mddocs/research/long-horizon-agent-benchmarks/terminal-bench-runner-mode-contract-v0.mdexamples/benchmark-lifecycle-state-smoke.pyexamples/benchmark-core-adapter-contract-smoke.pyexamples/terminal-bench-runner-mode-contract-smoke.py
Trigger
- no-progress streaks, repeated action loops, phase transitions, or periodic
review thresholds make a blocking
autonomous_replan_obligation_v0visible in active state, status, quota, or run history; or - a background planning lane surfaces
dreaming_proposal_v0/server_managed_planning_contract_v0as advisory context.
Expected behavior
An autonomous replan obligation is executable repair work: split, add, retire, or re-rank todos so the next delivery segment can advance. A dreaming proposal is advisory until promoted by an operator/controller decision and a normal quota/boundary check. Dreaming proposals may be displayed alongside a blocking replan obligation, but they stay a side lane: they can inform review or repair, not enter promotion/execution until the blocking replan obligation is absent or resolved. The two lanes must not collapse into each other.
Replan closeout is explicit. A normal validated progress refresh may record
useful work, but it must not silently close the
autonomous_replan_obligation_v0. After the agent performs the bounded replan
slice and writes back the selected todo/guidance changes, it must append a
structured ACK:
loopx refresh-state \
--goal-id <goal-id> \
--classification autonomous_replan_recorded \
--autonomous-replan-recorded \
--repair-delta-kind runnable_todo_set \
--delivery-batch-scale <scale> \
--delivery-outcome <outcome>The resulting run carries autonomous_replan_ack_v0 with
repair_delta_contract_v0. Classification remains a human-readable history
label; the structured ACK plus delta_present=true is the control-plane signal
that lets status/quota stop projecting the replan obligation. This keeps agents
responsible for actively closing the loop instead of relying on loose
classification wording such as autonomous_replan_validated_*.
If --autonomous-replan-recorded is used without a delta, the run is stored as
replan_noop/repair_noop; any progress outcome is downgraded so it cannot
hide the unresolved obligation.
A replan ACK should also change the work frontier. It should add, split,
supersede, complete-with-successor, or block a todo, or else record an explicit
watch-lane continuation. If the runnable todo set, user gate, blocker state,
monitor target, or effective_action does not change, the replan is likely a
no-op and should not hide the stall.
Visual Model
flowchart TD
S["status / run history"] --> R{"blocking replan obligation visible?"}
S --> D{"dreaming proposal present?"}
D -->|"yes"| V["display advisory dreaming side lane"]
D -->|"no"| Z["no dreaming side lane"]
R -->|"yes"| A["execute bounded replan repair"]
A --> T["update todos / guidance"]
T --> C["append structured replan ACK"]
C --> Q["rerun quota guard"]
V -. "may inform repair; no delivery spend while blocked" .-> A
R -->|"no"| G{"dreaming proposal eligible for promotion?"}
G -->|"yes"| U["ask/promote through user or controller gate"]
U --> P{"promoted and boundary approved?"}
P -->|"yes"| Q
P -->|"no"| X["proposal remains non-executable"]
G -->|"no"| N["normal selected interaction mode"]
Bad smell
A proposal from the dreaming/planning lane carries an agent_command or spends
delivery quota before promotion. The opposite failure is also costly: repeated
no-progress evidence is treated as optional brainstorming instead of a required
state repair. A subtler failure is treating
autonomous_replan_validated_* or another progress classification as equivalent
to a replan ACK; that hides whether the agent actually split/retired/added the
needed control-plane work and closed the obligation. A related bad case is a
replan ACK that records effort but leaves the exact same monitor/action
recommendation as the next machine-visible route.
Validation
examples/autonomous-replan-obligation-smoke.pyregression/autonomous-replan-vs-dreaming-contract.pydocs/archive/incidents/monitor-only-replan-stall-incident-20260621.mddocs/archive/incidents/agent-scoped-replan-precedence-incident-20260703.md
Trigger
- self-repair, replan, or no-progress handling records activity, but the next quota/status packet returns the same monitor/action recommendation;
- repeated monitor-only, replan, or repair-adjacent runs do not create a new runnable todo, blocker, successor, supersede record, user gate, capability change, workspace guard change, or monitor-target change;
- an agent says it repaired the state, but the machine-visible work frontier is identical before and after the repair.
Expected behavior
A successful repair/replan must change the machine-visible frontier. At least one of these surfaces should change:
- selected
effective_actionor interaction contract; - runnable todo set, claimed work lane, successor, or supersede relationship;
- concrete user question/todo or blocker;
- capability/workspace guard outcome;
- monitor target, expiry, watch-lane rationale, or evidence handle;
- active-state Next Action or goal-boundary projection.
If none of those change, the repair should be recorded as a no-op or unresolved blocker, not as progress. The next safe action should then be to create the missing successor/blocker/supersede/watch-lane record, or to stop with a clear reason that the current monitor is intentionally quiet. This is separate from IP-013's replan ACK: the ACK closes the obligation, while the delta contract proves that closing it did not hide the same stuck route.
Visual Model
flowchart TD
R["self-repair or replan run"] --> B["snapshot before/after frontier"]
B --> C{"any machine-visible delta?"}
C -->|"yes"| P["record progress and rerun quota"]
C -->|"no"| N["classify repair_noop / replan_noop"]
N --> D{"why no delta?"}
D -->|"stale route"| S["supersede or create successor todo"]
D -->|"real blocker"| K["record blocker or user todo"]
D -->|"intentional watch"| W["record monitor target + expiry"]
Bad smell
The agent performs a replan, appends a history row, and reports that the loop was handled. The next heartbeat still receives the same recommended action, same monitor target, same todo set, and same lack of blocker. That is not a resolved repair; it is an unclosed control-plane loop with better narration.
Validation
docs/archive/incidents/monitor-only-replan-stall-incident-20260621.mdskills/loopx-self-repair/references/repair-patterns.mdexamples/control_plane/heartbeat-quota-flow-smoke.py- future regression that compares before/after frontier fields for repair and replan closeout runs.
Trigger
- recent eligible turns have a thin-progress streak;
- delivery repeatedly lands as
single_surface, status-only, or shallow docs without a coherent artifact; - no safety boundary prevents a larger segment.
Expected behavior
The controller treats the derived cadence hint as a low-confidence steering
signal. When the hint says thin_progress with recommendation widen, the
next eligible turn should usually include an artifact, focused validation, and
state writeback, or write a blocker explaining why widening is unsafe.
Visual Model
flowchart TD
R["recent turns"] --> S{"thin-progress streak >= threshold?"}
S -->|"no"| C["keep current cadence"]
S -->|"yes"| B{"safe to widen?"}
B -->|"no"| G["ask gate or write blocker"]
B -->|"yes"| W["widen next eligible segment"]
W --> D["artifact + validation + writeback"]
Bad smell
The agent's native long-task ability is degraded because the control plane keeps asking it to do tiny heartbeat-shaped steps, or the host treats a derived hint as a hard permission or timing policy.
Validation
docs/long-task-cadence-policy.mdexamples/long-task-cadence-policy-smoke.py
Trigger
- the agent tells the user a connected LoopX plan, top-todo list, route change, benchmark branch policy, deprecation policy, or priority stack;
- the plan contains concrete future P0/P1/P2 work, user actions, route decisions, or cleanup/deprecation commitments;
- future agents would need that plan to choose the next bounded segment.
Expected behavior
User-facing plans are not durable control-plane state by themselves. Before the final response, the agent must do one of:
- add or update concrete
Agent Todo/User Todoitems; - refresh
Next Actionor the latest run'srecommended_action; - write a public-safe doc/catalog entry and add the corresponding todo;
- explicitly say the plan is speculative and no writeback was performed.
This keeps the model's understanding, the user's mental plan, and LoopX
state from diverging. It also prevents a later heartbeat from following a stale
recommended_action even though the previous turn already explained a better
route.
Visual Model
flowchart TD
P["agent explains plan / top todos / route change"] --> C{"concrete future work?"}
C -->|"no"| A["answer normally"]
C -->|"yes"| W{"writeback target"}
W --> T["todo add/update"]
W --> N["Next Action / refresh-state"]
W --> D["doc/catalog plus todo"]
W --> R["no-writeback rationale"]
T --> Q["quota/status can project it"]
N --> Q
D --> Q
R --> F["final says speculative/no durable change"]
Bad smell
The agent says "current Top Todo: P0 cloud Codex login, P0 pull clean benchmark workspaces, P1 split-control retrospective, P1 branch hygiene runbook", but the active LoopX state contains only one broad P0 and none of the P1 successor work. The next automation then behaves as if the plan never existed.
Validation
skills/loopx-project/SKILL.mdskills/loopx-self-repair/references/repair-patterns.mdexamples/control_plane/heartbeat-prompt-smoke.py- future status/quota smoke that flags user-facing plans without todo or refresh-state writeback.
Add or update a pattern when any of these happens:
- a good case demonstrates a reusable product behavior;
- a bad case or incident reveals a missing state projection;
- a smoke encodes a behavior that is not yet explained to humans;
- a dashboard/status field changes who owns the next action.
Every new pattern should link to at least one validation path. If validation does not exist yet, mark it as a future smoke rather than hiding the gap.
When a pattern is useful for partner/user explanation, add a visual artifact or explicitly mark the visual as future work. Good visuals should show ownership and the allowed next action, not just boxes for implementation modules.
Do not let this catalog become a second source of truth. The source of truth is still the runtime state, quota/status payloads, and event ledger. This catalog is the human-maintained map of the situations those payloads must express.