Initial commit: AirCoding V1.0.0 Alpha architecture baseline
Complete architecture document set with multi-model review remediation: - Frozen interface contracts, runtime semantics, DB schemas - Event/tool/error/provider registries - Scheduler and main agent state machines - C4 module/code views, solution architecture, baseline V1 - Multi-model review reports and joint assessment - Phase-gate remediation complete (P0/P1/P2/UX resolved) - Implementation plan with T-000A through T-045 - Reference folders kept as placeholders only
This commit is contained in:
425
AirPlan/docs/architecture/decisions-round-3.md
Normal file
425
AirPlan/docs/architecture/decisions-round-3.md
Normal file
@@ -0,0 +1,425 @@
|
||||
# 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<T = unknown> {
|
||||
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<T = unknown> {
|
||||
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<I = unknown, O = unknown> {
|
||||
name: string
|
||||
version: number
|
||||
description: string
|
||||
input_schema: JsonSchema<I>
|
||||
output_schema: JsonSchema<O>
|
||||
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<ToolEvent> | Promise<ToolResult<O>>
|
||||
}
|
||||
```
|
||||
|
||||
- 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<string, number>; 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
|
||||
<project>/.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
|
||||
<project>/.air/local/sessions/<session-id>/artifacts/
|
||||
```
|
||||
|
||||
- URI format:
|
||||
|
||||
```text
|
||||
artifact://project/<project-id>/session/<session-id>/<artifact-id>
|
||||
```
|
||||
|
||||
- Layout:
|
||||
|
||||
```text
|
||||
artifacts/
|
||||
command-runs/<command-run-id>/
|
||||
tool-runs/<tool-run-id>/
|
||||
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.
|
||||
Reference in New Issue
Block a user