Backend and state schema

August 17, 2026 ยท View on GitHub

Scope

v0.1 has no remote backend, user account or hosted database. This document defines the local state store that makes bounded agent work resumable, inspectable and safe.

The schemas are normative even when a harness performs the workflow through instructions rather than a shared executable.

Storage layout

<project>/.the-loop/
  config.json
  STOP                         external kill switch, present means stop
  runs/
    <run-id>/
      run.json                 current projected state
      lease.json               current writer lease
      events.ndjson            append-only audit events
      issues.json              current issue ledger projection
      evidence/
        <evidence-id>.json
      artifacts/               ignored local outputs and command logs
  grants/
    <grant-id>.json             authority grants and revocations
  installs/
    <receipt-id>.json           setup operations and uninstall ownership

.the-loop/ is ignored by Git except optional example fixtures under tests/. Runtime directories use owner-only permissions where the platform supports them.

Common rules

  • All records include schema_version.
  • IDs are UUIDv4 strings generated locally.
  • Timestamps use UTC RFC 3339 with a Z suffix.
  • Enum values use lowercase snake case.
  • Missing required data is an error. It is never inferred from prose.
  • Writes use a temporary sibling file followed by atomic replacement.
  • Ordinary worker and domain events are append-only under a valid lease. Run creation, authority control and the lease-independent kill-switch reducer follow their exact event-specific rules. Separately, an intent committed under a then-valid matching lease may be closed after expiry only by its exact declared semantic completion or reconciliation and the required deterministic, callback-free budget_reached marker sequence for that source event, as specified below.
  • Unknown schema versions are read-only until migrated.
  • Public examples use synthetic paths and actors.

Config record

Path: .the-loop/config.json

FieldTypeRequiredNotes
schema_versionstringyesInitial value 1.0.
project_idUUIDyesStable local project identity.
default_modeenumyesloop or auto in v0.1.
state_rootrelative pathyesDefault .the-loop. Must remain inside project unless explicitly approved.
kill_switchesstring arrayyesDefault ['.the-loop/STOP']. External paths require explicit config.
heartbeat_secondsintegeryesDefault 120, range 15 to 600.
lease_secondsintegeryesDefault 300, greater than heartbeat interval.
routingobjectyesCapability pins and disabled providers.
budgetsobjectyesSafe defaults for bounded missions.
telemetrybooleanyesMust be false in v0.1.
harnessesobjectyesLast discovery and behavior status per harness.

Budget object

FieldTypeDefaultMeaning
max_duration_secondsinteger7200Accumulated validated active-lease time for one mission.
max_stage_attemptsinteger3Maximum attempts for each lifecycle stage before stuck-loop halt.
max_mutationsinteger100State-changing action ceiling.
max_external_actionsinteger0No outward action without an explicit grant.
max_cost_usdfixed-point decimal string or nullnullUp to six fractional digits; null means no spending authority, not unlimited spend.

Install receipt

Path: .the-loop/installs/<receipt-id>.json

FieldTypeRequired
schema_versionstringyes
receipt_idUUIDyes
created_attimestampyes
actorstringyes
source_versionstringyes
target_rootpathyes
harnessesenum arrayyes
operationsoperation arrayyes
resultenumyes: complete, partial, rolled_back, failed

Each operation records action (copy, link, mkdir, skip), source digest, destination, pre-existing destination digest or null, resulting digest and rollback action. Uninstall may remove only an unchanged path created by that receipt.

Harness status

The harness enum covers the five first-class adapters: codex, claude_code, kimi_code, opencode and deepseek_harness.

FieldTypeMeaning
installedbooleanExecutable or host integration found.
versionstring or nullReported version.
discoveryenumverified, failed, denied, unverified.
behaviorenumverified, failed, denied, unverified.
skill_rootspath arrayRoots inspected by Doctor.
collisionsobject arrayName, sources and winning precedence where knowable.
pack_statusenumcomplete, incomplete, integrity_unverified, or unverified; ready requires complete.
missing_skillsstring arrayRequired 31-package candidate names not verified in the inspected roots.
pack_receipt_idUUID or nullComplete Setup receipt whose package and toolkit digests still match.
pack_digestdigest or nullPath-free digest of the verified receipt-owned pack identity.
environment_digestdigest or nullDoctor-derived harness, adapter, pack and runtime fingerprint supplied to an approved probe.
checked_attimestampFreshness of result.
evidence_idUUID or nullBehavior probe evidence.
behavior_evidenceobject or nullTyped matching harness, version, project scope, portable-skill-invocation capability, allowed permission result, Doctor-derived environment digest and observation time.

Run record

Path: .the-loop/runs/<run-id>/run.json

FieldTypeRequiredNotes
schema_versionstringyesInitial value 1.0.
run_idUUIDyesMatches directory.
assetobjectyesName, root, track and declared boundaries.
modeenumyesloop or auto in v0.1.
statusenumyesDefined below.
stageenum or nullCurrent lifecycle stage.
objectivestringyesOne bounded outcome.
done_gatestring arrayyesTestable conditions.
authority_grant_idUUIDyesActive grant, including default authority.
budgetsobjectyesFrozen mission budgets.
usageobjectyesDuration, attempts, mutations, external actions and recorded cost.
ownerobject or nullyesHarness, actor and session identifier.
selected_routesobjectyesCapability to provider decision.
open_blocking_issuesintegeryesDerived but stored for fast status.
last_heartbeat_attimestamp or nullyesLiveness, not proof of progress.
created_attimestampyes
updated_attimestampyes
terminal_reasonobject or nullyesCode and explanation for terminal or waiting state.

Run status enum

draft, ready, active, waiting_approval, waiting_external, blocked, failed, halted_kill_switch, complete, cancelled.

Stage enum

strategize, spec_pack, build, test, resolve, close.

State transitions

FromAllowed next states
draftready, cancelled
readyactive, cancelled
activewaiting_approval, waiting_external, blocked, failed, halted_kill_switch, complete, cancelled
waiting_approvalready, cancelled
waiting_externalready, cancelled
blockedready, cancelled
failedready, cancelled
halted_kill_switchready, cancelled
completenone
cancellednone

Returning to ready is an explicit recovery event. It requires revalidation, a current authority grant and a new lease before active.

The runtime records that recovery atomically: recovery_started may collapse the recoverable-to-ready transition plus fresh lease acquisition into one event whose resulting projection is directly active. This is the sole event-scoped recovery exception to the table. Separately, kill_switch_detected may reduce ready, active, waiting_approval, waiting_external, blocked or failed directly to halted_kill_switch; it cannot authorize work. The status enum in run.schema.json defines permitted values, not the transition graph.

The ordinary graph is necessary but not sufficient. lease_acquired alone activates ready; recovery_started alone establishes a fresh generation and may project a recoverable waiting, blocked, failed or halted run directly to active; authority_revoked reduces active work to waiting_approval unless kill-switch truth requires the chain to preserve or finish in halted_kill_switch; unknown local and external operation_reconciled project failed and waiting_external respectively; ordinary budget exhaustion projects failed; kill_switch_detected alone uses the lease-independent halt reducer; and the three terminal run events project their matching complete, failed or cancelled state. A pending semantic completion must exactly match its intent. Budget equality or callback completion at lease expiry may cause that semantic event to project failed atomically. No graph edge authorizes an event whose type-specific payload, lease envelope, pending adjacency, authority, budget or reason is invalid.

usage.stage_attempts is a complete map keyed by strategize, spec_pack, build, test, resolve and close. Each counter is monotonic and stage_started increments exactly its matching stage. usage.duration_seconds is a non-negative number with microsecond precision and increases by the complete time proven inside each validated lease interval: the interval begins or resumes at an authoritative lease event and is closed or extended by the next validated event or heartbeat, never beyond the prior expiry. Sub-second intervals are retained instead of rounded away. Process downtime without a valid lease does not count. A callback that returns at or after exact lease expiry records its semantic result but atomically leaves the run failed; it cannot return an active projection or continue without fresh-generation recovery.

Ordinary frozen-budget exhaustion makes an active run failed with one primary terminal reason code budget_reached:<field>. If a successful callback reaches one or more limits exactly, its semantic completion event atomically carries that failed projection before the contiguous budget_reached marker sequence is appended. Every simultaneously exhausted budget receives exactly one marker per unique (budget, stage) key. The stable priority and marker order is max_duration_seconds; max_stage_attempts in canonical stage order (strategize, spec_pack, build, test, resolve, close); max_mutations; max_external_actions; max_cost_usd; the first exhausted key supplies the ordinary primary reason. A crash between or within those records therefore cannot leave the run active; the next exclusive command repairs the missing suffix from the authoritative source event and verified marker prefix before doing any work. Each marker records the frozen limit, observed usage and, for max_stage_attempts, the affected stage. A deterministic marker schema or invariant ContractError propagates and is never reclassified as a transient missing marker; only a non-contract append error may use canonical-head verification to distinguish a repairable pre-append miss, a verified committed marker, or committed_state_unknown.

Unknown-operation truth has precedence over every budget reason. When operation_reconciled with outcome: unknown retains usage that exhausts one or more budgets, its projection remains failed with operation_outcome_unknown for local work or waiting_external with external_operation_outcome_unknown for outward work. A contiguous marker sequence in the stable order above records every unique exhausted (budget, stage) key without changing the run or lease projection. Exhausted usage still blocks recovery and new work. A missing marker suffix is repaired deterministically only from that reconciliation and the verified marker prefix and never replays the callback.

Cost limits and usage are canonical fixed-point decimal strings with up to six fractional digits, never binary floating-point values. Each callback reserves exactly one mutation; only the exact local_write plus repository pair is local in v0.1, while every other action or destination is fail-closed as external and must reserve exactly one external action. Internal audit, repair and halt writes do not consume the asset-mutation budget.

Lease record

Path: .the-loop/runs/<run-id>/lease.json

FieldTypeRequired
schema_versionstringyes
lease_idUUIDyes
run_idUUIDyes
lane_idUUID or nullyes
ownerobjectyes: harness, actor, session ID
acquired_attimestampyes
renewed_attimestampyes
expires_attimestampyes
generationintegeryes, increments on recovery

Lease invariants

  • Acquisition uses exclusive file creation when no lease exists and is recorded once for the initial generation; subsequent activity uses lease_renewed or recovery_started, never another lease_acquired for an established generation.
  • A non-expired lease cannot be replaced.
  • An expired lease can be replaced only through explicit recovery, which increments generation and writes an event. A ready run halted before its first lease resumes through recovery_started with previous_generation: null and a fresh generation-zero lease; this is the only recovery form without a prior lease.
  • recovery_started is the generation-establishing audit event for the replacement lease; a duplicate lease_acquired event is not required for that generation.
  • Every mutation supplies lease ID and generation.
  • A heartbeat may renew only its own matching lease.
  • Clock reversal or an unparsable timestamp halts mutation.
  • Each command uses one injected aware-UTC timestamp; now == expires_at is expired and is never renewed in place.
  • At or after lease expiry, every ordinary worker and domain event is rejected. If operation_intended committed under that lease while it was valid, only its exact declared semantic completion or operation_reconciled and the required contiguous deterministic budget_reached marker sequence for that source may close it. Completion or reconciliation clears pending state and leaves the run non-active. Each marker is callback-free, cannot interleave, uses the source event's timestamp and unchanged lease identity and projection, and may be repaired later under the run lock from that canonical source and an already verified marker prefix. Outside that closure, only necessary callback-free safety or recovery reducers may append: lease-independent kill or revocation truth, truthful expired-run or budget reduction, cancellation, and fresh-generation recovery. None authorizes callback replay, a replacement callback, renewal under the expired generation or any new ordinary action, so the one-writer valid-lease invariant remains intact.

Authority grant

Path: .the-loop/grants/<grant-id>.json

FieldTypeRequiredNotes
schema_versionstringyes
grant_idUUIDyes
levelenumyesattended, bounded, scoped, operational, full.
actorstringyesUser-confirmed identity label.
scopeobjectyesAssets, actions, destinations and explicit exclusions.
risks_shownstring arrayyesRealistic risks displayed before confirmation.
confirmation_textstringyesExact typed confirmation, excluding secrets.
confirmed_attimestampyes
expires_attimestampyesNo non-expiring elevated grant in v0.1.
revoked_attimestamp or nullyes
revoked_bystring or nullyes
permanent_invariantsstring arrayyesFixed set validated by schema.

Permanent invariants

The exact set is visible_authority, audit_log, evidence_required, run_ownership, leases, external_kill_switch, faithful_failure, no_silent_elevation.

The validator rejects any elevated grant that omits one. Explicit scope exclusions take precedence over positive assets, actions and destinations, including wildcard grants.

If a ready run has no lease and its bound grant expires or is revoked, acquisition is denied. A revoked grant still receives the required per-run audit marker, but v0.1 does not rewrite the ready run's authority binding before its first lease. Continuation uses a new run created under a current grant; explicit recovery with authority replacement remains available only to a leased or otherwise recoverable run.

Route decision

Stored in run.json and written as an event.

FieldTypeRequired
capabilitystringyes, namespaced such as lifecycle.test
harnessenumyes: codex, claude_code, kimi_code, opencode
trackenumyes: code, noncode
environment_digestdigestyes, relevant environment fingerprint without raw private values
requirementsstring arrayyes
candidatesobject arrayyes
selected_providerstringyes
selected_sourceenuminstalled, bundled
reasonstringyes
verified_attimestamp or nullyes
fallback_reasonstring or nullyes

Each candidate records source, compatibility, explicit capability evidence, description score, behavior status, typed behavior observations, rejection reasons and provenance type. Every typed observation records capability, harness, track, environment digest, outcome, observed_at, and an evidence ID. Raw prompt content and raw private environment values are not stored.

A current capability-scoped behavior_status: failed disqualifies that candidate. A selected verified candidate requires exactly one latest typed observation matching the route capability, harness, track, and environment digest; it must be passed, its evidence ID identifies the probe, and route verified_at must equal its observed_at. A newer matching pass may supersede an older failure, but the older observation remains visible. Route-record validation rejects a selected failed candidate, missing or mismatched observations, multiple observations tied at the latest matching time, a latest matching denied, failed, or unverified outcome, a verification-time mismatch, and description-only or untyped evidence presented as behavior proof.

Evidence record

Path: .the-loop/runs/<run-id>/evidence/<evidence-id>.json

FieldTypeRequired
schema_versionstringyes
evidence_idUUIDyes
run_idUUIDyes
stageenumyes
requirement_idsstring arrayyes
typeenumcommand, file, visual, source, manual, harness_probe
descriptionstringyes
procedurestring arrayyes
outcomeenumpassed, failed, blocked, denied, unverified
artifact_refsobject arrayyes
digeststring or nullyes
actorobjectyes
observed_attimestampyes
environmentobjectyes, redacted and allowlisted

An evidence record cannot use passed without a non-empty procedure and at least one result reference or explicit manual observation.

Issue ledger

Path: .the-loop/runs/<run-id>/issues.json

FieldTypeRequired
schema_versionstringyes
run_idUUIDyes
issuesissue arrayyes
updated_attimestampyes

Issue

FieldTypeRequired
issue_idUUIDyes
titlestringyes
severityenumcritical, high, medium, low
blockingbooleanyes
statusenumopen, acknowledged, resolving, verification_pending, closed, reopened, deferred
requirement_idsstring arrayyes
evidence_idsUUID arrayyes
reproductionstring arrayyes
ownerobject or nullyes
attempt_countintegeryes
resolutionstring or nullyes
regression_procedurestring arrayyes
created_attimestampyes
updated_attimestampyes

A blocking issue in any status other than closed prevents run completion. deferred is not equivalent to closed.

Audit event

Path: .the-loop/runs/<run-id>/events.ndjson

FieldTypeRequired
schema_versionstringyes
event_idUUIDyes
sequenceintegeryes, starts at 1 and increments by exactly one across the event log
run_idUUIDyes
lease_idUUID or nullyes
lease_generationinteger or nullyes
typestring enumyes
actorobjectyes
attimestampyes
dataobjectyes, type-specific and schema-validated
projectionobjectyes, complete resulting run, nullable lease and nullable pending_operation
previous_event_digeststring or nullyes
event_digeststringyes

Required event types include run_created, authority_granted, authority_revoked, lease_acquired, lease_renewed, route_selected, stage_started, heartbeat, evidence_recorded, issue_opened, issue_transitioned, stage_completed, operation_intended, operation_reconciled, budget_reached, kill_switch_detected, recovery_started, run_completed, run_failed and run_cancelled.

run_created appears exactly once as the first event. It cannot be replayed later, including while recovery is pending.

The audit log is the authoritative run-control record. Every event carries the complete after-state projection.run, projection.lease and projection.pending_operation; run_created therefore contains the complete initial run, and lease, recovery, heartbeat and control events contain all acquired, renewed, expiry, lane, owner, status, reason and usage state required for repair. Before mutation, the manager validates the full chain and projection continuity, then deterministically repairs run.json and lease.json from the latest complete state when either mutable projection is missing or stale. A corrupt chain is preserved and blocks mutation. Mutable projections are never accepted as a trusted seed over a valid audit head.

Before invoking a side-effecting callback, the manager appends operation_intended. Its complete pending record contains a unique operation_id, the exact permitted semantic completion event type and payload, an explicit local or external effect classification, and the exact mutation, external-action, cost and optional stage-attempt reservation plus the prior stage/cost needed for deterministic rollback. A successful callback is followed immediately by that exact semantic event and payload, which clears pending state. If the reservation exactly exhausts one or more budgets, that same completion event atomically projects the failed state with the first stable-priority budget as its primary reason before the complete marker sequence. No unrelated event may interleave.

An unmatched pending intent after an uncontrolled interruption is an unknown outcome, never a retry instruction. Reconciliation appends operation_reconciled with outcome: unknown, retains the full reservation, clears pending state and moves local work to failed with code operation_outcome_unknown, or outward work to waiting_external with code external_operation_outcome_unknown. When the runtime itself proves the callback has not begun, such as a kill switch detected immediately after intent, it instead appends outcome: known_not_started, rolls back that exact reservation, restores the prior stage/cost and then records the halt. This narrow rollback does not require recovery before the following safety-control event and cannot be inferred after a process crash. When pending state exists, only its exact declared semantic completion or reconciliation may close it, including when the lease expires after the intent committed while valid; the contiguous required budget-marker sequence may then follow with the source timestamp and unchanged projection. Such post-expiry closure must leave work non-active, cannot interleave an unrelated event and cannot authorize any new action. Without a valid lease, ordinary domain and worker events are rejected even when no operation is pending; only the expressly defined callback-free safety and recovery reducers remain available. Prevention of callback replay is a runtime invariant backed by the durable reconciled state.

Projection continuity keeps mission identity and frozen budgets immutable, keeps usage monotonic, permits authority changes only during explicit recovery, requires lease ID/generation/owner alignment with the event envelope, requires an active run owner to match the resulting lease owner, and enforces the intent-to-completion or intent-to-reconciliation adjacency contract.

Authority revocation and mutation intent share a stable per-grant cooperative lock. Mutation holds the shared side through its final grant validation and durable intent append; the official revocation path holds the exclusive side through the grant update and revocation audit. The lock boundary is the authority linearization point: a completed revocation prevents intent, while an already durable intent may finish exactly once under the authority it recorded. Runs sharing a grant use the same lock path. The revoked grant is irreversible truth. Its per-run authority_revoked marker records the persisted revoked_at and revoked_by; retry repairs an absent marker exactly once, accepts an already verified marker without duplication, and never restores the grant when audit state is pending or unverifiable. Every supported mutating entry repairs its run's marker before denying use of a shared revoked grant. Revocation remains a safety-reducing control when a kill-switch probe is present or indeterminate, or the run is already halted: the grant is persisted revoked, exactly one authority_revoked marker is appended or repaired, kill_switch_detected follows when needed, and the returned final projection is halted_kill_switch. The audit chain preserves both truths and grants no work authority.

The complete chain is validated before every append, including the first. Event timestamps never move backwards, and each acquisition or renewal expiry is later than its event timestamp.

kill_switch_detected is a safety-control event and may be recorded without lease context. It may move any recoverable nonterminal run (ready, active, either waiting state, blocked or failed) directly to halted_kill_switch. This exception can only reduce authority; it cannot authorize ordinary mutation or recovery, and complete or cancelled runs remain terminal.

Digest chaining detects accidental edits. It is not presented as tamper-proof security because a local owner can rewrite the entire store.

Projection writes and event-log operations require an explicit project root. Path preparation captures one immutable namespace snapshot containing the project-root identity plus the observed presence and identity of every intermediate component and target. The runtime additionally binds the device/inode identity of the project root, .the-loop and every configured state-root component to the run; every public command and the pre-intent, pre-callback and pre-commit boundaries recheck it, so a same-string clone or replacement cannot create a second history. Descriptor walking consumes the snapshot without re-baselining replacements, rejects unexpected appearance, disappearance, replacement, symlink and hard-link aliases, and verifies that the canonical path still names the operated inode before reporting success. Readers take a shared lock and appenders take an exclusive lock so a valid in-progress append is never reported as corruption. Locks are released by closing the retained descriptor, keeping cleanup leak-free without a separate unlock step that could turn a durable append into a reported failure.

Projection replacement snapshots the prior canonical bytes before mutation. If any post-replacement namespace, identity, permission or link-count check fails, the retained parent descriptor restores those bytes through a fresh private inode (or removes the new target when the projection was initially absent) before the operation reports failure.

Command and skill operations

The implementation exposes these logical operations. A harness may wrap them in a skill rather than a standalone CLI.

OperationMutationAuthority
setup plannoread access
setup applyyesexact install approval
doctorno, except optional report fileread access; harness probes may prompt
run createyesdefault local authority
run statusnoread access
run acquireyesvalid owner and authority
run heartbeatyesmatching lease
run transitionyesmatching lease plus valid schema transition
issue open/updateyesmatching lease
evidence addyesmatching lease
authority grant/revokeyestyped user confirmation or user revocation
run stopyesuser-controlled kill switch or cancel authority
run resumeyesexplicit recovery, current grant and fresh lease

Migration policy

  • Schema versions use major.minor.
  • A minor migration may add optional fields or enum values that old readers can safely ignore.
  • A major migration requires an explicit command, backup and migration evidence.
  • Migration writes a new tree, validates it, then swaps atomically.
  • Failed migration leaves the original tree untouched.
  • Downgrade is read-only unless a tested reverse migration exists.

Remote future

Cloud or Endless may later need a shared state adapter. Any adapter must preserve the same records, atomic ownership semantics and kill-switch behavior. A remote database cannot become a reason to remove local export, visible authority or faithful failure states.

Assumption ledger

AssumptionClassConsequence if wrong
One active writer per run or lane is sufficient.safe defaultAdd compare-and-swap storage without changing lease semantics.
UUIDv4 is acceptable for local IDs.safe defaultChange ID generation only in a major schema migration.
Owner-only filesystem permissions are available on required platforms.safe defaultDoctor warns and the user decides whether the environment is acceptable.
Cost cannot be measured reliably across every harness.safe defaultTreat missing cost as unverified and never as zero.