迁移路径: /run/media/airlongdian/EasyU/AirCoding -> /home/airlongdian/DataDevices/AirWorkSpace/AirCoding Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1336 lines
35 KiB
Markdown
Executable File
1336 lines
35 KiB
Markdown
Executable File
# 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:
|
|
|
|
1. Event payloads, DB columns, tool schemas, and persisted JSON are already `snake_case`.
|
|
2. Avoiding a camelCase↔snake_case mapper reduces V1.0.0 Alpha implementation cost and prevents boundary drift.
|
|
3. 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
|
|
|
|
```ts
|
|
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:
|
|
|
|
```ts
|
|
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
|
|
|
|
```ts
|
|
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
|
|
|
|
```ts
|
|
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
|
|
|
|
```ts
|
|
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
|
|
|
|
```ts
|
|
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`.
|
|
|
|
```ts
|
|
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:
|
|
|
|
```ts
|
|
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
|
|
|
|
```ts
|
|
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:
|
|
|
|
1. `EventIngestor` is the runtime entry point for events from agents/tools/workers.
|
|
2. `EventStore` validates durable event schema and applies domain projections transactionally.
|
|
3. EventBus publication of durable events happens after commit.
|
|
4. EventBus is live transport only and is not a recovery source of truth.
|
|
5. If a `subscribe` handler 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
|
|
|
|
```ts
|
|
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
|
|
|
|
```ts
|
|
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.
|
|
|
|
```ts
|
|
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:
|
|
|
|
```ts
|
|
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
|
|
|
|
```ts
|
|
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
|
|
|
|
```ts
|
|
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
|
|
|
|
```ts
|
|
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
|
|
|
|
```ts
|
|
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
|
|
|
|
```ts
|
|
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
|
|
|
|
```ts
|
|
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" // L0
|
|
| "role" // L1
|
|
| "safety" // L2 (added to align with prompt-layering-v1 §2)
|
|
| "project_rules" // L3
|
|
| "architecture" // L4
|
|
| "task_spec" // L5
|
|
| "evidence" // L6
|
|
| "conversation" // L7
|
|
| "tool_output" // L8
|
|
| "user_override" // L9
|
|
| "system_debug" // (applied within L9 when present)
|
|
|
|
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
|
|
|
|
```ts
|
|
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
|
|
|
|
```ts
|
|
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
|
|
|
|
```ts
|
|
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
|
|
```
|