Core state machines and transitions¶
The short answer is: LoopX is driven by cooperating state-machine families, but its highest-level model is not “nine machines messaging one another.” It is one effectful Agent Loop. The Harness interprets effect requests from an Agent or Host; bounded state machines such as Todo, Gate, Quota, Settlement, and Scheduler then determine the legal action for their part of the flow. They connect through durable facts, typed contracts, guarded transitions, and receipts instead of overwriting each other's state.
LoopX does not advance a Goal through one giant state machine. It assigns durable work, turn decisions, evidence settlement, scheduling, and UI projection to state machines with explicit owners. This chapter establishes vocabulary and abstraction levels first, explains why the design is split this way, shows the cooperation loop, and only then expands each state-machine family. You can understand the main path before drilling into one rule family.
Choose the level first: one Loop, three abstraction levels, nine state-machine families¶
The same implementation can be understood at three abstraction levels. First decide which level you are reading; do not mix terms from all three:
- Outer — Agent Loop asks “how does one turn lead to another?” Its minimal model is
effect request -> interpretation -> effect -> observation -> next effect. - Middle — control-plane protocol asks “who decides, who executes, and how is it proved?” It focuses on source facts, derived decisions, guarded writeback, receipts, and projections.
- Inner — domain state machine asks “which states and edges are legal in this bounded context?” Todo, Gate, Quota, Handoff, and others each own an interpretation table.
So “LoopX is a multi-state-machine system” is correct at the inner level. At the outer level, the more precise statement is: one Agent Loop is interpreted and constrained by multiple bounded state-machine families. The machines are decision tables inside the Harness, not nine peer microservices or nine Agents.
Vocabulary first: separate facts, decisions, actions, and proof¶
These terms recur throughout the chapter. On a first read, remember only what each one is and is not:
First read one turn in plain language: the Goal supplies the long-running boundary; the frontier supplies current work; the control plane derives this turn's decision; the Agent / Host executes an effect; a validated observation becomes durable fact; and the receipt plus projection connects the next turn.
- Goal and work — Goal, Todo, frontier, Vision / replan: Goal is the long-running identity and boundary; Todo is a work item; frontier is the viable-work boundary; Vision / replan preserves route and the basis for changing direction. They are not one whole-object Goal state, and one completed Todo is not Goal completion.
- Facts and views — source / authoritative fact, derived decision, projection: source is durable owner-written fact; decision is the current rule result; projection is a view for an Agent, Host, or person. Not every displayed field is writable, and projection is not a second source of truth.
- Action and proof — effect request / proposal, observation, transition, evidence, receipt: proposal requests an action; observation reports external fact; transition accepts legal change; evidence supports a judgment; receipt proves an identified commit. A request is not authority, and a tool result is not automatically completion.
- Constraints and runtime — Gate, lease, quota, Scheduler / heartbeat, Host / Runtime: these respectively scope decisions, grant temporary execution rights, determine turn eligibility, schedule the next wake, and execute effects. They are not five names for one global lock.
First separate three kinds of state¶
Not every field displayed as state is directly writable. The most important first step is to classify it by ownership:
- Source state is durable, replayable fact such as Todo
status,claimed_by, Goalactivation_state, events, and scheduler receipts. Only the corresponding write API or owner service may change it. - Derived decision is a current-turn judgment compiled from facts, such as quota
eligible,operator_gate, handoffcleared_with_successor, andinteraction_contract. Change source and recompute it. - Projection is a view for a person or Host, such as Workspace cards, status, review packet, and scheduler hint. It is rebuildable, and user actions still return to a write API.
A common mistake is treating a derived result as writable truth. A Todo has no durable running status;
running is derived from current quota selection, a lease, and run history. Moving a Workspace card to
done also cannot bypass the owners of Todo completion, evidence, and receipts.
Why this design instead of one large state machine?¶
The split does not exist to create terminology. These states change for fundamentally different reasons:
- Different lifetimes: Todos and evidence survive sessions; a quota decision is valid for one Turn; a projection can always be rebuilt.
- Different authority: an Agent may propose an effect, a Host may execute it, and only the transition owner may accept a fact change.
- Different recovery: a network call needs reconciliation, a Todo conflict needs rebase, and display drift only needs reprojection.
- Different concurrency boundaries: Goal identity, Todo snapshot, lease, and provider revision cannot be disguised as one global integer version.
- Different audit questions: the system must separately answer why an action was chosen, what happened, who accepted it, and why another turn should run.
Collapsing these concerns into Goal.status or allowing an Agent to replace GoalState wholesale could
erase another Agent's progress. A network timeout would no longer say whether to retry, read back, or
compensate. Display fields would also drift into becoming sources of truth.
Cooperation does not mean direct messaging. The nine state-machine families connect primarily through four stable interfaces:
- Source facts: reducers / interpreters read Todo, event, lease, Vision, and receipt facts.
- Typed decision / effect:
interaction_contract, the selected action, and the scheduler hint tell an Agent or Host which bounded action to perform. - Guarded transition: the owner validates proposal, identity, revision basis, and evidence before committing an event, receipt, or CAS.
- Readback / projection: committed facts and the operation receipt rebuild Workspace/status and derive the next effect.
See the cooperation skeleton first: facts and protocols connect the machines¶
This diagram shows only the main loop, not the internal states of any family. It answers how multiple state machines drive one system: the machines on the left read shared facts and participate in interpretation; one turn emits one bounded effect or ordered effect program; and an external observation must be validated and written back before it becomes fact for the next turn.
flowchart TD
Facts["durable facts<br/>Goal / Todo / event / receipt"] --> Interpret["state-machine interpretation<br/>Todo · Gate · Quota · Handoff"]
Interpret --> Decision["typed decision<br/>interaction contract"]
Decision --> Effect["Agent / Host<br/>executes bounded effect / program"]
Effect --> Observation["observation<br/>result or external change"]
Observation --> Validate["validate + guarded transition"]
Validate --> Facts
Facts -.-> Projection["projection<br/>Workspace / status"]
This is not a sequence diagram in which state machine A calls state machine B. Source facts, typed decisions, effects, observations, and receipts are what pass between stages. Each machine owns only its decision table and legal transitions.
L0 through L4: use the vocabulary to answer five architecture questions¶
L0: Why does LoopX need to exist?¶
A long-running Agent's model context is useful for reasoning, but it cannot be the sole owner of execution state. Context may be compacted, switched, or restarted, while external effects, permissions, collaboration, wait conditions, and acceptance evidence often outlive one session. If those facts exist only in a conversation, the next turn cannot reliably answer what happened, who may continue, whether to retry, or whether the Goal is complete.
LoopX therefore keeps the control plane outside model context: state is durable, changes are replayable, writes are guarded, and a bounded current view is projected back to the Agent. The model reasons and proposes actions; the control plane preserves facts, constrains transitions, and derives the next legal action on the following turn.
L1: Which components make up the control plane?¶
| Component | Responsibility | Typical LoopX carrier |
|---|---|---|
| Goal | Supplies the objective semantics and identity to advance; the current implementation does not place all intent under one typed owner | registry identity, active state, Agent Vision, and the Todo frontier |
| Authoritative state | Preserves recoverable execution facts | registry, Todo/event source, run history, receipts |
| Evidence | Proves an outcome, blocker, or external effect state | artifact refs, validation/readback, rollout/rollback events |
| Transition | Validates and commits one legal state change | Todo/Goal write APIs, settlement, scheduler ACK |
| Projection | Compiles facts into views for an Agent, Host, or person | quota/status, Workspace, review packet |
| Runtime | Interprets the turn decision and executes a bounded effect | Codex App/CLI, heartbeat, extension provider |
These components are not one JSON document that a Runtime may overwrite. The Runtime executes effects; authoritative-state and transition owners decide whether facts actually changed.
L2: What does authoritative state look like?¶
For orientation, first imagine the desired Goal control plane as this conceptual aggregate view. It describes the questions one read should answer, not a unified schema already implemented in LoopX 1.0:
GoalControlSnapshot: # desired read model, not the one writable schema
identity:
goal_id: ...
activation_state: active | stopped
intent:
objective: ...
acceptance: ...
terminal_conditions: ...
frontier:
completed_requirements: [...]
pending_requirements: [...]
todos: [...]
gates_and_blockers: [...]
outcome:
artifacts: [...]
evidence_refs: [...]
basis:
revision_basis: state_event_log | markdown_active_state | canonical_todo_snapshot
state_event_basis_sequence: ...
source_basis_digest: ...
todo_basis: # separate revision basis for the canonical Todo snapshot
source_authority: file_v0
provider_revision: ...
records_sha256: ...
LoopX 1.0 does not expose one GoalState object that may be replaced wholesale. More importantly,
objective, non-goals, acceptance, permissions, and terminal conditions do not yet have unified typed
canonical storage. The intent block above is a target model; it must not be presented as an implemented
authoritative envelope. Today's shared Goal alignment is a read-only aggregate: it obtains a source basis
from the event log, Markdown active state, or canonical Todo snapshot; identifies the Todo/lease snapshot
through a separate todo_basis; and then projects drift and conflict:
| Common abstract field | Actual LoopX expression |
|---|---|
goal_id |
Stable identity in the registry and every goal-scoped event |
phase |
Goal activation is only active | stopped; stage routing belongs to Agent Vision / Todo, not a universal Goal phase |
objective / acceptance / permissions / terminal conditions |
Currently distributed across project material, Vision, Todos, and runtime constraints; there is no unified typed canonical intent revision |
completed_requirements / pending_requirements |
Aggregated from available Todo, Vision-checkpoint, acceptance-gap, and frontier facts; not independently writable lists |
artifacts / evidence / blockers |
References and typed facts held by Todos, runs, events, and receipts |
version |
Owner-specific event append_sequence, source checksum, or opaque provider revision; no global Goal version exists |
This is the concrete form of the owner separation described above: reads may aggregate, while writes still return to the appropriate authority.
L3: How does an Agent modify authoritative state?¶
An Agent submits a proposal or typed effect, not a wholesale “the new state should look like this” value:
flowchart TD
Read["read source + revision basis"] --> Propose["Agent submits bounded proposal / effect"]
Propose --> Guard["transition owner / Runtime boundary validates schema, identity, authority, lease, evidence, and Gate"]
Guard --> Conflict{"does the source basis still match?"}
Conflict -->|no| Rebase["revision conflict / needs_rebase: read again"]
Conflict -->|yes| Commit["owner-specific guarded commit"]
Commit --> Event["append event / receipt, or CAS authoritative head"]
Event --> Readback["readback + rebuild projection"]
Readback --> Next["derive the next legal action"]
Here, “CAS” is a concurrency-control principle, not a claim that the entire repository has one integer
version:
- event-sourced Todo writes compare the validation checksum, last event, and append sequence before appending;
- the shared authority store performs its real compare-and-swap with the opaque
expected_provider_revision(a generation in the file provider); neitherauthority_revisionnorlease_epochmay substitute for it; - the local-state correctness module builds an
expected_revision, per-Goal lock, lease, and idempotency envelope in dry-run/shadow mode; it explicitly does not mean that the current apply path enforces those guarantees, because the caller still owns the actual lock, write, and event; - settlement binds writeback, spend, and scheduler receipts to
goal_id + agent_id + turn_instance_id.
The matching source anchors are
event_writeback.py,
local_state_write_correctness.py,
authority_store.ts,
coordination/executor.py,
and settlement.py.
The real pattern is “read basis -> propose -> validate/guard -> guarded commit -> event or receipt -> readback,” not direct replacement of a state object that merely looks complete.
L4: What if the Agent is wrong?¶
The control plane recovers a bad judgment separately from an external side effect that has already happened:
| Where the error occurs | Protection or recovery | What must not happen |
|---|---|---|
| The proposal is invalid | Schema, authority, Gate, and evidence validation reject the write | Write first and invent justification later |
| Source changes after it was read | Revision conflict / needs_rebase; reread and replan |
Silently overwrite newer facts |
External effect is running or its result is unknown |
reconcile the same invocation / idempotency identity |
Start the same effect again |
| A provider step governed by the settlement journal was prepared when writeback was interrupted | Read back with the same effect_ref: reuse committed, execute only when absent, and fail closed on unknown |
Redo the provider step before proving it absent |
| A committed effect later proves wrong | Append rollback / compensation evidence and create a successor or replan | Delete the old event or pretend it never happened |
| Projection disagrees with source | Repair the source or projection builder and project again | Edit the dashboard and call it repaired |
This is why side effects are modeled separately: a model can change its mind, but the external world cannot
be rolled back by editing context. Validation, version/identity guards, reconciliation, compensation events,
and replanning turn the error into another verifiable transition instead of an untraceable chat conclusion.
This idempotent-recovery guarantee has a precise boundary: it applies to settlement steps with a durable
journal, an effect_ref, and a provider readback resolver. It is not a promise that every external tool
call is automatically deduplicated. Settlement fails closed when the resolver is missing, raises, or
returns an unknown state.
How nine state-machine families compose one Loop¶
Now the state map is useful. The maintainer-level map separates the control plane into nine cooperating state-machine families. “Nine” is the current teaching map for core rules, not a protocol constant that requires every extension to register nine runtime services. This book groups them into four reader-oriented layers:
| Layer | Main machines | Question answered |
|---|---|---|
| Durable work and authority | Todo lifecycle, Gate decision scope, Owner route / handoff | What should run, who may run it, and which decision is missing? |
| Turn execution and settlement | Quota / runtime, Evidence / rollout / rollback | May this turn run, and which result may be written and charged? |
| Long-horizon continuity | Scheduler / heartbeat, Agent Vision / replan | When should the system wake again, and when must it change route instead of repeat? |
| Onboarding and presentation | Projection sink, Agent onboarding / automation enablement | How does a Goal enter a runtime, and how is its state shown safely? |
flowchart TD
Sources["Registry + active state + Todos + events"] --> Decide["quota should-run compiles an interaction contract"]
Decide --> Run["execute one bounded segment"]
Decide --> Gate["ask for a scoped decision"]
Decide --> Wait["wait / monitor quiet"]
Decide --> Repair["repair / replan"]
Run --> Validate["independent validation"]
Repair --> Validate
Validate --> Writeback["durable writeback"]
Writeback --> Spend["quota spend once"]
Spend --> Schedule["scheduler apply + ACK"]
Gate --> Schedule
Wait --> Schedule
Schedule --> Sources
Sources --> Projection["Workspace / status / review packet"]
Projection --> UserAction["user or Host proposes an action"]
UserAction --> Sources
There is no direct write shortcut from a projection back into the decision. A UI can propose a governed action; the actual transition still goes through a write API, validation, and a receipt. The arrows are fact and contract dependencies, not process messages between state machines.
1. Durable work: Todo, Gate, and Handoff¶
Todo lifecycle: only four durable statuses¶
loopx/control_plane/todos/contract.py
defines the durable Todo statuses:
claimed_by, resume_when, superseded_by, unblocks_todo_id, and no_followup are orthogonal
fields, not additional statuses. Together they determine the next legal route.
stateDiagram-v2
[*] --> Open
Open --> Blocked: blocker + reason
Blocked --> Open: blocker repaired / scope restored
Open --> Deferred: concrete resume_when
Open --> Done: validated completion
Blocked --> Done: blocker accepted as terminal outcome
Done --> [*]
Both done and deferred are terminal statuses, but satisfying a resume condition does not itself rewrite
the status. It first produces a derived candidate and then requires an explicit lifecycle choice:
flowchart LR
Deferred["persisted deferred Todo"] --> Condition{"resume condition satisfied?"}
Condition -->|no| Wait["remain deferred"]
Condition -->|yes| Candidate["deferred_resume_candidate<br/>status is still deferred"]
Candidate --> Reopen["explicit reopen<br/>status=open + clear resume_when"]
Candidate --> Supersede["explicit supersede<br/>predecessor=done + linked successor"]
Candidate --> Close["record no-follow-up rationale / closeout"]
| Source / derived field | Owner | Correct interpretation |
|---|---|---|
status |
Todo contract | Durable lifecycle; do not invent a fifth status |
claimed_by |
Todo metadata | A routing signal, not a distributed lock |
| task lease | lease lifecycle | Time-bounded execution right; recoverable after expiry |
Running |
Derived from quota + lease + run history | A bounded attempt exists; do not write it as Todo status |
superseded_by |
Todo relation | Preserve history and point to a replacement; do not delete the old Todo |
resume_when |
Todo relation | A verifiable recovery condition for deferred work, not “look later” |
Illegal transitions include marking open as done without completion evidence, claiming that a task is
running solely because it is claimed, treating a satisfied condition as an automatic deferred -> open,
deleting superseded history, or deferring “until the user checks” without a machine-readable condition.
supersede is not a simple deferred -> done edge either: the command records the predecessor as done,
writes note=superseded, and creates and links the successor.
Gate: constrain a decision scope, not the entire Goal¶
The Gate source consists of task_class=user_gate, decision_scope, required_decision_scopes,
global_gate, and agent-blocking fields. It first asks which action or lane the decision covers, then
decides whether this turn should ask the user or run an independent fallback.
flowchart LR
OpenGate["open Gate"] --> Scope{"covers selected action?"}
Scope -->|yes| Ask["ask for the concrete decision"]
Scope -->|no| Fallback["keep Gate visible + run independent fallback"]
Scope -->|conflicting fields| RepairProjection["repair projection"]
Ask -->|approve| Consume["consume only covered scopes"]
Ask -->|reject / cancel| Replace["supersede or compensate"]
Ask -->|later| Defer["write resume_when"]
This is why user_channel.action_required=true and agent_channel.must_attempt=true can both hold. The
first exposes an owner-held decision; the second authorizes only explicitly selected work independent of
that decision.
Handoff: cleared does not mean the route is complete¶
handoff_gate.py
derives six handoff states from Todo relations:
| Derived state | Meaning | Next step |
|---|---|---|
blocking |
An owner route still blocks the current Agent | Quiet wait or expose the concrete Gate |
cleared_with_successor |
The blocker is done and a successor exists | Route to the successor |
cleared_without_successor |
The blocker is done without a successor or no-follow-up | Run successor replan first |
cleared_no_followup |
The owner explicitly ended the route | Close out |
superseded |
A replacement Todo exists | Follow the replacement |
deferred |
The resume condition is not satisfied | Wait for or observe the condition |
These values are projections and must not be edited by hand. Repair cleared_without_successor by adding
a valid successor, reopening work, or recording no_followup, not by changing the displayed value to
cleared_with_successor.
2. Turn execution: Quota, Interaction Contract, and Settlement¶
Quota runtime: decide behavior before considering spend¶
loopx/control_plane/quota/states.py
fixes the decision precedence of quota runtime states:
This is reducer precedence when several conditions coexist, not a transition graph in which
blocked_health advances through each state until paused. Every turn decides again from current source
facts.
| Runtime state | Legal action this turn | Typical recovery |
|---|---|---|
blocked_health |
Repair registry, projection, workspace, or capability when authority allows | Validate the repair, then recompute |
operator_gate |
Expose the concrete owner decision | Recompute after approve / reject / defer |
focus_wait |
Recover only the named outcome or fresh evidence | Write recovery evidence or a compact blocker |
eligible |
Execute the one selected bounded action | Enter settlement |
waiting |
Observe a concrete external handle, or wait quietly | Material observation / satisfied condition |
throttled |
Do not deliver | Wait for the quota window or owner adjustment |
paused |
Do not run an automatic Turn | Explicit resume; the Goal activation owner controls active/stopped |
A quota state alone is not authorization. The interaction_contract also supplies user, Agent, and CLI
channels, workspace guards, capability gates, the selected Todo, execution obligations, and a scheduler
hint. must_attempt_work is a boolean obligation: when it is true, the Agent must attempt bounded work
and write back in this turn; it does not select the action. Run selection_command when Todo selection is
required; otherwise follow the capability packet or next_cli_actions[0]. Reuse the same
turn_instance_id across the quota guard, selection, refresh, spend, and scheduler settlement. Do not
infer the action from NOTIFY (which controls user output only) or from should_run in isolation.
Settlement: success lands in transaction order¶
turn_transaction_contract.json
defines the phases of a complete Turn:
host_execute
-> typed_result
-> validation
-> durable_writeback
-> quota_spend
-> scheduler_apply
-> scheduler_ack
stateDiagram-v2
[*] --> HostExecute
HostExecute --> TypedResult
TypedResult --> Validation
Validation --> DurableWriteback: receipt committed
Validation --> Failed: invalid or missing receipt
DurableWriteback --> QuotaSpend: writeback accepted
DurableWriteback --> Failed: rejected / identity mismatch
QuotaSpend --> SchedulerApply
SchedulerApply --> SchedulerAck
SchedulerApply --> RetryHostUpdate: host update failed
RetryHostUpdate --> SchedulerApply
SchedulerAck --> [*]
Failed --> RepairOrRetry
Settlement identity binds goal_id + agent_id + turn_instance_id, plus exactly one Todo or exactly one
autonomous replan obligation. This prevents receipts from an old Turn, another Agent, or another work
item from being reused.
Three invariants must hold:
- no durable writeback without a validation receipt;
- no spend when durable writeback is missing or rejected;
- no claim that the Host changed without an ACK or failure receipt for scheduler apply.
A failure does not erase the transaction. Failure kinds such as receipt_missing, identity_mismatch,
writeback_rejected, and quota_spend_rejected return control to repair or retry while preserving the
effect identity for idempotence.
When the journal retains a prepared provider effect, recovery must first read the provider with the same
effect_ref: only absent permits execution, while committed reuses the committed payload. A missing
resolver, an exception, or an unknown result fails closed. This guarantee covers only steps governed by
the settlement journal and resolver.
Evidence / rollout / rollback: append compensation; do not rewrite history¶
The evidence machine decides whether a transition is trustworthy, not what the user wants the system to do. It advances a hypothesis into an acceptable fact or preserves it as a concrete blocker:
hypothesis
-> evidence bundle
-> validated snapshot | blocker evidence
-> rollout event + mutation anchor
-> optional rollback / compensation event
-> successor
Artifact references, test or build results, external readbacks, and commit, PR, or document revisions can all serve as mutation anchors. Rollback does not delete the original evidence or rollout event. It appends a compensating fact and usually creates or unlocks a successor. “Changing the old state back” without recording why is not a legal recovery and cannot prove which fact a later projection used.
3. Long-horizon continuity: Scheduler, Monitor, and Vision¶
Scheduler / heartbeat: decide when to look again, not whether work is allowed¶
A scheduler hint is derived from the resolved quota and interaction contract. Common actions include run
now, wait for the user, wait for reassignment, wait for a material transition, wait for fresh evidence,
wait for any state change, and stop or return to the owner. Stops do not come only from Goal closure: a
stopped Goal, paused quota, or blocked peer coordination can also stop polling or return control. Only
terminal_no_followup means “stop because validated Goal closure was derived.” The runtime profile and
scheduler owner determine the actual cadence.
flowchart TD
Decision["resolved interaction contract"] --> Hint["scheduler hint"]
Hint --> Apply{"host cadence already matches?"}
Apply -->|no| HostUpdate["apply host update"]
Apply -->|yes| Ack["record host-match ACK"]
HostUpdate -->|success| Ack
HostUpdate -->|failure| Fail["record failure receipt"]
Ack --> NextTick["next tick re-runs quota"]
Fail --> Retry["bounded retry / backoff"]
When a reset_token or identity changes, cadence returns to its initial interval. Only an unchanged
identity advances through backoff. A schedule change does not spend quota and cannot turn a paused or
blocked Goal into an eligible one.
Continuous Monitor: observation is also a bounded state machine¶
A Monitor Todo must carry bounded stop or resume information such as expires_at, resume_when, or a
bounded no-change policy. A poll writes only compact observation facts: last_checked_at, result_hash,
consecutive_no_change, and whether the change is material. The external network poll, quota settlement,
and display projection are separate effects. An external result must first become a typed observation before
it can enter the Monitor write transaction.
- Before
next_due_at: quiet no-op; do not poll or spend. - Due with no change: write a no-change receipt and back off according to policy.
- Material change: write the observation and create an independent successor / Gate from explicit intent, then recompute quota.
- Expiry or stop condition reached: complete the Monitor and connect a successor or
no_followup.
For a Goal explicitly promoted to shared authority,
todo_monitor_poll.ts
commits a lease-free observation and its requested independent successors against one canonical revision,
with one CAS and one durable operation receipt. The transaction first validates actor registration,
claim/binding/exclusion, an active Monitor, and a genuinely advanced material-change generation. Any failed
guard writes neither half. Retrying the same operation replays the original receipt and successors. The same
evidence cannot generate duplicate work merely through a fresh material_change=true assertion. A no-change
observation updates observation state and cadence without creating a delivery Todo.
“Atomic” has a precise boundary here: it excludes the external network poll, quota spend, and
Markdown/dashboard delivery. After the canonical commit, projection delivery may still be pending; its
outbox retries display projection only, with retry_business_mutation=false, and must not repeat the committed
observation/successor mutation. Goals not promoted to shared authority still use the legacy writer, so this guarantee
must not be generalized into “all Goal state has migrated to one store.”
Treating a future next_due_at as an advancement frontier is wrong. It says when to observe, not how to
advance the Goal.
Agent Vision / replan: a route change must become a state delta¶
Vision is a bounded executable route per Agent, not a scratchpad. Routine continuation can submit a
vision_unchanged_reason. A material change in assumptions, scope, acceptance, or route requires a
bounded Vision patch plus a corresponding Todo or acceptance delta.
stateDiagram-v2
[*] --> VisionPatchProposed
VisionPatchProposed --> ActiveRoute: validation + write
ActiveRoute --> ActiveRoute: unchanged + reason
ActiveRoute --> ReplanRequired: frontier exhausted / drift / missing checkpoint
ReplanRequired --> VisionPatchProposed: Todo + acceptance + route delta
ActiveRoute --> VisionClosed: stage accepted, successor required
ActiveRoute --> NoFollowup: terminal evidence
ActiveRoute --> Superseded: replacement route
vision_closed closes the current stage but requires a successor. no_followup, retired, or
superseded expresses the corresponding closeout semantics. Answering “replanned” without a Vision,
Todo, acceptance, or no-follow-up delta is replan_noop and cannot clear the obligation.
4. Onboarding and presentation: Activation, Onboarding, and Projection¶
Goal activation does not mean automation is enabled¶
The typed Goal activation source has only active and stopped. active means that the control plane
may continue evaluation; it does not prove that a Host heartbeat is installed or that eligible work
currently exists. Complete onboarding still proceeds through:
project registered
-> registry/global visibility
-> quota can resolve Goal + Agent
-> optional Host consent and installation
-> first real tick verified
Conversely, stopped projects automatic Turns as paused. Deleting a Goal has a separate lifecycle
precondition. Do not collapse stop, disable automation, and delete into one action.
A projection sink reads facts; it does not own them¶
Workspace, status, frontstage, review packets, and external dashboards are projection sinks:
Repair a projection gap in the source or builder, then read back. Editing a dashboard row directly creates a second source of truth. Making a public sink depend on a private raw document, credentials, a transcript, or a local path breaks the public/private boundary.
What makes LoopX a closed loop¶
Closed-loop operation in LoopX is not “the Agent did something,” and it is not the same as Todo=done.
A more precise definition is: the result of an effect is validated and written back to the authoritative
source that owns that fact, so the control plane can derive either the next legal action or a proven stop.
If the result remains
only in chat, a workspace file, an external system, or dashboard prose without readback, durable writeback,
and a frontier audit, the path is still open-loop.
LoopX closes this feedback path at four nested levels:
| Closure level | Chain that must close | Closure evidence | Machine action while open |
|---|---|---|---|
| Effect loop | request -> external effect -> observation / readback | Exact revision, provider receipt, validation result, or explicit failure | Reconcile, retry, roll back, or record a blocker |
| Turn loop | decision -> execute -> validate -> writeback -> spend -> scheduler ACK | Ordered receipts under one settlement identity; provider readback for prepared effects | Resume a journal-governed step from the failed phase; fail closed on unknown readback |
| Work-graph loop | Todo -> outcome -> successor / Gate / monitor / no_followup |
Completion evidence and a next node that is runnable, explicitly waiting, or explicitly terminal | Expose a succession, handoff, or replan gap |
| Goal loop | Vision + acceptance -> multi-turn evidence -> frontier audit -> terminal | Acceptance, Todo sources, monitors, successors, handoffs, replans, and readbacks all close | Continue, wait, ask, replan, or repair; never pretend completion |
flowchart TD
Intent["Vision + Goal + acceptance"] --> Work["Todo / Gate / monitor frontier"]
Work --> Decide["quota + interaction contract"]
Decide --> Effect["bounded effect"]
Effect --> Observe["typed observation / external readback"]
Observe --> Validate["validation"]
Validate --> Writeback["durable writeback + receipts"]
Writeback --> Audit{"frontier + acceptance audit"}
Audit -->|"runnable successor"| Work
Audit -->|"explicit wait / Gate"| Schedule["scheduler apply + ACK"]
Schedule --> Work
Audit -->|"gap or drift"| Replan["repair / replan delta"]
Replan --> Work
Audit -->|"validated terminal no-followup"| Stop["stop recurring automation"]
Closure is more than one successful action: four checks¶
- Did the result come back? An external effect needs a typed observation or readback. An exit code of zero cannot replace authoritative provider, PR, file-revision, or deployment-state readback.
- Was the result persisted? After validation, evidence, the Todo outcome, the Vision checkpoint, and the Next Action must go through the owning write API. A chat summary is not writeback.
- Does the next step have a home? Completing a Todo must leave a runnable successor, a concrete Gate,
a wait with
resume_when/next_due_at, a repair or replan obligation, or evidence-backedno_followup. - Does the Host know whether to continue or stop? Scheduler apply needs an ACK or failure receipt, and
the next wake-up rereads canonical source.
terminal_no_followupis the basis for stopping recurring automation because the Goal is complete. A stopped Goal, paused quota, or blocked peer coordination may also produce a stop or return-to-owner action, but none of those proves Goal closure.
Terminal is a strict conjunction, not “the queue looks empty”¶
goal_frontier_is_terminal_no_followup in
loopx/control_plane/goals/goal_frontier/terminal.py
does not accept a handwritten terminal flag. It requires complete and closed Todo sources; no unresolved
advancement, monitor, successor, handoff, replan, acceptance, or autonomy-blocker frontier; and a structured
no_followup intent.
The following situations are therefore not closed-loop:
- A PR exists, but no exact-head CI or review readback, monitor, or successor exists.
- A Todo is marked done while acceptance is unmet or the Vision checkpoint is missing.
- An external operation succeeded, but durable writeback or the matching spend receipt is missing.
- Visible Todos are empty while a due monitor, blocked successor, Gate, or retryable sink remains.
- A heartbeat changed cadence, the Host never ACKed it, yet the control plane claims scheduling succeeded.
A closed loop does not require a positive result. A validated blocker, negative evidence, rollback, retired
path, or coverage-backed no_followup can close honestly. What matters is traceability, durable state, and
machine-verifiable continuation or termination.
One complete transition trace¶
Suppose an Agent is updating public documentation while homepage publication still waits for user approval:
- Read source: the documentation Todo is
openand claimable; the homepage Gate covers only the publication scope. - Compile decision: quota is
eligible; the user channel exposes the homepage Gate, while the Agent channel selects the independent documentation Todo. - Bind identity: the Turn binds the current Goal, Agent, Todo, and unique
turn_instance_id. - Execute: the Agent completes one bounded documentation change in the correct worktree.
- Validate: bilingual smoke, strict builds, and a boundary scan produce a validation receipt.
- Write back: Todo evidence records the revision, validation, and next action; completion creates a
successor or records
no_followup. - Account: spend exactly once after successful writeback.
- Schedule: after recomputation, if only the homepage Gate remains, choose human-gate backoff; the Host applies and ACKs it.
- Project: Workspace shows documentation complete and the homepage decision still open. The UI has neither swallowed nor widened the Gate scope.
If step 5 fails, the transition stops at validation. If step 6 fails, it must not reach spend. If the Host update in step 8 fails, record a failure receipt and retry within bounds instead of claiming that the cadence took effect.
Trace symptoms to owners¶
| Symptom | Inspect first | Do not | Recovery |
|---|---|---|---|
| UI says running, but no execution exists | quota selection, lease, run history | Invent a Todo running status |
Repair projection or stale lease |
| One Gate stops the whole Goal | decision scope and selected fallback | Delete the Gate or approve by default | Repair scope and recompute contract |
| Blocker cleared, but Agent still loops | handoff state and successor relation | Edit gate_state |
Add successor, reopen, or record no_followup |
| Spend exists without an artifact | settlement receipt and durable writeback | Add a chat explanation | Repair or compensate, then fix the spend path |
| Heartbeat waits longer and longer | reset token, identity, ACK/failure receipt | Shorten cadence unconditionally | Repair stale scheduler state |
| Monitor polls forever | next_due_at, result hash, stop condition |
Count each poll as delivery | Write bounded no-change / closeout |
| Monitor has an observation but the expected successor is missing | authority mode, operation receipt, material-change generation, projection outbox | Rerun the business mutation or edit the projection | Replay the same operation; retry only pending projection, or use new evidence for a new generation |
| Replan leaves the route unchanged | Vision/Todo/acceptance delta | Clear obligation with “replanned” | Write a material patch or unchanged reason |
| Dashboard conflicts with CLI | canonical source and projection freshness | Treat dashboard as source | Repair builder/source, then read back |
Source walkthrough entry points¶
| Machine | Primary fact or semantic owner | Continue reading |
|---|---|---|
| Todo lifecycle | loopx/control_plane/todos/contract.py |
Work graphs, authority, and peers |
| Gate / handoff | todos/contract.py, todos/handoff_gate.py |
Control-Plane Course Lesson 5 |
| Quota / interaction | loopx/control_plane/quota/, the loopx/quota.py facade |
One governed turn |
| Settlement | effect_program.ts, turn_transaction_contract.json |
Control-Plane Course Lesson 6 |
| Scheduler / heartbeat / Monitor | control_plane/scheduler/, coordination/todo_monitor_poll.ts |
Lesson 7 |
| Vision / replan | control_plane/goals/goal_vision_*, work_items/*replan* |
Long-horizon convergence |
| Activation / onboarding | control_plane/goals/activation.py, project bootstrap/connect |
Connect an existing Git project |
| Projection | The corresponding status/frontstage/Workspace builder | Durable state and read-only projections |
See the complete maintainer-level nine-machine table in State Machines. When changing a rule, do not copy an implementation backward from this teaching diagram. Confirm the current typed owner, protocol schema, characterization fixture, and migration boundary first.