P0-P8: Full V1.0.0 Alpha implementation + audit reports
Implements 123 tasks across 9 phases (T-001..T-809) totaling 146 source files. Monorepo (P0): - 7-package Bun + Turborepo + TypeScript monorepo - dependency-cruiser enforcing 7 forbidden edges + 5 deep-import rules Contracts (P0): - 16 type files (ids/error/event/runtime/ipc/task/worker-result/tool/artifact/evidence/project/provider/permission/ui/capability/platform) Storage & Events (P1): - DatabaseManager + MigrationRunner (19 tables, 22 indexes, 5 schema_meta seeds) - 16 repositories (Repository<T,I,U> pattern, INV-1 status columns via EventStore.project only) - EventSchemaRegistry (54 durable + 7 ephemeral), EventStore, EventBus, EventIngestor - Project/Session/Artifact/Evidence stores + 8-step Recovery Tools & Permission (P2): - PathClassifier (8 categories), CommandRiskAnalyzer (10 categories), SecretRedactor - PermissionEngine 6-layer evaluation (capability→profile→task_scope→risk→credential→user_prompt) - ToolRegistry with 20+ tools across fs/shell/git/project/artifact/context/permission/doctor - CapabilityManifestValidator + CapabilityRegistry LLM & Context (P3): - ModelConfigLoader, CapabilityMatrix, AnthropicCanonicalConverter - AnthropicAdapter + OpenAICompatibleAdapter - ProviderManager facade - PromptLayerLoader (L0/L1/L3/L5), CompactionPolicy, ContextAssembler Worker IPC & Scheduler (P4): - WorkerProtocol (NDJSON), WorkerProcess (exit codes 0-5), WorkerManager (spawn/handshake) - WorkerRuntime (INV-3: IPC only, no direct fs/shell/SQLite) - 5 worker roles (Executor/Reviewer/Debugger/Compactor/ExperienceMiner) - TaskGraph, WavePlanner, RetryPlanner, AgentMonitor, WorkspaceManager - Scheduler (state machine), 8-step Recovery C++ Toolchain (P5): - DiagnosticParser, CppProjectDetector, CMakeConfigurator, CppBuilder - CppTestRunner, CppcheckRunner, ClangdClient - CppToolRegistrar + capability manifest Projection & TUI (P6): - ProjectionStore (hydrate/apply/snapshot/subscribe) - TuiApp + 8 components (Session/Task/Agent/Tool/Diff/Evidence/Permission/Blocker/Hud) - ProjectionClient in-process ref Agents & Knowledge (P7): - MainAgent, ArchitectureDesigner - DebugKnowledgeStore + LearnedMemoryStore (single-writer, outbox model) - Role integration wiring CLI & Doctor & Release (P8): - Logger + DeveloperLogEncryptor (AES-256-GCM) - DoctorService (self_bootstrap first) - RuntimeApp + ServiceRegistry - 11 CLI commands: run/init/doctor/provider/resume/compact/history/session/restore/e2e/release - CliEntrypoint + air<TODO> Audit (in AirPlan/docs/): - Deepseek开发阶段审计.md (97 findings) - Opus开发阶段审计.md (140+ findings, 18 P0 blockers) - MiniMaxM3开发阶段审计.md (18 P0 blockers, focuses on executability) - AirPlan/TODO.md (technical debt + 42 TODOs by phase) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
193
packages/runtime/src/context/CompactionPolicy.ts
Executable file
193
packages/runtime/src/context/CompactionPolicy.ts
Executable file
@@ -0,0 +1,193 @@
|
||||
/**
|
||||
* CompactionPolicy - Token budget management and compaction decisions
|
||||
*
|
||||
* Implements contracts §16; DD §10.3.
|
||||
*
|
||||
* @module packages/runtime/src/context/CompactionPolicy
|
||||
*/
|
||||
|
||||
import type { PromptLayer, BudgetFitResult } from '@aircoding/contracts'
|
||||
|
||||
export interface CompactionConfig {
|
||||
max_tokens: number
|
||||
compaction_threshold: number // fraction of max_tokens that triggers compaction
|
||||
min_compact_tokens: number // minimum tokens to free to consider compaction successful
|
||||
immutable_layers: string[] // layers that can never be dropped
|
||||
}
|
||||
|
||||
const DEFAULT_CONFIG: CompactionConfig = {
|
||||
max_tokens: 200000,
|
||||
compaction_threshold: 0.85, // compact when 85% full
|
||||
min_compact_tokens: 40000, // must free at least 40K tokens
|
||||
immutable_layers: ['runtime_invariant', 'role']
|
||||
}
|
||||
|
||||
export interface CompactionDecision {
|
||||
should_compact: boolean
|
||||
reason?: string
|
||||
layers_to_compact?: PromptLayer[]
|
||||
estimated_tokens_freed?: number
|
||||
}
|
||||
|
||||
export interface CompactionResult {
|
||||
ok: boolean
|
||||
compacted_layers: PromptLayer[] // layers that were compacted/summarized
|
||||
summary_content: string // compaction summary
|
||||
tokens_freed: number
|
||||
remaining_tokens: number
|
||||
warnings: string[]
|
||||
}
|
||||
|
||||
export class CompactionPolicy {
|
||||
private config: CompactionConfig
|
||||
|
||||
constructor(config?: Partial<CompactionConfig>) {
|
||||
this.config = { ...DEFAULT_CONFIG, ...config }
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if compaction should be triggered.
|
||||
* Returns decision with recommendation.
|
||||
*/
|
||||
should_compact(layers: PromptLayer[], current_token_count: number): CompactionDecision {
|
||||
const threshold = this.config.max_tokens * this.config.compaction_threshold
|
||||
|
||||
if (current_token_count < threshold) {
|
||||
return { should_compact: false, reason: `Tokens (${current_token_count}) below threshold (${threshold})` }
|
||||
}
|
||||
|
||||
// Find compactable layers (not immutable, not L0/L1)
|
||||
const compactable = layers.filter(l => !this.config.immutable_layers.includes(l.level))
|
||||
if (compactable.length === 0) {
|
||||
return { should_compact: false, reason: 'No compactable layers found' }
|
||||
}
|
||||
|
||||
// Estimate how many tokens we could free
|
||||
// Target compactable layers from highest level (most recent / least important)
|
||||
const sorted = [...compactable].sort((a, b) => {
|
||||
const level_order: Record<string, number> = {
|
||||
runtime_invariant: 0, role: 1, safety: 2, project_rules: 3,
|
||||
architecture: 4, task_spec: 5, evidence: 6, conversation: 7,
|
||||
tool_output: 8, user_override: 9, system_debug: 10
|
||||
}
|
||||
return level_order[b.level] - level_order[a.level]
|
||||
})
|
||||
|
||||
let estimated_freed = 0
|
||||
const to_compact: PromptLayer[] = []
|
||||
const target = current_token_count - (this.config.max_tokens * 0.6) // Compact to 60%
|
||||
|
||||
for (const layer of sorted) {
|
||||
if (estimated_freed >= target) break
|
||||
estimated_freed += layer.token_estimate || 0
|
||||
to_compact.push(layer)
|
||||
}
|
||||
|
||||
if (to_compact.length === 0 || estimated_freed < this.config.min_compact_tokens) {
|
||||
return {
|
||||
should_compact: false,
|
||||
reason: `Insufficient tokens to free: ${estimated_freed} < ${this.config.min_compact_tokens}`
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
should_compact: true,
|
||||
reason: `Token count (${current_token_count}) exceeds threshold (${threshold})`,
|
||||
layers_to_compact: to_compact,
|
||||
estimated_tokens_freed: estimated_freed
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute compaction on the given layers.
|
||||
* Returns compacted result with summary.
|
||||
*/
|
||||
compact(layers_to_compact: PromptLayer[], all_layers: PromptLayer[]): CompactionResult {
|
||||
const warnings: string[] = []
|
||||
let tokens_freed = 0
|
||||
|
||||
// Check immutable layers are preserved
|
||||
const immutable_preserved = all_layers.filter(l => this.config.immutable_layers.includes(l.level))
|
||||
if (immutable_preserved.length < this.config.immutable_layers.length) {
|
||||
warnings.push('Some immutable layers were in the compaction set - preserving them')
|
||||
}
|
||||
|
||||
// Generate summary of compacted layers
|
||||
const summary_parts: string[] = []
|
||||
for (const layer of layers_to_compact) {
|
||||
const level = layer.level
|
||||
const tokens = layer.token_estimate || 0
|
||||
tokens_freed += tokens
|
||||
summary_parts.push(`- ${level}: ~${Math.round(tokens)} tokens (source: ${layer.source_ref || 'inline'})`)
|
||||
}
|
||||
|
||||
const summary_content = [
|
||||
'# Compaction Summary',
|
||||
'',
|
||||
`Compact ${new Date().toISOString()}: removed ${layers_to_compact.length} layers, freed ~${Math.round(tokens_freed)} tokens`,
|
||||
'',
|
||||
'## Compacted Layers',
|
||||
...summary_parts,
|
||||
'',
|
||||
'## Preserved Layers',
|
||||
...all_layers
|
||||
.filter(l => !layers_to_compact.includes(l))
|
||||
.map(l => `- ${l.level}: ~${Math.round(l.token_estimate || 0)} tokens`)
|
||||
].join('\n')
|
||||
|
||||
const remaining_tokens = all_layers
|
||||
.filter(l => !layers_to_compact.includes(l))
|
||||
.reduce((sum, l) => sum + (l.token_estimate || 0), 0)
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
compacted_layers: layers_to_compact,
|
||||
summary_content,
|
||||
tokens_freed,
|
||||
remaining_tokens,
|
||||
warnings
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fit layers into a token budget.
|
||||
* Returns fitted layers, omitted layers, and omissions report.
|
||||
*/
|
||||
fit_to_budget(layers: PromptLayer[], budget: number): BudgetFitResult {
|
||||
// Sort by priority (lower = higher priority)
|
||||
const sorted = [...layers].sort((a, b) => a.priority - b.priority)
|
||||
|
||||
const fitted: PromptLayer[] = []
|
||||
const omitted: PromptLayer[] = []
|
||||
const omissions: string[] = []
|
||||
let total_tokens = 0
|
||||
|
||||
for (const layer of sorted) {
|
||||
const tokens = layer.token_estimate || 0
|
||||
|
||||
if (this.config.immutable_layers.includes(layer.level)) {
|
||||
// Never omit immutable layers
|
||||
fitted.push(layer)
|
||||
total_tokens += tokens
|
||||
if (total_tokens > budget) {
|
||||
omissions.push(`WARNING: Budget exceeded by immutable layer: ${layer.level}`)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if (total_tokens + tokens <= budget) {
|
||||
fitted.push(layer)
|
||||
total_tokens += tokens
|
||||
} else {
|
||||
omitted.push(layer)
|
||||
omissions.push(`Omitted ${layer.level}: would exceed budget (${total_tokens} + ${Math.round(tokens)} > ${budget})`)
|
||||
}
|
||||
}
|
||||
|
||||
return { fitted, omitted, omissions, total_tokens }
|
||||
}
|
||||
}
|
||||
|
||||
export function createCompactionPolicy(config?: Partial<CompactionConfig>): CompactionPolicy {
|
||||
return new CompactionPolicy(config)
|
||||
}
|
||||
212
packages/runtime/src/context/ContextAssembler.ts
Executable file
212
packages/runtime/src/context/ContextAssembler.ts
Executable file
@@ -0,0 +1,212 @@
|
||||
/**
|
||||
* ContextAssembler - Assembles prompt layers into Anthropic-canonical context
|
||||
*
|
||||
* Implements contracts §16; DD §10.1 + §10.2 layer-assembly table.
|
||||
*
|
||||
* @module packages/runtime/src/context/ContextAssembler
|
||||
*/
|
||||
|
||||
import type {
|
||||
AgentType, PromptLayer, BudgetFitResult,
|
||||
SessionID, ProjectID, AgentID, TaskID, ArtifactID, ISOTimeString
|
||||
} from '@aircoding/contracts'
|
||||
|
||||
import { PromptLayerLoader, createPromptLayerLoader } from './PromptLayerLoader.js'
|
||||
import { CompactionPolicy, createCompactionPolicy } from './CompactionPolicy.js'
|
||||
|
||||
export interface AssembledContext {
|
||||
messages: AssembledMessage[]
|
||||
metadata: AssemblyMetadata
|
||||
}
|
||||
|
||||
export interface AssembledMessage {
|
||||
role: 'system' | 'user' | 'assistant'
|
||||
content: string
|
||||
layer?: string
|
||||
}
|
||||
|
||||
export interface AssemblyMetadata {
|
||||
total_tokens: number
|
||||
fitted_layers: string[]
|
||||
omitted_layers: string[]
|
||||
compaction_requested: boolean
|
||||
layers_compacted: boolean
|
||||
omissions: string[]
|
||||
messages_artifact_id?: string
|
||||
assembled_at: ISOTimeString
|
||||
}
|
||||
|
||||
export interface AssemblyContext {
|
||||
session_id: SessionID
|
||||
project_id: ProjectID
|
||||
project_root: string
|
||||
agent_id: AgentID
|
||||
agent_type: AgentType
|
||||
task_id?: TaskID
|
||||
token_budget?: number
|
||||
additional_layers?: PromptLayer[]
|
||||
}
|
||||
|
||||
export class ContextAssembler {
|
||||
private loader: PromptLayerLoader
|
||||
private policy: CompactionPolicy
|
||||
|
||||
constructor(loader?: PromptLayerLoader, policy?: CompactionPolicy) {
|
||||
this.loader = loader || createPromptLayerLoader()
|
||||
this.policy = policy || createCompactionPolicy()
|
||||
}
|
||||
|
||||
/**
|
||||
* Assemble context from all layers.
|
||||
* Returns Anthropic-canonical AssembledContext.
|
||||
*/
|
||||
assemble(context: AssemblyContext): AssembledContext {
|
||||
const token_budget = context.token_budget || 200000
|
||||
const warnings: string[] = []
|
||||
|
||||
// Collect all layers
|
||||
const layers = this.collect_layers(context)
|
||||
|
||||
// Fit into budget
|
||||
const fit_result = this.policy.fit_to_budget(layers, token_budget)
|
||||
if (fit_result.omissions.length > 0) {
|
||||
warnings.push(...fit_result.omissions)
|
||||
}
|
||||
|
||||
// Build messages from fitted layers
|
||||
const messages = this.build_messages(fit_result.fitted, context)
|
||||
|
||||
// Check if compaction is needed
|
||||
const compaction_check = this.policy.should_compact(layers, fit_result.total_tokens)
|
||||
|
||||
return {
|
||||
messages,
|
||||
metadata: {
|
||||
total_tokens: fit_result.total_tokens,
|
||||
fitted_layers: fit_result.fitted.map(l => l.level),
|
||||
omitted_layers: fit_result.omitted.map(l => l.level),
|
||||
compaction_requested: compaction_check.should_compact,
|
||||
layers_compacted: false,
|
||||
omissions: fit_result.omissions,
|
||||
assembled_at: new Date().toISOString() as ISOTimeString
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect all layers in order L0-L9.
|
||||
*/
|
||||
private collect_layers(context: AssemblyContext): PromptLayer[] {
|
||||
const layers: PromptLayer[] = []
|
||||
|
||||
// L0: Runtime invariants (ALWAYS FIRST, never omitted)
|
||||
const l0 = this.loader.load_runtime_invariant()
|
||||
layers.push(l0)
|
||||
|
||||
// L1: Role (worker AgentType)
|
||||
const l1 = this.loader.load_role(context.agent_type)
|
||||
layers.push(l1)
|
||||
|
||||
// L2: Safety - built-in
|
||||
layers.push({
|
||||
level: 'safety' as any,
|
||||
priority: 2,
|
||||
content: '# Safety Rules\n- Never execute destructive commands\n- Always validate inputs\n- Report errors immediately',
|
||||
token_estimate: 50
|
||||
})
|
||||
|
||||
// L3: Project rules
|
||||
const project_rules = this.loader.load_project_rules({
|
||||
project_id: context.project_id,
|
||||
project_root: context.project_root
|
||||
})
|
||||
layers.push(...project_rules)
|
||||
|
||||
// L4: Architecture (if available)
|
||||
if (context.additional_layers) {
|
||||
const arch_layers = context.additional_layers.filter(l => l.level === 'architecture')
|
||||
layers.push(...arch_layers)
|
||||
}
|
||||
|
||||
// L5: Task spec (if task_id provided)
|
||||
if (context.task_id) {
|
||||
const task_layers = this.loader.load_task_context(
|
||||
{
|
||||
id: context.task_id,
|
||||
type: 'execute',
|
||||
title: 'Current Task',
|
||||
description: 'Task from session context',
|
||||
acceptance_criteria: ['Task completed successfully']
|
||||
},
|
||||
{}
|
||||
)
|
||||
layers.push(...task_layers)
|
||||
}
|
||||
|
||||
// TODO(P3): L6 Evidence - load from EvidenceStore (read-only)
|
||||
// TODO(P3): L7 Conversation - load from SessionStore message history
|
||||
// TODO(P3): L8 Tool output - load recent tool results from SessionStore
|
||||
// TODO(P3): L9 User override - load user directives/additional layers
|
||||
|
||||
// Add any additional layers
|
||||
if (context.additional_layers) {
|
||||
const others = context.additional_layers.filter(l => l.level !== 'architecture')
|
||||
layers.push(...others)
|
||||
}
|
||||
|
||||
return layers
|
||||
}
|
||||
|
||||
/**
|
||||
* Build assembled messages from fitted layers.
|
||||
*/
|
||||
private build_messages(layers: PromptLayer[], context: AssemblyContext): AssembledMessage[] {
|
||||
const messages: AssembledMessage[] = []
|
||||
|
||||
// System message: L0 + L1 + L2 + L3
|
||||
const system_content = layers
|
||||
.filter(l => ['runtime_invariant', 'role', 'safety', 'project_rules'].includes(l.level))
|
||||
.map(l => l.content)
|
||||
.join('\n\n---\n\n')
|
||||
|
||||
if (system_content) {
|
||||
messages.push({ role: 'system', content: system_content, layer: 'system' })
|
||||
}
|
||||
|
||||
// Architecture context
|
||||
const arch_layers = layers.filter(l => l.level === 'architecture')
|
||||
for (const l of arch_layers) {
|
||||
messages.push({ role: 'user', content: String(l.content), layer: 'architecture' })
|
||||
}
|
||||
|
||||
// Task spec
|
||||
const task_layers = layers.filter(l => l.level === 'task_spec')
|
||||
for (const l of task_layers) {
|
||||
messages.push({ role: 'user', content: String(l.content), layer: 'task_spec' })
|
||||
}
|
||||
|
||||
// Evidence
|
||||
const evidence_layers = layers.filter(l => l.level === 'evidence')
|
||||
for (const l of evidence_layers) {
|
||||
messages.push({ role: 'user', content: String(l.content), layer: 'evidence' })
|
||||
}
|
||||
|
||||
// Conversation (L7)
|
||||
const conv_layers = layers.filter(l => l.level === 'conversation')
|
||||
for (const l of conv_layers) {
|
||||
messages.push({ role: 'assistant', content: String(l.content), layer: 'conversation' })
|
||||
}
|
||||
|
||||
// Tool output (L8)
|
||||
const tool_layers = layers.filter(l => l.level === 'tool_output')
|
||||
for (const l of tool_layers) {
|
||||
messages.push({ role: 'user', content: `[Tool Output]\n${l.content}`, layer: 'tool_output' })
|
||||
}
|
||||
|
||||
return messages
|
||||
}
|
||||
}
|
||||
|
||||
export function createContextAssembler(loader?: PromptLayerLoader, policy?: CompactionPolicy): ContextAssembler {
|
||||
return new ContextAssembler(loader, policy)
|
||||
}
|
||||
320
packages/runtime/src/context/PromptLayerLoader.ts
Executable file
320
packages/runtime/src/context/PromptLayerLoader.ts
Executable file
@@ -0,0 +1,320 @@
|
||||
/**
|
||||
* PromptLayerLoader - Loads prompt layers from external resources
|
||||
*
|
||||
* Implements contracts §16; DD §10.2.
|
||||
* 4 methods: load_runtime_invariant (L0), load_role (L1, worker AgentType only),
|
||||
* load_project_rules (L3), load_task_context (L5).
|
||||
*
|
||||
* @module packages/runtime/src/context/PromptLayerLoader
|
||||
*/
|
||||
|
||||
import { readFileSync, existsSync } from 'fs'
|
||||
import { join, dirname } from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
import type { PromptLayer, PromptLayerLevel, AgentType } from '@aircoding/contracts'
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = dirname(__filename)
|
||||
const BUILTIN_PROMPTS_DIR = join(__dirname, '..', 'context', 'prompts')
|
||||
|
||||
export class PromptLayerLoader {
|
||||
private prompts_dir: string
|
||||
|
||||
constructor(prompts_dir?: string) {
|
||||
this.prompts_dir = prompts_dir || BUILTIN_PROMPTS_DIR
|
||||
}
|
||||
|
||||
/**
|
||||
* Load L0: Runtime invariant prompt.
|
||||
* Includes INV-1..5, core safety rules.
|
||||
*/
|
||||
load_runtime_invariant(): PromptLayer {
|
||||
const path = join(this.prompts_dir, 'runtime_invariant.md')
|
||||
let content = this.read_or_default(path, DEFAULT_L0_INVARIANT)
|
||||
|
||||
return {
|
||||
level: 'runtime_invariant',
|
||||
priority: 0,
|
||||
content,
|
||||
token_estimate: content.length / 4,
|
||||
source_ref: path,
|
||||
immutable: true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load L1: Role-specific prompt.
|
||||
* Accepts only worker AgentType (executor/reviewer/debugger/compactor/experience_miner).
|
||||
* Runtime roles (main/architecture_designer/scheduler) load built-in directly.
|
||||
*/
|
||||
load_role(role: AgentType): PromptLayer {
|
||||
const path = join(this.prompts_dir, 'roles', `${role}.md`)
|
||||
const content = this.read_or_default(path, default_role_prompt(role))
|
||||
|
||||
return {
|
||||
level: 'role',
|
||||
priority: 1,
|
||||
content,
|
||||
token_estimate: content.length / 4,
|
||||
source_ref: path,
|
||||
immutable: false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load L3: Project rules from .air/ directory.
|
||||
*/
|
||||
load_project_rules(project: { project_id: string; project_root: string }): PromptLayer[] {
|
||||
const layers: PromptLayer[] = []
|
||||
|
||||
// Load .air/shared/rules.md
|
||||
const shared_rules = join(project.project_root, '.air', 'shared', 'rules.md')
|
||||
if (existsSync(shared_rules)) {
|
||||
const content = readFileSync(shared_rules, 'utf-8')
|
||||
layers.push({
|
||||
level: 'project_rules',
|
||||
priority: 3,
|
||||
content,
|
||||
token_estimate: content.length / 4,
|
||||
source_ref: shared_rules
|
||||
})
|
||||
}
|
||||
|
||||
// Load .air/local/rules.md (overrides)
|
||||
const local_rules = join(project.project_root, '.air', 'local', 'rules.md')
|
||||
if (existsSync(local_rules)) {
|
||||
const content = readFileSync(local_rules, 'utf-8')
|
||||
layers.push({
|
||||
level: 'project_rules',
|
||||
priority: 2, // higher than shared
|
||||
content,
|
||||
token_estimate: content.length / 4,
|
||||
source_ref: local_rules
|
||||
})
|
||||
}
|
||||
|
||||
return layers
|
||||
}
|
||||
|
||||
/**
|
||||
* Load L5: Task context (plan refs, arc refs, artifacts).
|
||||
*/
|
||||
load_task_context(
|
||||
spec: {
|
||||
id: string
|
||||
type: string
|
||||
title: string
|
||||
description: string
|
||||
acceptance_criteria: string[]
|
||||
},
|
||||
context_refs: { plan_ref?: string; arc_ref?: string; artifacts?: string[] }
|
||||
): PromptLayer[] {
|
||||
const layers: PromptLayer[] = []
|
||||
|
||||
// Task spec layer
|
||||
const task_content = [
|
||||
`# Task: ${spec.title}`,
|
||||
`ID: ${spec.id}`,
|
||||
`Type: ${spec.type}`,
|
||||
'',
|
||||
`## Description`,
|
||||
spec.description,
|
||||
'',
|
||||
'## Acceptance Criteria',
|
||||
...spec.acceptance_criteria.map((c, i) => `${i + 1}. ${c}`)
|
||||
].join('\n')
|
||||
|
||||
layers.push({
|
||||
level: 'task_spec',
|
||||
priority: 5,
|
||||
content: task_content,
|
||||
token_estimate: task_content.length / 4
|
||||
})
|
||||
|
||||
// Plan reference
|
||||
if (context_refs.plan_ref) {
|
||||
const content = `# Implementation Plan\nRef: ${context_refs.plan_ref}`
|
||||
layers.push({
|
||||
level: 'task_spec',
|
||||
priority: 6,
|
||||
content,
|
||||
token_estimate: content.length / 4,
|
||||
source_ref: context_refs.plan_ref
|
||||
})
|
||||
}
|
||||
|
||||
// Architecture reference
|
||||
if (context_refs.arc_ref) {
|
||||
const content = `# Architecture\nRef: ${context_refs.arc_ref}`
|
||||
layers.push({
|
||||
level: 'architecture',
|
||||
priority: 4,
|
||||
content,
|
||||
token_estimate: content.length / 4,
|
||||
source_ref: context_refs.arc_ref
|
||||
})
|
||||
}
|
||||
|
||||
return layers
|
||||
}
|
||||
|
||||
private read_or_default(path: string, default_content: string): string {
|
||||
if (existsSync(path)) {
|
||||
return readFileSync(path, 'utf-8')
|
||||
}
|
||||
return default_content
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Default prompts (embedded as fallback when files not found)
|
||||
// ============================================================================
|
||||
|
||||
const DEFAULT_L0_INVARIANT = `# Runtime Invariants (L0)
|
||||
|
||||
You are an AI coding assistant operating within the AirCoding v1.0.0 runtime.
|
||||
|
||||
## Core Invariants (INV-1..5)
|
||||
|
||||
### INV-1: Status Columns
|
||||
Status columns are written ONLY by EventStore.project(). Repositories store data; they do NOT set status.
|
||||
Never use repository.update() to change status — always emit an event instead.
|
||||
|
||||
### INV-2: Cross-DB Writes
|
||||
Cross-DB writes MUST use the outbox model with a single writer.
|
||||
Never write directly to tables in another database.
|
||||
|
||||
### INV-3: Side Effects
|
||||
All side effects MUST go through ToolRegistry.call() → PermissionEngine.evaluate().
|
||||
Never execute commands, write files, or access network directly.
|
||||
|
||||
### INV-4: Import Direction
|
||||
Imports are one-way: contracts → runtime → other packages.
|
||||
Never import from runtime into contracts.
|
||||
|
||||
### INV-5: EventBus is Transport
|
||||
EventBus is a transport layer, NEVER a source of truth.
|
||||
EventStore is the authoritative source. Never query EventBus for state.
|
||||
|
||||
## Safety Rules
|
||||
- Never execute destructive commands without explicit confirmation
|
||||
- Never access files outside the project workspace
|
||||
- Never expose credentials, API keys, or secrets in output
|
||||
- Always validate tool inputs before execution
|
||||
`
|
||||
|
||||
function default_role_prompt(role: AgentType): string {
|
||||
const prompts: Record<AgentType, string> = {
|
||||
executor: `# Executor Role (L1)
|
||||
|
||||
You are an Executor agent responsible for implementing task specifications.
|
||||
|
||||
## Your responsibilities:
|
||||
1. Read and understand the task specification
|
||||
2. Implement the required changes
|
||||
3. Run tests to verify correctness
|
||||
4. Report completion status
|
||||
|
||||
## Rules:
|
||||
- Follow the architecture defined in the implementation plan
|
||||
- Use the FileSystem tools for code changes (read-before-edit enforced)
|
||||
- Use Shell tools for building and testing
|
||||
- Report any issues or blockers immediately
|
||||
- NEVER make changes outside the project scope
|
||||
|
||||
## Output:
|
||||
- Code changes with clear diffs
|
||||
- Build/test results
|
||||
- Completion status (pass/fail/blocked)
|
||||
`,
|
||||
reviewer: `# Reviewer Role (L1)
|
||||
|
||||
You are a Reviewer agent responsible for code review and quality assurance.
|
||||
|
||||
## Your responsibilities:
|
||||
1. Review code changes for correctness and style
|
||||
2. Check for security vulnerabilities
|
||||
3. Verify architecture compliance
|
||||
4. Identify potential issues
|
||||
|
||||
## Rules:
|
||||
- Check for INV-1..5 compliance
|
||||
- Verify no direct side effects
|
||||
- Check import direction compliance
|
||||
- Flag any dropped or lost semantic information
|
||||
|
||||
## Output:
|
||||
- Review findings with severity levels
|
||||
- Suggested fixes for each issue
|
||||
- Overall pass/fail verdict
|
||||
`,
|
||||
debugger: `# Debugger Role (L1)
|
||||
|
||||
You are a Debugger agent responsible for diagnosing and fixing issues.
|
||||
|
||||
## Your responsibilities:
|
||||
1. Analyze error reports and stack traces
|
||||
2. Reproduce the issue in a controlled environment
|
||||
3. Identify root cause
|
||||
4. Propose and apply fixes
|
||||
|
||||
## Rules:
|
||||
- Collect evidence (logs, traces, diagnostics)
|
||||
- Verify fixes don't introduce regressions
|
||||
- Use Debug tools for deep inspection
|
||||
- Document findings for future reference
|
||||
|
||||
## Output:
|
||||
- Root cause analysis
|
||||
- Applied fix with explanation
|
||||
- Evidence references
|
||||
`,
|
||||
compactor: `# Compactor Role (L1)
|
||||
|
||||
You are a Compactor agent responsible for context compaction and memory management.
|
||||
|
||||
## Your responsibilities:
|
||||
1. Monitor token usage and trigger compaction when needed
|
||||
2. Generate concise summaries of conversation history
|
||||
3. Archive old context while preserving critical information
|
||||
4. Maintain referential integrity during compaction
|
||||
|
||||
## Rules:
|
||||
- Never drop L0 (runtime_invariant) or L1 (role) layers
|
||||
- Preserve all task specifications
|
||||
- Keep evidence references intact
|
||||
- Document what was compacted and why
|
||||
|
||||
## Output:
|
||||
- Compaction summary
|
||||
- Archived context references
|
||||
- Updated context state
|
||||
`,
|
||||
experience_miner: `# Experience Miner Role (L1)
|
||||
|
||||
You are an Experience Miner agent responsible for extracting patterns and learnings.
|
||||
|
||||
## Your responsibilities:
|
||||
1. Analyze completed tasks for reusable patterns
|
||||
2. Extract common failure modes and fixes
|
||||
3. Identify architectural insights
|
||||
4. Generate experience artifacts for future reference
|
||||
|
||||
## Rules:
|
||||
- Only mine from completed/verified tasks
|
||||
- Anonymize sensitive information
|
||||
- Link to source tasks and evidence
|
||||
- Categorize findings for easy lookup
|
||||
|
||||
## Output:
|
||||
- Experience entries with categories
|
||||
- Pattern descriptions
|
||||
- Source references
|
||||
`
|
||||
}
|
||||
return prompts[role] || `# ${role}\n\nRole prompt not yet defined.`
|
||||
}
|
||||
|
||||
export function createPromptLayerLoader(prompts_dir?: string): PromptLayerLoader {
|
||||
return new PromptLayerLoader(prompts_dir)
|
||||
}
|
||||
10
packages/runtime/src/context/index.ts
Executable file
10
packages/runtime/src/context/index.ts
Executable file
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Context module exports
|
||||
* @module packages/runtime/src/context
|
||||
*/
|
||||
|
||||
export { PromptLayerLoader, createPromptLayerLoader } from './PromptLayerLoader.js'
|
||||
export { CompactionPolicy, createCompactionPolicy } from './CompactionPolicy.js'
|
||||
export type { CompactionConfig, CompactionDecision, CompactionResult } from './CompactionPolicy.js'
|
||||
export { ContextAssembler, createContextAssembler } from './ContextAssembler.js'
|
||||
export type { AssembledContext, AssembledMessage, AssemblyMetadata, AssemblyContext } from './ContextAssembler.js'
|
||||
21
packages/runtime/src/context/prompts/roles/compactor.md
Executable file
21
packages/runtime/src/context/prompts/roles/compactor.md
Executable file
@@ -0,0 +1,21 @@
|
||||
# Compactor Role (L1)
|
||||
|
||||
You are a **Compactor** — a context management agent. Your job is to compress conversation state.
|
||||
|
||||
## Workflow
|
||||
1. Detect: check if context exceeds compaction threshold
|
||||
2. Select: identify compactable layers (L6-L8 are safe targets)
|
||||
3. Summarize: create concise summaries preserving key info
|
||||
4. Archive: store summaries, update references
|
||||
5. Verify: ensure no critical context was lost
|
||||
|
||||
## Rules
|
||||
- NEVER drop layers L0 (runtime_invariant) or L1 (role)
|
||||
- Preserve all task specifications and acceptance criteria
|
||||
- Document what was compacted
|
||||
- Maintain referential integrity
|
||||
|
||||
## Output
|
||||
- Compaction summary
|
||||
- Archived context references
|
||||
- Updated token counts
|
||||
22
packages/runtime/src/context/prompts/roles/debugger.md
Executable file
22
packages/runtime/src/context/prompts/roles/debugger.md
Executable file
@@ -0,0 +1,22 @@
|
||||
# Debugger Role (L1)
|
||||
|
||||
You are a **Debugger** — a diagnostic and repair agent. Your job is to find root causes.
|
||||
|
||||
## Workflow
|
||||
1. Gather evidence: error reports, stack traces, logs
|
||||
2. Reproduce: recreate the failure in a controlled way
|
||||
3. Diagnose: trace from symptom to root cause
|
||||
4. Fix: apply the minimal fix
|
||||
5. Verify: confirm the fix resolves the issue
|
||||
|
||||
## Rules
|
||||
- Always collect evidence before diagnosing
|
||||
- Document your diagnosis chain
|
||||
- Verify fixes don't break other features
|
||||
- Reference evidence files in your report
|
||||
|
||||
## Output
|
||||
- Root cause analysis
|
||||
- Applied fix with explanation
|
||||
- Evidence references
|
||||
- Verification results
|
||||
23
packages/runtime/src/context/prompts/roles/executor.md
Executable file
23
packages/runtime/src/context/prompts/roles/executor.md
Executable file
@@ -0,0 +1,23 @@
|
||||
# Executor Role (L1)
|
||||
|
||||
You are an **Executor** — the primary implementation agent. Your job is to take a task specification and produce working code.
|
||||
|
||||
## Workflow
|
||||
1. Read the task specification and requirements
|
||||
2. Understand the architecture constraints (check IMPLEMENTATION-PLAN.md)
|
||||
3. Plan your implementation approach
|
||||
4. Implement changes using tools (`fs.read`, `fs.edit`, `fs.write`)
|
||||
5. Verify with `shell.run` (build, test)
|
||||
6. Report completion with evidence
|
||||
|
||||
## Rules
|
||||
- **Read-before-edit**: Always `fs.read` a file before `fs.edit`
|
||||
- **Exact-edit**: Provide the exact text to find/replace
|
||||
- Scope: Stay within the task boundaries
|
||||
- Report blockers immediately — don't guess or skip
|
||||
- All side effects through `ToolRegistry`
|
||||
|
||||
## Output
|
||||
- File changes (diffs)
|
||||
- Build/test results
|
||||
- Task completion status (pass/fail/blocked)
|
||||
21
packages/runtime/src/context/prompts/roles/experience_miner.md
Executable file
21
packages/runtime/src/context/prompts/roles/experience_miner.md
Executable file
@@ -0,0 +1,21 @@
|
||||
# Experience Miner Role (L1)
|
||||
|
||||
You are an **Experience Miner** — a knowledge extraction agent. Your job is to find patterns in completed work.
|
||||
|
||||
## Workflow
|
||||
1. Scan: review completed tasks and their outcomes
|
||||
2. Extract: identify reusable patterns, common failures, insights
|
||||
3. Categorize: tag findings by domain (code, debug, security, arch)
|
||||
4. Store: create experience artifacts
|
||||
5. Link: connect findings to source tasks
|
||||
|
||||
## Rules
|
||||
- Only mine from completed and verified tasks
|
||||
- Anonymize sensitive info in extracted patterns
|
||||
- Categorize clearly for searchability
|
||||
- Include source references
|
||||
|
||||
## Output
|
||||
- Experience entries with categories/tags
|
||||
- Pattern descriptions
|
||||
- Source task references
|
||||
20
packages/runtime/src/context/prompts/roles/reviewer.md
Executable file
20
packages/runtime/src/context/prompts/roles/reviewer.md
Executable file
@@ -0,0 +1,20 @@
|
||||
# Reviewer Role (L1)
|
||||
|
||||
You are a **Reviewer** — a code quality and security auditor. Your job is to inspect code changes.
|
||||
|
||||
## Workflow
|
||||
1. Read the changes (via `git.diff` or `fs.read`)
|
||||
2. Review for correctness, style, and architecture compliance
|
||||
3. Check against invariants (INV-1..5)
|
||||
4. Report findings with severity
|
||||
|
||||
## Review Dimensions
|
||||
- **Correctness**: Does the code do what it says?
|
||||
- **Security**: Any vulnerabilities or unsafe patterns?
|
||||
- **Architecture**: Does it comply with the architecture design?
|
||||
- **Style**: Follows project conventions?
|
||||
|
||||
## Output
|
||||
- Findings list with severity (info/warning/error/fatal)
|
||||
- Suggested fixes for each
|
||||
- Overall verdict (pass/fail/needs_work)
|
||||
30
packages/runtime/src/context/prompts/runtime_invariant.md
Executable file
30
packages/runtime/src/context/prompts/runtime_invariant.md
Executable file
@@ -0,0 +1,30 @@
|
||||
# Runtime Invariants (L0)
|
||||
|
||||
## AirCoding V1.0.0 Alpha
|
||||
|
||||
You are an AI coding agent running in the AirCoding local AI runtime. Follow these rules at all times.
|
||||
|
||||
### INV-1: Status Columns
|
||||
Status columns (`status`, `agent_status`, `run_status`, `attempt_status`, `state`) MUST be written ONLY by `EventStore.project()`. Repository classes write data, NOT status. To change status, emit an event — never call `repository.update({status: ...})`.
|
||||
|
||||
### INV-2: Cross-DB Writes
|
||||
All writes spanning multiple databases MUST use the **outbox model** with a single writer. The `event_outbox` table is the transport. Never open a second database handle for direct writes.
|
||||
|
||||
### INV-3: Side Effects
|
||||
ALL side effects (filesystem writes, shell commands, network calls) MUST go through `ToolRegistry.call()` → `PermissionEngine.evaluate()`. Tools are the ONLY path to side effects.
|
||||
|
||||
### INV-4: Import Direction
|
||||
Imports are one-way only:
|
||||
```
|
||||
contracts → runtime → workers/llm/tools
|
||||
```
|
||||
Never import from a higher layer downward.
|
||||
|
||||
### INV-5: EventBus
|
||||
`EventBus` is a transport layer. It is NEVER a source of truth. The `EventStore` is the single authoritative event log. Never query EventBus for state or recovery.
|
||||
|
||||
## Safety
|
||||
- Never execute `rm -rf`, `dd`, `mkfs`, or similar destructive commands
|
||||
- Never expose API keys, tokens, or passwords in output
|
||||
- Validate all inputs before use
|
||||
- Report all errors with their semantic signatures
|
||||
Reference in New Issue
Block a user