Files
AirCoding/AirPlan/docs/architecture/event-registry-v1.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

25 KiB
Executable File

AirCoding Event Payload Registry V1

Date: 2026-05-27 Status: Canonical event and payload registry for V1.0.0 Alpha skeleton

This document defines the V1 RuntimeEvent type registry, persistence policy, payload shapes, and producer/consumer rules.

Event envelope is defined in baselineV1.md and packages/contracts/event.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
}

Registry rule:

event_type → payload schema → persistence policy → domain transaction → producer → consumers

Event producers must not decide persistence ad hoc. EventStore owns persistence policy by event type.

1. Common Payload Conventions

All payloads use snake_case JSON keys to match persisted DB column names and artifact metadata.

Common aliases:

type ISOTimeString = string
type JsonObject = Record<string, unknown>
type EventPersistence = "durable" | "ephemeral"
type EventDelivery = "eventbus" | "eventbus_and_db"

Common lightweight references:

EntityRef is defined in interface-contracts-v1.md §4 with the canonical EntityType enum (12 values including capability and provider). This document uses EntityRef by reference.

interface EvidenceLink {
  evidence_ref_id?: string
  artifact_id?: string
  diagnostic_id?: string
  claim: string
}

interface FailurePayload {
  error: AirError
  evidence_refs?: EvidenceLink[]
  metadata?: JsonObject
}

Payloads should contain identifiers needed for routing/querying even when the same identifiers are also extracted into top-level event columns and domain tables.

2. Persistence and Transaction Rules

Persistence categories:

durable   → inserted into events table and applied to domain tables in one transaction
ephemeral → published on EventBus only, not required for crash recovery

Rules:

  1. Durable event insert and corresponding domain table update must happen in the same SQLite transaction.
  2. Ephemeral events may be throttled or coalesced by EventBus/ProjectionStore.
  3. Ephemeral events that become important for diagnosis should be captured as artifacts or durable summaries, not promoted ad hoc.
  4. Streaming deltas are ephemeral; completed message/tool/command records are durable.
  5. route is append-only. Forwarders append their route segment; they never rewrite earlier route entries.
  6. route_text in SQLite is derived from route.join("/") for indexing.
  7. Payload schema changes require incrementing the event version for that event type.

3. Durable Event Registry

3.1 Session Events

session.created v1

Persistence: durable.

Producer: Main Agent / CLI startup.

Domain update: insert sessions row.

interface SessionCreatedPayload {
  session_id: string
  project_id: string
  project_root: string
  title?: string
  model_provider_id?: string
  model_id?: string
  metadata?: JsonObject
}

session.archived v1

Persistence: durable.

Domain update: update sessions.status = archived.

interface SessionArchivedPayload {
  session_id: string
  reason?: string
}

session.deleted v1

Persistence: durable.

Domain update: update sessions.status = deleted.

interface SessionDeletedPayload {
  session_id: string
  reason?: string
}

3.2 Message Events

user.message.created v1

Persistence: durable.

Domain update: insert messages row.

interface UserMessageCreatedPayload {
  message_id: string
  canonical_format: "anthropic"
  content_json: unknown
  parent_message_id?: string
  token_estimate?: number
  metadata?: JsonObject
}

assistant.message.started v1

Persistence: durable.

Domain update: insert/update message_drafts row with status = streaming.

interface AssistantMessageStartedPayload {
  message_id: string
  canonical_format: "anthropic"
  parent_message_id?: string
  route?: string[]
  metadata?: JsonObject
}

assistant.message.created v1

Persistence: durable.

Domain update: insert messages row and delete matching message_drafts row.

interface AssistantMessageCreatedPayload {
  message_id: string
  canonical_format: "anthropic"
  content_json: unknown
  parent_message_id?: string
  route?: string[]
  token_estimate?: number
  metadata?: JsonObject
}

assistant.message.failed v1

Persistence: durable.

Domain update: update message_drafts.status = error or create failure artifact if draft is absent.

interface AssistantMessageFailedPayload extends FailurePayload {
  message_id: string
  partial_content_json?: unknown
}

3.3 Agent Events

agent.started v1

Persistence: durable.

Domain update: insert agents row with status = running or starting.

interface AgentStartedPayload {
  agent_id: string
  agent_type: "executor" | "reviewer" | "debugger" | "compactor" | "experience_miner"
  task_id?: string
  pid?: number
  model_provider_id?: string
  model_id?: string
  workspace_id?: string
  metadata?: JsonObject
}

agent.completed v1

Persistence: durable.

Domain update: update agents.status = completed.

interface AgentCompletedPayload {
  agent_id: string
  task_id?: string
  summary: string
  worker_result_ref?: string
  metadata?: JsonObject
}

agent.failed v1

Persistence: durable.

Domain update: update agents.status = failed.

interface AgentFailedPayload extends FailurePayload {
  agent_id: string
  task_id?: string
}

agent.lost v1

Persistence: durable.

Domain update: update agents.status = lost.

interface AgentLostPayload {
  agent_id: string
  task_id?: string
  last_heartbeat_at?: ISOTimeString
  detection_reason: "heartbeat_timeout" | "process_exit_without_result" | "ipc_broken"
}

agent.cancelled v1

Persistence: durable.

Domain update: update agents.status = cancelled.

interface AgentCancelledPayload {
  agent_id: string
  task_id?: string
  reason: string
}

3.4 Task Events

task.created v1

Persistence: durable.

Domain update: insert tasks row and optional task_dependencies rows.

interface TaskDependencySpec {
  depends_on_task_id: string
  dependency_type: "hard" | "soft" | "conflict" | "serialization"
  reason?: string
  source?: "architecture" | "scheduler" | "worker" | "user" | "system"
}

interface TaskCreatedPayload {
  task_id: string
  type: "execute" | "review" | "debug" | "compact" | "mine_experience"
  title: string
  task_spec_json: unknown
  dependencies?: TaskDependencySpec[]
  metadata?: JsonObject
}

task.started v1

Persistence: durable.

Domain update: update tasks.status = running, set started_at, assigned_agent_id, workspace_id; insert task_attempts row.

interface TaskStartedPayload {
  task_id: string
  agent_id: string
  attempt_id: string
  attempt_index: number
  workspace_id?: string
}

task.completed v1

Persistence: durable.

Domain update: update tasks.status = completed, set worker_result_json, completed_at; update task_attempts.

interface TaskCompletedPayload {
  task_id: string
  agent_id?: string
  attempt_id?: string
  worker_result_json: unknown
  summary: string
  changed_files?: string[]
  evidence_refs?: EvidenceLink[]
}

task.blocked v1

Persistence: durable.

Domain update: update tasks.status = blocked; update task_attempts if applicable.

interface TaskBlockedPayload {
  task_id: string
  agent_id?: string
  reason: string
  blocker_kind: "user_decision" | "architecture_decision" | "environment" | "permission" | "dependency" | "external"
  evidence_refs?: EvidenceLink[]
  suggested_next_step?: string
}

task.failed v1

Persistence: durable.

Domain update: update tasks.status = failed; update task_attempts.failure_*.

interface TaskFailedPayload extends FailurePayload {
  task_id: string
  agent_id?: string
  attempt_id?: string
}

task.cancelled v1

Persistence: durable.

Domain update: update tasks.status = cancelled.

interface TaskCancelledPayload {
  task_id: string
  reason: string
  cancelled_by: "user" | "main" | "scheduler" | "system"
}

task.interrupted v1

Persistence: durable.

Domain update: update tasks.status = interrupted.

interface TaskInterruptedPayload {
  task_id: string
  reason: string
  resumable: boolean
  resume_ref?: string
}

3.5 Tool Events

tool.started v1

Persistence: durable.

Domain update: insert tool_runs row with status = running.

interface ToolStartedPayload {
  tool_run_id: string
  tool_name: string
  task_id?: string
  agent_id?: string
  origin_message_id?: string
  input_json: unknown
  metadata?: JsonObject
}

tool.completed v1

Persistence: durable.

Domain update: update tool_runs.status = ok, set output/artifacts/evidence/duration.

interface ToolCompletedPayload {
  tool_run_id: string
  output_json?: unknown
  duration_ms?: number
  artifact_ids?: string[]
  evidence_refs?: EvidenceLink[]
  metadata?: JsonObject
}

tool.failed v1

Persistence: durable.

Domain update: update tool_runs.status = error.

interface ToolFailedPayload extends FailurePayload {
  tool_run_id: string
  duration_ms?: number
}

tool.cancelled v1

Persistence: durable.

Domain update: update tool_runs.status = cancelled.

interface ToolCancelledPayload {
  tool_run_id: string
  reason: string
}

3.6 Command Events

command.started v1

Persistence: durable.

Domain update: insert command_runs row.

interface CommandStartedPayload {
  command_run_id: string
  task_id?: string
  agent_id?: string
  origin_message_id?: string
  tool_run_id?: string
  command: string
  cwd: string
  metadata?: JsonObject
}

command.completed v1

Persistence: durable.

Domain update: update command_runs.exit_code, artifact refs, diagnostics, duration.

interface CommandCompletedPayload {
  command_run_id: string
  exit_code: number
  duration_ms?: number
  stdout_artifact_id?: string
  stderr_artifact_id?: string
  combined_artifact_id?: string
  diagnostic_ids?: string[]
  parsed_diagnostics_json?: unknown
  metadata?: JsonObject
}

command.failed v1

Persistence: durable.

Domain update: update command_runs.exit_code when available and create failure artifacts.

interface CommandFailedPayload extends FailurePayload {
  command_run_id: string
  exit_code?: number
  duration_ms?: number
  stdout_artifact_id?: string
  stderr_artifact_id?: string
  combined_artifact_id?: string
}

3.7 Artifact, Diagnostic, and Evidence Events

artifact.created v1

Persistence: durable.

Domain update: insert artifacts row after file has been written temp → atomic rename.

interface ArtifactCreatedPayload {
  artifact_id: string
  type: string
  uri: string
  path: string
  original_name?: string
  size_bytes?: number
  sha256?: string
  task_id?: string
  agent_id?: string
  tool_run_id?: string
  command_run_id?: string
  associated_entity_type?: string
  associated_entity_id?: string
  metadata?: JsonObject
}

diagnostic.created v1

Persistence: durable.

Domain update: insert diagnostics row.

interface DiagnosticCreatedPayload {
  diagnostic_id: string
  task_id?: string
  agent_id?: string
  command_run_id?: string
  artifact_id?: string
  language?: string
  toolchain?: string
  severity?: string
  file?: string
  line?: number
  column?: number
  code?: string
  message: string
  semantic_signature: string
  metadata?: JsonObject
}

evidence.created v1

Persistence: durable.

Domain update: insert evidence_refs row.

interface EvidenceCreatedPayload {
  evidence_ref_id: string
  kind: string
  ref: string
  location_json?: unknown
  claim: string
  task_id?: string
  agent_id?: string
  tool_run_id?: string
  command_run_id?: string
  artifact_id?: string
  diagnostic_id?: string
  message_id?: string
}

3.8 Context and Summary Events

context.compaction.requested v1

Persistence: durable.

Domain update: insert compaction task if Scheduler accepts it.

interface ContextCompactionRequestedPayload {
  reason: "token_budget" | "user_request" | "session_checkpoint" | "manual"
  range_start_message_id?: string
  range_end_message_id?: string
  target_budget_tokens?: number
}

context.compaction.started v1

Persistence: durable.

Domain update: mark compaction task running.

interface ContextCompactionStartedPayload {
  task_id: string
  agent_id: string
  range_start_message_id?: string
  range_end_message_id?: string
}

context.compaction.completed v1

Persistence: durable.

Domain update: mark compaction task complete. The summaries row is inserted by summary.created, not by this event.

interface ContextCompactionCompletedPayload {
  task_id?: string
  agent_id?: string
  summary_id: string
  range_start_message_id?: string
  range_end_message_id?: string
  token_estimate_before?: number
  token_estimate_after?: number
}

context.compaction.failed v1

Persistence: durable.

Domain update: mark compaction task failed or blocked.

interface ContextCompactionFailedPayload extends FailurePayload {
  task_id?: string
  agent_id?: string
  range_start_message_id?: string
  range_end_message_id?: string
}

summary.created v1

Persistence: durable.

Domain update: insert summaries row.

interface SummaryCreatedPayload {
  summary_id: string
  type: string
  range_start_message_id?: string
  range_end_message_id?: string
  content_json: unknown
  metadata?: JsonObject
}

3.9 Permission Events

permission.decision.recorded v1

Persistence: durable.

Domain update: append permission decision record to events; optional future projection table.

interface PermissionDecisionRecordedPayload {
  decision_id: string
  subject: "tool" | "command" | "path" | "network" | "dependency" | "migration"
  action: "allow" | "deny" | "ask_user" | "block" | "refuse" | "announce_then_run"
  grant_scope: "none" | "once" | "session" | "project" | "global"
  reason?: string
  risk_level: "low" | "medium" | "high" | "critical"
  decided_by: "user" | "policy" | "high_permission_mode" | "system"
  scope_json?: unknown
  expires_at?: ISOTimeString
}

permission.prompt.requested v1

Persistence: durable.

Domain update: append event; optional UI projection.

interface PermissionPromptRequestedPayload {
  prompt_id: string
  subject: string
  risk_level: "low" | "medium" | "high" | "critical"
  reason: string
  options: string[]
  default_option?: string
  request_ref?: EntityRef
}

permission.prompt.resolved v1

Persistence: durable.

Domain update: append event and create permission.decision.recorded if applicable.

interface PermissionPromptResolvedPayload {
  prompt_id: string
  selected_option: string
  decision_id?: string
  resolved_by: "user" | "timeout" | "system"
}

3.10 Doctor and Dependency Events

doctor.run.started v1

Persistence: durable.

Domain update: append event and create optional report artifact later.

interface DoctorRunStartedPayload {
  run_id: string
  mode: "read_only" | "fix"
  trigger: "startup" | "manual" | "dependency_request" | "release_gate"
}

doctor.issue.found v1

Persistence: durable.

Domain update: append event and optional diagnostic/report artifact.

interface DoctorIssueFoundPayload {
  run_id: string
  issue_id: string
  severity: "info" | "warning" | "error" | "blocking"
  capability?: string
  dependency?: string
  message: string
  fix_available: boolean
  fix_requires_confirmation?: boolean
}

doctor.fix.started v1

Persistence: durable.

Domain update: append event and optional command/tool run rows.

interface DoctorFixStartedPayload {
  run_id: string
  issue_id: string
  fix_id: string
  strategy: string
}

doctor.fix.completed v1

Persistence: durable.

Domain update: append event and update report artifact.

interface DoctorFixCompletedPayload {
  run_id: string
  issue_id: string
  fix_id: string
  evidence_refs?: EvidenceLink[]
}

doctor.fix.failed v1

Persistence: durable.

Domain update: append event and update report artifact.

interface DoctorFixFailedPayload extends FailurePayload {
  run_id: string
  issue_id: string
  fix_id: string
}

doctor.run.completed v1

Persistence: durable.

Domain update: append event and create doctor report artifact.

interface DoctorRunCompletedPayload {
  run_id: string
  status: "passed" | "issues_found" | "fixed" | "failed"
  issue_count: number
  blocking_issue_count: number
  report_artifact_id?: string
}

3.11 Requirement and Architecture Events

requirement.changed v1

Persistence: durable.

Domain update: append event and mark impacted tasks when Scheduler applies it.

interface RequirementChangedPayload {
  change_id: string
  origin_message_id: string
  summary: string
  change_type: "clarification" | "scope_change" | "architecture_change" | "constraint_change" | "cancellation"
  affected_refs?: EntityRef[]
}

architecture.plan.updated v1

Persistence: durable.

Domain update: append event and artifact/plan references.

interface ArchitecturePlanUpdatedPayload {
  plan_ref: string
  update_kind: "created" | "revised" | "superseded"
  summary: string
  affected_task_ids?: string[]
  adr_refs?: string[]
  c4_refs?: string[]
}

architecture.impact.completed v1

Persistence: durable.

Domain update: append event; Scheduler consumes to continue/replan/block.

interface ArchitectureImpactCompletedPayload {
  assessment_id: string
  requirement_change_id?: string
  impact_level: "implementation" | "interface" | "architecture" | "product"
  decision: "silent_continue" | "requires_user_confirmation" | "requires_replan" | "reject_or_escalate"
  summary: string
  affected_task_ids?: string[]
  evidence_refs?: EvidenceLink[]
}

3.12 Workspace and Merge Events

workspace.created v1

Persistence: durable.

Domain update: insert workspaces row.

interface WorkspaceCreatedPayload {
  workspace_id: string
  task_id?: string
  agent_id?: string
  path: string
  strategy: "main" | "worktree" | "isolated_copy"
  base_ref?: string
  branch_name?: string
}

workspace.merge.started v1

Persistence: durable.

Domain update: append event and mark workspace merge in progress in metadata.

interface WorkspaceMergeStartedPayload {
  workspace_id: string
  task_id?: string
  strategy: "fast_forward" | "patch_apply" | "manual_merge" | "copy_back"
  target_ref?: string
}

workspace.merge.completed v1

Persistence: durable.

Domain update: update workspaces.status = merged, set merged_at.

interface WorkspaceMergeCompletedPayload {
  workspace_id: string
  task_id?: string
  merged_ref?: string
  diff_artifact_id?: string
}

workspace.merge.conflicted v1

Persistence: durable.

Domain update: update workspaces.status = conflicted.

interface WorkspaceMergeConflictedPayload {
  workspace_id: string
  task_id?: string
  conflict_files: string[]
  conflict_artifact_id?: string
  suggested_resolution?: "retry_serial" | "debugger" | "architecture_review" | "user_decision"
}

workspace.cleaned v1

Persistence: durable.

Domain update: update workspaces.status = cleaned or append cleanup metadata.

interface WorkspaceCleanedPayload {
  workspace_id: string
  reason: "merged" | "cancelled" | "abandoned" | "manual"
}

3.13 Memory and Debug Knowledge Events

memory.candidate.created v1

Persistence: durable.

Domain update: append event; future memory table may project it.

interface MemoryCandidateCreatedPayload {
  candidate_id: string
  source_ref: EntityRef
  memory_type: "project_rule" | "toolchain_rule" | "skill_update" | "debug_experience"
  summary: string
  evidence_refs?: EvidenceLink[]
}

memory.promoted v1

Persistence: durable.

Domain update: append event and update .air/shared/rules, skill, or learned-memory.db through the owning subsystem.

interface MemoryPromotedPayload {
  candidate_id: string
  target_ref: string
  promoted_by: "user" | "curator" | "system"
  summary: string
}

memory.archived v1

Persistence: durable.

Domain update: append event and mark memory inactive in owning subsystem.

interface MemoryArchivedPayload {
  candidate_id?: string
  memory_ref?: string
  reason: string
}

debug.record.created v1

Persistence: durable.

Domain update: insert/update debug-records.db and append event in session DB.

interface DebugRecordCreatedPayload {
  debug_record_id: string
  task_id?: string
  failure_signature: string
  summary: string
  evidence_refs?: EvidenceLink[]
  verification_refs?: EvidenceLink[]
}

4. Ephemeral Event Registry

Ephemeral events are for live rendering, streaming, and heartbeat. They are not required for crash recovery.

agent.heartbeat v1

Producer: child agent process.

Consumer: Scheduler, ProjectionStore.

interface AgentHeartbeatPayload {
  agent_id: string
  task_id?: string
  status: "starting" | "running"
  progress_text?: string
  current_step?: string
  resource_snapshot?: {
    pid?: number
    cpu_percent?: number
    memory_bytes?: number
  }
}

task.progress v1

Producer: Scheduler or worker agent.

Consumer: ProjectionStore, Main Agent summaries.

interface TaskProgressPayload {
  task_id: string
  agent_id?: string
  phase?: string
  progress_text: string
  percent?: number
}

assistant.message.delta v1

Producer: Main Agent / provider adapter.

Consumer: ProjectionStore; message_drafts may be periodically flushed by draft writer.

interface AssistantMessageDeltaPayload {
  message_id: string
  delta: unknown
  sequence: number
}

tool.progress v1

Producer: streaming tool.

Consumer: ProjectionStore, command/tool monitor.

interface ToolProgressPayload {
  tool_run_id: string
  message: string
  progress_json?: unknown
}

command.stdout.delta v1 and command.stderr.delta v1

Producer: shell/command runner.

Consumer: ProjectionStore, artifact writer.

interface CommandStreamDeltaPayload {
  command_run_id: string
  chunk: string
  sequence: number
  truncated?: boolean
}

hud.frame.rendered v1

Producer: TUI/HUD.

Consumer: local performance/debug logging only.

interface HudFrameRenderedPayload {
  frame_id: string
  duration_ms?: number
  dropped_frame_count?: number
}

5. Producer and Consumer Boundaries

Producer responsibilities:

  1. Emit valid RuntimeEvent envelopes.
  2. Provide IDs generated by the subsystem that owns the entity.
  3. Append route segment when forwarding.
  4. Never update SQLite domain tables directly unless the producer is the owning store/service.

EventStore responsibilities:

  1. Validate event version and payload schema.
  2. Apply durable events and domain table updates transactionally.
  3. Derive query columns such as task_id, agent_id, tool_run_id, command_run_id, route_text.
  4. Publish committed durable events to EventBus after commit.
  5. Reject unknown durable event types unless explicitly allowed by development-mode config.

ProjectionStore responsibilities:

  1. Hydrate from DB at startup/resume.
  2. Apply live durable and ephemeral events.
  3. Coalesce noisy deltas for TUI/HUD.
  4. Never become the source of truth for scheduling or recovery.

Scheduler responsibilities:

  1. Consume task/agent/workspace/architecture events.
  2. Drive retries, dependency unblocking, merge waves, and escalation.
  3. Treat missing heartbeat as agent.lost and persist that durable event.

6. V1 Event Type Index

Durable:

session.created
session.archived
session.deleted
user.message.created
assistant.message.started
assistant.message.created
assistant.message.failed
agent.started
agent.completed
agent.failed
agent.lost
agent.cancelled
task.created
task.started
task.completed
task.blocked
task.failed
task.cancelled
task.interrupted
tool.started
tool.completed
tool.failed
tool.cancelled
command.started
command.completed
command.failed
artifact.created
diagnostic.created
evidence.created
context.compaction.requested
context.compaction.started
context.compaction.completed
context.compaction.failed
summary.created
permission.decision.recorded
permission.prompt.requested
permission.prompt.resolved
doctor.run.started
doctor.issue.found
doctor.fix.started
doctor.fix.completed
doctor.fix.failed
doctor.run.completed
requirement.changed
architecture.plan.updated
architecture.impact.completed
workspace.created
workspace.merge.started
workspace.merge.completed
workspace.merge.conflicted
workspace.cleaned
memory.candidate.created
memory.promoted
memory.archived
debug.record.created

Ephemeral:

agent.heartbeat
task.progress
assistant.message.delta
tool.progress
command.stdout.delta
command.stderr.delta
hud.frame.rendered

7. V1.0.0 Alpha Cut Line

The V1.0.0 Alpha skeleton must implement schema validation and EventStore handling for all durable event names in the V1 index, even if some subsystem producers are initially stubs.

The first implementation may keep payload schemas in TypeScript with runtime validation generated from the same definitions. Unknown plugin/capability events must use a namespaced prefix and must not be durable until registered.