Files
AirCoding/AirPlan/docs/architecture/system-detailed-design.md
AirCoding 453df09c21 Detailed design: resolve four P1 cross-review findings
Apply repairs identified by the four-model cross-review (DeepSeek,
MIMO 2.5 Pro, GPT-5.5 Pro, Opus 4.8) and verify by regression:

- P1-01 Worker exit codes: align overview §11 and detailed-design §8.1
  with baselineV1 §8 (0 protocol-level completion / 1 uncaught exception
  / 2 startup or protocol error / 3 permission error / 4 parent cancelled
  / 5 hard timeout killed). Record that task outcomes are reported via
  WorkerResult.status, not exit codes.
- P1-02 PromptLayerLevel enum: add "safety" to interface-contracts §16
  so the enum fully covers prompt-layering-v1 §2 L0-L9 (plus
  system_debug applied within L9).
- P1-03 EventStore.project error handling: document in detailed-design
  §5.3 that a project() exception rolls back the full transaction,
  suppresses EventBus.publish(), returns AirError{kind:"system_error"},
  and triggers referential_check() on FK-off inconsistencies.
- P1-04 PromptLayerLoader completeness: record in detailed-design §10.2
  that PromptLayerLoader only owns L0/L1/L3/L5 while ContextAssembler
  composes L2/L4/L6/L7/L8/L9 from PermissionEngine, TaskSpec,
  SessionStore, and ToolRegistry sources; clarify runtime-role prompts.

Regression confirms baselineV1, overview, and detailed-design now share
identical exit code semantics, the PromptLayerLevel enum covers all ten
layers, EventStore error semantics are explicit, and the PromptLayer
loading responsibility split is fully documented.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 09:38:16 +08:00

84 KiB

AirCoding V1.0.0 Alpha System Detailed Design and UML Class Model

Date: 2026-05-29 Status: Detailed design derived strictly from frozen baselines and system overview design Scope: Implementation-facing class/method design, UML, sequence, and state designs for V1.0.0 Alpha

0. Authority and Traceability

This document is bound by the following frozen sources. It elaborates them into class-level design but does not introduce new public contracts, new event types, new DB columns, or new runtime semantics. Where a detail is not fixed by a baseline, it is marked IMPL (implementation freedom inside the contract boundary).

Source Role in this document
interface-contracts-v1.md Public TypeScript contracts. All classes implement these as-is.
c4/code-view.md Package/file layout and class inventory.
c4/module.md Container dependency direction.
db-schema-v1.md SQLite tables, enums, indexes.
event-registry-v1.md Event names, payloads, persistence policy.
scheduler-state-machine-v1.md Scheduler lifecycle and transitions.
main-agent-state-machine.md Main Agent lifecycle.
runtime-semantics-v1.md Ingestion boundary, FK-off, outbox, execution primitives.
scope-escalation-v1.md Escalation/impact routing.
security-model-v1.md Path/command risk, permission layering.
error-taxonomy-v1.md AirError, ErrorKind, retryability, signature.
prompt-layering-v1.md PromptLayer L0-L9.
provider-capability-matrix-v1.md Provider adapter behavior.
capability-trust-v1.md Capability lifecycle/trust.
artifact-naming-v1.md Artifact URI/ID/filename.
system-overview-design.md Container/component/flow overview.

Naming rule (from contracts §1): exported contract fields are snake_case; class names are PascalCase; private methods may use local camelCase. Method signatures below restate the frozen contract types verbatim.

1. Document Structure

  1. §2 System decomposition and module ownership
  2. §3 Contracts package detailed design
  3. §4 Storage and repositories
  4. §5 Event subsystem (Ingestor/Store/Bus/SchemaRegistry)
  5. §6 Project/session lifecycle
  6. §7 Scheduler subsystem
  7. §8 Worker/IPC subsystem
  8. §9 Tool + Permission + Capability subsystem
  9. §10 Context/Prompt/Compaction subsystem
  10. §11 Artifact/Evidence/Knowledge subsystem
  11. §12 Provider (LLM) subsystem
  12. §13 Projection + TUI subsystem
  13. §14 Agents (Main, Architecture Designer) subsystem
  14. §15 Toolchain C++ subsystem
  15. §16 Doctor/Logging/Migration/Recovery subsystem
  16. §17 CLI subsystem
  17. §18 Cross-cutting designs (error, transaction, FK-off, outbox)
  18. §19 Sequence designs
  19. §20 State machine designs
  20. §21 Traceability matrix

2. System Decomposition and Module Ownership

Dependency direction (frozen by c4/module.md), expressed as allowed imports:

contracts      → (none)
llm            → contracts
toolchain-cpp  → contracts
tui            → contracts
runtime        → contracts, llm (facade only)
cli            → contracts, runtime, tui, llm, toolchain-cpp
workers        → contracts + WorkerRuntime IPC surface (no direct runtime import)

Forbidden edges (frozen by contracts §23) are enforced by lint boundaries and reviewed at the architecture gate. The detailed class design below never crosses these edges.

Ownership summary (frozen by code-view §11 State Ownership):

State Owning class Reader access
session DB SessionStore, EventStore runtime services only
live events EventBus runtime publish/subscribe
UI projection ProjectionStore TUI read-only via ProjectionClient
artifacts ArtifactStore tools/workers via runtime API
evidence EvidenceStore reports/reviews/debug
tasks/agents Scheduler repositories are storage-only
permission decisions PermissionEngine ToolRegistry requests
model config ProviderManager runtime/Doctor read via API
project rules/context ContextAssembler/ProjectStore workers get excerpts

3. Contracts Package Detailed Design

packages/contracts is type-only. It contains zero runtime logic, only export interface, export type, and nominal aliases. File layout is frozen by code-view §3.

IMPL note: the contracts package may include tiny pure type-guards (e.g. is_air_error) only if they have no dependencies; default is to keep it declaration-only to satisfy code-view §3 rule "type-only package has no implementation deps" (todo T-002).

Design rules for the contracts package:

  1. Every interface in interface-contracts-v1.md §2-§21 is exported from the file mapped in code-view §3 "Contract Ownership".
  2. No interface gains extra fields here. Field additions require an ADR (contracts §22 rule 6).
  3. JsonSchema<T> stays nominal (JsonObject); no runtime schema engine lives in contracts.
  4. Re-export surface is index.ts which barrel-exports every contract file.

Contract-to-file map (frozen by code-view §3):

ids.ts          → primitive ID aliases, Clock, IdGenerator
error.ts        → ErrorKind, ErrorSeverity, Retryability, AirError
event.ts        → EntityType, EntityRef, EventSource, RuntimeEvent, EventFilter
runtime.ts      → AgentType, AgentRuntimeContext, ContextPack
ipc.ts          → IpcDirection, IpcEnvelope, IpcKind, IpcMessage, ControlMessage, payloads
task.ts         → Task*, VerificationPolicy, TaskConstraints, TaskSpec, TaskGraph, Scheduler*
worker-result.ts→ WorkerStatus, WorkerResult, ExecutorResult, ReviewerResult, DebuggerResult,
                   CompactorResult, ExperienceMinerResult, BlockerReport, Risk, FollowUpTask
tool.ts         → ToolCategory, ToolDefinition, ToolExecutor, StreamingToolExecutor,
                   ToolExecutionContext, ToolResultEnvelope, ToolEvent, ToolRegistry
permission.ts   → PathPolicy, PermissionRequestContext, PermissionAction, PermissionGrantScope,
                   PermissionDecision, PermissionRecordResult, PermissionEngine
artifact.ts     → ArtifactRef, ArtifactCreateInput, ArtifactContext, ArtifactReadResult,
                   ArtifactStore
evidence.ts     → EvidenceRef, EvidenceCreateInput, EvidenceStore
project.ts      → ProjectContext, ProjectInitOptions, ProjectStore, SessionContext,
                   OpenSessionOptions, SessionManager
provider.ts     → ProviderCapabilityMatrix, ModelRequirement, ProviderCompletionInput,
                   ProviderStreamEvent, ProviderAdapter, ProviderManager, ModelAssignment
ui.ts           → all *Projection, ProjectionSnapshot, ProjectionStore, ProjectionClient
capability.ts   → CapabilityManifestV1, ValidationResult, CapabilityRegistry
platform.ts     → cross-platform tier enums referenced by Doctor (from cross-platform-matrix)

IMPL: storage.ts, scheduler.ts, workers.ts, context.ts, projection.ts, doctor.ts, knowledge.ts, diagnostics.ts from overview §4 may either be separate files or be merged into the above; the binding requirement is that the exported symbol set equals the contract set. This design keeps the code-view §3 list as canonical and treats overview §4 as the superset note.

4. Storage and Repositories

Module: packages/runtime/src/storage/. Classes: DatabaseManager, MigrationRunner, repositories under repositories/.

4.1 DatabaseManager

Implements TransactionManager (contracts §6) over Bun's SQLite.

class DatabaseManager implements TransactionManager
  +open(path: string): DatabaseHandle
  +transaction<T>(fn: (tx: TransactionHandle) => Promise<T>): Promise<T>
  -applyPragmas(db): void     // WAL, synchronous=NORMAL, foreign_keys=OFF (db-schema §1)
  -handleFor(tx): RawDb

Rules (db-schema §1): on open, set journal_mode=WAL, synchronous=NORMAL, foreign_keys=OFF. transaction wraps BEGIN/COMMIT/ROLLBACK. A durable event insert plus its domain update run inside one transaction call (runtime-semantics §3). TransactionHandle.id is an opaque token mapping to the active raw transaction; nested calls reuse the active handle (IMPL: single-writer per session DB, so no real nesting needed).

4.2 MigrationRunner

class MigrationRunner
  +migrate(db: DatabaseHandle): Promise<void>
  -currentVersion(db): number          // reads schema_meta.schema_version
  -targetVersion(): number             // = 1 for V1.0.0 Alpha
  -applyV1(db): void                   // creates all tables/indexes from db-schema §2-§18

V1 has a single target schema_version = 1. migrate is idempotent: if schema_meta is missing it creates the full schema and seeds the initial keys (db-schema §2). It updates aircoding_version_last_opened on every open. Destructive/non-trivial migrations follow overview §15 (plan, backup, confirm) but V1 only needs create-on-empty.

4.3 Repository layer

All repositories implement Repository<TRecord, TInsert, TUpdate> (contracts §6) or its narrow extensions. They are thin persistence adapters: no scheduling, permission, or projection policy (contracts §6, code-view §9). Records mirror db-schema columns exactly (snake_case).

Repository inventory (code-view §9) and their record types:

Repository Record (db-schema table) Notable methods beyond CRUD
SessionRepository sessions (§3) list_active()
MessageRepository messages (§4) list_by_session(session_id, since?)
MessageDraftRepository message_drafts (§5) upsert, delete_for_message
EventRepository events (§6) insert(rec, tx), query(filter) (contracts §6)
TaskRepository tasks (§7) list_by_status, list_runnable_candidates (contracts §6)
TaskDependencyRepository task_dependencies (§8) list_for_task, list_dependents
TaskAttemptRepository task_attempts (§9) next_attempt_index(task_id), list_by_task
AgentRepository agents (§10) list_active, update_heartbeat
ToolRunRepository tool_runs (§11) list_by_task, list_by_origin_message
CommandRunRepository command_runs (§12) list_by_task (status derived, §4.4)
ArtifactRepository artifacts (§13) list_by_entity, get_by_uri
DiagnosticRepository diagnostics (§14) list_by_signature, list_by_command_run
EvidenceRepository evidence_refs (§15) list_for_entity(type,id)
WorkspaceRepository workspaces (§16) list_by_status, list_gc_candidates
SummaryRepository summaries (§17) get, insert
UiStateRepository ui_state (§18) upsert(scope,key,value), read(scope,key)

SessionStore aggregates all repositories (code-view §9):

class SessionStore
  +sessions: SessionRepository
  +messages: MessageRepository
  +message_drafts: MessageDraftRepository
  +events: EventRepository
  +tasks: TaskRepository
  +task_dependencies: TaskDependencyRepository
  +task_attempts: TaskAttemptRepository
  +agents: AgentRepository
  +tool_runs: ToolRunRepository
  +command_runs: CommandRunRepository
  +artifacts: ArtifactRepository
  +diagnostics: DiagnosticRepository
  +evidence: EvidenceRepository
  +workspaces: WorkspaceRepository
  +summaries: SummaryRepository
  +ui_state: UiStateRepository
  +referential_check(): Promise<OrphanReport>   // FK-off invariants (§18.3)

4.4 Derived command status

command_runs has no physical status column (db-schema §12, runtime-semantics §5). The repository exposes a pure derivation used by projection and reports:

function derive_command_status(row): "running" | "ok" | "error" | "cancelled" | "unknown"
  completed_at == null            → "running"
  cancellation metadata present   → "cancelled"
  exit_code === 0                 → "ok"
  exit_code != 0 (non-null)       → "error"
  otherwise                       → "unknown"

This matches CommandRunProjection.status (contracts §17) so projection never invents a value.

4.5 Enum validation

Every closed-enum TEXT column (db-schema §21, 18 rows) is validated on insert/update. IMPL: a shared assert_enum(table, column, value) helper backed by the db-schema §21 table; on violation it throws an AirError of kind system_error (programmer error, never user-facing).

5. Event Subsystem

Module: packages/runtime/src/events/. Classes: EventSchemaRegistry, EventStore, EventBus, EventIngestor. All implement contracts §7 verbatim.

5.1 EventIngestor

The single runtime entry point for events from agents/tools/workers (runtime-semantics §2).

class EventIngestor implements EventIngestor (contracts §7)
  +ingest<T>(event: RuntimeEvent<T>): Promise<void>
  +ingest_ephemeral<T>(event: RuntimeEvent<T>): Promise<void>
  -policyFor(type): EventPersistence    // durable | ephemeral, from registry

ingest flow (runtime-semantics §2):

validate envelope + schema/version (EventSchemaRegistry)
  → look up persistence policy by event.type
  → durable:   EventStore.append(event)         // tx + projection + post-commit publish
  → ephemeral: EventBus.publish(event)           // live only

If ingest receives a type whose policy is ephemeral, it delegates to ingest_ephemeral. The ingestor never creates scheduler tasks, permission decisions, or memory promotions; those are follow-up events emitted by owning services (runtime-semantics §2).

5.2 EventSchemaRegistry

class EventSchemaRegistry implements EventSchemaRegistry (contracts §7)
  +register(type, version, schema: JsonObject): void
  +validate(type, version, payload): boolean
  +list(): Array<{type, version}>
  +get_schema(type, version): JsonObject | undefined

Seeded at startup from event-registry-v1.md §3 (durable) and §4 (ephemeral). Unknown type+version fails validation → ingestion rejects with AirError kind system_error. Payload schema change requires a new version (event-registry §2 rule 7).

5.3 EventStore

class EventStore implements EventStore (contracts §7)
  +append<T>(event, options?): Promise<void>
  +append_many(events, options?): Promise<void>
  +query(filter: EventFilter): Promise<RuntimeEvent[]>
  -toRecord(event): PersistedEventRecord       // route_text = route.join("/")
  -project(event, tx): void                    // domain table update per registry map

append algorithm (runtime-semantics §3, event-registry §2):

DatabaseManager.transaction(tx => {
  schema validate (must already be durable policy)
  EventRepository.insert(toRecord(event), tx)
  project(event, tx)            // domain projection from §5.4 map
})
EventBus.publish(event)         // AFTER commit (contracts §7 rule 3)

Error handling for project(): If project(event, tx) throws an exception (e.g., due to FK-off referential inconsistency, constraint violation, or programmer error), the entire transaction rolls back. EventBus.publish() is never called. The exception propagates to the caller as an AirError with kind: "system_error". If the failure is due to FK-off inconsistency (e.g., referencing a non-existent tasks.id), the error is logged to developer log and SessionStore.referential_check() is triggered asynchronously to diagnose and repair orphaned references.

route_text is always derived route.join("/") (event-registry §2 rule 6); route itself is append-only (rule 5) — EventStore never rewrites prior route entries.

5.4 Durable projection map

project(event, tx) switches on event.type and applies exactly the domain update fixed by event-registry §3. The full map (no event may project differently):

Event type Domain update
session.created insert sessions
session.archived / session.deleted update sessions.status
user.message.created insert messages
assistant.message.started upsert message_drafts (status=streaming)
assistant.message.created insert messages + delete matching message_drafts
assistant.message.failed message_drafts.status=error or failure artifact
agent.started insert agents (starting/running)
agent.completed/failed/lost/cancelled update agents.status
task.created insert tasks (+ optional task_dependencies)
task.started tasks.status=running, set started/agent/workspace; insert task_attempts
task.completed tasks.status=completed, set worker_result_json, completed_at; update attempt
task.blocked tasks.status=blocked; update attempt
task.failed tasks.status=failed; update task_attempts.failure_*
task.cancelled tasks.status=cancelled
task.interrupted tasks.status=interrupted
tool.started insert tool_runs (running)
tool.completed tool_runs.status=ok + output/artifacts/evidence/duration
tool.failed tool_runs.status=error
tool.cancelled tool_runs.status=cancelled
command.started insert command_runs
command.completed update exit_code, artifact refs, diagnostics, duration
command.failed update exit_code when available + failure artifacts
artifact.created insert artifacts (after temp→rename)
diagnostic.created insert diagnostics
evidence.created insert evidence_refs
context.compaction.requested insert compaction task if accepted
context.compaction.started mark compaction task running
context.compaction.completed mark compaction task complete (no summary row)
context.compaction.failed mark compaction task failed/blocked
summary.created insert summaries (only place that does)
permission.decision.recorded append (optional future projection table)
permission.prompt.requested/resolved append (optional UI projection)
doctor.* append (+ optional report artifact / command rows)
requirement.changed append; mark impacted tasks when Scheduler applies
architecture.plan.updated append + plan/artifact refs
architecture.impact.completed append; Scheduler consumes
workspace.created insert workspaces
workspace.merge.started append + mark merge in progress (metadata)
workspace.merge.completed workspaces.status=merged, set merged_at
workspace.merge.conflicted workspaces.status=conflicted
workspace.cleaned workspaces.status=cleaned
memory.candidate.created append
memory.promoted append + write rules/skill/learned-memory.db via owner (outbox §18.4)
memory.archived append + mark memory inactive via owner
debug.record.created insert/update debug-records.db (outbox §18.4) + append session event

5.5 EventBus

class EventBus implements EventBus (contracts §7)
  +publish<T>(event: RuntimeEvent<T>): void
  +subscribe(filter: EventFilter, handler): Subscription
  +drain?(): Promise<void>
  -match(filter, event): boolean

Rules: live transport only, never a recovery source of truth (contracts §7 rule 4). If a handler throws, the error is caught, logged to developer log, and does not propagate; subscription stays active (contracts §7 rule 5). drain flushes pending async handlers for clean shutdown.

Ephemeral coalescing (event-registry §4): agent.heartbeat, task.progress, assistant.message.delta, tool.progress, command.stdout.delta, command.stderr.delta, hud.frame.rendered may be throttled before reaching subscribers.

6. Project and Session Lifecycle

Module: packages/runtime/src/project/ and sessions/. Classes: ProjectLocator, ProjectInitializer, ProjectStore, SessionManager, SessionStore.

6.1 ProjectStore

Implements contracts §8.

class ProjectStore implements ProjectStore
  +locate(start_path): Promise<ProjectContext | undefined>
  +initialize(project_root, options?): Promise<ProjectContext>
  +open(project_root): Promise<ProjectContext>

locate walks up from start_path looking for .air/shared/project.json (overview §8.1). initialize creates the .air/shared and .air/local trees (overview §8.1), generates a stable project_id UUID stored in .air/shared/project.json (not derived from path; overview §8.1), seeds default rules when options.default_rules, and emits nothing durable yet (no session DB until a session opens). open loads the existing ProjectContext (project_id, roots, schema_version).

Delegated helpers (IMPL, code-view §4):

  • ProjectLocator.locate(start) — upward search.
  • ProjectInitializer.scaffold(root, options) — directory + file creation.

6.2 SessionManager and SessionStore

class SessionManager implements SessionManager (contracts §8)
  +open_session(project, options?): Promise<SessionContext>
  +close_session(session_id): Promise<void>

class SessionStore                 // §4.3 aggregate of repositories

open_session flow:

resolve session_id (options or IdGenerator.session_id())
compute db_path = .air/local/sessions/<session-id>/session.db   (db-schema header)
DatabaseManager.open(db_path); MigrationRunner.migrate(db)
SessionStore bound to this db
ingest session.created (durable → inserts sessions row)
return SessionContext { session_id, project_id, project_root, db_path, artifact_root }

close_session flushes ui_state (db-schema §1), publishes a terminal session event when archiving, and releases the DB handle. Session provider/model selection (model_provider_id, model_id) is captured at open and is immutable for the session (overview §14).

7. Scheduler Subsystem

Module: packages/runtime/src/scheduler/. Classes: Scheduler, TaskGraph, WavePlanner, RetryPlanner, WorkspaceManager, AgentMonitor (code-view §4). The Scheduler is an orchestration service, not a coding agent (scheduler-state-machine §intro). All durable state goes through EventStore; in-memory queues rebuild from SQLite (scheduler-state-machine §1).

7.1 Scheduler

Implements contracts §9.

class Scheduler implements Scheduler
  +create_tasks(session_id, specs: TaskSpec[]): Promise<void>
  +add_dependency(session_id, task_id, dependency: TaskDependencySpec): Promise<void>
  +load_graph(session_id): Promise<TaskGraph>
  +run_until_idle(session_id): Promise<SchedulerRunResult>
  +cancel_task(task_id, reason): Promise<void>
  -plan_wave(graph): SchedulerWavePlan
  -dispatch(wave): Promise<void>
  -collect_results(): Promise<void>
  -state: SchedulerState     // §20.2 lifecycle

create_tasks ingests task.created per spec (durable → inserts tasks + dependency rows). run_until_idle drives the lifecycle in §20.2 until a terminal graph state, returning SchedulerRunResult (contracts §9). The Scheduler asks the user only through Main Agent / PermissionEngine (scheduler-state-machine §8); it never prompts directly.

7.2 TaskGraph

class TaskGraph
  +session_id: SessionID
  +tasks: Map<TaskID, TaskNode>
  +dependencies: TaskDependencyRecord[]
  +get_runnable_tasks(): TaskNode[]      // hard deps satisfied, not conflicting
  +mark_terminal(task_id, status): void
  +dependents_of(task_id): TaskNode[]
  +validate_refs(): OrphanReport          // FK-off (§18.3)

get_runnable_tasks honors dependency semantics (scheduler-state-machine §4 PLANNING_WAVE): hard deps must be completed; soft deps affect priority only; conflict/serialization deps block concurrent dispatch on overlapping write areas.

7.3 WavePlanner

class WavePlanner
  +plan(graph, resources: ResourceSnapshot): SchedulerWavePlan
  -serialize_write_conflicts(candidates): {runnable, serialized}
  -assign_workspace(task): WorkspacePlan
  -assign_model(task): ModelAssignment

Produces SchedulerWavePlan (contracts §9). Rules from scheduler-state-machine §4: different write areas → concurrent; same area uncertain conflict → serialize; reviewers read-only and concurrent except against unstable unmerged outputs; debugger serializes on same failure surface; machine resources cap concurrency (§7 resource-aware). Inferred conflict/serialization edges are persisted via task_dependencies + durable events so restart need not rediscover them (scheduler-state-machine §1).

7.4 RetryPlanner

class RetryPlanner
  +decide(task: TaskNode, attempts: TaskAttemptRecord[], error: AirError): RetryDecision

RetryDecision (scheduler-state-machine §5): action ∈ {retry, retry_serial, debug, skip, block, cancel}. Rules: retry only when plausibly recoverable; later retries change one dimension (model/context/command/serialization/debugger); identical failure_signature escalates faster; environment impossibility → block, never infinite retry; architecture/interface mismatch → route to Architecture Designer; fallback skip only when VerificationPolicy.fallback_allowed and task non-critical. Budget = TaskConstraints.retry_budget.

7.5 WorkspaceManager

class WorkspaceManager
  +create_workspace(plan: WorkspacePlan): Promise<WorkspaceRef>
  +merge_workspace(workspace_id): Promise<MergeResult>
  +cleanup_workspace(workspace_id): Promise<void>

Strategies (db-schema §16, scheduler-state-machine §MERGING): main (no merge), worktree (git merge/patch), isolated_copy (copy-back/patch). Emits workspace.created, workspace.merge.started, and a terminal workspace.merge.completed or workspace.merge.conflicted. GC retention follows overview §15 (active until merge/cancel; merged 7d; abandoned 3d; cleaned keeps DB row).

7.6 AgentMonitor

class AgentMonitor
  +record_heartbeat(event): void          // coalesced into agents.last_heartbeat_at + tasks.heartbeat_at
  +detect_lost_agents(): Promise<AgentLost[]>
  +enforce_timeouts(): Promise<void>

Heartbeat coalescing interval default 5s or meaningful status change (runtime-semantics §4). Missing heartbeat past threshold → inspect process → alive-but-silent → status ping/soft cancel; gone-without-result → emit agent.lost (scheduler-state-machine §MONITORING). Soft timeout asks for checkpoint and may extend; hard timeout cancels/kills and marks task failed/interrupted (scheduler-state-machine §MONITORING timeout table).

8. Worker and IPC Subsystem

Module: packages/runtime/src/workers/. Classes: WorkerManager, WorkerProcess, WorkerProtocol, and roles under roles/. IPC is NDJSON over stdio (contracts §10, ADR-0005).

8.1 WorkerManager and WorkerProcess

class WorkerManager
  +spawn(task_spec: TaskSpec, context_pack: ContextPack): Promise<WorkerProcess>
  +cancel(agent_id, reason): Promise<void>

class WorkerProcess
  +agent_id: AgentID
  +pid?: number
  +send(envelope: IpcEnvelope): void
  +on_message(handler: (m: IpcEnvelope) => void): void

spawn starts a Bun child process, then performs the handshake (§8.2). WorkerProcess owns the NDJSON pipe; stdout carries protocol only, stderr is fatal/logging (baselineV1 §8).

Worker exit codes (baselineV1 §8, authoritative):

Code Meaning
0 protocol-level completion (including task failed/blocked via WorkerResult)
1 uncaught exception
2 startup/protocol error
3 permission error
4 parent cancelled
5 hard timeout killed

Design decision: Task success/failure is communicated through WorkerResult.status, not exit codes. Exit code 0 means the worker completed the IPC protocol correctly and returned a valid WorkerResult; the actual task outcome (completed/failed/blocked/cancelled) is in the result payload. Non-zero exit codes indicate process-level or protocol-level failures that prevent normal result delivery.

8.2 WorkerProtocol and handshake

class WorkerProtocol
  +encode(msg: IpcMessage): string         // NDJSON line
  +decode(line: string): IpcMessage
  +validate_direction(msg): void           // parent_to_worker vs worker_to_parent
  +check_protocol_version(v: number): boolean

Handshake (contracts §10): parent spawns → parent sends agent.start control (with TaskSpec, ContextPack, AgentRuntimeContext) → worker replies worker.ready { protocol_version, worker_version } → parent validates protocol_version; mismatch terminates worker with protocol.error.

Direction typing (contracts §10): parent→worker = control, tool.result, tool.stream; worker→parent = event, log, tool.call, worker.result, worker.checkpoint, protocol.error. validate_direction rejects messages on the wrong channel.

8.3 Worker roles and WorkerRuntime

Roles implement WorkerRole<TResult> (contracts §10). The in-worker WorkerRuntime is the only side-effect surface (contracts §10, §23: workers never write SQLite or touch fs/shell/network except through parent-mediated tools).

interface WorkerRole<TResult> { run(task_spec, context_pack, runtime): Promise<WorkerResult<TResult>> }

class WorkerRuntime
  +emit(event: RuntimeEvent): Promise<void>            // → IPC event → parent EventIngestor
  +call_tool<I,O>(name, input): Promise<ToolResultEnvelope<O>>   // → IPC tool.call
  +checkpoint(data): Promise<void>                     // → IPC worker.checkpoint

Role inventory and constraints (code-view §10):

Role class output_contract Write access Required result
ExecutorRole ExecutorResult scoped project writes ExecutorResult in WorkerResult
ReviewerRole ReviewerResult read-only ReviewerResult
DebuggerRole DebuggerResult scoped writes only when assigned DebuggerResult
CompactorRole CompactorResult summaries/artifacts only CompactorResult
ExperienceMinerRole ExperienceMinerResult candidates/rules/skills only when assigned ExperienceMinerResult

Each role's run ends by returning a WorkerResult<TResult> with the matching agent_type (contracts §11). status ∈ {completed, failed, blocked, cancelled}; a code-changing result cannot be completed unless verification passed or was explicitly skipped with evidence/risk and fallback_allowed (runtime-semantics §9.4, overview §10.9). Self-escalation returns blocked with a BlockerReport rather than improvising (scope-escalation §9).

8.4 Execution discipline enforcement (Executor/Debugger)

Read-before-edit and exact-edit are enforced at the tool layer, not just by prompt (§9.4); the role loop additionally:

  1. records a read observation before fs.edit/fs.patch (runtime-semantics §9.1);
  2. keeps changes within TaskScope.write_area/allowed_paths (overview §10.9 rule 3);
  3. runs VerificationPolicy.commands before declaring completed (contracts §9, §11);
  4. attaches evidence refs for diffs/builds/tests (overview §10.9 rule 6).

9. Tool, Permission, and Capability Subsystem

Module: packages/runtime/src/tools/, security/, capabilities/.

9.1 ToolRegistry

Implements contracts §12.

class ToolRegistry implements ToolRegistry
  +register<I,O>(definition: ToolDefinition<I,O>, executor: ToolExecutor<I,O>): void
  +register_streaming<I,O>(definition, executor: StreamingToolExecutor<I,O>): void
  +call<I,O>(name, input, context: ToolExecutionContext): Promise<ToolResultEnvelope<O>>
  +call_streaming<I,O>(name, input, context): AsyncIterable<ToolEvent | ToolResultEnvelope<O>>
  +list(): ToolDefinition[]
  -validate_input(def, input): void        // JsonSchema check

call algorithm (contracts §12, §23 "tool → side effect without PermissionEngine" forbidden):

look up definition+executor (else AirError tool_error)
validate input against input_schema
build PermissionRequestContext from context + definition.permissions + input paths/command
decision = PermissionEngine.evaluate(request)
branch on decision.action (§9.3)
emit tool.started (durable → tool_runs running)
run executor.execute(input, context)        // streaming consumed internally for call()
emit tool.completed | tool.failed | tool.cancelled
return ToolResultEnvelope

call_streaming exposes ToolEvent progress and ends with exactly one final ToolResultEnvelope (contracts §12 streaming rule). BuiltInToolRegistrar registers fs/shell/git/project/artifact/ context/permission/doctor tools (code-view §4); each tool declares category, permissions, and streaming (contracts §12).

9.2 PermissionEngine

Implements contracts §13.

class PermissionEngine implements PermissionEngine
  +evaluate(context: PermissionRequestContext): Promise<PermissionDecision>
  +record(decision, context): Promise<PermissionRecordResult>

class PathClassifier
  +classify(path, project_root): PathRiskClassification   // 8 categories, realpath-normalized

class CommandRiskAnalyzer
  +analyze(command, cwd): CommandRiskAnalysis              // 10 categories

class SecretRedactor
  +redact(text): string                                   // for logs/evidence

evaluate applies the frozen layered order (contracts §13, runtime-semantics §8, overview §12):

1 tool capability declaration
2 permission profile (permission_template)
3 TaskSpec scope allowed/denied paths
4 path/command/network risk classification (PathClassifier + CommandRiskAnalyzer)
5 credential/system-sensitive override
6 user prompt workflow if required

Key invariants: project-level allow never overrides task scope; credential/system-sensitive overrides broad allows (runtime-semantics §8, overview §12 rules 8-9); paths normalized via realpath before prefix checks (overview §12 rule 1); .git/ internals protected (rule 2). record writes a permission.decision.recorded durable event and returns PermissionRecordResult; on write failure it returns {ok:false, error} (contracts §13).

9.3 PermissionDecision branching

ToolRegistry.call branches on PermissionDecision.action (contracts §13, scope-escalation §7):

action ToolRegistry behavior
allow execute; create backup first if backup_required
announce_then_run emit visible notice, then execute unless interrupted; bounded by grant_scope
ask_user suspend; emit permission.prompt.requested; resume on permission.prompt.resolved
deny do not execute; return ToolResultEnvelope{status:"error"}; caller may pick safe path
block return blocked outcome → task.blocked upstream
refuse return AirError{kind:"policy_error"}; no execution

9.4 Filesystem tool execution rules

fs.edit/fs.patch enforce read-before-edit (runtime-semantics §9.1-§9.3, overview §10.9):

  • require an active task read observation for the target file or explicit expected_existing_sha256;
  • old_string exact match; non-unique match fails unless replace_all; no indentation guessing;
  • patch paths must be within scope; rejected hunks become artifacts; partial apply only if unchanged rejected paths are provably untouched, else atomic fail;
  • successful edit/patch emits a diff artifact (→ artifact.created).

9.5 CapabilityRegistry

Implements contracts §18; lifecycle/trust from capability-trust-v1.

class CapabilityRegistry implements CapabilityRegistry
  +discover(): Promise<CapabilityManifestV1[]>
  +validate(manifest): Promise<ValidationResult>
  +enable(capability_id): Promise<void>
  +disable(capability_id): Promise<void>
  +register_tools(tool_registry: ToolRegistry): Promise<void>

class CapabilityManifestValidator
  +validate(manifest): ValidationResult        // schema_version=1, tool schemas, permissions

Lifecycle (overview §6, capability-trust §7): discovered → validated → doctor_checked → enabled → registered → active → disabled|failed|updated. Trust levels (contracts §18): built_in, project_local, user_installed, verified_publisher, untrusted. Trust affects default enablement/prompt posture but never bypasses ToolRegistry or PermissionEngine (overview §6). Dependency installs go only through Doctor (contracts §23 "capability → dependency install outside Doctor" forbidden).

10. Context, Prompt, and Compaction Subsystem

Module: packages/runtime/src/context/. Classes: ContextAssembler, PromptLayerLoader, CompactionPolicy (contracts §16).

10.1 ContextAssembler

class ContextAssembler implements ContextAssembler
  +assemble(input: ContextAssembleInput): Promise<AssembledContext>
  -load_layers(purpose): Promise<PromptLayer[]>
  -fit_budget(layers, budget): BudgetFitResult

assemble builds an Anthropic-canonical context (AssembledContext.canonical_format="anthropic", contracts §16). It loads ordered layers (§10.2), fits them to token_budget via fit_budget, and reports omissions. When the context is too large for inline return it writes a messages artifact and sets messages_artifact_id; ContextPack.assembled_context_ref points to it (contracts §16). If the budget cannot fit required layers it sets compaction_requested=true (consumed by Scheduler, §10.3).

10.2 PromptLayerLoader and layer order

class PromptLayerLoader implements PromptLayerLoader
  +load_runtime_invariant(): PromptLayer
  +load_role(role: AgentType): PromptLayer
  +load_project_rules(project: ProjectContext): PromptLayer[]
  +load_task_context(spec: TaskSpec, refs: TaskContextRefs): PromptLayer[]

PromptLayer.level is the frozen PromptLayerLevel union (contracts §16), ordered L0-L9 per prompt-layering-v1 §2 and overview §13:

Level enum (contracts) L# (prompt-layering) Loader method / source
runtime_invariant L0 Runtime invariant load_runtime_invariant()
role L1 Role / agent mode load_role(role)
safety L2 Safety and permission policy ContextAssembler internal (see below)
project_rules L3 Project rules and user preferences load_project_rules(project)
architecture L4 Architecture baseline and current plan ContextAssembler internal (see below)
task_spec L5 Task specification and acceptance criteria load_task_context(spec, refs)
evidence L6 Relevant code / artifacts / evidence ContextAssembler internal (see below)
conversation L7 Recent conversation and decision context ContextAssembler internal (see below)
tool_output L8 Tool result history / diagnostics ContextAssembler internal (see below)
user_override L9 Immediate instruction ContextAssembler internal (see below)
system_debug (system debug directive, applied within L9 when present) ContextAssembler internal

Design decision — Layer loading responsibility:

PromptLayerLoader interface (contracts §16) provides 4 methods for layers that require external configuration or resource loading (L0, L1, L3, L5). The remaining layers are assembled by ContextAssembler.load_layers() internally:

Layer Source Assembled by
L2 Safety PermissionEngine.current_profile() + ~/.air/permissions.yaml + project permissions ContextAssembler
L4 Architecture TaskSpec.context_refs.arc_ref → load from plan/ADR/C4 docs ContextAssembler
L6 Evidence TaskSpec.context_refs.artifacts + EvidenceStore.list_for_task() ContextAssembler
L7 Conversation SessionStore.messages.list_by_session() (recent N messages) ContextAssembler
L8 Tool output SessionStore.tool_runs + command_runs for current task ContextAssembler
L9 Immediate TaskSpec.description + acceptance_criteria + immediate user instruction ContextAssembler

This design keeps PromptLayerLoader focused on resource-backed layers while ContextAssembler owns the assembly logic for session/task-specific layers.

Design decision — Runtime roles (main/architecture/scheduler):

AgentType (contracts §5) only covers worker roles (executor/reviewer/debugger/compactor/ experience_miner). Runtime roles (main/architecture/scheduler) are not child processes and do not use PromptLayerLoader.load_role(). Instead:

  • MainAgent loads its L1 role prompt from built-in resources directly
  • ArchitectureDesigner loads its L1 role prompt from built-in resources directly
  • Scheduler does not use LLM prompts (it is a pure orchestration service)

Higher-priority layers win on budget pressure; immutable=true layers (L0, L1, L2) are never dropped (prompt-layering L0 Mutable: no).

10.3 CompactionPolicy

class CompactionPolicy implements CompactionPolicy
  +should_compact(messages, token_budget): boolean
  +compact(messages, target_tokens): Promise<CompactionResult>

ContextAssembler may request compaction but does not compact itself; Scheduler creates a compact task that runs CompactorRole (overview §13, runtime-semantics §7). Sequence: context.compaction.requested → context.compaction.started → summary.created → context.compaction.completed. Only summary.created inserts the summaries row (runtime-semantics §7); the completion event references the summary_id and never duplicates the row. Original messages are preserved for backtracking.

11. Artifact, Evidence, and Knowledge Subsystem

Module: packages/runtime/src/artifacts/ and knowledge/.

11.1 ArtifactStore

Implements contracts §14; naming from artifact-naming-v1.

class ArtifactStore implements ArtifactStore
  +create(input: ArtifactCreateInput, context: ArtifactContext): Promise<ArtifactRef>
  +get(artifact_id): Promise<ArtifactRef | undefined>
  +read(artifact_id): Promise<ArtifactReadResult>
  -write_temp_then_rename(bytes): {path, sha256, size}

create algorithm (runtime-semantics §6.2, overview §8.4):

write temp file → compute sha256 + size → atomic rename to artifact path
artifact_id = art_<ulid>
uri = artifact://project/<project-id>/session/<session-id>/<artifact-id>
filename = <timestamp>-<artifact-id>-<slug><extension>
ingest artifact.created (durable → inserts artifacts row)

If the DB insert fails after rename, startup recovery scans orphaned files and registers or quarantines them (runtime-semantics §6.2). Artifact type is from the closed set (db-schema §21: log, diff, screenshot, pcap, report, diagnostic, bundle, other).

11.2 EvidenceStore

class EvidenceStore implements EvidenceStore
  +create(input: EvidenceCreateInput): Promise<EvidenceRef>
  +list_for_entity(entity_type, entity_id): Promise<EvidenceRef[]>

Emits evidence.created (durable → evidence_refs). Worker results embed full EvidenceRef[] when evidence is part of the conclusion; lightweight payloads carry evidence_ref_ids (contracts §14). kind is from the closed set (db-schema §21: build_output, test_output, log, screenshot, diff, metric, other).

11.3 DebugKnowledgeStore and LearnedMemoryStore

Project-level DBs (db-schema §20), implement contracts §20.

class DebugKnowledgeStore implements DebugKnowledgeStore   // debug-records.db
  +insert(record: DebugRecord): Promise<void>
  +lookup_by_signature(failure_signature): Promise<DebugRecord[]>
  +lookup_by_task(task_id): Promise<DebugRecord[]>
  +update(debug_record_id, patch): Promise<void>

class LearnedMemoryStore implements LearnedMemoryStore     // learned-memory.db
  +insert(memory: LearnedMemory): Promise<void>
  +lookup_by_type(memory_type): Promise<LearnedMemory[]>
  +update_status(memory_id, status): Promise<void>
  +scan_stale(): Promise<LearnedMemory[]>

Writes to these DBs are cross-store side effects: the session DB records intent/completion events and the owning store performs the external write (outbox model, §18.4; runtime-semantics §6.3-§6.4). debug.record.created and memory.promoted are the session-side durable markers.

12. Provider (LLM) Subsystem

Module: packages/llm/src/. Classes per code-view §5. packages/llm owns adapters, model config, conversion, and ProviderManager; runtime calls it only through the facade (code-view §2 rule 5).

12.1 ProviderManager

Implements contracts §15.

class ProviderManager implements ProviderManager
  +load_config(): Promise<void>
  +select_model(requirement: ModelRequirement): Promise<ModelAssignment>
  +complete(input: ProviderCompletionInput): AsyncIterable<ProviderStreamEvent>
  -adapter_for(provider_id): ProviderAdapter

select_model matches a ModelRequirement against the capability matrix and returns a ModelAssignment (contracts §9/§15). complete routes to the right adapter and yields normalized ProviderStreamEvents. Runtime/session provider+model are fixed for the session; there is no runtime switching API (overview §14).

12.2 ProviderAdapter implementations

interface ProviderAdapter (contracts §15)
  provider_id; list_models(); validate_model(model_id); complete(input); count_tokens?(input)

class AnthropicAdapter implements ProviderAdapter
class OpenAICompatibleAdapter implements ProviderAdapter
  -converter: AnthropicCanonicalConverter
class AnthropicCanonicalConverter   // canonical ↔ provider format
class ToolUseConverter
class StreamNormalizer              // provider stream → ProviderStreamEvent

Adapters convert external formats to/from the Anthropic canonical internal format and must not silently drop semantic prompt/tool information (contracts §23 "provider adapter → silent semantic prompt loss" forbidden; overview §14). ModelConfigLoader loads global ~/.air/models.yaml and project config; CapabilityMatrixRegistry holds ProviderCapabilityMatrix rows (provider-capability-matrix-v1). LLM output is never allowed to perform direct file/shell side effects (contracts §23).

13. Projection and TUI Subsystem

Modules: packages/runtime/src/projection/ (ProjectionStore) and packages/tui/src/.

13.1 ProjectionStore

Implements contracts §17.

class ProjectionStore implements ProjectionStore
  +hydrate(session_id): Promise<void>
  +apply(event: RuntimeEvent): void
  +snapshot(): ProjectionSnapshot
  +subscribe(handler): Subscription
  -projections: { session, tasks, agents, tool_runs, command_runs, artifacts, permission_prompts, blockers }

hydrate rebuilds from DB via repositories (code-view §4: ProjectionStore → SessionStore). apply handles all durable events plus key ephemeral events (agent.heartbeat, task.progress, assistant.message.delta, tool.progress, command.stdout.delta, command.stderr.delta); unknown event types are ignored (contracts §17 comment). command_runs projection status uses the derivation in §4.4. ProjectionStore is never a scheduling/recovery source of truth (overview §9.3).

13.2 TUI

Module packages/tui/src/ (code-view §7). TuiApp consumes a ProjectionClient and renders components; it imports only packages/contracts (contracts §17, §23; code-view §2 rule 3).

class TuiApp
  +start(): void
  +stop(): void
class ProjectionClient implements ProjectionClient
  +snapshot(): ProjectionSnapshot
  +subscribe(handler): Subscription
components: SessionView, TaskListView, AgentStatusView, ToolRunView, DiffView, EvidenceView,
           PermissionPrompt, BlockerReport, HudView

Rules (code-view §7): components render projections only; permission prompts emit user decisions through the narrow UiCommandChannel (contracts §17), never private runtime services; UI never mutates domain tables; diff/evidence views link back to artifact/evidence refs. V1 transport is in-process (contracts §17): ProjectionClient is a direct interface reference, not IPC. HUD presets Full/Essential/Minimal and permission announce_then_run visualization per overview §14.

14. Agents Subsystem

Module: packages/runtime/src/agents/main/ and architecture/. These run inside the runtime process (overview §7). Main Agent stays idle-ready; background work is dispatched to workers via Scheduler (main-agent-state-machine §Idle Principle).

14.1 MainAgent

class MainAgent
  +handle_user_message(message): Promise<void>
  +present_progress(): Promise<void>
  +present_blocker(blocker: BlockerReport): Promise<void>
  -classify_intent(message): "chat" | "task" | "direct"
  -state: MainAgentState     // §20.1

Lifecycle is the frozen state machine (main-agent-state-machine.md, §20.1 here): IDLE → CLASSIFYING → ANSWERING | DELEGATING | DIRECT_MODE; DELEGATING → SCHEDULING | ARCHITECTURE_DESIGNING → CONFIRMING → EXECUTING → (INTERRUPTING | ARCHITECTURE_REVISING) → SUMMARIZING → IDLE. Direct mode uses permission_template="main_direct" and writes only to the main workspace (contracts §10, runtime-semantics §16). Requirement changes emit requirement.changed (main-agent-state-machine events table). Confirmation gating: implementation-only silent → EXECUTING; architecture-level → Architecture Designer assessment → low-permission user confirm / high-permission auto-proceed (main-agent-state-machine §Confirmation Gating).

14.2 ArchitectureDesigner

class ArchitectureDesigner
  +assess_impact(change): Promise<ArchitectureImpact>
  +update_architecture_docs(update): Promise<DocumentUpdate>

Emits architecture.impact.completed and architecture.plan.updated (event-registry §3, overview §10.7). It owns the architecture review gate: interface/schema/event/package-boundary/security/ runtime-semantics/ADR/C4/plan consistency; it does not replace Reviewer (overview §10.7). Gate trigger conditions and result rules (silent_continue / requires_user_confirmation / requires_replan / reject_or_escalate) follow scope-escalation §4 and overview §10.7. It calls the LLM only through the ProviderManager facade (code-view §4) and routes escalations through Main Agent.

15. Toolchain C++ Subsystem

Module: packages/toolchain-cpp/src/ (code-view §6). Exposes cpp.* tools through capability registration, not direct runtime coupling (code-view §2 rule 4).

class CppToolRegistrar { +register(tool_registry): void }
class CppProjectDetector { +detect(project_root): Promise<CppDetectOutput> }
class CMakeConfigurator { +configure(input): Promise<CppCmakeConfigureOutput> }
class CppBuilder { +build(input): Promise<CppBuildOutput> }
class CppTestRunner { +run_tests(input): Promise<CppTestOutput> }
class CppcheckRunner { +run(input): Promise<CppcheckOutput> }
class ClangdClient { +query(input): Promise<ClangdQueryOutput> }
class DiagnosticParser {
  +parse_compiler_output(output): Diagnostic[]
  +semantic_signature(diagnostic): string
}

Workflow (overview §10.8): detect → configure (CMake+Ninja preferred, Make fallback) → build → parse diagnostics → test → cppcheck → clangd query when needed → Debugger on failure → scoped fix → Reviewer → architecture gate if contracts/schema/events/boundaries changed. DiagnosticParser performs deterministic extraction and semantic_signature only; LLM interpretation lives in runtime Debugger/Reviewer, never inside toolchain-cpp (runtime-semantics §12). Diagnostic matches contracts §21; compile_commands.json is generated/located when clangd/static analysis needs it (overview §10.8).

16. Doctor, Logging, Migration, and Recovery Subsystem

Module: packages/runtime/src/doctor/, logging/, plus MigrationRunner (§4.2) and recovery in SessionManager/Scheduler.

16.1 DoctorService

Implements contracts §19.

class DoctorService implements DoctorService
  +run(input: DoctorRunInput): Promise<DoctorRunOutput>
  +check_capability(capability): Promise<DoctorIssue[]>
  -self_bootstrap(): DoctorIssue[]    // Bun, SQLite, shell, .air writability

Self-bootstrap before any capability check (runtime-semantics §18, overview §15): verify Bun runtime, SQLite, basic shell, .air/ writability; on failure report a blocking issue and skip remaining checks. Modes (contracts §19): read_only, fix (under PermissionEngine), and bundle export (local artifact, no auto-upload, overview §15). Emits doctor.* events (event-registry §3).

16.2 Logging

class Logger implements Logger { debug/info/warn/error(message, data?) }
class DeveloperLogEncryptor implements DeveloperLogEncryptor { encrypt_log_chunk(chunk): Promise<Uint8Array> }
class SecretRedactor { redact(text): string }   // shared with PermissionEngine §9.2

air.log is user-facing with redacted operational errors; air.developer.log is encrypted and more detailed (overview §15, baselineV1 §23). All logs redact secrets/auth refs/provider keys via SecretRedactor. Failures link log artifacts through evidence refs rather than copying sensitive content into user summaries.

16.3 Recovery

On startup/resume (scheduler-state-machine §9, overview §15, runtime-semantics §6.2/§14):

  1. load tasks with status running/interrupted, active agents/workspaces;
  2. check process liveness by PID; reconnect IPC if alive, else emit agent.lost;
  3. mark task failed/interrupted by resumability;
  4. preserve workspaces until merge/cleanup decision;
  5. orphan-artifact scan registers or quarantines files;
  6. FK-off orphan scan (§18.3) logs and re-parents/archives dangling references;
  7. workspace GC applies retention (overview §15);
  8. rebuild scheduler queue from pending/failed-with-retry tasks.

17. CLI Subsystem

Module: packages/cli/src/ (code-view §8). CliEntrypoint.main(argv) routes to command classes; a RuntimeFactory builds the RuntimeApp.

class CliEntrypoint { +main(argv): Promise<number> }
class RuntimeFactory { +create(options): Promise<RuntimeApp> }
commands: RunCommand, InitCommand, DoctorCommand, ProviderCommand (read-only), E2ECommand,
          ReleaseCommand, plus catalog: resume, compact, history, session list, restore (overview §14)

ProviderCommand is read-only (provider list/current); there is no runtime provider/model switch command (overview §14). restore supports file/time/session granularities over the git-backed backup repo (overview §15, runtime-semantics §19).

18. Cross-Cutting Designs

18.1 AirError construction

All failures use AirError (contracts §3, error-taxonomy-v1). Construction helper:

function make_air_error(opts: {
  kind: ErrorKind,
  severity?: ErrorSeverity,       // default "error"
  message: string,
  detail?: string,
  retryability?: Retryability,    // default "unknown"
  semantic_signature?: string,    // default derived from kind+message hash
  cause_ref?: EntityRef,
  cause_refs?: EntityRef[],
  user_action?: string,
  metadata?: JsonObject
}): AirError

semantic_signature is the stable grouping key for repeated failure detection, debug knowledge lookup, and Scheduler retry/debug/escalation routing (error-taxonomy §3, overview §9.2). When not provided, derive from kind + normalized_message_hash. Scheduler decisions use kind, retryability, severity, task scope, permission result, architecture impact, verification evidence, and repetition count (overview §9.2).

18.2 Transaction discipline

All durable event + domain update pairs run inside one DatabaseManager.transaction call (runtime-semantics §3, db-schema §1). Pseudo-code:

await db.transaction(async tx => {
  await event_repo.insert(to_record(event), tx)
  await domain_repo.insert_or_update(domain_row, tx)
})
event_bus.publish(event)   // AFTER commit

Artifact file writes use temp → sha256/size → atomic rename → DB record (runtime-semantics §6.2). If the DB insert fails after rename, recovery scans orphaned files (§16.3).

18.3 FK-off invariants

foreign_keys = OFF (db-schema §1) is compensated by application-level checks (runtime-semantics §5, overview §8.2). The 8 invariants:

  1. tasks.session_id → existing sessions.id
  2. task_attempts.task_id → existing tasks.id
  3. agents.task_id → existing tasks.id when not null
  4. tool_runs.task_id, tool_runs.agent_id → existing rows when not null
  5. command_runs.task_id, command_runs.agent_id, command_runs.tool_run_id → existing rows
  6. workspaces.task_id, workspaces.agent_id → existing rows when not null
  7. diagnostics.command_run_id, diagnostics.artifact_id → existing rows when not null
  8. evidence_refs foreign columns → existing rows when not null

SessionStore.referential_check() (§4.3) runs at startup and periodically; violations are logged to developer log and either re-parented or archived (runtime-semantics §5).

18.4 Outbox / compensation for cross-DB writes

Writes to project-level DBs (debug-records.db, learned-memory.db) or external files follow the outbox model (runtime-semantics §6.3-§6.4, overview §8.3):

1. Insert durable session event recording intent/request
2. Insert/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

Example: memory.promoted → session event (step 1) → LearnedMemoryStore.insert (step 3) → memory.promoted completion evidence (step 4). If step 3 fails, the session event remains and recovery retries or marks failed.

18.5 Security invariants

From contracts §23, security-model-v1, overview §12:

  • LLM output is untrusted until validated by runtime/tool schemas and PermissionEngine.
  • Provider output cannot directly modify files or run commands.
  • Credentials are referenced by auth_ref, never copied into events/artifacts.
  • No automatic upload of source, logs, screenshots, bundles, pcaps, or artifacts.
  • Destructive/system-sensitive actions require confirmation or policy block.
  • Path policy uses realpath normalization; symlink escapes are not allowed by string-prefix checks.
  • .git/ internals are protected from arbitrary write tools.
  • sudo risk is determined by command intent/target/system sensitivity, not string alone.

19. Sequence Designs

19.1 User request → task execution → completion

User
  │ user.message.created
  ▼
MainAgent (CLASSIFYING)
  │ classify_intent → "task"
  ▼
MainAgent (DELEGATING)
  │ architecture impact? → ArchitectureDesigner.assess_impact
  │                        architecture.impact.completed
  ▼
MainAgent (SCHEDULING)
  │ Scheduler.create_tasks(specs)
  │ task.created (durable)
  ▼
Scheduler (PLANNING_WAVE)
  │ WavePlanner.plan → SchedulerWavePlan
  ▼
Scheduler (DISPATCHING)
  │ WorkspaceManager.create_workspace
  │ workspace.created (durable)
  │ ContextAssembler.assemble → ContextPack
  │ WorkerManager.spawn
  │ agent.started (durable)
  │ task.started (durable)
  ▼
WorkerProcess (ExecutorRole)
  │ WorkerRuntime.call_tool("fs.read", ...)
  │   → IPC tool.call → parent ToolRegistry.call
  │   → PermissionEngine.evaluate → allow
  │   → tool.started (durable)
  │   → execute → tool.completed (durable)
  │   → IPC tool.result
  │ WorkerRuntime.call_tool("fs.edit", ...)
  │   → (same flow, read-before-edit enforced)
  │ WorkerRuntime.call_tool("shell.run", verification)
  │   → command.started, command.completed (durable)
  │ return WorkerResult { status: "completed", ... }
  ▼
Scheduler (COLLECTING_RESULTS)
  │ validate WorkerResult
  │ task.completed (durable)
  │ agent.completed (durable)
  ▼
Scheduler (MERGING)
  │ WorkspaceManager.merge_workspace
  │ workspace.merge.completed (durable)
  ▼
Scheduler (REVIEWING_WAVE) [if required]
  │ create review task → ReviewerRole
  │ ReviewerResult { verdict: "approved" }
  ▼
Scheduler (COMPLETED)
  │ SchedulerRunResult { status: "completed" }
  ▼
MainAgent (SUMMARIZING)
  │ present result to user
  │ trigger ExperienceMiner task (optional)
  ▼
MainAgent (IDLE)

19.2 Tool call with permission prompt

WorkerRuntime.call_tool("shell.run", { command: "rm -rf build/" })
  │
  ▼
ToolRegistry.call
  │ PermissionEngine.evaluate
  │   → PathClassifier.classify → project_build_output
  │   → CommandRiskAnalyzer.analyze → destructive
  │   → decision: ask_user (risk: medium)
  ▼
ToolRegistry suspends
  │ permission.prompt.requested (durable)
  ▼
ProjectionStore.apply → PermissionPromptProjection
  │
  ▼
TUI PermissionPrompt renders
  │ user selects "Allow once"
  ▼
UiCommandChannel emits permission response
  │ permission.prompt.resolved (durable)
  │ PermissionEngine.record
  ▼
ToolRegistry resumes
  │ tool.started (durable)
  │ execute shell command
  │ command.started, command.completed (durable)
  │ tool.completed (durable)
  ▼
IPC tool.result → WorkerRuntime

19.3 Compaction flow

ContextAssembler.assemble
  │ fit_budget fails → compaction_requested = true
  ▼
Scheduler receives compaction request
  │ create_tasks([{ type: "compact", ... }])
  │ task.created (durable)
  │ context.compaction.requested (durable)
  ▼
Scheduler dispatches CompactorRole
  │ task.started, agent.started (durable)
  │ context.compaction.started (durable)
  ▼
CompactorRole
  │ snapshot immutable message range
  │ generate summary via LLM
  │ ArtifactStore.create(summary artifact)
  │ artifact.created (durable)
  │ return CompactorResult { summary_id }
  ▼
Scheduler collects result
  │ summary.created (durable) ← inserts summaries row
  │ context.compaction.completed (durable)
  │ task.completed (durable)
  ▼
Original messages preserved for backtracking

19.4 Debug knowledge capture

ExecutorRole fails with build error
  │ WorkerResult { status: "failed", error: AirError }
  ▼
Scheduler (REPAIRING_OR_CONTINUING)
  │ RetryPlanner.decide → "debug"
  │ create debug task
  ▼
DebuggerRole
  │ analyze failure evidence
  │ lookup DebugKnowledgeStore.lookup_by_signature
  │ (no match) → diagnose root cause
  │ fix or return blocker
  │ return DebuggerResult { diagnosis, root_cause, fixed: true }
  ▼
Scheduler
  │ task.completed (durable)
  │ debug.record.created (durable) ← session event
  │ DebugKnowledgeStore.insert (outbox step 3)
  ▼
Future similar failure
  │ DebugKnowledgeStore.lookup_by_signature → hit
  │ DebuggerRole applies known fix faster

20. State Machine Designs

20.1 Main Agent State Machine

From main-agent-state-machine.md. States and transitions:

┌─────────────────────────────────────────────────────────────────┐
│                           IDLE                                   │
│                  Waiting for user input                          │
└───────────────────────────┬─────────────────────────────────────┘
                            │ user.message.created
                            ▼
┌─────────────────────────────────────────────────────────────────┐
│                        CLASSIFYING                               │
│                  LLM classifies intent                           │
└────────┬──────────────────┬──────────────────┬──────────────────┘
         │ chat/Q&A         │ task request      │ /direct
         ▼                  ▼                   ▼
    ┌─────────┐      ┌─────────────┐     ┌─────────────┐
    │ANSWERING│      │ DELEGATING  │     │ DIRECT_MODE │
    │→ IDLE   │      └──┬──────┬───┘     │ /done→IDLE  │
    └─────────┘         │      │         └─────────────┘
                  simple│ needs│plan
                        ▼      ▼
              ┌──────────┐  ┌─────────────────────┐
              │SCHEDULING│  │ARCHITECTURE_DESIGNING│
              │→ EXECUTING│ │→ CONFIRMING          │
              └──────────┘  └─────────────────────┘
                                    │
                                    ▼
                            ┌─────────────┐
                            │ CONFIRMING  │
                            │ user confirm│
                            └──┬──────┬───┘
                       confirm │      │ reject → IDLE
                               ▼
                        ┌─────────────┐
                        │  EXECUTING  │
                        │  Scheduler  │
                        └──┬──────┬───┘
              requirement  │      │ all done
              change       ▼      │
                    ┌─────────────┐│
                    │INTERRUPTING ││
                    └──┬──────┬───┘│
              exec-only│ design    │
                       ▼      ▼    │
              resume  ┌──────────┐ │
              EXECUTING│ARCH_REV │ │
                       │→CONFIRM │ │
                       └─────────┘ │
                                   ▼
                            ┌─────────────┐
                            │ SUMMARIZING │
                            │ → IDLE      │
                            └─────────────┘

State-to-permission_template (main-agent-state-machine §State-to-AgentRuntimeContext):

State permission_template
IDLE, CLASSIFYING, ANSWERING, CONFIRMING, SUMMARIZING N/A
DIRECT_MODE main_direct
EXECUTING per-task (Scheduler assigns)
INTERRUPTING, ARCHITECTURE_REVISING N/A (delegation)

20.2 Scheduler State Machine

From scheduler-state-machine-v1.md §4. States:

IDLE
  │ task.created / execution request
  ▼
LOADING_GRAPH
  │ reconstruct TaskGraph from DB
  │ detect orphaned agents → agent.lost
  │ validate dependencies
  ├─ valid runnable → PLANNING_WAVE
  ├─ no runnable + blocked → BLOCKED
  ├─ all terminal success → COMPLETED
  └─ invalid beyond repair → BLOCKED

PLANNING_WAVE
  │ WavePlanner.plan
  ├─ runnable wave → DISPATCHING
  └─ no runnable → BLOCKED

DISPATCHING
  │ create workspaces
  │ assemble context
  │ spawn workers
  │ emit task.started, agent.started
  └─ → MONITORING

MONITORING
  │ track heartbeats, progress, timeouts
  │ handle requirement.changed
  ├─ all agents terminal → COLLECTING_RESULTS
  ├─ user/global cancel → CANCELLED
  └─ architecture/user blocker → BLOCKED

COLLECTING_RESULTS
  │ validate WorkerResult
  │ persist artifacts/evidence
  │ classify task terminal status
  ├─ workspaces need merge → MERGING
  ├─ review required → REVIEWING_WAVE
  ├─ more work → REPAIRING_OR_CONTINUING
  └─ all done → COMPLETED

MERGING
  │ WorkspaceManager.merge_workspace
  ├─ success + review → REVIEWING_WAVE
  ├─ success + more → REPAIRING_OR_CONTINUING
  ├─ conflict recoverable → REPAIRING_OR_CONTINUING
  └─ unrecoverable → BLOCKED

REVIEWING_WAVE
  │ schedule review tasks
  │ collect ReviewerResult
  └─ → REPAIRING_OR_CONTINUING

REPAIRING_OR_CONTINUING
  │ retry failed within budget
  │ create repair tasks
  │ skip allowed failures
  ├─ more runnable → PLANNING_WAVE
  ├─ blocked → BLOCKED
  ├─ all done → COMPLETED
  └─ cancelled → CANCELLED

Terminal: COMPLETED | BLOCKED | CANCELLED

20.3 Task Status Transitions

From db-schema §7, scheduler-state-machine §2:

pending ──task.started──▶ running
running ──task.completed──▶ completed
running ──task.failed──▶ failed
running ──task.blocked──▶ blocked
running ──task.cancelled──▶ cancelled
running ──task.interrupted──▶ interrupted
failed ──retry──▶ pending (new attempt)
interrupted ──resume──▶ pending
blocked ──decision received──▶ pending

20.4 Agent Status Transitions

From db-schema §10:

starting ──ready──▶ running
running ──agent.completed──▶ completed
running ──agent.failed──▶ failed
running ──agent.lost──▶ lost
running ──agent.cancelled──▶ cancelled

20.5 Workspace Status Transitions

From db-schema §16:

active ──merge success──▶ merged
active ──merge conflict──▶ conflicted
active ──abandon──▶ abandoned
conflicted ──decision──▶ abandoned | active (retry)
merged ──GC (7d)──▶ cleaned
abandoned ──GC (3d)──▶ cleaned

20.6 Capability Lifecycle

From capability-trust-v1 §7, overview §6:

discovered ──validate──▶ validated
validated ──doctor_check──▶ doctor_checked
doctor_checked ──enable──▶ enabled
enabled ──register_tools──▶ registered
registered ──activate──▶ active
active ──disable──▶ disabled
active ──failure──▶ failed
active ──update──▶ updated (→ discovered)

21. Traceability Matrix

This matrix maps frozen baseline sections to detailed design sections, ensuring no baseline requirement is unaddressed.

21.1 Contracts → Detailed Design

Contracts section Detailed design section
§2 Core Primitive Types §3 (contracts package)
§3 Error Contracts §3, §18.1
§4 Entity References §3
§5 Runtime Event Contracts §3, §5
§6 Transaction and Storage §4, §18.2
§7 EventBus/Store/Ingestor §5
§8 Project and Session §6
§9 Task and Scheduler §7
§10 Worker and IPC §8
§11 WorkerResult §8.3
§12 Tool Contracts §9.1
§13 Permission Contracts §9.2, §9.3
§14 Artifact and Evidence §11
§15 Provider Contracts §12
§16 Context Contracts §10
§17 Projection/UI Contracts §13
§18 Capability Contracts §9.5
§19 Doctor and Logging §16
§20 Debug/Learned Memory §11.3
§21 Diagnostic Contracts §15 (DiagnosticParser)
§22 Versioning Rules §3 (contracts package rules)
§23 Boundary Rules §2, §18.5

21.2 State Machines → Detailed Design

State machine baseline Detailed design section
main-agent-state-machine.md §14.1, §20.1
scheduler-state-machine-v1.md §7, §20.2
db-schema §7 task status §20.3
db-schema §10 agent status §20.4
db-schema §16 workspace status §20.5
capability-trust-v1 §7 §9.5, §20.6

21.3 DB Schema → Detailed Design

DB schema table Repository (§4.3) Event projection (§5.4)
schema_meta MigrationRunner
sessions SessionRepository session.*
messages MessageRepository user/assistant.message.*
message_drafts MessageDraftRepository assistant.message.started/failed
events EventRepository all durable
tasks TaskRepository task.*
task_dependencies TaskDependencyRepository task.created
task_attempts TaskAttemptRepository task.started/completed/failed
agents AgentRepository agent.*
tool_runs ToolRunRepository tool.*
command_runs CommandRunRepository command.*
artifacts ArtifactRepository artifact.created
diagnostics DiagnosticRepository diagnostic.created
evidence_refs EvidenceRepository evidence.created
workspaces WorkspaceRepository workspace.*
summaries SummaryRepository summary.created
ui_state UiStateRepository — (not event-driven)
debug_records (project DB) DebugKnowledgeStore debug.record.created
learned_memories (project DB) LearnedMemoryStore memory.*

21.4 Code View → Detailed Design

Code view package Detailed design sections
packages/contracts §3
packages/runtime §4-§11, §13-§14, §16
packages/llm §12
packages/toolchain-cpp §15
packages/tui §13.2
packages/cli §17

21.5 Overview → Detailed Design

Overview section Detailed design section
§2 System Goal §0 (authority), §2
§4 Container Overview §2, §3
§5 Dependency Rules §2
§6 Runtime Component Overview §4-§11
§7 Runtime Agent Overview §14
§8 State and Data Overview §4, §6, §11
§9 Event, Error, Projection §5, §18.1, §13.1
§10 Execution Flow Overview §7, §8, §9, §19
§11 IPC and Worker Overview §8
§12 Permission and Security §9.2, §9.3, §18.5
§13 Context, Memory, Compaction §10, §11.3, §19.3
§14 UI/HUD and Provider §12, §13
§15 Doctor, Restore, Recovery §16
§16 Implementation Phase Mapping (implementation phase, not design)
§17 Validation Overview (test phase, not design)

22. UML Class Diagrams (Mermaid)

22.1 Contracts Package

classDiagram
    class RuntimeEvent~T~ {
        +id: UUID
        +type: string
        +version: number
        +timestamp: ISOTimeString
        +session_id: SessionID
        +project_id?: ProjectID
        +source: EventSource
        +route: string[]
        +payload: T
    }
    class EventSource {
        +kind: "main"|"architecture_designer"|"scheduler"|"agent"|"tool"|"system"
        +id?: string
        +agent_type?: AgentType
    }
    class TaskSpec {
        +id: TaskID
        +type: TaskType
        +title: string
        +description: string
        +acceptance_criteria: string[]
        +scope: TaskScope
        +dependencies: TaskDependencySpec[]
        +verification: VerificationPolicy
        +constraints: TaskConstraints
        +context_refs: TaskContextRefs
        +output_contract: WorkerOutputContract
    }
    class WorkerResult~T~ {
        +task_id: TaskID
        +agent_id: AgentID
        +agent_type: AgentType
        +status: WorkerStatus
        +summary: string
        +changed_files: string[]
        +artifacts: ArtifactRef[]
        +verification: VerificationResult[]
        +risks: Risk[]
        +follow_up_tasks: FollowUpTask[]
        +evidence_refs: EvidenceRef[]
        +result: T
    }
    class AirError {
        +error_id: UUID
        +kind: ErrorKind
        +severity: ErrorSeverity
        +message: string
        +detail?: string
        +retryability: Retryability
        +semantic_signature: string
        +cause_ref?: EntityRef
        +cause_refs?: EntityRef[]
        +user_action?: string
        +metadata?: JsonObject
    }
    class ToolDefinition~I,O~ {
        +name: string
        +version: number
        +description: string
        +input_schema: JsonSchema~I~
        +output_schema: JsonSchema~O~
        +category: ToolCategory
        +permissions: ToolPermissionSpec
        +streaming: boolean
    }
    class PermissionDecision {
        +action: PermissionAction
        +grant_scope: PermissionGrantScope
        +risk_level: string
        +reason: string
        +required_confirmation?: boolean
        +backup_required?: boolean
        +evidence_ref_ids?: EvidenceRefID[]
    }
    class ArtifactRef {
        +artifact_id: ArtifactID
        +uri: string
        +path: string
        +type: string
        +sha256?: string
        +size_bytes?: number
    }
    class EvidenceRef {
        +evidence_ref_id: EvidenceRefID
        +kind: string
        +ref: string
        +claim: string
        +location_json?: unknown
    }
    RuntimeEvent --> EventSource
    WorkerResult --> ArtifactRef
    WorkerResult --> EvidenceRef
    AirError --> EntityRef

22.2 Runtime Core Services

classDiagram
    class RuntimeApp {
        +start(options): Promise~void~
        +shutdown(): Promise~void~
    }
    class ServiceRegistry {
        +get~T~(token): T
        +register(token, service): void
    }
    class ProjectStore {
        +locate(start_path): Promise~ProjectContext~
        +initialize(root, options): Promise~ProjectContext~
        +open(root): Promise~ProjectContext~
    }
    class SessionManager {
        +open_session(project, options): Promise~SessionContext~
        +close_session(session_id): Promise~void~
    }
    class DatabaseManager {
        +open(path): DatabaseHandle
        +transaction~T~(fn): Promise~T~
    }
    class EventIngestor {
        +ingest~T~(event): Promise~void~
        +ingest_ephemeral~T~(event): Promise~void~
    }
    class EventStore {
        +append~T~(event, options): Promise~void~
        +append_many(events, options): Promise~void~
        +query(filter): Promise~RuntimeEvent[]~
    }
    class EventBus {
        +publish~T~(event): void
        +subscribe(filter, handler): Subscription
        +drain(): Promise~void~
    }
    class ProjectionStore {
        +hydrate(session_id): Promise~void~
        +apply(event): void
        +snapshot(): ProjectionSnapshot
        +subscribe(handler): Subscription
    }
    RuntimeApp --> ServiceRegistry
    RuntimeApp --> ProjectStore
    RuntimeApp --> SessionManager
    SessionManager --> DatabaseManager
    EventIngestor --> EventStore
    EventIngestor --> EventBus
    EventStore --> DatabaseManager
    EventStore --> EventBus : publishes after commit
    ProjectionStore --> EventBus : subscribes

22.3 Scheduler Subsystem

classDiagram
    class Scheduler {
        +create_tasks(session_id, specs): Promise~void~
        +add_dependency(session_id, task_id, dep): Promise~void~
        +load_graph(session_id): Promise~TaskGraph~
        +run_until_idle(session_id): Promise~SchedulerRunResult~
        +cancel_task(task_id, reason): Promise~void~
        -plan_wave(graph): SchedulerWavePlan
        -dispatch(wave): Promise~void~
        -collect_results(): Promise~void~
    }
    class TaskGraph {
        +session_id: SessionID
        +tasks: Map~TaskID, TaskNode~
        +dependencies: TaskDependencyRecord[]
        +get_runnable_tasks(): TaskNode[]
        +mark_terminal(task_id, status): void
    }
    class WavePlanner {
        +plan(graph, resources): SchedulerWavePlan
    }
    class RetryPlanner {
        +decide(task, attempts, error): RetryDecision
    }
    class WorkspaceManager {
        +create_workspace(plan): Promise~WorkspaceRef~
        +merge_workspace(workspace_id): Promise~MergeResult~
        +cleanup_workspace(workspace_id): Promise~void~
    }
    class AgentMonitor {
        +record_heartbeat(event): void
        +detect_lost_agents(): Promise~AgentLost[]~
        +enforce_timeouts(): Promise~void~
    }
    class WorkerManager {
        +spawn(task_spec, context_pack): Promise~WorkerProcess~
        +cancel(agent_id, reason): Promise~void~
    }
    Scheduler --> TaskGraph
    Scheduler --> WavePlanner
    Scheduler --> RetryPlanner
    Scheduler --> WorkspaceManager
    Scheduler --> AgentMonitor
    Scheduler --> WorkerManager

22.4 Tool and Permission Subsystem

classDiagram
    class ToolRegistry {
        +register~I,O~(definition, executor): void
        +register_streaming~I,O~(definition, executor): void
        +call~I,O~(name, input, context): Promise~ToolResultEnvelope~O~~
        +call_streaming~I,O~(name, input, context): AsyncIterable
        +list(): ToolDefinition[]
    }
    class PermissionEngine {
        +evaluate(context): Promise~PermissionDecision~
        +record(decision, context): Promise~PermissionRecordResult~
    }
    class PathClassifier {
        +classify(path, project_root): PathRiskClassification
    }
    class CommandRiskAnalyzer {
        +analyze(command, cwd): CommandRiskAnalysis
    }
    class CapabilityRegistry {
        +discover(): Promise~CapabilityManifestV1[]~
        +validate(manifest): Promise~ValidationResult~
        +enable(capability_id): Promise~void~
        +disable(capability_id): Promise~void~
        +register_tools(tool_registry): Promise~void~
    }
    ToolRegistry --> PermissionEngine
    PermissionEngine --> PathClassifier
    PermissionEngine --> CommandRiskAnalyzer
    CapabilityRegistry --> ToolRegistry : registers tools

22.5 Worker and IPC

classDiagram
    class WorkerProcess {
        +agent_id: AgentID
        +pid?: number
        +send(envelope): void
        +on_message(handler): void
    }
    class WorkerProtocol {
        +encode(msg): string
        +decode(line): IpcMessage
        +validate_direction(msg): void
        +check_protocol_version(v): boolean
    }
    class WorkerRuntime {
        +emit(event): Promise~void~
        +call_tool~I,O~(name, input): Promise~ToolResultEnvelope~O~~
        +checkpoint(data): Promise~void~
    }
    class WorkerRole~T~ {
        <<interface>>
        +run(task_spec, context_pack, runtime): Promise~WorkerResult~T~~
    }
    class ExecutorRole {
        +run(...): Promise~WorkerResult~ExecutorResult~~
    }
    class ReviewerRole {
        +run(...): Promise~WorkerResult~ReviewerResult~~
    }
    class DebuggerRole {
        +run(...): Promise~WorkerResult~DebuggerResult~~
    }
    class CompactorRole {
        +run(...): Promise~WorkerResult~CompactorResult~~
    }
    class ExperienceMinerRole {
        +run(...): Promise~WorkerResult~ExperienceMinerResult~~
    }
    WorkerRole <|.. ExecutorRole
    WorkerRole <|.. ReviewerRole
    WorkerRole <|.. DebuggerRole
    WorkerRole <|.. CompactorRole
    WorkerRole <|.. ExperienceMinerRole
    WorkerRole --> WorkerRuntime
    WorkerProcess --> WorkerProtocol

22.6 Provider (LLM) Subsystem

classDiagram
    class ProviderManager {
        +load_config(): Promise~void~
        +select_model(requirement): Promise~ModelAssignment~
        +complete(input): AsyncIterable~ProviderStreamEvent~
    }
    class ProviderAdapter {
        <<interface>>
        +provider_id: ProviderID
        +list_models(): Promise~ProviderCapabilityMatrix[]~
        +validate_model(model_id): Promise~ProviderCapabilityMatrix~
        +complete(input): AsyncIterable~ProviderStreamEvent~
        +count_tokens?(input): Promise~number~
    }
    class AnthropicAdapter {
        +provider_id: "anthropic"
    }
    class OpenAICompatibleAdapter {
        +provider_id: string
        -converter: AnthropicCanonicalConverter
    }
    class AnthropicCanonicalConverter {
        +to_provider(messages): unknown[]
        +from_provider(response): unknown[]
    }
    class StreamNormalizer {
        +normalize(stream): AsyncIterable~ProviderStreamEvent~
    }
    ProviderManager --> ProviderAdapter
    ProviderAdapter <|.. AnthropicAdapter
    ProviderAdapter <|.. OpenAICompatibleAdapter
    OpenAICompatibleAdapter --> AnthropicCanonicalConverter
    ProviderAdapter --> StreamNormalizer

22.7 Context and Compaction

classDiagram
    class ContextAssembler {
        +assemble(input): Promise~AssembledContext~
        -load_layers(purpose): Promise~PromptLayer[]~
        -fit_budget(layers, budget): BudgetFitResult
    }
    class PromptLayerLoader {
        +load_runtime_invariant(): PromptLayer
        +load_role(role): PromptLayer
        +load_project_rules(project): PromptLayer[]
        +load_task_context(spec, refs): PromptLayer[]
    }
    class CompactionPolicy {
        +should_compact(messages, budget): boolean
        +compact(messages, target): Promise~CompactionResult~
    }
    class PromptLayer {
        +level: PromptLayerLevel
        +priority: number
        +content: unknown
        +token_estimate?: number
        +source_ref?: string
        +immutable?: boolean
    }
    ContextAssembler --> PromptLayerLoader
    ContextAssembler --> CompactionPolicy
    PromptLayerLoader --> PromptLayer

22.8 Agents

classDiagram
    class MainAgent {
        +handle_user_message(message): Promise~void~
        +present_progress(): Promise~void~
        +present_blocker(blocker): Promise~void~
        -classify_intent(message): string
        -state: MainAgentState
    }
    class ArchitectureDesigner {
        +assess_impact(change): Promise~ArchitectureImpact~
        +update_architecture_docs(update): Promise~DocumentUpdate~
    }
    MainAgent --> Scheduler
    MainAgent --> ContextAssembler
    MainAgent --> ProviderManager
    ArchitectureDesigner --> ContextAssembler
    ArchitectureDesigner --> ProviderManager
    ArchitectureDesigner --> EventIngestor

23. Design Freeze Checklist

Before implementation begins, verify:

  • All contracts §2-§21 interfaces are exported from packages/contracts
  • All db-schema §3-§18 tables have corresponding repositories
  • All event-registry §3 durable events have projection handlers in EventStore
  • All state machines (Main Agent, Scheduler, Task, Agent, Workspace, Capability) are implemented
  • All forbidden paths (contracts §23) are enforced by lint/import boundaries
  • All closed enums (db-schema §21) are validated on insert/update
  • FK-off invariants (§18.3) are checked at startup and periodically
  • Outbox model (§18.4) is used for cross-DB writes
  • Security invariants (§18.5) are enforced

End of System Detailed Design.