Data model — runs and their satellites
A run is one execution; this page draws its per-run streams, its tree, the collections its launch and its outcome bind it to, and its assistant supervision.
Drawn from the code at commit
61a305262— main at v3.266.1, after #2391, which carries the launch rung; how this page is kept true: README.
Block 1 — the run and its per-run streams
- Every stream names its run with a top-level
run_id.run_seqis the one exception — its run id lives inside the compound_id. The counter is one row per (tenant value, run), not one per run. - A gap in
seqis normal. interactionsis the durable question/answer record andruns.checkpoint.interaction_idis only a pointer to the pending one.devis a tenant value, not a team. A server started withDisableAuthstamps the store ctx under the iddevfor both tenant and owner, so every row written in that ctx — for exampleevents— carriestenant_id: dev.
Block 2 — the run tree and the run's identity edges
- The four self-edges look alike and mean different things.
parent_run_idis the tree.subbot_childrenis the parent's subbot re-attach map.forked_fromrecords the run a fork was taken from. A delegated run is not in its source's tree. source.schedule_idmay be a cloudscheduled_bots._id, a host-cron entry name or a trigger subscription id — polymorphic, hence the dotted edge here, where the automation page draws one solid edge per resolvable value —scheduled_botsfor a cloud schedule id.- Every satellite here is optional.
run_secrets._idnever appears on the run document: the publisher writes it into the queue message.
Block 3 — assistant supervision and outcomes
- One idempotence motif, three keys:
(run_id, outcome_seq)settles a routing decision,(watch_id, outcome_event_id)settles a watch delivery,(tenant_id, operator_id, invocation_key)settles a mission. Each is a unique index. - The run locks in NATS KV are the runner's execution lease. They are not the
leasescollection, which is charted on the automation page.
Where a run's bytes live
Outside Mongo, a cloud run writes into six S3 key spaces of one bucket — beside a seventh, uploads/, where the server stages what a launch form uploads before any run exists — and one NATS KV bucket, and its launch travels on a JetStream subject; a second KV bucket holds the fleet's rollout epoch. The marked participants of Block 3 are the two stores a run itself writes; the streams are transport rather than state, so no entity draws them. The six run key spaces are keyed by the run id alone, with no tenant in the key: they are deleted by store.DeleteRun — DELETE /api/runs/{id}, and the org purge, which deletes each run of a purged org through it before any Mongo row goes (flow-org-team-lifecycle.md).
| store | key or name | what it holds |
|---|---|---|
S3, bucket iterion-artifacts (the default name) | artifacts/<run>/<node>/<version>.json | one object per artifact revision of a node |
| S3 | attachments/<run>/<name>/<file> | the attachment bodies runs.attachments indexes — plus the reserved name __gitdiff, where a per-file diff too large to inline is offloaded and deliberately left OUT of that index |
| S3 | tools/<run>/<toolUseID>/input · /output | one object per tool call and direction |
| S3 | ir/<run>.json · ir/<run>-bundle-<digest>.json | the compiled IR when it rides out of band, and the immutable bundle snapshot when it exceeds the queue payload — both deleted by DeleteRunIR |
| S3 | sessions/<run>/<ref> | the backend conversation a resume reattaches to |
| S3 | runfiles/<run>/<rel path> | tool-produced files |
| S3 | uploads/<upload>/<file> | an upload a launch form staged and no run claimed yet — reached only through its staged_uploads record, read under the tenant filter, never presigned, swept after an hour (bytes, then record) |
NATS KV iterion-run-locks | the run id | the runner's execution lease (LeaseInfo: runner id, started_at, run_status, tenant, pool); bucket TTL LockTTL, 60s by default |
NATS KV iterion-runner-rollout | epoch.high-water | the monotonic runner rollout epoch, no TTL |
JetStream ITERION_RUNS, ITERION_RUNS_DLQ | iterion.queue.runs, iterion.queue.runs.dlq | the run work queue and its park; a sovereign pool gets its own ITERION_RUNS_POOL_<pool> pair on an exact, never wildcard, subject |
Run status vocabulary
IsTerminalisfinished,failed,failed_resumable,cancelled.failed_resumableandcancelledare terminal and resumable.- A local resume has no queue and CASes from one of the four resumable statuses to
running— on an answered human gate,replayAnsweredGatecan first writepaused_waiting_human, the status a replay that dies before its claim leaves. continuation_statesays who owns a terminal run's future:redelivery_pending(the queue still holds the message),retry_armed(a scheduled retry will resume it),final(acting on it is the consumer's decision).deletedis written only byDeleteRun. A reader queryingrunsby status therefore meets a ninth value the enum does not declare.
Collections
| collection | package | document struct | tenant field (values) | unique / TTL indexes | twin |
|---|---|---|---|---|---|
runs | pkg/store/mongo | store.Run | tenant_id — team id, or dev under DisableAuth; empty on local runs | none unique beyond _id; no TTL | file run.json |
events | pkg/store/mongo | store.Event | tenant_id — team id | unique (run_id, seq); TTL ts after eventsTTLDays | file events.jsonl |
run_logs | pkg/store/mongo | runLogDoc | tenant_id — team id | unique (run_id, offset); TTL ts after eventsTTLDays | file run.log |
run_seq | pkg/store/mongo | bson.M literal, no Go struct | inside _id — {tenant_id, run_id}, team id | _id only; no TTL | none (cloud-only) |
interactions | pkg/store/mongo | interactionDoc wrapping store.Interaction | tenant_id — team id | _id {run_id, interaction_id}; no TTL | file interactions/<id>.json |
user_messages | pkg/store/mongo | userMessageDoc wrapping store.QueuedUserMessage | tenant_id — team id | _id {run_id, message_id}; no TTL | file user_messages.jsonl |
run_plans | pkg/store/mongo | runPlanDoc | tenant_id — team id | unique (run_id, seq); TTL ts after eventsTTLDays | file plans/<NNNN>.json |
run_notes | pkg/store/mongo | runNoteDoc | tenant_id — team id | unique (run_id, seq); no TTL | file notes/<NNNN>.json |
run_turns | pkg/store/mongo | runTurnDoc for store.TurnCheckpoint | tenant_id — team id | unique (run_id, node_id, loop_iter, turn_index); TTL ts | file turns/<node>/<iter>/<turn>.json |
run_tags | pkg/store/mongo | runTagsDoc | tenant_id — team id | unique run_id; no TTL | file tags.json |
staged_uploads | pkg/store/mongo | store.StagedUpload | tenant_id — team id | _id is the upload id; index created_at; no TTL index — the staging sweep reaps after an hour, bytes then record | file uploads/<id>/meta.json |
run_gitmeta | pkg/store/mongo | runGitMetaDoc | tenant_id — team id | unique run_id; no TTL | file gitmeta.json |
run_route_decisions | pkg/store/mongo | store.RouteDecision | tenant_id — team id; the watermark sentinel is untenanted | unique (run_id, outcome_seq); no TTL | file route_decisions.json |
retry_circuits | pkg/store/mongo | store.RetryCircuitState | tenant_id — team id | unique (tenant_id, key); TTL updated_at after 30 days | none (cloud-only) |
run_secrets | pkg/secrets | secrets.RunSecretsRecord | tenant_id — team id | none unique beyond _id; TTL expires_at, expireAfterSeconds 0 | none (cloud-only) |
teams | pkg/identity | identity.Team | _id is the tenant | unique slug, unique partial runner_pool; no TTL | none (cloud-only) |
users | pkg/identity | identity.User | none — global | unique email; no TTL | none (cloud-only) |
bot_sources | pkg/botsource | botsource.BotSource | tenant_id — team id, or platform: | unique (tenant_id, slug); no TTL | memory-only (local mode and tests) |
board_issues | pkg/dispatcher/boardmongo | issueDoc wrapping native.Issue | tenant_id — team id | _id is native:<uuid>; no TTL | file issues/<id>.json |
scheduled_bots | pkg/cloudsched | cloudsched.ScheduledBot | tenant_id — team id | none unique beyond _id; no TTL | none (cloud-only) — the in-process store is test-only; ~/.iterion/schedules.yaml is the host counterpart |
assistant_run_watches | pkg/runwatch | runwatch.Watch | tenant_id — team id, a plain tenant parameter on the per-operator reads | unique partial (tenant_id, owner_id, target_run_id) on state: active; no TTL | file assistant-run-watches.json |
assistant_run_watch_episodes | pkg/runwatch | runwatch.Episode | tenant_id — team id, ignored by the claim queries | unique (watch_id, outcome_event_id); no TTL | file assistant-run-watches.json |
assistant_missions | pkg/assistantmission | assistantmission.Mission | tenant_id — team id, a plain tenant parameter on the per-operator reads, ignored by the reconcile and claim queries | unique (tenant_id, operator_id, invocation_key), unique partial active_target_key; no TTL | file assistant-missions.json |
"S3: artifacts and backend sessions" | pkg/store/blob | key spaces, not documents | the run id is the key's second path segment — the upload id under uploads/, scoped by its staged_uploads record | none | S3 |
"NATS KV: run locks" | pkg/queue/nats | nats.LeaseInfo | tenant_id inside the lease payload | bucket TTL LockTTL, default 60s | NATS KV |
Related
- persisted-formats.md — the filesystem run-store layout,
run.json, the status table, the checkpoint shape and the event vocabulary. - resume.md — resume, rewind and fork over the checkpoint and
run_turns. - outcome-router.md — the decision table, the activation watermark sentinel and the claim semantics of
run_route_decisions. - assistant-dock.md — the assistant-side contract over watches, episodes and missions.
- cloud-architecture.md — control plane versus data plane, the queue internals and the multitenancy enforcement layers.
- adr/095-terminal-state-contract-and-failure-taxonomy.md · adr/053-unified-run-streaming.md · adr/073-cloud-twins-for-fs-only-run-detail-seams.md · adr/103-run-addressed-by-id-across-teams.md
