# 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: ```text 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` 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): ```text 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. ```text class DatabaseManager implements TransactionManager +open(path: string): DatabaseHandle +transaction(fn: (tx: TransactionHandle) => Promise): Promise -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 ```text class MigrationRunner +migrate(db: DatabaseHandle): Promise -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` (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): ```text 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 // 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: ```text 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). ```text class EventIngestor implements EventIngestor (contracts §7) +ingest(event: RuntimeEvent): Promise +ingest_ephemeral(event: RuntimeEvent): Promise -policyFor(type): EventPersistence // durable | ephemeral, from registry ``` `ingest` flow (runtime-semantics §2): ```text 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 ```text 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 ```text class EventStore implements EventStore (contracts §7) +append(event, options?): Promise +append_many(events, options?): Promise +query(filter: EventFilter): Promise -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): ```text 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 ```text class EventBus implements EventBus (contracts §7) +publish(event: RuntimeEvent): void +subscribe(filter: EventFilter, handler): Subscription +drain?(): Promise -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. ```text class ProjectStore implements ProjectStore +locate(start_path): Promise +initialize(project_root, options?): Promise +open(project_root): Promise ``` `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 ```text class SessionManager implements SessionManager (contracts §8) +open_session(project, options?): Promise +close_session(session_id): Promise class SessionStore // §4.3 aggregate of repositories ``` `open_session` flow: ```text resolve session_id (options or IdGenerator.session_id()) compute db_path = .air/local/sessions//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. ```text class Scheduler implements Scheduler +create_tasks(session_id, specs: TaskSpec[]): Promise +add_dependency(session_id, task_id, dependency: TaskDependencySpec): Promise +load_graph(session_id): Promise +run_until_idle(session_id): Promise +cancel_task(task_id, reason): Promise -plan_wave(graph): SchedulerWavePlan -dispatch(wave): Promise -collect_results(): Promise -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 ```text class TaskGraph +session_id: SessionID +tasks: Map +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 ```text 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 ```text 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 ```text class WorkspaceManager +create_workspace(plan: WorkspacePlan): Promise +merge_workspace(workspace_id): Promise +cleanup_workspace(workspace_id): Promise ``` 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 ```text class AgentMonitor +record_heartbeat(event): void // coalesced into agents.last_heartbeat_at + tasks.heartbeat_at +detect_lost_agents(): Promise +enforce_timeouts(): Promise ``` 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 ```text class WorkerManager +spawn(task_spec: TaskSpec, context_pack: ContextPack): Promise +cancel(agent_id, reason): Promise 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 ```text 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` (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). ```text interface WorkerRole { run(task_spec, context_pack, runtime): Promise> } class WorkerRuntime +emit(event: RuntimeEvent): Promise // → IPC event → parent EventIngestor +call_tool(name, input): Promise> // → IPC tool.call +checkpoint(data): Promise // → 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` 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. ```text class ToolRegistry implements ToolRegistry +register(definition: ToolDefinition, executor: ToolExecutor): void +register_streaming(definition, executor: StreamingToolExecutor): void +call(name, input, context: ToolExecutionContext): Promise> +call_streaming(name, input, context): AsyncIterable> +list(): ToolDefinition[] -validate_input(def, input): void // JsonSchema check ``` `call` algorithm (contracts §12, §23 "tool → side effect without PermissionEngine" forbidden): ```text 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. ```text class PermissionEngine implements PermissionEngine +evaluate(context: PermissionRequestContext): Promise +record(decision, context): Promise 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): ```text 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. ```text class CapabilityRegistry implements CapabilityRegistry +discover(): Promise +validate(manifest): Promise +enable(capability_id): Promise +disable(capability_id): Promise +register_tools(tool_registry: ToolRegistry): Promise 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 ```text class ContextAssembler implements ContextAssembler +assemble(input: ContextAssembleInput): Promise -load_layers(purpose): Promise -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 ```text 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 ```text class CompactionPolicy implements CompactionPolicy +should_compact(messages, token_budget): boolean +compact(messages, target_tokens): Promise ``` `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. ```text class ArtifactStore implements ArtifactStore +create(input: ArtifactCreateInput, context: ArtifactContext): Promise +get(artifact_id): Promise +read(artifact_id): Promise -write_temp_then_rename(bytes): {path, sha256, size} ``` `create` algorithm (runtime-semantics §6.2, overview §8.4): ```text write temp file → compute sha256 + size → atomic rename to artifact path artifact_id = art_ uri = artifact://project//session// filename = -- 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 ```text class EvidenceStore implements EvidenceStore +create(input: EvidenceCreateInput): Promise +list_for_entity(entity_type, entity_id): Promise ``` 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. ```text class DebugKnowledgeStore implements DebugKnowledgeStore // debug-records.db +insert(record: DebugRecord): Promise +lookup_by_signature(failure_signature): Promise +lookup_by_task(task_id): Promise +update(debug_record_id, patch): Promise class LearnedMemoryStore implements LearnedMemoryStore // learned-memory.db +insert(memory: LearnedMemory): Promise +lookup_by_type(memory_type): Promise +update_status(memory_id, status): Promise +scan_stale(): Promise ``` 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. ```text class ProviderManager implements ProviderManager +load_config(): Promise +select_model(requirement: ModelRequirement): Promise +complete(input: ProviderCompletionInput): AsyncIterable -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 `ProviderStreamEvent`s. Runtime/session provider+model are fixed for the session; there is no runtime switching API (overview §14). ### 12.2 ProviderAdapter implementations ```text 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. ```text class ProjectionStore implements ProjectionStore +hydrate(session_id): Promise +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). ```text 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 ```text class MainAgent +handle_user_message(message): Promise +present_progress(): Promise +present_blocker(blocker: BlockerReport): Promise -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 ```text class ArchitectureDesigner +assess_impact(change): Promise +update_architecture_docs(update): Promise ``` 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). ```text class CppToolRegistrar { +register(tool_registry): void } class CppProjectDetector { +detect(project_root): Promise } class CMakeConfigurator { +configure(input): Promise } class CppBuilder { +build(input): Promise } class CppTestRunner { +run_tests(input): Promise } class CppcheckRunner { +run(input): Promise } class ClangdClient { +query(input): Promise } 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. ```text class DoctorService implements DoctorService +run(input: DoctorRunInput): Promise +check_capability(capability): Promise -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 ```text class Logger implements Logger { debug/info/warn/error(message, data?) } class DeveloperLogEncryptor implements DeveloperLogEncryptor { encrypt_log_chunk(chunk): Promise } 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`. ```text class CliEntrypoint { +main(argv): Promise } class RuntimeFactory { +create(options): Promise } 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: ```text 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: ```text 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): ```text 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 ```text 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 ```text 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 ```text 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 ```text 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: ```text ┌─────────────────────────────────────────────────────────────────┐ │ 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: ```text 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: ```text 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: ```text 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: ```text 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: ```text 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 ```mermaid 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 ```mermaid 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 ```mermaid 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 ```mermaid 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 ```mermaid 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~ { <> +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 ```mermaid classDiagram class ProviderManager { +load_config(): Promise~void~ +select_model(requirement): Promise~ModelAssignment~ +complete(input): AsyncIterable~ProviderStreamEvent~ } class ProviderAdapter { <> +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 ```mermaid 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 ```mermaid 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.