# 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

`<project>/.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 <path>` — restore most recent backup of a single file.
2. `air restore time <timestamp>` — restore all files backed up since a given timestamp.
3. `air restore session <session-id>` — restore all files backed up during a specific session.

All restore operations use the git-backed backup repository at `<project>/.air/local/backups/`. Restore creates a new commit with the restored content, preserving full history.
