# Architecture Decisions — Round 3 Date: 2026-05-26 Source: implementation-interface freeze discussion ## D-038: RuntimeEvent Envelope Uses Route Chain - **Decision**: Runtime events use a structured `route: string[]` route chain instead of correlation/causation IDs. - **Reason**: Route chain is easier to display, append, and inspect across Main Agent → Architecture Designer → Scheduler → Worker → Tool flows. - **Envelope**: ```ts interface RuntimeEvent { id: string type: string version: number timestamp: string session_id: string project_id?: string source: EventSource route: string[] payload: T } ``` - **Durability**: Not declared by the event. EventStore decides durability by event type. ## D-039: IPC Uses NDJSON over stdio - **Decision**: Parent Scheduler and child agents communicate via NDJSON over stdio. - **stdout**: Protocol only (`event`, `control`, `log` messages). - **stderr**: Crash fallback and non-structured fatal diagnostics only. - **Message union**: ```ts type IpcMessage = | { kind: "event"; event: RuntimeEvent } | { kind: "control"; control: ControlMessage } | { kind: "log"; level: "debug" | "info" | "warn" | "error"; message: string; data?: unknown } ``` - **Exit codes**: - `0`: protocol-level completion, including task completed/blocked/failed - `1`: uncaught exception - `2`: startup/protocol error - `3`: permission error - `4`: parent cancelled - `5`: hard timeout killed ## D-040: Durable Event Type Set - **Durable**: - `session.created`, `session.archived`, `session.deleted` - `user.message.created`, `assistant.message.created` - `agent.started`, `agent.completed`, `agent.failed` - `task.created`, `task.started`, `task.completed`, `task.blocked`, `task.failed`, `task.cancelled` - `tool.started`, `tool.completed`, `tool.failed` - `command.started`, `command.completed`, `command.failed` - `artifact.created` - `context.compaction.completed`, `summary.created` - `memory.candidate.created`, `memory.promoted`, `memory.archived` - `debug.record.created` - `permission.decision.recorded` - `requirement.changed`, `architecture.plan.updated`, `architecture.impact.completed` - **Ephemeral only**: - `agent.heartbeat`, `task.progress`, `token.delta`, `stdout.delta`, `stderr.delta`, `hud.frame.rendered`, `tool.progress` - **Rationale**: Persist tool/agent starts so crash recovery can detect half-finished operations; avoid persisting high-frequency stream deltas. ## D-041: TaskSpec Contract ```ts interface TaskSpec { id: string type: "execute" | "review" | "debug" | "compact" | "mine_experience" title: string description: string acceptance_criteria: string[] scope: { write_area?: string expected_files?: string[] allowed_paths?: string[] denied_paths?: string[] } dependencies: { hard: string[]; soft: string[] } verification: { commands?: string[] required: boolean fallback_allowed: boolean } constraints: { max_turns: number soft_timeout_ms: number hard_timeout_ms: number retry_budget: number model_policy: "scheduler_forced" | "agent_select" model_id?: string } context_refs: { plan_ref?: string arc_ref?: string parent_task_results?: string[] artifacts?: string[] } output_contract: "ExecutorResult" | "ReviewerResult" | "DebuggerResult" | "CompactorResult" | "ExperienceMinerResult" } ``` - **allowed_paths/denied_paths** are task-level narrowing rules on top of global PermissionEngine. - **verification.commands** are Scheduler recommendations; agents may add more and must report actual commands run. - **retry_budget** is per task. ## D-042: WorkerResult Contract ```ts interface WorkerResult { task_id: string agent_id: string agent_type: "executor" | "reviewer" | "debugger" | "compactor" | "experience_miner" status: "completed" | "failed" | "blocked" | "cancelled" summary: string changed_files: string[] diff_ref?: string artifacts: ArtifactRef[] verification: VerificationResult[] risks: Risk[] follow_up_tasks: FollowUpTask[] evidence_refs: EvidenceRef[] result: T } ``` - **failed**: task goal not achieved; Scheduler may retry or skip. - **blocked**: upper-level decision needed; continued attempts are not useful. - **summary**: human-readable 3–6 sentence summary covering what was done, evidence, conclusion, and risk. It is for TUI/Main Agent display, not scheduling logic. - **changed_files** exists for all worker types and may be empty. ## D-043: ArtifactRef and EvidenceRef Separation - **Artifact**: persistent file or large data object (build log, test log, screenshot, pcap, core dump, diff, terminal cast). - **Evidence**: a claim-supporting reference that may point to an artifact excerpt, structured result, message, tool run, command run, or diagnostic. ```ts interface ArtifactRef { id: string type: "build_log" | "test_log" | "screenshot" | "pcap" | "core_dump" | "terminal_cast" | "diff" | "report" | "other" uri: string size_bytes?: number sha256?: string created_at: string } interface EvidenceRef { id: string kind: "artifact_excerpt" | "structured_result" | "message" | "tool_run" | "command_run" | "diagnostic" ref: string location?: { line_start?: number; line_end?: number; byte_start?: number; byte_end?: number } claim: string } ``` ## D-044: Tool API Contract ```ts interface ToolDefinition { name: string version: number description: string input_schema: JsonSchema output_schema: JsonSchema category: "filesystem" | "shell" | "build" | "test" | "debug" | "static_analysis" | "gui" | "network" | "memory" | "project" | "internal" permissions: { read_paths?: PathPolicy write_paths?: PathPolicy execute?: boolean network?: boolean system_sensitive?: boolean } streaming: boolean execute(input: I, context: ToolExecutionContext): AsyncIterable | Promise> } ``` - Inputs and outputs are both JSON-schema validated. - Streaming tools must emit a final `tool.result` containing `ToolResult`. - Bash is a regular shell tool; PermissionEngine performs extra command risk analysis. ## D-045: Capability Dependencies Are Managed by Doctor/Setup - Capability manifests declare dependencies but do not install them directly. - `air doctor` / `air setup` detect and repair missing dependencies. - First startup runs read-only doctor automatically. - If issues exist, user is prompted to run fix. - High-permission mode can use `announce_then_run` for dependency installation after the first startup; user not interrupting means allow. - First startup, even in high-permission mode, shows the report and asks before `doctor --fix`. - `credentials` and `system_sensitive` risks always require explicit confirmation. ```ts interface DependencySpec { id: string kind: "executable" | "system_package" | "npm_package" | "pip_package" | "lsp_server" | "formatter" | "mcp_server" | "env_var" | "permission" | "service" | "network_port" required_for: string[] severity: "required" | "optional" | "degraded" detect: DependencyDetectSpec install?: { low_permission: "never" | "prompt" | "safe_cache" high_permission: "never" | "announce_then_run" | "safe_cache" | "prompt" commands?: InstallCommandTemplate[] docs?: string } risk: "none" | "downloads_code" | "needs_root" | "network_capture" | "credentials" | "system_sensitive" } ``` ## D-046: ContextAssembler Contract - ContextAssembler outputs Anthropic canonical messages. Provider-specific conversion happens only at the LLM adapter boundary. - ContextAssembler records omissions so agents know what was not included. - If usage passes compaction threshold, it publishes `context.compaction.requested` but does not compact itself. ```ts interface ContextRequest { session_id: string project_id: string requester: { kind: "main" | "architecture_designer" | "scheduler" | "agent"; agent_type?: string; task_id?: string } purpose: "user_response" | "architecture_planning" | "task_execution" | "review" | "debug" | "compaction" | "experience_mining" model: { provider_id: string; model_id: string; context_window_tokens: number; max_output_tokens: number } budget_policy: { reserved_output_tokens: number; safety_margin_tokens: number; target_usage_ratio: number } include: { project_rules: boolean project_profile: boolean task_graph: boolean recent_messages: number related_artifacts?: string[] related_tasks?: string[] related_files?: string[] } rule_set_ref?: string } interface AssembledContext { request_id: string messages: AnthropicMessageParam[] included_refs: { messages: string[]; summaries: string[]; artifacts: string[]; rules: string[]; tasks: string[] } token_estimate: { total: number; by_section: Record; model_id: string } compaction: { needed: boolean; reason?: string; requested_event_id?: string } omissions: { ref: string; reason: string }[] } ``` ## D-047: Compaction Rules Use Markdown + Frontmatter - Rule files are Markdown with YAML frontmatter. - Paths: - Built-in system default - User template: `~/.air/compaction-rules.md` - Project override: `.air/shared/compaction-rules.md` - Three-tier inheritance: system default → user template → project override. - Message classification is done by ContextClassifier before compaction. - `drop_if_needed` is allowed because original messages are preserved. ## D-048: TUI Reuses OpenCode UI Primitives, Not Business State - Use `@opentui/solid`, `@opentui/core`, and `@opentui/keymap`. - Copy/adapt OpenCode's generic theme, dialog, toast, keymap, layout, spinner, border, markdown/code/diff rendering patterns. - Do not reuse OpenCode's SDK/sync/session business layer. - AirCoding defines its own event store, session/message/task/agent/tool state model. - OpenCode session UI is used as a rendering reference only. ## D-049: Project State Lives in `.air/local`, Shared Knowledge in `.air/shared` - Project source of truth moves into the project directory: ```text /.air/ shared/ # git-shareable project knowledge local/ # portable but gitignored runtime state ``` - `.air/shared` contains project profile, permissions, compaction rules, project rules, and plan/docs. - `.air/local` contains sessions, artifacts, runtime state, backups, debug/learned DBs, locks, temp, and workspaces. - `~/.air` contains only user config, cache, global skills, global logs, and a project index. - `project_id` is a stable UUID generated at initialization, not an absolute-path hash. ## D-050: Developer Logs Use Development-Team Public Key Encryption - `air.developer.log` is encrypted with the development team's public key. - No local user private key is required for decrypting developer logs. - `air doctor --bundle` may include full diagnostics and is not automatically redacted. - Rationale: redaction may remove information needed to debug real failures. - Diagnostic bundles are never auto-uploaded; user must explicitly export/send them. ## D-051: Session DB Uses Canonical Messages + Domain State Tables - `messages` stores complete Anthropic canonical content JSON. - `message_drafts` stores streaming assistant intermediate state for crash recovery and is deleted after final message completion. - `message_parts` is not a source-of-truth table in MVP. - Domain tables are the source of truth for scheduling/recovery/query: - `tasks`, `task_dependencies`, `task_attempts` - `agents`, `tool_runs`, `command_runs` - `artifacts`, `diagnostics`, `evidence_refs`, `workspaces`, `events` - `ui_state` stores lightweight UI recovery state only. - ProjectionStore rebuilds TUI view models from DB + live EventBus. ## D-052: Query-Friendly Columns and Tables Are Preferred over Parsing JSON - Canonical JSON preserves fidelity; frequently queried relations are extracted to columns/tables. - `tool_runs` and `command_runs` include `origin_message_id`. - `artifacts` include common foreign keys (`task_id`, `agent_id`, `tool_run_id`, `command_run_id`) while retaining generic associated entity fields. - `events` extract source/task/agent/tool/command IDs and store both `route_json` and `route_text`. - `task_dependencies`, `task_attempts`, `diagnostics`, `evidence_refs`, and `workspaces` are first-class tables. ## D-053: UI State Is Flushed Periodically and on Exit - UI changes are held in memory during interaction. - `ui_state` flushes periodically (e.g. every 10 seconds) and on normal exit. - Crash/power loss may lose a few seconds of UI-only state, but never scheduling/message state. ## D-054: Artifact Layout Is Project-Local - Artifacts live under: ```text /.air/local/sessions//artifacts/ ``` - URI format: ```text artifact://project//session// ``` - Layout: ```text artifacts/ command-runs// tool-runs// builds/ tests/ screenshots/ pcaps/ core-dumps/ diffs/ reports/ ``` - Internal filenames may use artifact IDs or semantic names; DB preserves `original_name`. - Logs may be gzip-compressed; screenshots, pcaps, and core dumps are not double-compressed by default. - Session delete/purge deletes artifacts; archive keeps them. ## D-055: Schema Migration Requires User Confirmation - Opening a project auto-detects `.air` schema versions. - Old schema triggers a migration plan display. - User confirmation is required even in high-permission mode. - Migration backs up `.air` first. - Migration failure must rollback. ## D-056: HUD/TUI Consumes ProjectionStore Only - DB is persistent state source. - EventBus is real-time state source. - ProjectionStore is the only display projection consumed by TUI/HUD. - HUD does not query SQLite directly. - ProjectionStore hydrates from DB on startup and applies EventBus updates during runtime. - If EventBus disconnects/restarts, ProjectionStore rehydrates from DB. - HUD presets only control visible fields, not underlying state. ## D-057: UI Design Asset Capability Is Optional but Supported - AirCoding is not a general creative-media agent, but software development often requires UI assets, icons, visual mockups, and design specs. - Runtime supports an optional `ui-design-assets` capability. - MVP includes text/SVG/design-spec oriented tools: - ASCII/wireframe mockup generation - design specification generation - SVG icon generation - screenshot design analysis - external image-generation prompt generation - Bitmap image generation/editing is provider-backed and post-MVP. - Generated design assets are artifacts first: - `ui_mockup` - `icon` - `illustration` - `svg_asset` - `design_spec` - `design_prompt` - Generated assets are shown to the user before being written into project files. UI/design choices are subjective and should not be silently finalized even in high-permission mode. ## D-058: Anthropic Claude Skills Are a Tool/Skill Reference Source - Anthropic/Claude open-source Skills are included as a reference for AirCoding's skill and tool organization. - AirCoding should study and borrow: - `SKILL.md` structure and frontmatter conventions - skill directory layout (`scripts/`, `references/`, `assets/`) - how reusable workflows are packaged as discoverable skills - how skill descriptions guide trigger/retrieval behavior - how skills interact with project rules and tool/capability execution - Claude Skills are a reference for AirCoding's SkillGenerator, ExperienceMiner output format, and Capability documentation. - AirCoding still keeps Project Rules, Skills, MCP, and Capabilities as separate concepts: - Project Rules: authoritative project constraints - Skills: reusable procedural knowledge - MCP: external service/tool protocol - Capabilities: runtime-registered tool bundles with dependencies/triggers/evidence types ## D-059: Execution-Layer Primitives Align With Claude Code for Code Quality - AirCoding should align execution-layer behavior with Claude Code as much as possible to maximize code quality, correctness, and safe modification behavior. - Claude Code is the primary behavioral reference for execution-layer primitives. - This includes: - file read/edit/write safety boundaries - exact and conservative diff/update application semantics - patch granularity and conflict handling - tool input/output schema style - permission checks around filesystem and shell operations - TAOR/TORI-style execution loops and result feedback patterns - read-before-edit discipline - small-step edits with verification before completion - avoiding unrelated refactors and premature abstractions during task execution - build/test/debug evidence collection before declaring completion - project-rule and memory adherence during edits - failure handling that diagnoses root cause instead of random retries - explicit blocker escalation when implementation discovers architecture/interface conflicts - OpenCode remains the runtime/TUI structure reference, but execution primitives must favor Claude Code semantics whenever there is a tradeoff. - Codex remains a reference for broad tool surface and shell/patch/test direct loops, but AirCoding should not sacrifice Claude Code-style edit discipline for tool breadth. - Rationale: Executor correctness, safe code modification, patch quality, verification discipline, project-rule adherence, and failure recovery matter more than matching OpenCode's internal execution implementation or Codex-style general tool breadth.