This document defines the canonical locations for all durable state and artifacts in Codex Autorunner (CAR), the retention taxonomy, and the "no shadow state" contract.
All durable artifacts must live under one of these roots:
Location: <repo_root>/.codex-autorunner/
Purpose: Per-repository runtime state, tickets, context, and configuration.
Contents:
tickets/- Ticket queue (required)contextspace/- Durable context (active_context.md, decisions.md, spec.md)config.yml- Generated repo configstate.sqlite3- Run state databasecodex-autorunner.log- Runner logslock- Lock file for exclusive accessruns/- Run artifacts and dispatchflows/- Flow artifactsflows.db- Flow store databasepma/- PMA state and queuearchive/- Reviewable retained output (worktree snapshots underarchive/worktrees/, run archives underarchive/runs/). These are not live source of truth; see retention classes below.bin/- Generated helper scriptsworkspace/- Compatibility-only input (not a canonical store). CAR reads durable context fromcontextspace/. Theworkspace/fallback is used during archive/reset operations only, and has a single owner:_contextspace_source()incore/archive.py. No new runtime code may read fromworkspace/directly. If this directory still exists,car doctormay warn until you migrate or remove it. See workspace → contextspace migration.app_server_workspaces/- App-server supervisor/workspace state when the effective destination isdockerfilebox/- Shared attachment ingress/compatibility root (filebox/inbox,filebox/outbox)
Notable repo-local artifacts:
flows/<run_id>/chat/inbound.jsonl- Mirrored inbound chat events for a flow runflows/<run_id>/chat/outbound.jsonl- Mirrored outbound chat events for a flow runtickets/ingest_state.json- Canonical ticket-ingest receipt (ingested,ingested_at,source)filebox/outbox/- Legacy artifact ingress for the active target scope, includingcar renderscreenshot/observe/demo outputs; journal-backed delivery records are the source of truthusage/opencode_turn_usage.jsonl- Per-turn OpenCode usage snapshots (canonical, high-confidence source for repo usage reports)
Resolution: resolve_repo_state_root(repo_root) in core/state_roots.py
Location: <hub_root>/.codex-autorunner/
Purpose: Hub-level state for typed managed resources (repos[]), orchestration metadata, and cross-resource coordination.
Contents:
manifest.yml- Managed hub resources list (repos:and per-repodestinationconfig)hub_state.json- Hub stateconfig.yml- Hub configcodex-autorunner-hub.log- Hub logstemplates/- Hub-scoped templatesruntimes/- CAR-managed runtime workspace roots under<runtime>/<workspace_id>/chat/channel_directory.json- Cross-platform channel directory used for lightweight routing contextorchestration.sqlite3- Hub SQLite store for orchestration metadata, bindings, executions, transcript mirrors, and event projections
Resolution: Hub root is typically the hub's repo root, using repo-local patterns.
The orchestration.sqlite3 database is the canonical store for:
- Thread/binding metadata: Durable CAR thread registrations plus external channel bindings (Discord channel IDs, Telegram chat IDs) to consistent thread targets under repos, worktrees, or filesystem-scoped roots
- Execution state: Active/running/queued orchestration work items
- Transcript mirrors: Plain-text user/assistant message copies for search, previews, and debugging
- Event projections: Aggregated views of automation events, flow executions
What orchestration.sqlite3 does NOT store (remains authoritative elsewhere):
- Flow engine internals: Still stored in repo-local
flows.db - Delivery/outbox state: Discord and Telegram state databases remain authoritative for message delivery
- Runtime conversation history: Downstream runtimes (Codex, OpenCode) own their canonical conversation state
- Reasoning traces/tool payloads: Kept in runtime-native stores
Resolution: resolve_hub_orchestration_db_path(hub_root) in core/state_roots.py
CAR may allocate hub-local directories for runtime-specific durable files under:
<hub_root>/.codex-autorunner/runtimes/<runtime>/
Use resolve_hub_runtimes_root and resolve_hub_runtime_root for path authority.
CAR does not install third-party runtimes as part of the state-root contract.
Resolution:
resolve_hub_runtimes_root(hub_root)resolve_hub_runtime_root(hub_root, runtime=...)
Location: ~/.codex-autorunner/ (configurable via CAR_GLOBAL_STATE_ROOT)
Purpose: Cross-repo caches, shared resources, update state.
Contents:
update_cache/- Cached update artifactsupdate_status.json- Update statusupdate_snapshots/- Timestamped update rollback snapshots, including orchestration DB copies created by macOS safe refreshlocks/- Cross-repo locks (e.g., telegram bot lock)workspaces/- App-server workspaces (default for non-docker destinations)
Update snapshot retention:
- Update orchestration (staged install, cutover, restart, health, rollback)
lives in the Python
UpdateEngine(codex_autorunner.core.update.engine). Bothscripts/safe-refresh-local-mac-hub.shandscripts/safe-refresh-local-linux-hub.share thin wrappers that delegate topython -m codex_autorunner.core.update.runner. - The engine stores update rollback snapshots under
~/.codex-autorunner/update_snapshots/whenUPDATE_STATUS_PATHuses the global state root. - Snapshot pruning runs after every
snapshot-dbupdate phase. The default keeps only the newest snapshot directory needed for rollback. - Override with
UPDATE_SNAPSHOT_MAX_COUNT. Set it to0to disable pruning. - Manual inspection and pruning:
du -sh ~/.codex-autorunner/update_snapshots
find ~/.codex-autorunner/update_snapshots -maxdepth 1 -mindepth 1 -type d -print
python -m codex_autorunner.core.update_transaction prune-snapshots \
--snapshot-root ~/.codex-autorunner/update_snapshotsThe pruning helper only deletes directories with valid
orchestration/snapshot.json metadata, skips the current update run when
provided, and skips candidate directories when lsof reports open files inside
them or cannot prove they are closed.
Docker destination override:
- When a repo/worktree runs with effective destination
docker, supervisor state root is forced to:<repo_root>/.codex-autorunner/app_server_workspaces
- Rationale: docker-wrapped commands execute inside the repo bind mount, so state must be writable and visible from that mount.
- This still satisfies the canonical state contract because the override remains under repo-local
.codex-autorunner/.
Resolution: resolve_global_state_root() in core/state_roots.py
Config Override: Set state_roots.global in config or CAR_GLOBAL_STATE_ROOT env var.
Canonical roots define where CAR is allowed to store state. Retention classes define how long that state should be kept.
A path can be canonical without being retained forever. For example,
state.sqlite3, lock, and workspace directories are canonical locations but
are runtime state, not durable artifacts.
| Class | Description | Cleanup Behavior |
|---|---|---|
durable |
Source-of-truth or long-lived artifacts | Never deleted; may be compacted in place |
reviewable |
Bounded review artifacts | Pruned by explicit age/count/byte policy |
ephemeral |
Runtime, staging, or transient artifacts | Aggressively pruned when inactive |
cache-only |
Rebuildable caches | Deleted freely when not in active use |
The canonical retention contract is defined in
.codex-autorunner/contextspace/spec.md with detailed per-path mappings.
Key durable artifacts (never deleted):
tickets/— ticket queuecontextspace/— durable context docs (active_context.md, decisions.md, spec.md)context_log.md— context log snapshotsruns/<run_id>/dispatch_history/andreply_history/— turn historyflows.db— flow engine store- Hub
orchestration.sqlite3— orchestration store - Stable reports (
reports/latest-*,final_report.md)
Key reviewable artifacts (bounded pruning; these are retained output, not live source of truth):
archive/worktrees/**— worktree snapshots (reviewable retained output)archive/runs/**— run archives (reviewable retained output; FlowStore inflows.dbis the canonical live run-history store)flows/<run_id>/— flow artifactsreports/history filesgithub_context/review/runs/— review run artifacts
Key ephemeral artifacts (aggressive pruning):
state.sqlite3— runtime state databaseapp_server_workspaces/— repo-local workspace statefilebox/inbox/,filebox/outbox/— attachment staging*.logfilesuploads/**update-standalone.loglockfiles
Key cache-only artifacts:
update_cache/— update artifacts cache
Use car cleanup state to reclaim disk space across all retention families:
# Preview what would be deleted
car cleanup state --dry-run
# Clean repo-local state only
car cleanup state --scope repo
# Clean global state only
car cleanup state --scope global
# Clean all scopes
car cleanup state --scope allFor details on the cleanup contract and safety guards, see State Cleanup Operations.
These paths exist only as fallback or migration inputs. They must not regain runtime ownership, and no new code path may treat them as canonical:
| Path | Canonical owner | Fallback behavior |
|---|---|---|
.codex-autorunner/workspace/ |
contextspace/ |
_contextspace_source() in core/archive.py falls back to workspace/ during archive/reset only |
Legacy numeric run directories under runs/<int>/ |
flows.db / FlowStore |
Not canonical for new runs; retained only for backwards compatibility |
Incomplete staging dirs under archive/ without META.json |
Ignored | Intentionally invisible to retention pruning |
Several cleanup and archive operations are intentionally best-effort:
- Retention pruning after archive: worktree/CAR-state archive treats post-archive retention pruning as best-effort follow-up that must not fail the archive itself.
- Run-archive fallback:
core/flows/archive_helpers.pyfalls back to default run-archive retention policy when repo config cannot be loaded. - Incomplete snapshots ignored:
prune_worktree_archive_root()skips snapshot directories that lackMETA.json, keeping incomplete staging work out of retention logic. - Stable report preservation:
prune_report_directory()always preserves stable-prefix outputs (latest-*,final_report.md) even when the history budget is tight. - Global workspace cleanup skip: global workspace cleanup skips entirely when hub manifest visibility is too weak to prove which shared workspaces are active.
- Filebox symlinks ignored:
prune_filebox_root()ignores symlinks and non-files.
These locations are explicitly non-canonical (ephemeral, disposable):
| Location | Purpose | Notes |
|---|---|---|
/tmp/, $TMPDIR |
Temporary files | Never durable |
$XDG_CACHE_HOME or ~/.cache |
Optional caches | Must be rebuildable |
__pycache__/ |
Python bytecode | Auto-generated |
Rule: Any location outside the canonical roots must be:
- A true cache (data is derivable from canonical sources)
- Explicitly documented here
- Safe to delete without data loss
This section defines which store is authoritative for which data domain.
Location: <repo_root>/.codex-autorunner/flows.db
Remains authoritative for:
- Flow definition storage and versioning
- Flow execution state machine (pending, running, completed, failed)
- Flow step orchestration and dependency resolution
- Flow variable and context persistence
The hub orchestration.sqlite3 may contain projections of flow execution status, but flows.db is the source of truth for flow-engine internals.
Discord: <repo_root>/.codex-autorunner/discord_state.sqlite3
Telegram: <repo_root>/.codex-autorunner/telegram_state.sqlite3
These transport-specific databases remain authoritative for:
- Per-channel/topic delivery state and outbox queues
- Platform-specific message metadata
- Webhook state and callback tracking
Binding metadata for Discord and Telegram ordinary turns lives in hub
orchestration.sqlite3, while delivery state remains in the transport
databases.
All canonical PMA state lives in orchestration.sqlite3 tables:
| Table | Owner | Purpose |
|---|---|---|
orch_thread_targets |
PmaThreadStore |
Managed thread registrations and lifecycle |
orch_thread_executions |
PmaThreadStore |
Managed turn execution records |
orch_thread_actions |
PmaThreadStore |
Thread-level action records |
orch_queue_items |
PmaQueue / PmaThreadStore |
Lane items and thread-execution queue items |
orch_automation_rules |
AutomationStore |
Unified automation rule definitions, including built-ins |
orch_automation_events |
AutomationStore |
Normalized lifecycle, SCM, schedule, repo, and manual events |
orch_automation_jobs |
AutomationStore |
Durable automation job ledger and execution refs |
orch_automation_job_attempts |
AutomationStore |
Per-attempt execution results and errors |
orch_automation_schedules |
AutomationStore |
Derived schedule state for scheduled rules |
orch_automation_subscriptions |
Migration diagnostics | Legacy PMA subscription residue, not runtime source of truth |
orch_automation_timers |
Migration diagnostics | Legacy PMA timer residue, not runtime source of truth |
orch_automation_wakeups |
Migration diagnostics | Unsupported legacy wakeup residue reported by migration diagnostics |
orch_reactive_debounce_state |
PmaReactiveStore |
Reactive debounce timestamps |
Migration blockers for remaining compatibility rows are surfaced by
car doctor --json, car hub orchestration status --json, and
car automation migration-status --json. The JSON diagnostics report pending
schema versions, legacy residue counts, legacy executor-shape blockers, and
operator next steps. Normal runtime no longer imports, mirrors, or
auto-materializes legacy PMA subscription, timer, or wakeup rows into unified
automation rules.
Filesystem mirrors are not the source of truth:
| Mirror path | Owner | Notes |
|---|---|---|
.codex-autorunner/pma/queue/*.jsonl |
PmaQueue |
Rewritten after every queue mutation; replay_pending reads from SQLite |
.codex-autorunner/pma/automation_store.json |
Migration diagnostics | Obsolete legacy automation artifact; remove after diagnostics are clear |
.codex-autorunner/pma/reactive_state.json |
PmaReactiveStore |
Rewritten after every debounce update |
.codex-autorunner/pma/threads.sqlite3 |
PmaThreadStore |
Legacy thread mirror, gated by CAR_LEGACY_MIRROR_ENABLED |
Deleting any mirror file does not affect correctness; SQLite remains authoritative and mirrors are regenerated on the next write.
Separate from persistence mirrors, PmaStateStore owns PMA runtime state
in .codex-autorunner/pma/state.json. This file is not part of the canonical
queue/thread/automation persistence path.
Locations:
<repo_root>/.codex-autorunner/pma/deliveries.jsonl<repo_root>/.codex-autorunner/pma/thread_*.json<repo_root>/.codex-autorunner/pma/queue.json
These are pre-orchestration artifacts used only for initial migration into hub SQLite. No new data should be written to these paths.
Invariant: All durable state and artifacts must be representable under a canonical root. No "shadow" state directories outside these roots.
- No ad-hoc roots: Don't create new state directories outside the roots
- Single source of truth: Canonical roots are the source of truth
- Portable state: All durable state can be moved by relocating the root
- Testable boundaries: Tests verify no writes outside allowed roots
core/state_roots.pyprovides the single authority for root resolution- Call sites should use
resolve_repo_state_root()andresolve_global_state_root() - Tests verify boundary enforcement (see
tests/core/test_state_roots.py)
The core/state_roots.py module provides:
def resolve_repo_state_root(repo_root: Path) -> Path:
"""Return the repo-local state root (.codex-autorunner)."""
def resolve_global_state_root(*, config=None, repo_root=None) -> Path:
"""Resolve the global state root for cross-repo caches and locks."""
def resolve_hub_state_root(hub_root: Path) -> Path:
"""Return the hub-scoped state root."""
def resolve_hub_orchestration_db_path(hub_root: Path) -> Path:
"""Return the canonical orchestration SQLite path under the hub state root."""
def resolve_hub_templates_root(hub_root: Path) -> Path:
"""Return the hub-scoped templates root."""
def resolve_hub_runtimes_root(hub_root: Path) -> Path:
"""Return the hub-scoped root for CAR-managed runtime workspaces."""
def resolve_hub_runtime_root(hub_root: Path, *, runtime: str) -> Path:
"""Return the managed root for one runtime under the hub."""from codex_autorunner.core.state_roots import resolve_repo_state_root
state_root = resolve_repo_state_root(repo_root)
db_path = state_root / "state.sqlite3"
log_path = state_root / "codex-autorunner.log"When adding new durable artifacts:
- Determine the appropriate root (repo-local, hub, or global)
- Use
resolve_*_state_root()functions, not ad-hoc path construction - Document the artifact in this file
- Ensure tests cover the boundary