Spec: Task Bundle V2
Mirrored from
docs/design/task-artifacts/specs/task-bundle-v2.md. Edit the source document in the repository, not this generated page.
Task Bundle V2 defines the canonical on-disk contract for Orbit tasks after the schema reset. The contract is source-of-truth storage, not merely a rendering preference: implementations should expose the bundle shape below rather than maintaining long-lived compatibility with the previous task schema.
Why This Exists
Section titled “Why This Exists”The current task bundle is easy to inspect but hard to sync and evolve. Long prose, append-heavy audit arrays, status directory moves, and workspace-local IDs all interact badly with shared registries and long-lived agent workflows. This spec pins the reset target so cutover work and new code converge on one artifact shape.
Normative Language
Section titled “Normative Language”must marks required behavior for conforming v2 readers and writers. should marks a recommended default that improves authoring or UX but is not a storage-level validity rule unless a transition policy explicitly enforces it.
Bundle Path
Section titled “Bundle Path”Every canonical task bundle lives at:
~/.orbit/tasks/workspaces/<workspace-id>/<task-id>/The workspace-local projection lives at:
.orbit/tasks/<task-id> -> ~/.orbit/tasks/workspaces/<workspace-id>/<task-id><task-id> must be the canonical ID inside the current allocation authority. The canonical v2 format is ORB- plus a five-digit decimal suffix (ORB-00000 through ORB-99999). <workspace-id> is assigned once per workspace as <slug>-<6char> and stored in .orbit/config.yaml. Old T<YYYYMMDD>-<N> IDs are not valid v2 identifiers or lookup aliases.
Required files:
task.yamldescription.mdacceptance.mdplan.mdexecution-summary.mdevents.jsonlcomments.jsonlRequired directories:
review-threads/artifacts/artifacts/manifest.yaml is required when artifacts/files/ contains any files.
Local Store and Workspace Projection
Section titled “Local Store and Workspace Projection”The home-directory bundle is the active source of truth for task content. Local-first Orbit must keep allocation and local operational metadata under:
~/.orbit/tasks/index.sqliteCanonical workspace bundles live under:
~/.orbit/tasks/workspaces/<workspace-id>/<task-id>/The index must record allocation authority state, canonical IDs, workspace bindings, materialized bundle paths, local execution bindings such as workspace_path and repo_root, generated status/relation/tag index rows, and task-lock reservation bindings or pointers.
Each workspace must have .orbit/config.yaml with at least:
schema_version: 1workspace_id: orbit-a3f9c2.orbit/tasks/ is a symlink projection to canonical bundles. Task mutations must make the canonical bundle and registry metadata durable before reporting success. If .orbit/tasks/ is deleted, Orbit rebuilds projection links from .orbit/config.yaml and index.sqlite. If .orbit/config.yaml is missing, Orbit must prompt to rebind by matching the checkout path, repo root, and optional remote fingerprints against index.sqlite; ambiguous matches must not silently attach to a workspace.
Delete verifies that any projection entry is a symlink, unregisters the task binding and generated index rows, removes the canonical bundle directory, then removes the projection entry. A projection path that exists as a non-symlink must stop the delete before unregistering rather than removing unrelated workspace files.
Envelope
Section titled “Envelope”task.yaml must contain only structured metadata:
schema_version: 1id: ORB-00000title: Short titlestatus: proposedtype: chorepriority: mediumcomplexity: nulljob_run_id: nullrelations: - type: resolves target: F2026-05-007tags: []context_files: []external_refs: []created_by: codex:gpt-5.5planned_by: nullimplemented_by: nullcreated_at: 2026-05-11T00:00:00Zupdated_at: 2026-05-11T00:00:00ZThe envelope must not contain description, acceptance_criteria, plan, execution_summary, history, comments, or review_threads.
The envelope must not contain local-only workspace_path or repo_root; those live in the local registry workspace binding.
The envelope must not contain the old batch_id name or internal execution-routing fields such as agent and model; job-run membership is stored as job_run_id, while durable task attribution uses created_by, planned_by, and implemented_by.
Task-only relation types must target ORB- IDs. produces and resolves may additionally target friction, learning, and ADR IDs.
Documents
Section titled “Documents”Document sidecars are UTF-8 Markdown:
description.mdmay be empty only for machine-generated placeholder tasks.acceptance.mdshould have at least one bullet or checkbox before a task entersin-progress; storage does not define a waiver list.plan.mdmust be non-empty before enteringin-progresswhen the transition policy requires a plan.execution-summary.mdmust be non-empty before enteringreview.
Convenience reads:
descriptionreturnsdescription.mdas a string.acceptancereturnsacceptance.mdas a string; UI code may parse checkboxes for display.planreturnsplan.mdas a string.execution-summaryreturnsexecution-summary.mdas a string.
Events
Section titled “Events”events.jsonl is append-only. Each line is a JSON object with:
schema_version: event row schema version, initially1.event_id: stable per-task event ID.at: RFC 3339 timestamp.by: actor label.type: event type.note: optional string.from_status: optional status.to_status: optional status.
Writers must not rewrite prior event lines except during explicit cutover or repair commands. Appends must write one JSON object plus newline, flush and sync the file before success, and preserve a valid prefix if the process crashes. Readers must tolerate a final unterminated or invalid JSON line as tail corruption; repair may truncate only that final corrupt tail.
Comments
Section titled “Comments”comments.jsonl is append-only. Each line is a JSON object with:
schema_version: comment row schema version, initially1.comment_idatbybody
Comment bodies are Markdown strings. Multi-line bodies must be JSON escaped inside one JSONL row, not spread across lines.
Comment appends follow the same atomicity and tail-repair rules as events.jsonl.
Review Threads
Section titled “Review Threads”Each review thread uses:
review-threads/<thread-id>.yamlreview-threads/<thread-id>.mdThe YAML file stores status, file path, line/range metadata, external review IDs, timestamps, and message metadata. The Markdown file stores message bodies in chronological order with stable message anchors.
The YAML file must include schema_version: 1. YAML rewrites use write-temp-in-same-directory, file sync, atomic rename, and parent-directory sync.
Review-thread rewrites must validate the complete replacement set before writing and must not remove review-threads/ before the replacement is durable. Crash recovery may leave stale thread files behind, but it must not leave the bundle unreadable solely because the directory vanished.
Relations
Section titled “Relations”Relations are directed entries stored in the task envelope. The initial relation type set is:
blocked_by: source task is blocked by target task.child_of: source task is a child/subtask of target task.spawned_from: source task was created from target task.regression_from: source task tracks a regression introduced by target task.supersedes: source task replaces target task.related_to: source task is associated with target task without stronger semantics.
Writers must validate the relation type set, reject self-edges, reject duplicate (type, target) entries on one source task, and reject cycles for hierarchy and blocking relation families. Relation types are source-implied: create-subtask writes only child_of -> parent on the child, and create-dependent-task writes only blocked_by -> dependency on the blocked task. Inverse lookup is generated from indexes, not stored as peer records.
Local indexes should materialize (source_task_id, relation_type, target_task_id) and inverse lookup rows so lineage queries do not scan every task bundle.
Generated Views
Section titled “Generated Views”The local registry must maintain generated status and terminal-month views or rebuild them from bundles/events on demand. These views replace the current review/ and done/<yyyy-mm>/ directory browsing affordances without making lifecycle state part of the path.
The initial registry projections are:
task_bundle_index(task_id, workspace_id, status, priority, job_run_id, created_at, updated_at, terminal_month).task_bundle_tags(task_id, workspace_id, tag).task_bundle_relations(source_task_id, workspace_id, relation_type, target_task_id). The physical column name is historical;producesandresolvesrows may store non-task artifact IDs intarget_task_id.
Indexes are generated data. The bundle envelope is canonical, and repair/rebuild paths may delete and regenerate index rows from bundles. The task_bundle_index.updated_at value is the envelope version stamp. Query paths should treat a missing row, incomplete index, or updated_at mismatch as a cache miss: rebuild from registered bundles when possible, otherwise fall back to bundle reads rather than treating the index as proof that tasks do not exist.
Local Locks
Section titled “Local Locks”Task lock reservations are not task artifacts. They remain local operational state in SQLite, keyed by workspace binding and canonical task IDs, with TTL/release semantics and audit events. File-overlap checks remain the conflict mechanism for actual work exclusion. During cutover, v2 reservations carry workspace_id alongside the legacy .orbit path so cleanup can still find older path-scoped rows; a later old-store removal can make workspace_id mandatory. In v2 mode, a missing .orbit/config.yaml workspace binding is an error at reservation write time.
Crash Consistency
Section titled “Crash Consistency”The file bundle does not provide all-or-nothing transactions across Markdown sidecars, JSONL logs, artifacts, envelope metadata, and generated SQLite indexes. The supported failure model is detect-and-repair:
task.yamlremains canonical for structured metadata.- JSONL tail corruption is repaired only at the final partial row; corruption before the tail is an error.
- The last event with
to_statusmust matchtask.yaml.status; mismatches are corruption and must fail reads. - Review-thread tombstones hide deleted thread IDs if a rewrite crashes before orphan file pruning.
- Generated indexes are invalid when count or
updated_atstamps differ from registered bundle envelopes and must be rebuilt from bundles. - Artifact manifest entries must reference existing relative files with matching size and SHA-256; unmanifested files are ignored until a future compaction/prune command removes them.
Artifacts
Section titled “Artifacts”Artifacts are stored under artifacts/files/ and listed in artifacts/manifest.yaml:
schema_version: 1files: - path: planning-duel/winner.json blob: files/planning-duel/winner.json media_type: application/json sha256: "<64 lowercase hex chars>" size_bytes: 1234 created_by: codex:gpt-5.5 created_at: 2026-05-11T00:00:00ZArtifact paths must be relative, UTF-8, slash-separated, canonical paths and must not contain ., .., or leading ./ components. Writers that ingest hand-authored manifests should normalize leading ./ before validation. sha256 must be a 64-character lowercase hex SHA-256 digest; writer code should format digest bytes with lowercase hex ({:x}), not uppercase.
The bundle format does not guarantee cross-file transactions. Writers must keep single-file updates atomic and keep partial multi-file states readable; generated repair/indexing commands reconcile cases such as appended events before envelope status rewrite or artifact files written before manifest rewrite.
Cutover
Section titled “Cutover”Cutover from the current pre-reset task schema must:
- Create or reuse
.orbit/config.yamlwith a stableworkspace_id. - Allocate a canonical
ORB-00000ID. - Record allocation and workspace binding metadata in
~/.orbit/tasks/index.sqlite. - Materialize the canonical bundle under
~/.orbit/tasks/workspaces/<workspace-id>/<task-id>/. - Create
.orbit/tasks/<task-id>as a symlink to the canonical bundle. - Move YAML
descriptiontodescription.md. - Render YAML
acceptance_criteriaintoacceptance.md. - Preserve existing
plan.md. - Preserve existing
execution-summary.md. - Convert YAML
historytoevents.jsonl. - Convert YAML
commentstocomments.jsonl. - Convert YAML
review_threadstoreview-threads/. - Rewrite
task.yamlwith schema version 1 and no old ID aliases. - Rewrite or release active task-lock reservations.
- Record generated status, terminal-month, relation, tag, and semantic-index rebuild inputs.
Cutover must be idempotent for interrupted local runs. A partially converted task must either repair cleanly on rerun or fail with a diagnostic that names the task ID and incomplete step. The command may emit an old-ID-to-new-ID report for humans, but that report is not a persisted lookup contract.
Agent Signature
Section titled “Agent Signature”Last revised by codex on 2026-05-11.