# Storage model & on-disk contracts

> Detailed storage and contract section from [`docs/architecture.md`](../architecture.md). This section was split out after the main document exceeded 1,000 lines.

## Storage model

Files created by `okstra` are stored in the following three areas according to their purpose.

### 1. Stable task root

This is the task's primary directory.
The task manifest, task index, instruction set, runs, and history are collected under this root.

- `<target-project>/.okstra/tasks/<task-group>/<task-id>/`
  - `task-manifest.json`
  - `task-index.md`
  - `instruction-set/`
    - `analysis-profile.md`, `analysis-packet.md`, `analysis-material.md`, `reference-expectations.md`, `task-brief.md`
    - `verification-target.md` for final verification and optional `directive.txt`
    - `host-orchestration-rules.md`, the staged copy of the host orchestration rules for this task type, when the run has one
    - `clarification-response.md`, the user's carried-in clarification answers, when the run has any
    - `selected-direction.json`, the validated implementation direction snapshot for a new `implementation-planning` run
    - `final-report-schema.json`, `final-report-template.md`
  - `runs/<task-type>/`
    - `manifests/run-manifest-<task-type>-<seq>.json`
    - `state/team-state-<task-type>-<seq>.json`
    - `prompts/lead-execution-prompt-<task-type>-<seq>.md` and selected `<worker>-worker-prompt-<task-type>-<seq>.md` files, each with adjacent `.meta.json` invocation metadata
    - `prompts/duty-contracts-<task-type>-<seq>/`, the immutable run-scoped duty catalog snapshot
    - `reports/final-report-<task-type>-<seq>.{data.json,md,html}`
    - implementation-planning-only `design-prep-requests/` and `design-prep-inputs/`
    - `status/final-<task-type>-<seq>.status`
    - Claude compatibility launcher sessions under `sessions/claude-resume-<task-type>-<seq>.sh`
    - optional lead-written `logs/errors-<task-type>-<seq>.jsonl`
    - `worker-results/`
  - `history/timeline.json` and optional append-only `history/fix-cycles.jsonl`
  - optional append-only `recap/recap-log.jsonl`
  - optional inert agent notes under `notes/<slug>-<YYYY-MM-DD>.md`; a run reads them only when explicitly carried through `--clarification-response`
- `<target-project>/.okstra/tasks/<task-group>/.recap/recap-log.jsonl` — optional append-only log of group-scoped recap turns (`okstra recap record --task-group`), a sibling of the group's task directories; the scanners over `.okstra/tasks/<group>/` recognise a task by its `runs/` or `container/` subtree, so the dot-directory is never read as one

Actual directory segments may be normalized into slugs to create safe paths.

v2 run artifacts persist `participantRef`, `roleExecutionRef`, `invocationRef`, `attempt`, and `executionLabel` as stored fields. Readers must not reparse those strings from `workerId`, provider, or model labels. v1 bytes stay unread-write. A mixed or partial v2 payload is rejected.

Shared Git common directories and other-stage refs are outside the mutation-audit enforcement surface. Dispatch records observed projection only; it does not claim to police every ref that a shared object store can see.
However, the logical task key always retains the original input values in the form `project-id:task-group:task-id`.

### 2. Per-run execution artifacts

Artifacts from each execution accumulate beneath the current run's resolved `run_dir`.
`runs/<task-type>/` is the run directory for non-stage runs. As exceptions, `implementation` uses `runs/implementation/stage-<N>/`, and single-stage `final-verification` uses `runs/final-verification/stage-<N>/` as its `run_dir`. Whole-task `final-verification` retains the non-stage form `runs/final-verification/`.

```text
<target-project>/.okstra/tasks/<task-group>/<task-id>/runs/<task-type>/
```

The representative files below are all relative to the resolved run directory (`run_dir`). Therefore, report, log, and session paths for stage-isolated runs include the `stage-<N>/` segment. The carry sidecar is the exception — it stays flat under the task-type run dir (see the `carry/stage-<N>.json` entry below).

- `manifests/run-manifest-<task-type>-<seq>.json`
- `state/team-state-<task-type>-<seq>.json`
- `prompts/lead-execution-prompt-<task-type>-<seq>.md` *(the only lead prompt file; the run manifest's `resources.claudeExecutionPromptPath` is a key alias resolving to it)*
- `prompts/<worker>-worker-prompt-<task-type>-<seq>.md`
- `prompts/<prompt>.md.meta.json` *(invocation identity, assignment, duty identity, source paths, and the five catalog/assignment/duty/instruction/prompt digests)*
- `prompts/duty-contracts-<task-type>-<seq>/` *(run-scoped snapshot; dispatch verification rejects drift from its catalog digest)*

After the host-native lead takes over, the lead and its assigned workers add the following result files to the current run.
- `sessions/claude-resume-<task-type>-<seq>.sh`
- `reports/final-report-<task-type>-<seq>.data.json` *(schema v2 source of truth for both audiences)*
- `reports/final-report-<task-type>-<seq>.md`
- `reports/final-report-<task-type>-<seq>.html`     *(Phase 7 deterministic post-processing: task-specific, self-contained HTML for human reviewers, with inline CSS/JS)*
- `design-prep-requests/design-prep-request-<seq>-PREP-<NNN>.md` *(implementation-planning only: deterministic AI-prepared request for a `provisional` or `blocked` design item)*
- `design-prep-inputs/design-prep-input-<seq>-PREP-<NNN>-r<revision>-<uuid>.md` *(implementation-planning only: append-only confirmed user/wizard response)*
- `user-responses/user-response-<task-type>-<seq>.md` *(A sidecar downloaded under the same name by the HTML `Export user response` button; storing it here lets `--resume-clarification` automatically attach it to `clarification-response.md` in the instruction set — `clarification_items.clarification_response_with_sidecars`)*
- `worker-results/<worker>-audit-<task-type>-<seq>.md` *(Per-worker Reading Confirmation sidecar; for auditing rather than report content)*
- `status/final-<task-type>-<seq>.status`
- `runs/implementation/carry/stage-<N>.json` *(implementation only: execution evidence sidecar. Stage-SHARED like `consumers.jsonl` — it lives flat under the task-type run dir, NOT under `stage-<N>/`, because the next stage's carry-in and `backfill_done_from_carry` glob it without knowing the producing run's layout)*
- `consumers.jsonl` *(implementation-planning only: backlinks to the impl-run that consumed each stage in this plan; append-only)*

#### Direction-selection handoff authorities

| Artifact | Content authority | Digest contract |
|---|---|---|
| Option-selection data report | The immutable `data.json` records the validated candidates. | It does not store the handoff digest. |
| User-response sidecar | In comparison mode, the sidecar records the confirmed `IO-NNN` choice without changing the report. | `source-data-sha256` records the digest of the exact `data.json` bytes. Planning prepare recomputes SHA-256 over those bytes and rejects a mismatch. |
| Selected-direction snapshot | Planning prepare writes the normalized choice to `instruction-set/selected-direction.json`; the planning report cites it through `selectedDirectionRef`. | `sourceDataSha256` records the digest that planning prepare verified. |

Preselected-validation mode records its upstream confirmation in the option-selection report and needs no user-selection sidecar. Planning prepare still writes the selected-direction snapshot with the verified source-data digest.

Design-preparation storage has three separate authorities:

| Artifact | Owner | Mutation contract |
|---|---|---|
| `reports/final-report-implementation-planning-<seq>.data.json` | report writer / approved planning snapshot | Immutable after approval. Phase 7 may populate report-owned cells before approval, but a later CLI or wizard response never edits this snapshot. |
| `design-prep-requests/design-prep-request-<seq>-PREP-<NNN>.md` | Okstra | Deterministic from the snapshot, report language, and request-template version. Re-materialization is idempotent; different existing content is never overwritten. |
| `design-prep-inputs/design-prep-input-<seq>-PREP-<NNN>-r<revision>-<uuid>.md` | user or wizard | Append-only and written only after semantic confirmation. Existing revisions are never edited or reserved before confirmation. |

The resolver selects the highest unique revision whose assessment fingerprint still matches the approved snapshot. Duplicate revision numbers and stale fingerprints are skipped with warnings rather than granted authority. A markerless legacy plan has no sidecars to migrate: it proceeds with the `legacy-unassessed` warning. This compatibility path does not rewrite its report.
The final result files are not created by saving worker stdout. The report writer writes its narrative Markdown, and Phase 7 assembles `final-report-<task-type>-<seq>.data.json` from all role-owned inputs before deriving the human HTML sibling. The full reading copy Markdown is rendered on demand with `okstra render-final-report`. New bundles use `schemas/final-report-v3.0.schema.json`; historical v2 data remains readable through `schemas/final-report-v2.0.schema.json`. Both use `templates/reports/final-report-v2.template.md` for the reading copy. Rendering never modifies source data or a pre-existing Markdown input.
For the standalone Claude launcher, `sessions/claude-resume-<task-type>-<seq>.sh` remains an interruption-recovery compatibility helper. In-host Codex runs use host task continuity plus the generic `leadSession` manifest identity.

The resolved run directory collects execution history. It divides its contents into type-specific subdirectories such as `manifests/`, `state/`, `prompts/`, `reports/`, `status/`, `sessions/`, and `worker-results/`, then distinguishes each run-level artifact and result file with a `-<task-type>-<seq>` suffix (a three-digit, zero-padded per-category counter, such as `001` or `002`).
Worker prompt history is retained not under `/tmp`, but always as a canonical artifact under `prompts/` for the current run.

`state/team-state-<task-type>-<seq>.json` stores invocation-aware dispatch
records. Code-owned worker launches record the invocation fields directly in
`workerDispatches[]`, including the `sessionId` the dispatch minted for that
attempt. That id is the only handle on a worker that runs as its own CLI
process, because such a session tags its transcript with neither an agent nor a
team name; it is empty for providers whose CLI takes no session-id argument. A
row is keyed by its dispatch ID rather than appended blindly: a row repeating a
`dispatchId` already present replaces that row in place, so one dispatch ID is
recorded at most once and a re-sent dispatch cannot make the run read as a
duplicate. Rows carrying no dispatch ID have no key to collapse on and still
stack. Nothing prunes the array and no row is ever dropped, so a replaced row
takes its `paneId` out of the ledger with it and the pane-reclaim gate no longer
offers that pane. Host-native calls use `agentDispatches[]`, and
`agentResultLinks[]` associates an accepted result path with exactly one
dispatch ID. The inverse is also exclusive: one dispatch ID cannot authorize
multiple accepted paths. State mutation uses a run-state lock and unique
temporary files so parallel dispatch and result-link writers do not lose each
other's updates. `host-native-spec-link-gate` never means that the host attested the
delivered prompt bytes; `promptDeliveryVerified` remains false.

The complete run manifest is published before any run-backed prompt. Its
`agentContract`, `invocationAssignments`, run-scoped duty root, catalog digest,
and invocation reservation root are immutable for the lifetime of the run.
Every invocation metadata file refers back to that manifest and to one
`assignmentRef`. A reservation binds `invocationId`, `workerId`, assignment,
audience, dispatch kind, prompt path, and adjacent metadata path before the
prompt can be reused. This run-scoped state belongs under `runs/.../prompts/`,
not the task-wide `instruction-set/`, because consecutive runs and parallel
implementation stages must not share mutable invocation identity.

The manifest's `authorizedPaths` allowlists instruction, prompt, and result
roots. Materialization resolves each requested path to its real path, rejects a
path outside the corresponding root, rejects a symbolic-link escape, and
rejects an undeclared result path before writing. Persisted instruction
provenance is a non-empty list of `{kind: project|runtime, path: <relative POSIX
path>}` objects. The path is normalized POSIX text relative to its named
authority; installed runtime absolute paths are not durable metadata.

Prompt publication is recoverable rather than pretending that two independent
files can be replaced atomically. A create-only prompt is written first and its
adjacent metadata is written last as the completion marker. Under the
per-invocation publication lock, a retry recovers a byte-identical orphan
prompt, but rejects different existing bytes. A reservation lock prevents two
concurrent writers from mixing prompt and metadata identities.

LLM calls outside a task run use
`<project>/.okstra/agent-invocations/<purpose>/`. Identity is scoped by the
`(purpose, invocationId)` pair, and both values must be canonical slugs. For an
invocation ID `<invocation-id>`, the exact immutable paths are
`<invocation-id>.instructions.md`, `<invocation-id>.prompt.md`, adjacent
`<invocation-id>.prompt.md.meta.json`, `<invocation-id>.result.json`, and
`<invocation-id>.prompt.md.completion.json`, plus a private `.tmp/` capture
area. The result envelope binds purpose, invocation ID, prompt metadata path,
and `returnedBody`; the completion marker is published last and binds the
canonical result envelope digest. The same raw returned body may be used by two
independent invocations because identity comes from the envelope, while a
cross-invocation envelope link is rejected. Code review uses purpose
`code-review`; schedule narrative verification uses `schedule-verification`.
Consumers parse only the `returnedBody` emitted by
`agent-prompt verify-completion`. A branch review's final report may use the
documented `.project-docs/code-reviews/<branch>/` exception, but its invocation
specification, result envelope, and completion record remain under `.okstra/`.

The persisted prompt's `**Worker Preamble Path:**` records the selected functional audience contract: analysis uses `templates/worker-prompt-preamble.md`, implementation executor/verifier uses `templates/implementation-worker-preamble.md`, and report writing uses `templates/report-writer-prompt-preamble.md`. Every initial prompt also persists `**Worker Error Contract Path:** templates/worker-error-contract.md`. Active-run and run-context runtime resources expose the same audience map plus the shared error-contract path, so dispatch replay and context-cost accounting do not infer an audience from a provider/model name.
Unlike before, `analysis-profile.md`, `analysis-material.md`, `reference-expectations.md`, `task-brief.md`, skill copies, and `final-report-template.md` are not duplicated for every run.
These materials retain canonical copies in `instruction-set/` under the stable task root.
For `final-verification`, `verification-target.md` is also stable-task instruction-set state. Prepare owns the snapshot and stores its project-relative path plus SHA-256 digest in task/run manifests and active-run context. Initial analysis prompts carry the compact worktree/scope/base/head/path/digest identity and read this sidecar on demand instead of copying its diff stat.
Each wrapper `<prompt>.log` beside a persisted `<prompt>.md` is a growing execution transcript, not another prompt snapshot. `okstra log-report` pairs the siblings and reports prompt/transcript bytes separately while keeping the legacy log-oriented `sizeBytes` and `totalBytes` fields.
When the same non-stage task type runs again, it reuses the same `runs/<task-type>/` run directory. Reruns of `implementation` and single-stage `final-verification` reuse their resolved `runs/<task-type>/stage-<N>/` run directories. In both cases, existing artifacts are not overwritten because run-level filenames are separated by `-<task-type>-<seq>` suffixes within type-specific subdirectories. Because `<seq>` is scanned independently for each category directory (`manifests/`, `prompts/`, `reports/`, `status/`, `state/`, `sessions/`, `worker-results/`) beneath the resolved run directory, values may differ by category even within the same run.
If flat legacy artifacts remain at the top level of a task-type run directory, the next execution automatically moves them into the corresponding type-specific subdirectories.

#### Rerun archive

Because a rerun reuses the run directory, prior-sequence artifacts pile up in place. A task that re-ran `implementation-planning` twenty-four times left over two hundred files in one `worker-results/`, and checks that glob that directory counted another run's files as this run's — their failure counts grew from 30 to 140 across the reruns.

Prepare therefore moves prior-sequence artifacts out of the two directories no consumer reads across sequences:

```text
runs/<task-type>/archive/run-<seq>/worker-results/…
runs/<task-type>/archive/run-<seq>/prompts/…
runs/<task-type>/archive/README.md
```

- **Rotated:** `worker-results/` and `prompts/`. Their only readers (`run_audit`, `context_cost`, the plan-body provenance check) scope themselves to the current run's sequence already; `okstra log-report` still counts archived wrapper transcripts because it matches on path components rather than depth.
- **Not rotated:** `reports/`, `manifests/`, `state/`, `status/`, `sessions/`, `logs/`, `carry/`, and `user-responses/`. `state/` is read across sequences by `okstra plan-items seed --prior-state`, by the prior-planning summary, and by `okstra time-report`; `logs/` is globbed one level deep as `runs/*/logs/errors-*.jsonl` by `okstra error-report`, `okstra error-zip`, and prepare's own prior-run error digest, so an archived path would silently drop from all three.
- **Retention floor:** the sequence the latest published report's run manifest recorded for that category (`runSequencesByCategory`). That sequence and everything above it stay in place; only older sequences move. When the report-to-manifest mapping cannot be resolved, nothing moves.
- The move never deletes, never overwrites an existing archive entry, and never touches an entry without a `-<task-type>-<seq>` suffix (`prompts/.agent-invocations/` stays put). Because the retention floor is always at or below the directory's highest sequence, sequence allocation still sees the same maximum and stays monotonic.
- `archive/README.md` is rewritten on each rotation with what moved, from which sequence, the retention floor used, and how to restore.

### 3. Project-level discovery and installed skill assets
Shared project discovery pointers are created at the following paths.

```text
<target-project>/.okstra/discovery/latest-task.json
<target-project>/.okstra/discovery/task-catalog.json
```

Division of responsibilities:

- `.okstra/discovery/latest-task.json`: a current-task convenience pointer to the most recently prepared okstra task bundle in the current project
- `.okstra/discovery/task-catalog.json`: the canonical project-level catalog that maintains the list of okstra task bundles prepared in the current project by `taskKey`, `taskGroup`, and `taskId`
- `instruction-set/reference-expectations.md`: a task-level canonical artifact that identifies the config files, deployment manifests, and expected values the current task should reference
- `~/.claude/skills/okstra-*/...`, `~/.agents/skills/okstra-*/...`, `~/.claude/agents/...`: skill/agent assets seeded into the user's home directory by `okstra install` (project-local seeding no longer occurs; refresh them with `okstra install --refresh`)

The following former files are no longer generated by okstra.

- `CLAUDE.md`
- `.project-docs/ai/claude-project-guide.md`
- `.project-docs/ai/claude-skill-index.md`
- `.project-docs/ai/okstra/okstra-guide.md`
- `.project-docs/ai/okstra/worker-catalog.md`

### 4. Manager-owned cross-project state

`okstra manager` does not replace the project-local `.okstra` canonical state. It stores global manager state that groups multiple project-local tasks in the user's home directory.

```text
~/.okstra/managers/<manager-id>/
├── manager.json
├── projects.json
└── task-groups/<safe-task-group>/<safe-task-id>/
    ├── manifest.json
    ├── children.json
    ├── directives.jsonl
    ├── snapshots.json
    ├── events.jsonl
    └── child-context/<safe-project-id>-<safe-child-task-id>.md
```

Storage authority:

- `manager.json`: manager identity and schema version.
- `projects.json`: the projectId / projectRoot / role / tags registered with the manager. `projectRoot` must be an existing directory, and setup-equivalent registration is performed only when project-local `.okstra/project.json` is absent.
- `manifest.json`, `children.json`, `directives.jsonl`: manager-owned plan, child assignment, and shared/project directives.
- `snapshots.json`: a read-side snapshot that `task sync` imports from the child project's `.okstra`. It is not the source of truth for project state.
- `events.jsonl`: manager events such as `task-created` and `child-launch-prepared`.
- `child-context/*.md`: child lead context prepared by `task run`. Sibling project reports/snapshots are passed only as read-side source material.

Path segments are normalized into slugs. If a slug would be empty, as can happen with non-ASCII values, a `u-<sha1-prefix>` fallback segment is used. However, `manifest.json` and the child `taskKey` preserve the original input values (`project-id:task-group:task-id`).

## Task manifest contract

`okstra set-work-status` can register an existing brief without creating a run. Such a task
has `registrationSource: direct`, its identity and brief path, and user-managed `workStatus`.
No workflow phase or run status is synthesized. A direct completion stores its work and
verification summary in `<task-root>/work-records/<digest>.json`; `latestWorkRecordPath`
points to it and each record's `previousRecordPath` preserves earlier completions. Actual
run preparation preserves these fields. Group memory records the direct source and result
path; status, recap, and later run inputs expose that path without treating it as a final report.
The command checks required completion summaries and reports sharing failures separately
from saved state (`tests/inspect/test_okstra_set_work_status.py`).

`task-manifest.json` is the canonical metadata file the active Okstra lead uses to understand task continuity.
As a general rule, this manifest records the absolute `projectRoot` path only once and expresses the remaining generated paths as project-relative fields whenever possible.

Examples of key fields:

- `projectId`
- `taskGroup`
- `taskId`
- `taskKey`
- `projectRoot`
- `taskType`
- `workCategory`
- `taskBriefPath`
- `relatedTasks`
- `currentStatus`
- `taskRootPath`
- `instructionSetPath`
- `referenceExpectationsPath`
- `runsPath`
- `historyTimelinePath`
- `latestRunPath`
- `latestRunStatus`
- `latestRunPromptsPath`
- `latestReportRecordPath`
- `latestResumeCommandPath`
- `workflow.currentPhase`
- `workflow.currentPhaseState`
- `workflow.phaseStates`
- `workflow.lastCompletedPhase`
- `workflow.nextRecommendedPhase` *(object `{phase, status, rationale}`; `status` is `ready` / `pending` / `blocked` / `terminal`. `phase` is non-empty only under `ready` — a property of the projection, not a constraint the struct enforces: `prepare` lowers a `ready` pointer to `pending` and keeps its `phase`, so a stored non-`ready` pointer that still names a phase is a normal manifest state. Read `status` to decide whether anything can be launched.)*
- `workflow.awaitingApproval`
- `workflow.lastSafeCheckpoint`
- `phaseOutcome` *(artifact-derived semantic phase outcome; for example, when the implementation carry proves that all stages passed, the implementation phase outcome may be recorded as completed even if run contract validation failures remain)*
- `inputs`
- `artifacts`
- `resultContract`
- `leadSession`
- `claudeSession` *(Claude-host compatibility only)*
- `fixCycles` *(derived summary — `{count, openCycleId, latest:{cycle, symptom, targetReport, closedAt}}`; recalculated on every prepare)*

This manifest serves the following purposes.

- Let the active lead understand task continuity before reading the brief
- Establish which files should be read first
- Establish how config files and deployment manifests should be interpreted against expected values
- Establish the task key under which results accumulate
- Make it quick to locate related tasks and the latest run

## Task index contract

`task-index.md` is a summary document for quick human reading.

Main contents:
- task key
- current task type
- work category
- current task status
- latest run status
- current phase
- current phase state
- next recommended phase
- reference expectations
- latest run
- latest report
- resume command

This document is only a quick summary and is not the source of truth.
Always use `task-manifest.json` when checking canonical metadata.

The rendered `task-index.md` is an English-literal artifact. The headings, field labels, and `## Notes` footnote of the template (`templates/project-docs/task-index.template.md`) ship as English literals regardless of the `reportLanguage` setting, matching the okstra-schedule-gen schedule contract. `tests/contract/test_docs_runtime_contract.py` enforces this contract.

## Run manifest contract

Each execution records the current run contract in the resolved `<run-dir>/manifests/run-manifest-<task-type>-<seq>.json`. The non-stage or stage-qualified rule above determines `<run-dir>`.

`manifests/run-context-<task-type>-<seq>.json`, starting with schemaVersion `2.0`, stores `identity` + `pathHints` rather than directly storing every legacy path key. The host-side reader hydrates `pathHints` to reconstruct the former flat keys (`RUN_MANIFEST_RELATIVE_PATH`, `TEAM_STATE_PATH`, and so on) in memory.

`state/active-run-context-<task-type>-<seq>.json` is also the compact intake for lead Phase 1 starting with schemaVersion `2.0`. It stores only `identity` + `pathHints` and worker identities instead of repeated path strings, and the deterministic dispatcher hydrates it into the legacy active context shape when reading it.

The path fields in `manifests/run-manifest-<task-type>-<seq>.json` are still stored as paths relative to the target project root for validator, team dispatch, and inspect compatibility.
An in-host prepare records the run as `prepared` and the task as `ready-for-lead`; the current native host session then owns execution. The standalone Claude launcher records `in-progress` after preparing its compatibility resume script.

Main contents:

- task key
- task type
- work category
- run datetime segment
- task brief relative path
- analysis target
- related tasks
- selected workers
- worker model assignments
- lead session id and accounting mode
- Claude session id only on the Claude compatibility path
- resume command relative path
- expected report relative path
- expected status relative path
- prompt snapshot relative path
- final-verification target snapshot relative path and digest (empty for other task types)
- `worker prompt directory relative path`
- `worker prompt relative path by worker id`
- current run status
- workflow snapshot
- team contract
- `fixCycleId` *(only when attached to a fix cycle; field omitted otherwise)*

## Timeline contract

`history/timeline.json` accumulates the history of runs belonging to a task.

The history generally includes the following.

- run timestamp
- run directory relative path
- run manifest relative path
- run time segment
- task type
- work category
- status
- worker prompt directory relative path
- report relative path
- resume command relative path
- related tasks
- workflow snapshot
- `fixCycleId` *(only when attached to a fix cycle; field omitted otherwise)*

An entry is written once, when the run is prepared (`render.render_timeline`, called only from `prepare_task_bundle`). Its `status`, `workflowSnapshot`, and `reportRecordPath` are therefore the values at preparation time: `status` is `prepared` or `in-progress`, the snapshot is the workflow as it stood before the run, and the report path is the run's *reserved* record path. The run's current facts live in its own `run-manifest-<task-type>-<seq>.json`, which `validators/validate-run.py` updates when the run ends (`status`, `validation`, `workflowSnapshot`); task-level current facts (`currentStatus`, `latestRunStatus`, `workflow.*`, `latestReportRecordPath`) live in `task-manifest.json`. Read-side projections (`okstra model-io history-input` / `recap-input`, `okstra recap assemble`) overlay each entry with its run-manifest through `okstra_ctl.timeline_runs.current_run_facts`, and a run whose `validation.status` is still `not-run` projects no report path — the report sequence is allocated from files on disk, so a prepared-but-never-run entry's reservation is reused by the next run and would otherwise point at that later run's report.

Reruns of the same task type reuse the same resolved run directory family. Non-stage runs use `runs/<task-type>/`, while `implementation` and single-stage `final-verification` runs use `runs/<task-type>/stage-<N>/`. Sequence-specific artifacts live beneath that resolved run directory, and each execution accumulates as a separate history entry because `run-manifest-<task-type>-<seq>.json` and related artifact paths are separated by per-category sequence suffixes (`<task-type>-<seq>`). The cross-category identifier is the manifest's `runDateTimeSegment` ISO timestamp field.

## Task-group context contract

`.okstra/briefs/<task-group>/group-context.md` is the one document a task-group shares: why the group exists, how its result is measured (with the denominator named), group-wide constraints, and ticket relations. It is optional — a group without it prepares as before. The skeleton comes from `okstra group-context init` (`templates/reports/group-context.template.md`; frontmatter `type: group-context`, `task-group`, `created`, `generator`) and the user fills the four sections `Why This Group Exists`, `Definition of Better`, `Group-Wide Constraints`, `Ticket Relations` (the last two accept `_(none)_`). `validators/validate-brief.py` dispatches on the frontmatter `type`, so the group's directory validation covers briefs and the context together (`okstra_ctl.group_context.validate_group_context`).

Preparation validates the file before any side effect and refuses one that still carries a `<...>` placeholder line (`run._validate_group_context_preflight`), then copies it to `instruction-set/task-group-context.md` and the analysis packet carries its human sections as `## Task-Group Context` between `## Source Files` and `## Task-Specific Brief Extract`, behind a fixed preface that separates the group's measure and constraints from the brief's scope. The wizard's brief suggestions skip the file and list the group's briefs in start order (not started, then in progress, then done).

The file's tail is okstra's: between `<!-- okstra:task-memory:begin -->` and `<!-- okstra:task-memory:end -->` sits `## Task Memory`, rewritten by `report-finalize`'s `record-group-memory` step after every validated run (`okstra_ctl.group_context.record_task_memory`, under a directory flock). It opens with the group's start order — the brief ordinal (`DEV-10627-2-…` → 2), with the briefs' Related Task Graph `depends-on` / `blocks` / `blocked-by` edges quoted as `waits for <ticket>` while the named ticket is not done, never reordering — each brief marked `done` / `in progress` / `not started` from the catalog's task-manifest first (`group_context.catalog_progress`: a terminal next-phase pointer is `done`, any other catalog task is `in progress` with `<phase> (<state>)` as progress) and from the recorded entries only for tasks the catalog does not know, followed by one `### <task-id>` entry per task: latest phase and next phase, headline (240 chars), decisions accumulated across the task's runs (newest first, 5 kept), watch-out items from the report's blockers (3), open non-continuation follow-ups (3), and the record path. When the group has no file, the step creates one with the frontmatter, the H1, and the region only (`generator: okstra-report-finalize`); the validator applies the four-section rule only when a human `## ` section is present, and `okstra group-context init` inserts the human skeleton above an existing region instead of refusing. Preparation splits the copy at the markers: the region reaches every sibling task as `## Task-Group Memory` after the brief extract, minus the task's own entry, and is omitted when no sibling has recorded. The read side joins the same three sources for the okstra-inspect recap facet (`okstra recap assemble --task-group`, projected by `okstra model-io recap-input --task-group`): the briefs give the order, the catalog gives status / run count / next phase / report, and the region gives each recorded task's conclusion (`okstra_ctl.recap.assemble_group_recap`).

## Lead operating contract

After `okstra` prepares a run, the host-native Okstra lead follows this default sequence when reading the current task.

1. If task browsing or task-id disambiguation is required, first read `.okstra/discovery/task-catalog.json`.
2. If the current task key or task path is not specified, read `.okstra/discovery/latest-task.json` as the current-task pointer.
3. Read `task-manifest.json`.
4. If the current `state/active-run-context-<task-type>-<seq>.json` exists, read it as the primary input for lead Phase 1. This file is a compact intake that reconstructs run artifact paths from `identity` + `pathHints` clues. If it does not exist, fall back to the current `manifests/run-manifest-<task-type>-<seq>.json` and `team-state`.
5. Read `instruction-set/analysis-profile.md` and `instruction-set/analysis-packet.md`.
   For final-verification, use the compact target identity from active context and open `instruction-set/verification-target.md` only when its full mapping or diff stat is needed.
6. Read `task-index.md` only when a quick summary is needed.
7. Lazily read `analysis-material.md`, `reference-expectations.md`, `task-brief.md`, and `final-report-template.md` when the packet is insufficient or when source citations/report writing require them.
8. Consult `history/timeline.json` and previous run results if necessary.
9. As `Okstra lead`, organize roles from `workerAssignments[]`. The host provider stays native; Claude, Codex, Antigravity, Grok, and Kimi assignments use their persisted `native-session` or `cli-wrapper` runner.
10. Save each selected worker prompt under the current run's `prompts/` directory at the assigned worker prompt history path before dispatching the worker.
    Every selected initial analyser receives the same full-core semantic body for its task, including final-verification. Dispatch and Phase 7 validate the same normalized equality policy; implementation executor, report-writer, and `-reverify-r<N>-` prompts are separate audiences.
11. In convergence Round 0, the lead groups findings by semantic meaning and ticket set, then writes `state/convergence-groups-<task-type>-<seq>.json`. The deterministic engine seeds the working queue and generates each roster-aware reverify plan; lightweight reverify reads only that persisted batch and its embedded evidence.
12. Collect a result or terminal status for each required worker.
13. Write the final structured report to `reports/final-report-<task-type>-<seq>.data.json` using the versioned schema and exactly one task-type deliverable.
14. Run Phase 7 so the task-specific human HTML is derived from the report record, then update `status/final-<task-type>-<seq>.status`, `manifests/run-manifest-<task-type>-<seq>.json`, `task-manifest.json`, and `task-index.md` to match the current state. The full reading copy is rendered on demand.

Convergence keeps its decision trail under the run's `state/` directory:

- `state/convergence-groups-<task-type>-<seq>.json` — lead-authored semantic groups and resolved analyser roster.
- `state/convergence-work-<task-type>-<seq>.json` — engine-owned queue, finding state, and round history.
- `state/convergence-round-<N>-plan-<task-type>-<seq>.json` — immutable roster-aware dispatch plan for one round.
- `state/convergence-round-<N>-results-<task-type>-<seq>.json` — adapter outcomes and parsed votes supplied to the reducer.
- `state/convergence-<task-type>-<seq>.json` — validated terminal schema v1.4 state consumed by reporting and Phase 7. Historical v1.0-v1.3 artifacts are read as they are.
- `state/migrations/` — byte-for-byte archives of invalid or partial legacy state replaced during an explicit Round 0 restart.

A valid terminal legacy final is reused unchanged, and a matching valid working state resumes. Invalid new-engine working state fails closed unless seeding receives `--restart-from-round0`; an existing final can be replaced only when its migration archive still matches its original bytes.

Recommended worker status values:

- `completed`
- `timeout`
- `error`
- `not-run`
