# AirCoding Runtime Semantics V1 Date: 2026-05-27 Status: Canonical runtime semantics corrections for V1.0.0 Alpha This document resolves implementation-critical semantics that cut across the event registry, DB schema, Scheduler, ArtifactStore, PermissionEngine, ContextAssembler, and ExperienceMiner. ## 1. Source of Truth This document refines, not replaces: - `interface-contracts-v1.md` - `event-registry-v1.md` - `db-schema-v1.md` - `scheduler-state-machine-v1.md` - `artifact-naming-v1.md` - `security-model-v1.md` If a runtime behavior conflicts with older wording, this document is authoritative for V1.0.0 Alpha runtime semantics. ## 2. Event Ingestion Boundary All runtime events from workers, tools, agents, and internal services enter through `EventIngestor`. ```text producer → EventIngestor.ingest(event) → schema/version validation → persistence policy lookup → durable: EventStore transaction + domain projection + post-commit EventBus publish → ephemeral: EventBus publish/coalescing only ``` Responsibilities: | Component | Responsibility | |---|---| | EventIngestor | accepts events, validates routing, chooses durable vs ephemeral path | | EventStore | validates durable event payload, inserts event, applies domain projection transactionally | | EventBus | live publish/subscribe only | | Domain services | emit follow-up events; do not hide policy inside EventStore | EventStore must not create scheduler tasks, permission decisions, memory promotions, or doctor fixes by policy. Those are follow-up actions emitted by the owning services. ## 3. Durable Projection Rule For same-session DB domain updates: ```text BEGIN insert events row apply domain table projection COMMIT publish committed event to EventBus ``` For external DB/file side effects, use the cross-store semantics in section 6. ## 4. Heartbeat Semantics `agent.heartbeat` remains an ephemeral event for live UI updates, but Scheduler must coalesce heartbeat timestamps into domain rows. Rule: ```text agent.heartbeat event → EventBus live publish → Scheduler/AgentMonitor updates agents.last_heartbeat_at and tasks.heartbeat_at at a throttled interval ``` Default coalescing interval: ```text 5 seconds or meaningful status change, whichever comes first ``` Recovery uses `agents.last_heartbeat_at`, `tasks.heartbeat_at`, process liveness, and task attempt state. It does not require replaying ephemeral heartbeat events. ## 5. Command Run Status Semantics `command_runs` V1 schema has no explicit `status` column. Runtime derives status: | Row state | Derived status | |---|---| | `completed_at` is null | `running` | | `exit_code = 0` | `ok` | | `exit_code` non-zero | `error` | | cancellation metadata present | `cancelled` | | inconsistent row | `unknown` | ProjectionStore may expose derived command status. A later schema version may add a physical `status` column if needed. ## 6. Cross-DB and File Transaction Semantics Some durable events refer to project-level DBs or files outside the session DB: ```text debug-records.db learned-memory.db .air/shared/rules/*.md skills artifact files workspace files ``` SQLite cannot provide one transaction across arbitrary files and DBs. V1.0.0 Alpha uses an outbox/compensation model. ### 6.1 Session DB First For Intent For external side effects: ```text 1. Insert durable session event recording intent/request. 2. Insert or update session domain row with pending/external status where applicable. 3. Perform external DB/file operation through owning service. 4. Emit durable completed/failed event with evidence. 5. On restart, recovery scans pending external intents and reconciles. ``` ### 6.2 Artifact Files Artifact files use: ```text write temp file compute hash/size atomic rename insert artifact row + artifact.created event ``` If the DB insert fails after rename, startup recovery scans orphaned files and either registers or quarantines them. ### 6.3 Debug Records `debug.record.created` means: ```text session DB records debug-record intent/completion event DebugKnowledgeStore writes debug-records.db if debug-records.db write fails, emit debug.record.failed or task.failed with AirError ``` ### 6.4 Memory and Rule Promotion `memory.promoted` means: ```text candidate was approved/promoted by owning service rule/skill/learned-memory write is performed by ExperienceMiner/Curator service session event records completed promotion and target ref ``` If the file/DB write fails, emit a failure event and leave the candidate unpromoted or pending repair. ## 7. Summary and Compaction Ownership Only `summary.created` inserts a `summaries` row. `context.compaction.completed` records compaction task completion and references the created summary: ```ts interface ContextCompactionCompletedPayload { task_id?: string agent_id?: string summary_id: string range_start_message_id?: string range_end_message_id?: string token_estimate_before?: number token_estimate_after?: number } ``` Sequence: ```text context.compaction.requested context.compaction.started summary.created context.compaction.completed ``` This avoids duplicate summary rows. ## 8. Permission Layering Permission evaluation order: ```text 1. tool capability declaration 2. permission profile 3. TaskSpec scope allowed/denied paths 4. path/command/network risk classification 5. credential/system-sensitive override 6. user prompt workflow if required ``` Project-level allow does not override task scope. Credential and system-sensitive boundaries override broad project/local allow. Canonical decision: ```ts action: allow | deny | ask_user | block | refuse | announce_then_run grant_scope: none | once | session | project | global ``` ## 9. Execution Primitive Semantics Claude Code-quality execution is enforced through tool contracts, not only prompts. ### 9.1 Read-Before-Edit Token `fs.read` returns or records a read observation: ```ts interface FileReadObservation { path: string sha256?: string observed_at: ISOTimeString task_id?: TaskID agent_id?: AgentID } ``` `fs.edit` and `fs.patch` require either: ```text active task read observation for the target file or explicit expected_existing_sha256 ``` ### 9.2 Exact Edit Behavior `fs.edit` rules: 1. `old_string` must match exactly. 2. If `replace_all` is false, `old_string` must occur exactly once. 3. If no match or ambiguous match, fail with `tool_error` and no file write. 4. The tool does not guess indentation or nearby replacements. 5. Successful edit emits a diff artifact. ### 9.3 Patch Behavior `fs.patch` rules: 1. Patch paths must be within task scope and permission policy. 2. Rejected hunks become artifacts. 3. Partial patch application is allowed only if the patch tool can prove unchanged rejected paths were not written; otherwise fail atomically. 4. Successful patch emits a diff artifact. ### 9.4 Completion Gate A code-changing WorkerResult cannot be `completed` unless: ```text required verification passed, or verification is explicitly skipped with reason/evidence/risk, and fallback_allowed is true ``` ## 10. Project Scanner Semantics Project initialization scanner collects full directory tree metadata with no directory exclusion and no depth limit. Rationale: the user wants complete project shape visibility, and directory tree metadata alone is not comparable to reading file contents. Safety boundaries: 1. Do not recurse through symlinks by default; record symlink target metadata instead. 2. Handle permission errors as entries with error metadata, not fatal scanner failure. 3. Avoid reading file contents during tree scan. 4. Record special file types without opening them. 5. Provide progress and cancellation hooks for UI responsiveness. 6. Detect obvious filesystem cycles or mount anomalies and record them as scanner warnings. No directories such as `.git`, `node_modules`, build directories, or vendor directories are excluded from the directory tree. ## 11. Learning, Skills, and Experience Lifecycle ExperienceMiner produces candidates; it does not silently rewrite durable project rules or skills unless assigned an explicit promotion task. Lifecycle: ```text candidate.created → evidence threshold check → user/project policy approval → promotion write through owning store → memory.promoted or promotion failed → rollback/archive if stale or harmful ``` Candidate types: ```text project_rule toolchain_rule skill_update debug_experience ``` Promotion requirements: | Candidate | Promotion requirement | |---|---| | debug_experience | verified failure + fix evidence | | project_rule | repeated pattern or explicit user confirmation | | toolchain_rule | verified toolchain evidence or explicit user confirmation | | skill_update | reviewed patch and trust policy approval | Skills use Claude Skills-style directory structure where applicable: ```text SKILL.md scripts/ references/ assets/ ``` Skill invocation must pass through capability/tool trust rules when it causes side effects. ## 12. C++ Diagnostic Ownership `toolchain-cpp` owns deterministic extraction of compiler/test/static-analysis diagnostics and semantic signatures. LLM-based interpretation belongs to runtime Debugger/Reviewer context, not hidden inside low-level toolchain code, unless a future injected diagnostic interpretation service is explicitly added. This preserves package direction: ```text toolchain-cpp → contracts runtime/debugger → llm/provider facade ``` ## 13. V1.0.0 Alpha Cut Line V1.0.0 Alpha includes: ```text complete C++ configure/build/test/static-analysis/debug/fix/review workflow local and built-in capability manifests GUI/network evidence tools TUI/HUD projection release gates for Linux tier 1 ``` V1.0.0 Alpha may defer: ```text third-party plugin registry/signing container sandboxing multi-machine scheduling Windows-native deep support advanced semantic merge production bitmap image generation providers ``` ## 14. FK-Off Application Invariants With `foreign_keys = OFF`, the runtime must enforce referential consistency in application code. Invariants: 1. `tasks.session_id` must reference an existing `sessions.id`. 2. `task_attempts.task_id` must reference an existing `tasks.id`. 3. `agents.task_id` must reference an existing `tasks.id` when not null. 4. `tool_runs.task_id`, `tool_runs.agent_id` must reference existing rows when not null. 5. `command_runs.task_id`, `command_runs.agent_id`, `command_runs.tool_run_id` must reference existing rows when not null. 6. `workspaces.task_id`, `workspaces.agent_id` must reference existing rows when not null. 7. `diagnostics.command_run_id`, `diagnostics.artifact_id` must reference existing rows when not null. 8. `evidence_refs` foreign columns must reference existing rows when not null. Enforcement: - Repository insert methods validate foreign key existence before insert. - On session startup recovery, run orphan scan: rows referencing deleted parents are logged and either re-parented or archived. - Release gate includes orphan scan validation. ## 15. Workspace GC Policy `/.air/local/workspaces/` and the `workspaces` table grow over time. Retention rules: 1. Active workspaces (`status = active`) are preserved until merge or explicit cancel. 2. Merged workspaces (`status = merged`) are preserved for 7 days after `merged_at`, then cleaned. 3. Conflicted workspaces (`status = conflicted`) are preserved until user/system decision, then moved to `abandoned`. 4. Abandoned workspaces are preserved for 3 days, then cleaned. 5. `cleaned` status means filesystem artifacts removed; DB row retained with metadata. Cleanup runs on session startup and periodically during idle. ## 16. Direct Mode Semantics `/direct` enters a foreground execution lane where Main Agent acts with Executor-level permissions without dispatching to Scheduler. Rules: 1. Direct mode is a Main Agent state, not a separate agent type. 2. Direct mode uses `permission_template: "main_direct"`. 3. Direct mode tasks write to the main workspace only; no worktree creation. 4. Direct mode does not block Scheduler-owned background tasks from running concurrently. 5. `/done` exits direct mode, triggers evidence collection, and returns to IDLE. 6. Direct mode changes are recorded in the event log with `source.kind = "main"` and metadata indicating direct mode. ## 17. ExperienceMiner Trigger Ownership ExperienceMiner is triggered by: 1. `debug.record.created` event → Scheduler creates `mine_experience` task. 2. Session end → Scheduler creates final `mine_experience` task. 3. N-turn/tool-call interval (configurable, default 10) → Scheduler creates periodic `mine_experience` task. 4. Stale rule/skill discovered during execution → worker emits `memory.candidate.created` with `memory_type = skill_update`, Scheduler creates `mine_experience` task. The Scheduler owns trigger creation; ExperienceMiner never self-triggers. Curator periodic dedup runs as a scheduled `mine_experience` task with `memory_type = "curator_dedup"`. ## 18. Doctor Self-Bootstrap Doctor validates its own prerequisites before running capability checks: 1. Verify Bun runtime version. 2. Verify SQLite availability. 3. Verify basic shell access. 4. Verify `.air/` directory writability. 5. Then proceed to capability/dependency checks. If self-bootstrap fails, Doctor reports a `blocking` issue and skips remaining checks. ## 19. air restore Semantics `air restore` supports three granularities: 1. `air restore file ` — restore most recent backup of a single file. 2. `air restore time ` — restore all files backed up since a given timestamp. 3. `air restore session ` — restore all files backed up during a specific session. All restore operations use the git-backed backup repository at `/.air/local/backups/`. Restore creates a new commit with the restored content, preserving full history.