Files
AirCoding/AirPlan/docs/architecture/decisions-round-3.md
AirCoding 33a76a1ebc Move project from external drive to local NVMe
迁移路径: /run/media/airlongdian/EasyU/AirCoding -> /home/airlongdian/DataDevices/AirWorkSpace/AirCoding

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-02 09:51:49 +08:00

426 lines
17 KiB
Markdown
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 36 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.