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
35 KiB
AirCoding Interface Contracts V1
Date: 2026-05-27 Status: Canonical implementation-facing interface contract set for V1.0.0 Alpha
This document freezes V1 public TypeScript interfaces and service boundaries. Implementations may use classes, functions, or modules, but exported contracts, dependency direction, and serialization shapes must remain stable unless a later ADR updates them.
1. Naming and Serialization Conventions
V1 TypeScript public contracts use snake_case field names. Runtime events, tool payloads, DB JSON metadata, IPC messages, and WorkerResult structures therefore share one JSON-compatible field convention.
Rationale:
- Event payloads, DB columns, tool schemas, and persisted JSON are already
snake_case. - Avoiding a camelCase↔snake_case mapper reduces V1.0.0 Alpha implementation cost and prevents boundary drift.
- Provider adapters may translate external provider formats, but AirCoding internal contracts remain
snake_case.
Implementation code may use local camelCase variables internally, but exported interfaces in packages/contracts must keep snake_case field names unless a later ADR changes this.
2. Core Primitive Types
export type ISOTimeString = string
export type UUID = string
export type ProjectID = string
export type SessionID = string
export type MessageID = string
export type TaskID = string
export type AgentID = string
export type ToolRunID = string
export type CommandRunID = string
export type ArtifactID = string
export type EvidenceRefID = string
export type WorkspaceID = string
export type SummaryID = string
export type CapabilityID = string
export type ProviderID = string
export type ModelID = string
export type WaveID = string
export type JsonObject = Record<string, unknown>
export type JsonValue = null | boolean | number | string | JsonValue[] | { [key: string]: JsonValue }
// JsonSchema<T> is nominal-only: T provides no compile-time runtime check, but documents the expected shape for tool schema consumers.
export type JsonSchema<T = unknown> = JsonObject
Testability primitives:
export interface Clock {
now(): ISOTimeString
}
export interface IdGenerator {
uuid(): UUID
project_id(): ProjectID
session_id(): SessionID
message_id(): MessageID
task_id(): TaskID
agent_id(): AgentID
tool_run_id(): ToolRunID
command_run_id(): CommandRunID
artifact_id(): ArtifactID
evidence_ref_id(): EvidenceRefID
workspace_id(): WorkspaceID
summary_id(): SummaryID
}
3. Error Contracts
export type ErrorKind =
| "user_error"
| "project_error"
| "env_error"
| "dependency_error"
| "permission_error"
| "tool_error"
| "command_error"
| "build_error"
| "test_error"
| "static_analysis_error"
| "debug_error"
| "provider_error"
| "model_capability_error"
| "context_error"
| "agent_error"
| "scheduler_error"
| "workspace_error"
| "merge_error"
| "architecture_error"
| "policy_error"
| "system_error"
| "unknown_error"
export type ErrorSeverity = "info" | "warning" | "error" | "fatal"
export type Retryability = "retryable" | "retryable_after_change" | "not_retryable" | "unknown"
export interface 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
}
All tool, event, worker, provider, and command failures use AirError. Do not introduce parallel error_kind / retryable / failure_signature shapes.
4. Entity References
export type EntityType =
| "session"
| "message"
| "task"
| "agent"
| "tool_run"
| "command_run"
| "artifact"
| "diagnostic"
| "workspace"
| "summary"
| "capability"
| "provider"
export interface EntityRef {
type: EntityType
id: string
}
5. Runtime Event Contracts
export type AgentType = "executor" | "reviewer" | "debugger" | "compactor" | "experience_miner"
export interface EventSource {
kind: "main" | "architecture_designer" | "scheduler" | "agent" | "tool" | "system"
id?: string
agent_type?: AgentType
}
export interface RuntimeEvent<TPayload = unknown> {
id: UUID
type: string
version: number
timestamp: ISOTimeString
session_id: SessionID
project_id?: ProjectID
source: EventSource
route: string[]
payload: TPayload
}
export interface EventFilter {
session_id?: SessionID
types?: string[]
task_id?: TaskID
agent_id?: AgentID
tool_run_id?: ToolRunID
command_run_id?: CommandRunID
route_prefix?: string[]
since?: ISOTimeString
}
Event payload schemas and persistence policy are defined in event-registry-v1.md.
6. Transaction and Storage Contracts
export interface TransactionHandle {
id: string
}
export interface DatabaseHandle {
path: string
}
export interface TransactionManager {
transaction<T>(fn: (tx: TransactionHandle) => Promise<T>): Promise<T>
}
Repository records mirror db-schema-v1.md columns and use snake_case.
export interface SessionRecord {
id: SessionID
project_id: ProjectID
project_root: string
title?: string
status: "active" | "archived" | "deleted"
created_at: ISOTimeString
updated_at: ISOTimeString
exited_at?: ISOTimeString
model_provider_id?: ProviderID
model_id?: ModelID
metadata_json?: string
}
export interface MessageRecord {
id: MessageID
session_id: SessionID
role: string
canonical_format: "anthropic"
content_json: string
parent_message_id?: MessageID
route_json?: string
created_at: ISOTimeString
token_estimate?: number
metadata_json?: string
}
export type TaskType = "execute" | "review" | "debug" | "compact" | "mine_experience" | "docs"
export type TaskStatus = "pending" | "running" | "completed" | "failed" | "blocked" | "cancelled" | "interrupted"
export interface TaskRecord {
id: TaskID
session_id: SessionID
type: TaskType
status: TaskStatus
title: string
task_spec_json: string
worker_result_json?: string
assigned_agent_id?: AgentID
workspace_id?: WorkspaceID
retry_count: number
created_at: ISOTimeString
started_at?: ISOTimeString
completed_at?: ISOTimeString
heartbeat_at?: ISOTimeString
metadata_json?: string
}
export type TaskInsert = Omit<TaskRecord, "retry_count" | "started_at" | "completed_at" | "heartbeat_at" | "worker_result_json"> & {
retry_count?: number
}
export type TaskUpdate = Partial<Omit<TaskRecord, "id" | "session_id" | "created_at">>
export type PersistedEventInsert = Omit<PersistedEventRecord, "route_text"> & {
route_text?: string
}
export type TaskDependencyType = "hard" | "soft" | "conflict" | "serialization"
export interface TaskDependencyRecord {
id: UUID
session_id: SessionID
task_id: TaskID
depends_on_task_id: TaskID
dependency_type: TaskDependencyType
reason?: string
created_at: ISOTimeString
}
export interface PersistedEventRecord {
id: UUID
session_id: SessionID
type: string
version: number
timestamp: ISOTimeString
source_kind: string
source_id?: string
agent_type?: AgentType
task_id?: TaskID
agent_id?: AgentID
tool_run_id?: ToolRunID
command_run_id?: CommandRunID
route_json: string
route_text: string
payload_json: string
}
Repository layer:
export interface Repository<TRecord, TInsert, TUpdate> {
get(id: string, tx?: TransactionHandle): Promise<TRecord | undefined>
insert(record: TInsert, tx?: TransactionHandle): Promise<void>
update(id: string, patch: TUpdate, tx?: TransactionHandle): Promise<void>
}
export interface TaskRepository extends Repository<TaskRecord, TaskInsert, TaskUpdate> {
list_by_status(session_id: SessionID, statuses: TaskStatus[], tx?: TransactionHandle): Promise<TaskRecord[]>
list_runnable_candidates(session_id: SessionID, tx?: TransactionHandle): Promise<TaskRecord[]>
}
export interface EventRepository {
insert(event: PersistedEventRecord, tx: TransactionHandle): Promise<void>
query(filter: EventFilter): Promise<PersistedEventRecord[]>
}
Repositories are persistence adapters only. They must not contain Scheduler, permission, or projection policy.
7. EventBus, EventStore, and Ingestion Contracts
export interface Subscription {
unsubscribe(): void
}
export interface EventBus {
publish<T>(event: RuntimeEvent<T>): void
subscribe(filter: EventFilter, handler: (event: RuntimeEvent) => void | Promise<void>): Subscription
drain?(): Promise<void>
}
export interface EventStore {
append<T>(event: RuntimeEvent<T>, options?: EventAppendOptions): Promise<void>
append_many(events: RuntimeEvent[], options?: EventAppendOptions): Promise<void>
query(filter: EventFilter): Promise<RuntimeEvent[]>
}
export interface EventAppendOptions {
expected_session_id?: SessionID
transaction?: TransactionHandle
}
export interface EventIngestor {
ingest<T>(event: RuntimeEvent<T>): Promise<void>
ingest_ephemeral<T>(event: RuntimeEvent<T>): Promise<void>
}
export interface EventSchemaRegistry {
register(type: string, version: number, schema: JsonObject): void
validate(type: string, version: number, payload: unknown): boolean
list(): Array<{ type: string; version: number }>
get_schema(type: string, version: number): JsonObject | undefined
}
Rules:
EventIngestoris the runtime entry point for events from agents/tools/workers.EventStorevalidates durable event schema and applies domain projections transactionally.- EventBus publication of durable events happens after commit.
- EventBus is live transport only and is not a recovery source of truth.
- If a
subscribehandler throws, the error is caught, logged to developer log, and does not propagate to the publisher. The subscription remains active.
8. Project and Session Contracts
export interface ProjectContext {
project_id: ProjectID
project_root: string
air_root: string
shared_root: string
local_root: string
schema_version: number
}
export interface ProjectInitOptions {
force?: boolean
title?: string
default_rules?: boolean
}
export interface ProjectStore {
locate(start_path: string): Promise<ProjectContext | undefined>
initialize(project_root: string, options?: ProjectInitOptions): Promise<ProjectContext>
open(project_root: string): Promise<ProjectContext>
}
export interface SessionContext {
session_id: SessionID
project_id: ProjectID
project_root: string
db_path: string
artifact_root: string
}
export interface OpenSessionOptions {
session_id?: SessionID
title?: string
model_provider_id?: ProviderID
model_id?: ModelID
}
export interface SessionManager {
open_session(project: ProjectContext, options?: OpenSessionOptions): Promise<SessionContext>
close_session(session_id: SessionID): Promise<void>
}
9. Task and Scheduler Contracts
export interface TaskDependencySpec {
depends_on_task_id: TaskID
dependency_type: TaskDependencyType
reason?: string
source?: "architecture" | "scheduler" | "worker" | "user" | "system"
}
export interface TaskScope {
write_area?: string
expected_files?: string[]
allowed_paths?: string[]
denied_paths?: string[]
}
export interface VerificationPolicy {
commands?: string[]
required: boolean
fallback_allowed: boolean
}
export interface TaskConstraints {
max_turns: number
soft_timeout_ms: number
hard_timeout_ms: number
retry_budget: number
model_policy: "scheduler_forced" | "agent_select"
model_provider_id?: ProviderID
model_id?: ModelID
}
export interface TaskContextRefs {
plan_ref?: string
arc_ref?: string
parent_task_results?: ArtifactID[]
artifacts?: ArtifactID[]
}
export type WorkerOutputContract =
| "ExecutorResult"
| "ReviewerResult"
| "DebuggerResult"
| "CompactorResult"
| "ExperienceMinerResult"
export interface 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
}
export interface TaskNode {
task_id: TaskID
spec: TaskSpec
status: TaskStatus
assigned_agent_id?: AgentID
retry_count: number
workspace_id?: WorkspaceID
}
export interface TaskGraph {
session_id: SessionID
tasks: Map<TaskID, TaskNode>
dependencies: TaskDependencyRecord[]
}
export interface WorkspacePlan {
strategy: "main" | "worktree" | "isolated_copy"
path?: string
base_ref?: string
branch_name?: string
reason: string
}
export interface ModelAssignment {
mode: "scheduler_forced" | "agent_select"
provider_id?: ProviderID
model_id?: ModelID
allowed_models?: Array<{ provider_id: ProviderID; model_id: ModelID }>
requirement: ModelRequirement
reason: string
}
export interface SchedulerWavePlan {
wave_id: WaveID
runnable_task_ids: TaskID[]
serialized_task_ids: TaskID[]
workspace_assignments: Record<TaskID, WorkspacePlan>
model_assignments: Record<TaskID, ModelAssignment>
reason: string
}
export interface SchedulerRunResult {
status: "completed" | "blocked" | "cancelled" | "idle"
completed_task_ids: TaskID[]
blocked_task_ids: TaskID[]
summary: string
}
export interface Scheduler {
create_tasks(session_id: SessionID, specs: TaskSpec[]): Promise<void>
add_dependency(session_id: SessionID, task_id: TaskID, dependency: TaskDependencySpec): Promise<void>
load_graph(session_id: SessionID): Promise<TaskGraph>
run_until_idle(session_id: SessionID): Promise<SchedulerRunResult>
cancel_task(task_id: TaskID, reason: string): Promise<void>
}
10. Worker and IPC Contracts
IPC uses NDJSON messages with explicit direction, correlation IDs, and request/response envelopes.
export type IpcDirection = "parent_to_worker" | "worker_to_parent"
export interface IpcEnvelope<TPayload = unknown> {
id: UUID
direction: IpcDirection
kind: IpcKind
timestamp: ISOTimeString
session_id: SessionID
agent_id: AgentID
correlation_id?: UUID
protocol_version: number
payload: TPayload
}
export type IpcKind =
| "control"
| "event"
| "log"
| "tool.call"
| "tool.result"
| "tool.stream"
| "worker.result"
| "worker.checkpoint"
| "protocol.error"
export type IpcMessage =
| IpcEnvelope<ControlMessage>
| IpcEnvelope<RuntimeEvent>
| IpcEnvelope<LogPayload>
| IpcEnvelope<ToolCallRequest>
| IpcEnvelope<ToolCallResponse>
| IpcEnvelope<ToolStreamPayload>
| IpcEnvelope<WorkerResult>
| IpcEnvelope<WorkerCheckpointPayload>
| IpcEnvelope<ProtocolErrorPayload>
export interface LogPayload {
level: "debug" | "info" | "warn" | "error"
message: string
data?: unknown
}
export type ControlMessage =
| {
type: "agent.start"
version: 1
task_spec: TaskSpec
context_pack: ContextPack
runtime: AgentRuntimeContext
}
| { type: "agent.cancel"; reason: string }
| { type: "agent.pause"; reason: string }
| { type: "agent.resume" }
| { type: "agent.extend_timeout"; extra_ms: number; reason: string }
| { type: "worker.ready"; protocol_version: number; worker_version: string }
export interface AgentRuntimeContext {
session_id: SessionID
project_id: ProjectID
agent_id: AgentID
worktree_path?: string
permission_template: "main_direct" | "executor" | "reviewer" | "debugger" | "system"
}
export interface ContextPack {
refs: {
plan_ref?: string
arc_ref?: string
task_refs?: TaskID[]
artifact_refs?: ArtifactID[]
rule_refs?: string[]
}
assembled_context_ref?: ArtifactID
notes?: string[]
}
export interface ToolCallRequest {
tool_call_id: UUID
tool_name: string
input: unknown
context: ToolExecutionContext
}
export interface ToolCallResponse {
tool_call_id: UUID
result: ToolResultEnvelope
}
export interface ToolStreamPayload {
tool_call_id: UUID
event: ToolEvent
}
export interface WorkerCheckpointPayload {
checkpoint_id: UUID
task_id: TaskID
data: unknown
}
export interface ProtocolErrorPayload {
error: AirError
received_message_id?: UUID
}
IPC handshake: parent sends agent.start after spawn; worker responds with worker.ready carrying protocol_version before receiving tasks. If protocol_version does not match, the parent terminates the worker with a protocol.error.
IPC direction typing: ParentToWorkerMessage covers control, tool.result, tool.stream. WorkerToParentMessage covers event, log, tool.call, worker.result, worker.checkpoint, protocol.error. Both share IpcEnvelope shape; runtime validation enforces direction.
Worker role:
export interface WorkerRole<TResult = unknown> {
run(task_spec: TaskSpec, context_pack: ContextPack, runtime: WorkerRuntime): Promise<WorkerResult<TResult>>
}
export interface WorkerRuntime {
emit(event: RuntimeEvent): Promise<void>
call_tool<I, O>(name: string, input: I): Promise<ToolResultEnvelope<O>>
checkpoint(data: unknown): Promise<void>
}
11. WorkerResult Contracts
export type WorkerStatus = "completed" | "failed" | "blocked" | "cancelled"
export interface VerificationResult {
name: string
status: "passed" | "failed" | "skipped" | "unknown"
evidence_ref_ids?: EvidenceRefID[]
notes?: string
}
export interface Risk {
severity: "low" | "medium" | "high"
summary: string
}
export interface FollowUpTask {
title: string
description: string
type?: TaskType
}
export interface WorkerResult<TResult = unknown> {
task_id: TaskID
agent_id: AgentID
agent_type: AgentType
status: WorkerStatus
summary: string
changed_files: string[]
diff_ref?: ArtifactID
artifacts: ArtifactRef[]
verification: VerificationResult[]
risks: Risk[]
follow_up_tasks: FollowUpTask[]
evidence_refs: EvidenceRef[]
result: TResult
}
export interface ExecutorResult {
implementation_summary: string
changed_files: string[]
verification_commands: string[]
}
export interface ReviewerResult {
verdict: "approved" | "changes_requested" | "blocked"
findings: ReviewFinding[]
}
export interface ReviewFinding {
severity: "low" | "medium" | "high"
category: "correctness" | "security" | "scope" | "architecture" | "test" | "maintainability"
message: string
file?: string
line?: number
evidence_ref_ids?: EvidenceRefID[]
}
export interface BlockerReport {
impact_level: "implementation" | "interface" | "architecture" | "product" | "permission" | "environment" | "policy"
reason: string
required_decision: string
options?: Array<{ label: string; tradeoff: string }>
evidence_ref_ids?: EvidenceRefID[]
suggested_default?: string
}
export interface DebuggerResult {
diagnosis: string
root_cause?: string
fixed: boolean
blocker?: BlockerReport
}
export interface CompactorResult {
summary_id: SummaryID
range_start_message_id?: MessageID
range_end_message_id?: MessageID
token_estimate_before?: number
token_estimate_after?: number
}
export interface MemoryCandidate {
candidate_id: UUID
memory_type: "project_rule" | "toolchain_rule" | "skill_update" | "debug_experience"
summary: string
evidence_ref_ids?: EvidenceRefID[]
}
export interface ExperienceMinerResult {
candidates: MemoryCandidate[]
}
12. Tool Contracts
export type ToolCategory =
| "filesystem"
| "shell"
| "git"
| "project"
| "build"
| "test"
| "debug"
| "static_analysis"
| "gui"
| "network"
| "memory"
| "context"
| "artifact"
| "permission"
| "doctor"
| "internal"
export interface ToolPermissionSpec {
read_paths?: PathPolicy
write_paths?: PathPolicy
execute?: boolean
network?: boolean
system_sensitive?: boolean
credentials?: boolean
}
export interface PathPolicy {
allow?: string[]
deny?: string[]
source?: string
}
export interface ToolDefinition<I = unknown, O = unknown> {
name: string
version: number
description: string
input_schema: JsonSchema<I>
output_schema: JsonSchema<O>
category: ToolCategory
permissions: ToolPermissionSpec
streaming: boolean
}
export interface ToolExecutor<I = unknown, O = unknown> {
execute(input: I, context: ToolExecutionContext): Promise<ToolResultEnvelope<O>>
}
export interface StreamingToolExecutor<I = unknown, O = unknown> {
execute_streaming(input: I, context: ToolExecutionContext): AsyncIterable<ToolEvent>
execute_final(input: I, context: ToolExecutionContext): Promise<ToolResultEnvelope<O>>
}
export interface ToolExecutionContext {
session_id: SessionID
project_id: ProjectID
task_id?: TaskID
agent_id?: AgentID
origin_message_id?: MessageID
permission_template: string
cwd?: string
}
export interface ToolResultEnvelope<T = unknown> {
status: "ok" | "error" | "cancelled"
output?: T
error?: AirError
artifact_ids?: ArtifactID[]
evidence_ref_ids?: EvidenceRefID[]
metadata?: JsonObject
}
export interface ToolEvent {
type: "progress" | "artifact" | "result"
payload: unknown
}
export interface ToolRegistry {
register<I, O>(definition: ToolDefinition<I, O>, executor: ToolExecutor<I, O>): void
register_streaming<I, O>(definition: ToolDefinition<I, O>, executor: StreamingToolExecutor<I, O>): void
call<I, O>(name: string, input: I, context: ToolExecutionContext): Promise<ToolResultEnvelope<O>>
call_streaming<I, O>(name: string, input: I, context: ToolExecutionContext): AsyncIterable<ToolEvent | ToolResultEnvelope<O>>
list(): ToolDefinition[]
}
Tool streaming rule: call() consumes stream events internally and returns the final ToolResultEnvelope; call_streaming() exposes progress events and must end with exactly one final result envelope.
13. Permission Contracts
export interface PermissionRequestContext {
session_id: SessionID
task_id?: TaskID
agent_id?: AgentID
tool_name?: string
command?: string
paths?: string[]
network?: boolean
requested_action: string
reason: string
}
export type PermissionAction = "allow" | "deny" | "ask_user" | "block" | "refuse" | "announce_then_run"
export type PermissionGrantScope = "none" | "once" | "session" | "project" | "global"
export interface PermissionDecision {
action: PermissionAction
grant_scope: PermissionGrantScope
risk_level: "low" | "medium" | "high" | "critical"
reason: string
required_confirmation?: boolean
backup_required?: boolean
evidence_ref_ids?: EvidenceRefID[]
}
export interface PermissionRecordResult {
ok: boolean
error?: AirError
}
export interface PermissionEngine {
evaluate(context: PermissionRequestContext): Promise<PermissionDecision>
record(decision: PermissionDecision, context: PermissionRequestContext): Promise<PermissionRecordResult>
}
Permission checks are layered: tool capability policy → permission profile → task scope → path/command/network risk → credential/system-sensitive override → user prompt workflow.
14. Artifact and Evidence Contracts
export interface ArtifactRef {
artifact_id: ArtifactID
uri: string
path: string
type: string
sha256?: string
size_bytes?: number
}
export interface EvidenceRef {
evidence_ref_id: EvidenceRefID
kind: string
ref: string
claim: string
location_json?: unknown
}
export interface ArtifactCreateInput {
type: string
original_name?: string
content?: string
source_path?: string
associated_entity_type?: string
associated_entity_id?: string
metadata?: JsonObject
}
export interface ArtifactContext {
session_id: SessionID
task_id?: TaskID
agent_id?: AgentID
tool_run_id?: ToolRunID
command_run_id?: CommandRunID
}
export interface ArtifactReadResult {
artifact: ArtifactRef
content?: string | Uint8Array
content_type?: string
}
export interface EvidenceCreateInput {
kind: string
ref: string
claim: string
location_json?: unknown
task_id?: TaskID
agent_id?: AgentID
tool_run_id?: ToolRunID
command_run_id?: CommandRunID
artifact_id?: ArtifactID
diagnostic_id?: string
message_id?: MessageID
}
export interface ArtifactStore {
create(input: ArtifactCreateInput, context: ArtifactContext): Promise<ArtifactRef>
get(artifact_id: ArtifactID): Promise<ArtifactRef | undefined>
read(artifact_id: ArtifactID): Promise<ArtifactReadResult>
}
export interface EvidenceStore {
create(input: EvidenceCreateInput): Promise<EvidenceRef>
list_for_entity(entity_type: string, entity_id: string): Promise<EvidenceRef[]>
}
Worker results embed full EvidenceRef[] when evidence is part of the worker conclusion; lightweight payloads may carry evidence_ref_ids.
15. Provider Contracts
export interface ProviderCapabilityMatrix {
provider_id: ProviderID
provider_kind: "anthropic" | "openai" | "openrouter" | "ollama" | "anthropic_compatible" | "openai_compatible" | "custom"
model_id: ModelID
enabled: boolean
quality_tier: "frontier" | "strong" | "standard" | "cheap" | "local" | "unknown"
cost_tier: "high" | "medium" | "low" | "free" | "unknown"
context_window_tokens?: number
max_output_tokens?: number
supports: JsonObject
conversion: JsonObject
}
export interface ModelRequirement {
required: JsonObject
preferred?: JsonObject
min_quality_tier?: "frontier" | "strong" | "standard" | "cheap" | "local"
max_cost_tier?: "high" | "medium" | "low" | "free"
min_context_window_tokens?: number
allow_lossy_conversion?: boolean
}
export interface ProviderCompletionInput {
provider_id: ProviderID
model_id: ModelID
canonical_format: "anthropic"
messages: unknown[]
tools?: unknown[]
tool_choice?: unknown
system?: unknown
max_output_tokens?: number
temperature?: number
metadata?: JsonObject
}
export interface ProviderStreamEvent {
type: "message_start" | "content_delta" | "tool_use" | "message_stop" | "error"
payload: unknown
}
export interface ProviderAdapter {
provider_id: ProviderID
list_models(): Promise<ProviderCapabilityMatrix[]>
validate_model(model_id: ModelID): Promise<ProviderCapabilityMatrix>
complete(input: ProviderCompletionInput): AsyncIterable<ProviderStreamEvent>
count_tokens?(input: unknown): Promise<number>
}
export interface ProviderManager {
select_model(requirement: ModelRequirement): Promise<ModelAssignment>
complete(input: ProviderCompletionInput): AsyncIterable<ProviderStreamEvent>
}
16. Context Contracts
export interface ContextAssembleInput {
task_id?: TaskID
purpose: "main" | "execute" | "review" | "debug" | "compact" | "mine_experience"
refs?: string[]
token_budget?: number
}
export interface AssembledContext {
canonical_format: "anthropic"
messages: unknown[]
omissions: string[]
refs: string[]
token_estimate?: number
compaction_requested?: boolean
messages_artifact_id?: ArtifactID
}
export interface ContextAssembler {
assemble(input: ContextAssembleInput): Promise<AssembledContext>
}
export type PromptLayerLevel =
| "runtime_invariant"
| "role"
| "project_rules"
| "task_spec"
| "architecture"
| "evidence"
| "tool_output"
| "conversation"
| "user_override"
| "system_debug"
export interface PromptLayer {
level: PromptLayerLevel
priority: number
content: unknown
token_estimate?: number
source_ref?: string
immutable?: boolean
}
export interface BudgetFitResult {
fitted: PromptLayer[]
omitted: PromptLayer[]
omissions: string[]
total_tokens: number
}
export interface PromptLayerLoader {
load_runtime_invariant(): PromptLayer
load_role(role: AgentType): PromptLayer
load_project_rules(project: ProjectContext): PromptLayer[]
load_task_context(spec: TaskSpec, context_refs: TaskContextRefs): PromptLayer[]
}
export interface CompactionPolicy {
should_compact(messages: unknown[], token_budget: number): boolean
compact(messages: unknown[], target_tokens: number): Promise<CompactionResult>
}
export interface CompactionResult {
summary_id: SummaryID
range_start_message_id?: MessageID
range_end_message_id?: MessageID
token_estimate_before: number
token_estimate_after: number
}
context.assemble tool may return messages_artifact_id when context is too large for inline return; ContextPack.assembled_context_ref points to that artifact.
17. Projection/UI Contracts
export interface SessionProjection {
session_id: SessionID
title?: string
status: string
}
export interface TaskProjection {
task_id: TaskID
title: string
status: TaskStatus
progress_text?: string
}
export interface AgentProjection {
agent_id: AgentID
agent_type: AgentType
status: string
task_id?: TaskID
progress_text?: string
}
export interface ToolRunProjection {
tool_run_id: ToolRunID
tool_name: string
status: string
task_id?: TaskID
}
export interface CommandRunProjection {
command_run_id: CommandRunID
command: string
status: "running" | "ok" | "error" | "cancelled" | "unknown"
exit_code?: number
}
export interface ArtifactProjection {
artifact_id: ArtifactID
type: string
uri: string
}
export interface PermissionPromptProjection {
prompt_id: string
subject: string
risk_level: string
reason: string
options: string[]
}
export interface BlockerProjection {
task_id?: TaskID
reason: string
required_decision: string
}
export interface ProjectionSnapshot {
session?: SessionProjection
tasks: TaskProjection[]
agents: AgentProjection[]
tool_runs: ToolRunProjection[]
command_runs: CommandRunProjection[]
artifacts: ArtifactProjection[]
permission_prompts: PermissionPromptProjection[]
blockers: BlockerProjection[]
updated_at: ISOTimeString
}
// ProjectionStore.apply handles all durable events and key ephemeral events
// (agent.heartbeat, task.progress, assistant.message.delta, tool.progress,
// command.stdout.delta, command.stderr.delta). Unknown event types are ignored.
export interface ProjectionStore {
hydrate(session_id: SessionID): Promise<void>
apply(event: RuntimeEvent): void
snapshot(): ProjectionSnapshot
subscribe(handler: (snapshot: ProjectionSnapshot) => void): Subscription
}
export interface ProjectionClient {
snapshot(): ProjectionSnapshot
subscribe(handler: (snapshot: ProjectionSnapshot) => void): Subscription
}
TUI may consume only ProjectionClient or projection contracts, never runtime internals, SQLite, or EventBus directly.
V1 transport: TUI runs in-process with runtime. ProjectionClient is a direct interface reference, not IPC. UI command API (permission prompt responses, user blocker decisions) emits typed events through a narrow UiCommandChannel interface. This boundary is enforced by module imports: packages/tui may only import from packages/contracts, never from packages/runtime/src/*.
18. Capability Contracts
export interface CapabilityManifestV1 {
schema_version: 1
capability_id: CapabilityID
display_name: string
version: string
description: string
publisher?: string
source: JsonObject
trust_level: "built_in" | "project_local" | "user_installed" | "verified_publisher" | "untrusted"
tools: Array<{
name: string
version: number
category: ToolCategory
description: string
input_schema: JsonSchema
output_schema: JsonSchema
streaming?: boolean
permissions: ToolPermissionSpec
}>
dependencies?: JsonObject[]
permissions: JsonObject
events?: { produced?: string[]; consumed?: string[] }
artifact_types?: string[]
config_schema?: JsonSchema
entrypoint?: JsonObject
}
export interface ValidationResult {
ok: boolean
errors: string[]
warnings: string[]
}
export interface CapabilityRegistry {
discover(): Promise<CapabilityManifestV1[]>
validate(manifest: CapabilityManifestV1): Promise<ValidationResult>
enable(capability_id: CapabilityID): Promise<void>
disable(capability_id: CapabilityID): Promise<void>
register_tools(tool_registry: ToolRegistry): Promise<void>
}
19. Doctor and Logging Contracts
export interface DoctorRunInput {
mode: "read_only" | "fix"
scope?: "startup" | "project" | "toolchain" | "release_gate"
capabilities?: CapabilityID[]
bundle?: boolean
}
export interface DoctorRunOutput {
run_id: string
status: "passed" | "issues_found" | "fixed" | "failed"
issue_count: number
blocking_issue_count: number
report_artifact_id?: ArtifactID
bundle_artifact_id?: ArtifactID
}
export interface DoctorService {
run(input: DoctorRunInput): Promise<DoctorRunOutput>
}
export interface Logger {
debug(message: string, data?: unknown): void
info(message: string, data?: unknown): void
warn(message: string, data?: unknown): void
error(message: string, data?: unknown): void
}
export interface DeveloperLogEncryptor {
encrypt_log_chunk(chunk: Uint8Array): Promise<Uint8Array>
}
## 20. Debug Knowledge and Learned Memory Contracts
```ts
export interface DebugRecord {
debug_record_id: UUID
task_id?: TaskID
failure_signature: string
summary: string
root_cause?: string
fix_ref?: string
evidence_refs?: EvidenceRef[]
verification_refs?: EvidenceRef[]
created_at: ISOTimeString
updated_at: ISOTimeString
metadata_json?: JsonObject
}
export interface DebugKnowledgeStore {
insert(record: DebugRecord): Promise<void>
lookup_by_signature(failure_signature: string): Promise<DebugRecord[]>
lookup_by_task(task_id: TaskID): Promise<DebugRecord[]>
update(debug_record_id: UUID, patch: Partial<DebugRecord>): Promise<void>
}
export interface LearnedMemory {
memory_id: UUID
memory_type: "project_rule" | "toolchain_rule" | "skill_update" | "debug_experience"
summary: string
content?: string
source_ref?: EntityRef
status: "candidate" | "promoted" | "archived" | "rejected"
created_at: ISOTimeString
updated_at: ISOTimeString
metadata_json?: JsonObject
}
export interface LearnedMemoryStore {
insert(memory: LearnedMemory): Promise<void>
lookup_by_type(memory_type: LearnedMemory["memory_type"]): Promise<LearnedMemory[]>
update_status(memory_id: UUID, status: LearnedMemory["status"]): Promise<void>
scan_stale(): Promise<LearnedMemory[]>
}
## 21. Diagnostic Contracts
```ts
export type DiagnosticSeverity = "error" | "warning" | "info" | "hint"
export interface Diagnostic {
diagnostic_id: UUID
task_id?: TaskID
agent_id?: AgentID
command_run_id?: CommandRunID
artifact_id?: ArtifactID
language?: string
toolchain?: string
severity: DiagnosticSeverity
file?: string
line?: number
column?: number
code?: string
message: string
semantic_signature: string
created_at: ISOTimeString
metadata_json?: JsonObject
}
## 22. Compatibility and Versioning Rules
1. Public contract files in `packages/contracts` should export all interfaces in this document.
2. Event payload schema changes increment event `version`.
3. Tool schema changes increment tool `version`.
4. SQLite schema changes increment `schema_meta.schema_version` and require migration plan.
5. Provider adapter conversion behavior changes must update conversion tests.
6. Interface-breaking changes require ADR update and plan/todo synchronization.
## 23. Non-Negotiable Boundary Rules
Do not implement paths that violate these contracts:
```text
TUI → SQLite direct query
TUI → runtime private service import
worker → SQLite direct write
worker → filesystem/shell/network side effect outside tool IPC
tool → side effect without PermissionEngine
capability → dependency install outside Doctor
provider adapter → silent semantic prompt loss
repository → scheduling policy
EventBus → recovery source of truth
LLM output → direct file/shell side effect