Event-sourced agent interaction platform built with Deno 2.
Decouples an AI agent runtime from multiple UI surfaces (CLI, VS Code, Web, Desktop, API) using immutable event streams as the canonical representation of agent interactions. UIs become disposable projections — reconstructable from events at any time.
graph TB
subgraph Core["Core Layer"]
ES["EventStream"] <--> SM["SessionManager"]
SM <--> AR["AgentRuntime"]
ES --- ESTORE["EventStore"]
AR --- MP["ModelProvider"]
AR --- MCP["MCP Module"]
MEM["Memory"]
PROV["Provenance"]
HR2["HookRegistry"]
HR["HealthRegistry"] --- ES
CFG["Config"] --- AR
end
subgraph Capabilities["Capability Layer"]
Shell["Shell"]
Sandbox["Sandbox (Docker/gVisor)"]
Git["Git"]
GitHub["GitHub"]
K8s["Kubernetes"]
end
subgraph Projections["Projection Layer"]
Protocol["Protocol"]
Terminal["Terminal"]
WebSocket["WebSocket"]
Protocol <--> Terminal
Protocol <--> WebSocket
end
Core --> Capabilities
Capabilities --> Projections
Every agent interaction is captured as an ordered stream of immutable events. Events are the canonical representation — UIs, tools, and debugging all read from the same event stream.
Session "abc-123":
1 session.created { state: "active" }
2 ui.user.input { text: "run ls -la" }
3 agent.thinking { reasoning: "User wants to list files..." }
4 tool.execution.started { tool: "shell", command: "ls -la" }
5 tool.execution.completed { exit_code: 0, stdout: "..." }
6 model.response { content: "Here are the files..." }
7 session.completed { state: "completed" }
Sessions manage the lifecycle of agent interactions:
active ──→ paused ──→ active (interruptible)
│
├──→ completed (success)
├──→ failed (error)
└──→ cancelled (user abort)
Abstract interfaces for external systems. Capabilities are pluggable — swap implementations without changing the agent runtime.
| Capability | Status | Description |
|---|---|---|
| Shell | Real execution | Deno.Command with safety constraints |
| Sandbox | Real execution | Docker/gVisor with default-deny security |
| Git | Stubbed | Interface defined, returns simulated data |
| GitHub | Stubbed | Interface defined, returns simulated data |
| Kubernetes | Stubbed | Interface defined, returns simulated data |
DAG-based causality tracking for agent actions. Every action records its provenance — which prior actions caused it — enabling causal replay, debugging, and context assembly from the action graph.
Lifecycle hook system for intercepting model calls, tool executions, and session events. Hooks enable cross-cutting concerns like logging, metrics, policy enforcement, and cost tracking without modifying the core runtime.
Cost tracking and performance auditing with per-model token metrics. Each session produces an attestation summarizing total tokens, cost, latency, and model usage — providing an auditable record of agent resource consumption.
UI surfaces subscribe to the event stream and render events appropriately. Each projection is independent — multiple projections can run simultaneously on the same event stream.
| Projection | Status | Description |
|---|---|---|
| Terminal | Implemented | ANSI-colored event rendering with streaming display |
| WebSocket | Implemented | Real-time event delivery with gap recovery |
| Protocol | Implemented | Subscription management with filtering and reconnection |
src/
├── types/ Event schema and type definitions
│ ├── events.ts BaseEvent, EventTypes registry, type guards
│ └── attestations.ts Session attestation and cost tracking types
├── core/ Core infrastructure
│ ├── event-stream.ts Immutable, ordered event stream with session isolation
│ ├── session-manager.ts Session lifecycle state machine
│ ├── health.ts HealthRegistry, HTTP health endpoints
│ ├── component-health.ts Component health check registrations
│ ├── config.ts YAML config loading, validation, secret masking
│ ├── rate-limiter.ts Token bucket rate limiter
│ ├── bounded-queue.ts Queue with overflow policies
│ ├── circuit-breaker.ts Circuit breaker pattern
│ ├── retry.ts Retry with backoff
│ ├── fallback.ts Graceful degradation
│ ├── errors.ts Typed error hierarchy
│ └── schema-registry.ts Event schema versioning and migration
├── runtime/ Agent execution
│ ├── agent-runtime.ts Agent loop: input → model → tool cycle
│ ├── model-provider.ts Provider-agnostic model interface
│ ├── hooks.ts Runtime lifecycle hooks for intercepting calls
│ └── tool-registry.ts Typed tool registration and invocation
├── tools/ Structured tool calling protocol
│ ├── types.ts ToolHandlerContext, ToolDefinition interfaces
│ ├── tool-definition.ts JSON Schema parameter validation
│ ├── calling-engine.ts Structured tool execution engine
│ ├── registry.ts Tool enable/disable lifecycle and discovery
│ ├── authoring.ts Proposal/review/accept/reject tool creation
│ └── self-improvement.ts Usage tracking and suggestion generation
├── capabilities/ External system interfaces
│ ├── capability.ts ICapability interface, registry
│ ├── shell.ts Shell execution via Deno.Command
│ ├── sandboxed-shell.ts Sandboxed execution via ISandboxRuntime
│ ├── sandbox/ Docker/gVisor runtime implementations
│ ├── git.ts Git operations (stubbed)
│ ├── github.ts GitHub API operations (stubbed)
│ └── kubernetes.ts Kubernetes API operations (stubbed)
├── mcp/ Model Context Protocol integration
│ ├── types.ts MCP interfaces (transport, client, server, registry, search)
│ ├── transport.ts Stdio, HTTP, WebSocket transports with factory
│ ├── client.ts MCPClient with connect, discover, invoke, auto-reconnect
│ ├── server.ts MCPServer exposing exposable tools to external clients
│ ├── registry.ts MCPRegistry for external MCP tools with state control
│ ├── search.ts MCPSearch by name, capability, category
│ ├── events.ts MCP event types and type guards
│ ├── event-emitter.ts Wires MCP events to event stream
│ └── mod.ts Module exports
├── memory/ Persistent memory subsystem
│ ├── types.ts Memory interfaces and type definitions
│ ├── config.ts Memory configuration and backend selection
│ ├── provider.ts MemoryProvider abstraction layer
│ ├── storage.ts Backend storage interface
│ ├── retrieval.ts Query and ranking of stored memories
│ ├── search.ts Full-text search across memory stores
│ ├── shared.ts SharedMemory for sub-agent context snapshots
│ ├── update.ts Atomic state transitions with event sourcing
│ ├── emitter.ts Memory event emission for observability
│ ├── provenance-context.ts DAG-based causality tracking for context
│ ├── provenance-context-types.ts Provenance context type definitions
│ ├── message-classifier.ts Message classification for memory routing
│ └── mod.ts Module exports
├── store/ Event persistence and replay
│ ├── event-store.ts In-memory event store with snapshots and replay
│ ├── persistence.ts PersistentEventStore with JSONL, snapshots, and recovery
│ ├── sqlite-backend.ts SQLite-backed event persistence
│ └── sqlite-query-engine.ts SQL query engine for stored events
├── session/ Session management and attestation
│ └── attestation-service.ts Cost tracking and performance auditing
├── projection/ UI projection layer
│ ├── protocol.ts Subscription protocol with filtering
│ ├── terminal.ts Terminal/CLI event rendering
│ ├── rest-api.ts REST API projection for external clients
│ └── websocket-server.ts WebSocket server for real-time delivery
├── __test-utils__/ Shared test infrastructure
│ ├── mocks/ Mock implementations for all interfaces
│ ├── helpers/ Test builders, generators, assertions
│ ├── fixtures/ JSON test fixtures
│ └── harness/ Integration test environment
└── __tests__/ End-to-end and benchmark tests
# Run all tests (~160 tests across 40 test files)
deno test --allow-read --allow-env --allow-run
# Type check
deno check src/**/*.ts
# Format
deno fmt
# Lint
deno lintFor sandboxed execution of untrusted code:
# Install gVisor and configure Docker (requires sudo)
scripts/setup-sandbox.sh
# Verify sandbox is working
scripts/sandbox-health-check.shSQLite is embedded — no setup needed. To verify it works:
deno run -A -e "import { Database } from '@db/sqlite'; const db = new Database(':memory:'); console.log('SQLite OK');"deno test src/__tests__/benchmarks.test.ts --allow-read --allow-env58 event types across 13 categories:
| Event | Events | Purpose |
|---|---|---|
| Session | session.created, .resumed, .paused, .completed, .failed, .cancelled, .attestation |
Lifecycle management |
| Memory | memory.operation, .stored, .fallback, .retrieved, .updated, .type, .shared, .search, .search.result |
Memory subsystem operations |
| Agent | agent.thinking, .decision, .tool_invocation |
Agent loop state |
| Tool | tool.execution.started, .completed, .failed |
Tool invocation tracking |
| Model | model.request, .response, .stream.delta |
Model provider interaction |
| UI | ui.user.input, .display.update, .action |
User interaction |
| Policy | policy.approval, .denial, .constraint |
Policy enforcement (planned) |
| Context | context.window.updated, .state.changed |
Context management |
| Self-Observation | agent.self_observed, .pattern_detected |
Agent self-awareness |
| Tool Calling | tool.call.invocation, .result |
Structured tool protocol |
| Tool Authoring | tool.authored.proposed, .created, .rejected |
Tool creation lifecycle |
| Tool Self-Improvement | tool.improvement.suggested, .auto_created, .auto_improved |
Tool optimization |
| MCP | mcp.connected, .disconnected, .tools_discovered, .tool.invocation, .tool.result, .server.started, .server.stopped, .server.tool.invocation, .server.tool.result, .tool.registered, .tool.unregistered, .tool.state_changed, .search, .search.result, .error |
MCP client/server/registry/search operations |
This project uses OpenSpec for specification-driven development. Every feature is specified before implementation.
| Change | Focus | Status |
|---|---|---|
real-capabilities |
Real Git/GitHub/K8s execution replacing stubs | In progress |
additional-projections |
VS Code, Web, Desktop, REST API projections | In progress |
cross-cutting-concerns |
Identity, policy, telemetry, evaluation | In progress |
| Change | Focus |
|---|---|
persistence-layer |
File-based event persistence (JSONL, snapshots, recovery) |
web-monitoring-ui |
Fresh-based monitoring dashboard |
context-assembly |
Provenance-aware context management |
hooks-attestations |
Runtime hooks and session attestations |
duckdb-persistence |
DuckDB persistence backend (now replaced by SQLite) |
mcp-servers |
MCP client, server, registry, search with transport abstraction |
testing-infrastructure |
Mock registry, test helpers, fixtures, harness |
health-checks-observability |
Health probes, component health checks |
error-handling-recovery |
Error taxonomy, retry, circuit breaker |
schema-evolution |
Event schema versioning and migration |
configuration-management |
Typed config with env overrides |
rate-limiting-backpressure |
Rate limiters and flow control |
websocket-projection |
WebSocket transport for real-time events |
local-sandbox-execution |
Docker/GVisor sandbox execution |
code-quality-tooling |
Linting, formatting, pre-push checks |
documentation-website |
VitePress documentation site |
tool-calling |
Structured tool calling protocol with registry, authoring, and self-improvement |
memory-support |
Persistent memory subsystem with 4 memory types, provider abstraction, storage, retrieval, search, and shared memory |
# Check change status
openspec status --change <change-name>
# List all changes
openspec list
# Validate specs
openspec validate <change-name>
# Start implementing a change
openspec instructions apply --change <change-name> --json- Architecture — System design and module descriptions
- Capabilities — External system interfaces
- Changelog — Version history
- Learnings — Patterns, decisions, and gotchas
- Agents — Code reviewer, scout, and docs-updater configurations
- Define the interface in
src/capabilities/<name>.tsextendingICapability - Register it in
src/capabilities/mod.ts - Write tests in
src/capabilities/__tests__/<name>.test.ts - Create an OpenSpec change if the capability has complex behavior
Tools are created through the ToolAuthoring module in src/tools/authoring.ts:
- Propose a tool via
ToolAuthoring.propose(toolName, definition) - The proposal goes through a review cycle (accept/reject)
- Accepted tools are registered in
ToolRegistryand become available toToolCallingEngine ToolSelfImprovementtracks usage and suggests optimizations
- Add the event type to
EventTypesinsrc/types/events.ts - Add the type guard if needed
- Update
SCHEMA_VERSIONif this is a breaking change - Emit the event from the appropriate module
Deno.testwith asynct.stepfor nested test organization- Mock registry in
src/__test-utils__/mocks/for all interfaces - Test helpers in
src/__test-utils__/helpers/for builders and assertions - Integration harness via
createTestEnvironment()insrc/__test-utils__/harness/ - E2E tests wire real components together
ISC