292 KiB
Architecture
This section provides the complete architectural blueprint for CleverAgents. It defines the system's structural composition, technology choices, layer boundaries, data flow, and operational characteristics. An implementor should be able to build the entire system from this section combined with the behavioral specification above.
Architecture Overview
!!! adr "Architecture Decision" The layered architecture, DDD boundaries, and deployment modes are defined in ADR-001: Layered Architecture.
CleverAgents is architected as a layered, event-driven application following Domain-Driven Design (DDD) principles with clean architecture boundaries. The system is organized into four primary layers — Presentation, Application, Domain, and Infrastructure — with strict dependency rules: outer layers depend on inner layers, never the reverse. Cross-cutting concerns (logging, configuration, security) are handled through dependency injection and aspect-oriented patterns.
The architecture supports two deployment modes:
- Local Mode: A single-process CLI application where all components run in the same Python process. The CLI is the primary entry point, the database is SQLite, and all resources are local. A2A flows over stdio (agent as subprocess).
- Server Mode: A multi-user service where the CLI acts as a thin client communicating with a remote CleverAgents server via A2A over HTTP (JSON-RPC 2.0). The server hosts shared storage (PostgreSQL), namespace resolution, and remote plan execution (via LangGraph Platform RemoteGraph).
Both modes share the same domain model and application layer. The infrastructure layer provides swappable implementations for each deployment target. All communication uses the Agent-to-Agent Protocol (A2A) standard — there is no REST API.
High-Level Component Diagram
@startuml
skinparam componentStyle rectangle
skinparam defaultFontSize 12
skinparam packageStyle frame
skinparam packageFontSize 14
skinparam packageFontStyle bold
skinparam componentFontSize 11
package "Presentation Layer" <<Frame>> #E0F7FA {
component [CLI\n(Typer)] as CLI
component [TUI\n(Textual)] as TUI
component [Web\n(Textual Web)] as Web
component [IDE Plugin\n(Embedded TUI)] as IDE
component [A2A Server\n(JSON-RPC 2.0 Endpoint)] as A2ASRV
}
interface "A2A\n(Agent-to-Agent Protocol)" as A2A #FFD54F
note right of A2A : Local: A2A over stdio\nServer: A2A over HTTP\nJSON-RPC 2.0 wire format
package "Application Layer" <<Frame>> #E3F2FD {
package "Service Facade" {
component [PlanService] as PS
component [ProjectService] as ProjS
component [ActorService] as ActS
component [ContextService] as CtxS
component [ToolService] as ToolS
component [SkillService] as SkillS
component [ResourceSvc] as ResS
}
package "Workflow Engine" {
component [PlanLifecycle] as PL
component [SessionWorkflow] as SW
component [CorrectionFlow] as CF
component [MergeWorkflow] as MW
}
package "Event Bus" {
component [StructuredLog] as SL
component [EventEmitter] as EE
component [MetricsCollector] as MC
}
component [DI Container\n(dependency-injector DeclarativeContainer)] as DI #D1C4E9
}
package "Domain Layer" <<Frame>> #F3E5F5 {
package "Domain Models" {
component [Plan / Decision / Project\nResource / Actor / Action\nTool / Skill / Session\nInvariant / AutomationProf\nCheckpoint / CorrectionAtmpt] as DM
}
package "Domain Services" {
component [InvariantEnforcer\nAutonomyCtrl\nContextBuilder\nMergeResolver] as DS
note bottom of DS : Repository Interfaces\n(Protocol classes)
}
package "Domain Events" {
component [PlanCreated / PhaseChanged\nDecisionMade / ToolInvoked\nApplyCompleted / CorrectionReq] as DE
}
}
package "Infrastructure Layer" <<Frame>> #FFEBEE {
package "Database" {
component [SQLite / SQLAlchemy\nAlembic / UoW] as DB
}
package "Indexing" {
component [Tantivy / FAISS\nNeo4j / Qdrant / RDFLib] as IDX
}
package "Sandbox" {
component [GitWorktree / FsCopy\nTxnRollback / NoOp] as SBX
}
package "LLM / AI Runtime" {
component [LangChain / LangGraph\nProviderRegistry\nRxPY Bridge / MCP SDK] as LLM
}
package "External Integrations" {
component [MCP Servers\nAgent Skills Std\nREST/gRPC Clients] as EXT
}
package "LSP Runtime" {
component [LSP Server Manager\nLSPToolAdapter\nLanguage Servers] as LSPR
}
package "File System / OS" {
component [Watchdog / File I/O\nGit CLI / Subprocess\nOS Process / Signals] as FS
}
}
CLI -[hidden]right-> TUI
TUI -[hidden]right-> Web
Web -[hidden]right-> IDE
IDE -[hidden]right-> A2ASRV
CLI -down-> A2A
TUI -down-> A2A
Web -down-> A2A
IDE -down-> A2A
A2ASRV -down-> A2A
A2A -down-> PS
A2A -down-> SW
A2A -down-> EE
PS -down-> DI
DI -down-> DM
DS -down-> DB
DS -down-> IDX
@enduml
Standards and Protocols in the Architecture
!!! adr "Architecture Decision" The four protocol standards and their architectural roles are defined across ADR-026: Agent-to-Agent Protocol (A2A), ADR-027: Language Server Protocol (LSP) Integration, ADR-028: Agent Skills Standard (AgentSkills.io), and ADR-029: Model Context Protocol (MCP) Adoption.
Each adopted standard maps to an explicit architectural boundary:
- A2A (Agent-to-Agent Protocol) — the A2A standard (JSON-RPC 2.0) connects Presentation-layer clients (CLI, TUI, IDE) to the Application layer. Standard A2A operations handle agent messaging (
message/send,message/stream), task lifecycle, and streaming updates. CleverAgents_cleveragents/-prefixed extension methods handle platform operations (plans, registries, context, sync). In local mode A2A flows over stdio; in server mode over HTTP. This keeps clients interchangeable and supports third-party client and agent development. - LSP (Language Server Protocol) — LSP servers are registered in the LSP Registry (Infrastructure layer) and bound to actor graph nodes to provide language intelligence. The
LSPToolAdapterexposes LSP capabilities as tools (diagnostics, hover, go-to-definition, completions, references, rename) that actors call during reasoning, and the LSP Runtime automatically enriches actor context with diagnostic and type information. This gives actors the same semantic code understanding that human developers get from IDEs. See LSP Integration. - MCP (Model Context Protocol) — external MCP servers reside in the Infrastructure layer. The MCPToolAdapter bridges them into first-class Tools in the Domain model with extended capability metadata. MCP tools are composed into skills and used as tool nodes in actor graphs, giving the system standardized tool discovery and invocation.
- Agent Skills (https://AgentSkills.io) — Agent Skills are ingested into the Skill Registry and loaded by the actor runtime via progressive disclosure. Inside actor graphs they appear as tool nodes whose execution is instruction-driven — the agent follows multi-step procedures that may call MCP tools, built-ins, or other Agent Skills. This adds portable, reusable workflows that complement schema-driven MCP tools.
A2A Integration Architecture
!!! adr "Architecture Decision" The Agent-to-Agent Protocol is defined in ADR-026: Agent-to-Agent Protocol (A2A). The adoption of the A2A standard is defined in ADR-047: A2A Standard Adoption. See also Core Concepts > Server > A2A for the behavioral specification.
Of the four standards CleverAgents adopts, A2A has the deepest architectural integration. While MCP, Agent Skills, and LSP plug into the Infrastructure and Domain layers to provide capabilities to actors, A2A defines the fundamental boundary between the Presentation and Application layers — it is the protocol surface through which every client operation flows, in every deployment mode. This section provides the full architectural detail.
Why A2A Exists
The architecture faces a structural tension: the system has multiple Presentation-layer surfaces (CLI, TUI, Web, IDE plugin) and multiple deployment modes (local, server). Without a shared contract:
- Each client would implement its own calling convention into the Application layer.
- Local and server modes would develop behavioral drift.
- Third-party client development would be impractical.
- Testing would require N x M test matrices (N clients x M modes).
CleverAgents adopts the Agent-to-Agent Protocol standard (a2a-protocol.org), built on JSON-RPC 2.0, as the sole protocol for all client-server communication. The contract is the same whether the transport is stdio (local) or HTTP (server). This is the architectural guarantee that clients are interchangeable.
A2A in the Layer Diagram
A2A sits at the boundary between the Presentation and Application layers. Every arrow from a Presentation-layer component to the Service Facade in the High-Level Component Diagram represents an A2A method call:
@startuml
skinparam componentStyle rectangle
skinparam defaultFontSize 11
package "Presentation Layer" #E0F7FA {
component [CLI] as CLI
component [TUI] as TUI
component [Web] as Web
component [IDE Plugin\n(Embedded TUI)] as IDE
component [A2A Server Endpoint\n(JSON-RPC 2.0)] as A2ASRV
}
interface "A2A" as A2A_IF #FFD54F
package "Application Layer" #E3F2FD {
component [Service Facade\n(PlanService, ProjectService,\nActorService, ContextService,\nToolService, SkillService,\nResourceService)] as SF
component [SessionWorkflow] as SW
component [EventEmitter] as EE
}
CLI -down-> A2A_IF
TUI -down-> A2A_IF
Web -down-> A2A_IF
IDE -down-> A2A_IF
A2ASRV -down-> A2A_IF
A2A_IF -down-> SF
A2A_IF -down-> SW
A2A_IF -down-> EE
note right of A2A_IF
**Local mode**: A2A over stdio
**Server mode**: A2A over HTTP
JSON-RPC 2.0 wire format
end note
@enduml
Transport Modes
The transport mechanism is the core architectural enabler that makes A2A work across deployment modes. The A2A Python SDK provides both transports:
Local Mode — A2A over stdio
CLI ──stdio──→ Agent Subprocess ──→ A2aLocalFacade ──→ ServiceFacade.method() ──→ Domain / Infrastructure
The client spawns the agent as a subprocess. A2A JSON-RPC messages flow over stdin/stdout. Standard A2A operations (message/send, message/stream) drive the conversation. Extension methods (_cleveragents/*) are intercepted by A2aLocalFacade and routed to in-process Application-layer services. No network, no authentication — the agent runs with local user permissions.
Server Mode — A2A over HTTP
CLI ──HTTP──→ CleverAgents A2A Server ──→ ServiceFacade.method() ──→ Domain / Infrastructure
│ │
└── RemoteGraph.invoke() ──→ LangGraph Platform ────────┘
The client connects to the CleverAgents server via the A2A SDK's HTTP transport. All methods — both standard A2A and _cleveragents/ extensions — flow through the single A2A JSON-RPC 2.0 endpoint. The server delegates actor execution to LangGraph Platform via RemoteGraph. Authentication is required via HTTP auth schemes declared in the Agent Card.
External Agent Mode — Split Routing
CLI ──HTTP──→ External A2A Server (standard A2A operations: message/send, message/stream, etc.)
──HTTP──→ CleverAgents Server (_cleveragents/* extension methods only)
When an agent is hosted on an external A2A-compatible server, the client routes standard A2A operations to that server and _cleveragents/ extension methods to the CleverAgents server. This enables interoperability with any A2A-compliant agent.
This transport architecture is enforced by:
- The same A2A test suite runs in both modes. Contract tests validate that a given method with given parameters produces the same response shape over stdio and HTTP.
- No Presentation-layer module imports from Infrastructure. Architecture tests (import-linter) verify this on every commit, preventing clients from bypassing A2A.
- The Service Facade is the sole entry point. No Application-layer service is exposed directly to the Presentation layer.
Complete Method Routing
Every A2A method routes to a specific Application-layer service method. The following tables are the authoritative mapping.
Standard A2A Operations → Service Mapping
| A2A Operation | Service Method |
|---|---|
initialize |
Capability negotiation — returns supported extensions, automation profiles, tool registries |
authenticate |
Token validation against user/token store |
session/new |
SessionWorkflow.create() |
session/load |
SessionWorkflow.resume() |
session/list |
SessionWorkflow.list() |
session/prompt |
SessionWorkflow.tell() |
session/cancel |
PlanService.cancel() (cancels in-progress plan execution) |
session/set_mode |
AutomationProfileService.switch() |
session/set_model |
ProviderRegistry.switch_model() |
session/fork |
SessionWorkflow.fork() |
session/resume |
SessionWorkflow.resume() |
Plan Extension Methods → Service Mapping
| A2A Extension Method | Service Method |
|---|---|
_cleveragents/plan/use |
PlanService.create_plan() |
_cleveragents/plan/execute |
PlanLifecycle.execute() |
_cleveragents/plan/apply |
PlanLifecycle.apply() |
_cleveragents/plan/cancel |
PlanService.cancel() |
_cleveragents/plan/status |
PlanService.get_status() |
_cleveragents/plan/tree |
PlanService.get_tree() |
_cleveragents/plan/explain |
PlanService.explain_decision() |
_cleveragents/plan/correct |
CorrectionFlow.correct() |
_cleveragents/plan/diff |
PlanService.get_diff() |
_cleveragents/plan/artifacts |
PlanService.get_artifacts() |
_cleveragents/plan/prompt |
PlanService.inject_guidance() |
_cleveragents/plan/rollback |
PlanLifecycle.rollback() |
_cleveragents/plan/list |
PlanService.list() |
Registry Extension Methods → Service Mapping (pattern applies to all entity types)
| A2A Extension Method Pattern | Service Method Pattern |
|---|---|
_cleveragents/registry/{entity}/list |
{Entity}Service.list() |
_cleveragents/registry/{entity}/show |
{Entity}Service.show() |
_cleveragents/registry/{entity}/add |
{Entity}Service.add() |
_cleveragents/registry/{entity}/update |
{Entity}Service.update() |
_cleveragents/registry/{entity}/remove |
{Entity}Service.remove() |
Where {entity} is one of: actor, skill, tool, validation, resource, resource_type, project, action, automation_profile, invariant, lsp.
Context Extension Methods → Service Mapping
| A2A Extension Method | Service Method |
|---|---|
_cleveragents/context/show |
ContextService.show() |
_cleveragents/context/inspect |
ContextService.inspect() |
_cleveragents/context/simulate |
ContextService.simulate() |
_cleveragents/context/set |
ContextService.set() |
Sync, Namespace, and Health Extension Methods
| A2A Extension Method | Service Method |
|---|---|
_cleveragents/sync/pull |
SyncService.pull() |
_cleveragents/sync/push |
SyncService.push() |
_cleveragents/sync/status |
SyncService.status() |
_cleveragents/namespace/list |
NamespaceService.list() |
_cleveragents/namespace/show |
NamespaceService.show() |
_cleveragents/namespace/members |
NamespaceService.members() |
_cleveragents/health/check |
Health check handler |
_cleveragents/diagnostics/run |
Diagnostic runner |
Streaming Architecture
A2A streaming uses Server-Sent Events (SSE) via message/stream. The server pushes TaskStatusUpdateEvent and TaskArtifactUpdateEvent to the client in real-time during long-running operations:
Event types and their CleverAgents mappings:
| Event Type | Emitted When | Payload |
|---|---|---|
TaskStatusUpdateEvent |
Agent produces response tokens, task state changes, automation profile switched | status (state + message with agent response Parts) |
TaskArtifactUpdateEvent |
Tool execution results, plan artifacts generated | artifact (named artifact with Parts) |
Example streaming event:
{
"jsonrpc": "2.0",
"method": "task/statusUpdate",
"params": {
"taskId": "task_01HXM8C2...",
"status": { "state": "working" },
"message": { "role": "agent", "parts": [{ "kind": "text", "text": "Running git-diff on platform-repo..." }] }
}
}
Causal Ordering Guarantee: Events within a single session/plan lifecycle are delivered in causal order. A phase transition event always arrives after the preceding phase completion.
In local mode (stdio), events flow as JSON-RPC messages over stdout. In server mode (HTTP), the A2A SDK manages the SSE streaming connection. Both modes deliver the same event types with the same payload shapes.
Authentication and Authorization
Authentication follows the A2A standard Agent Card-based discovery:
- Agent Card discovery: Client fetches the server's Agent Card (via
/.well-known/agent.jsonor configured URL). The Agent Card declares supported capabilities,_cleveragentsextensions, and authentication schemes. - HTTP authentication: Client authenticates using the scheme declared in the Agent Card (OAuth2, API key, Bearer token). Typically
Authorization: Bearer <server.token>header on every request. - All subsequent operations: Authenticated. The
A2A-Versionheader is sent on every request.
Local mode (stdio): Authentication is bypassed entirely. The agent subprocess runs with the user's local permissions.
Server mode: Every connection must discover the Agent Card and authenticate before any operation. Unauthenticated requests receive a JSON-RPC error:
{
"jsonrpc": "2.0",
"id": 1,
"error": { "code": -32002, "message": "Authentication required" }
}
Authorization is namespace-scoped: users can only access entities within namespaces they have been granted access to. The local/ namespace is never accessible via server.
Error Taxonomy
A2A uses JSON-RPC 2.0 integer error codes. Standard protocol errors use the reserved range; application-specific errors use -32001 to -32099:
| Error Code | Meaning | Domain Exception(s) |
|---|---|---|
-32700 |
Parse error (malformed JSON) | — |
-32600 |
Invalid request | — |
-32601 |
Method not found | A2aOperationNotFoundError |
-32602 |
Invalid params | ValidationError |
-32603 |
Internal error | Any unhandled Exception |
-32001 |
Entity not found | ResourceNotFoundError |
-32002 |
Authentication required | AuthenticationError |
-32003 |
Authorization forbidden | AuthorizationError |
-32004 |
Invalid state | BusinessRuleViolation |
-32005 |
Already exists | DuplicateEntityError |
-32006 |
Budget exceeded | BudgetExceededError |
-32007 |
Version mismatch | A2aVersionMismatchError |
-32008 |
Plan error | PlanError |
All errors follow the JSON-RPC 2.0 error structure: { "code": <int>, "message": <string>, "data": <optional object> }. Clients should dispatch on code for precise error handling.
A2A Versioning
- The JSON-RPC protocol version is always
"2.0"(in thejsonrpcfield) - A2A protocol version is declared in the Agent Card and sent via the
A2A-VersionHTTP header - CleverAgents extension version is declared in the Agent Card's extensions section under
_cleveragents.version - Backward-compatible additions (new optional params, new extension methods) are permitted within a major version; breaking changes require a major version bump
- Servers support the current extension version plus one prior minor version
A2A and the IDE Plugin
The IDE plugin is an embedded TUI — essentially the same Textual-based interface used by the standalone TUI, but hosted inside an IDE environment. Like all Presentation-layer clients, it communicates with the backend exclusively through A2A. It does not access the Domain or Infrastructure layers directly — all plan operations, validation queries, and context lookups go through A2A. This means:
- The IDE plugin works identically in local mode (A2A over stdio to a subprocess) and server mode (A2A over HTTP).
- IDE features stay exactly aligned with CLI/TUI behavior (same A2A operations, same JSON-RPC 2.0 format).
- The IDE plugin receives
TaskStatusUpdateEvent/TaskArtifactUpdateEventstreaming events for real-time plan and session updates. - Extension methods (
_cleveragents/fs/*,_cleveragents/terminal/*) allow the agent to interact with the IDE's workspace and integrated terminal.
Note that the Language Server Protocol (LSP) in CleverAgents is unrelated to the IDE plugin. LSP serves an entirely different purpose: providing language intelligence to actors and agents in the Infrastructure layer. See LSP Integration and ADR-027: Language Server Protocol (LSP) Integration for the full LSP architecture.
Server and Client Architecture
!!! adr "Architecture Decision" The server application architecture is defined in ADR-048: Server Application Architecture. The A2A standard adoption is defined in ADR-047: A2A Standard Adoption.
This section defines the complete architecture of the CleverAgents server application and the client-side components that interact with it. The server is a separate application (distinct deployment unit) that shares the same Domain and Application layers as the client.
Server Application Structure
The server follows the same four-layer architecture as the client (ADR-001), sharing two layers and providing its own implementations for the other two:
| Layer | Client | Server | Shared? |
|---|---|---|---|
| Domain | Business rules, domain models, domain events | Same | Yes — identical package |
| Application | Service facades, workflows, event bus | Same | Yes — identical package |
| Infrastructure | SQLite, local sandbox, local file access, A2aLocalFacade |
PostgreSQL, LangGraph Platform RemoteGraph, server-side sandbox, A2A SDK server | No |
| Presentation | CLI (Typer), TUI (Textual), IDE plugin | A2A JSON-RPC 2.0 endpoint | No |
The shared Domain and Application layers are consumed as a Python package dependency. This ensures zero behavioral drift between local and server modes.
@startuml
skinparam componentStyle rectangle
skinparam defaultFontSize 11
package "Client Application" #E0F7FA {
component [CLI / TUI / IDE Plugin] as ClientPres
component [Application Layer\n(shared)] as ClientApp
component [Domain Layer\n(shared)] as ClientDom
component [Infrastructure\n(SQLite, local sandbox,\nA2aLocalFacade)] as ClientInfra
}
package "Server Application" #FFEBEE {
component [A2A JSON-RPC 2.0\nEndpoint] as ServerPres
component [Application Layer\n(shared)] as ServerApp
component [Domain Layer\n(shared)] as ServerDom
component [Infrastructure\n(PostgreSQL, RemoteGraph,\nA2A SDK server)] as ServerInfra
}
component [LangGraph\nPlatform] as LGP #FFF9C4
ClientPres -down-> ClientApp
ClientApp -down-> ClientDom
ClientDom -down-> ClientInfra
ServerPres -down-> ServerApp
ServerApp -down-> ServerDom
ServerDom -down-> ServerInfra
ServerInfra -right-> LGP : RemoteGraph
ClientPres -right[hidden]-> ServerPres
note bottom of ClientApp : Same Python package
note bottom of ServerApp : Same Python package
@enduml
Server Presentation Layer
The server's sole client-facing interface is an A2A JSON-RPC 2.0 endpoint implemented using the A2A Python SDK's server-side connection handler. There is no REST API, no GraphQL, no separate admin endpoint.
The endpoint handles three categories of messages:
- Standard A2A operations (
message/send,message/stream): Routed toSessionWorkflowand actor execution (via LangGraph Platform RemoteGraph); Agent Card served at discovery endpoint - Extension methods (
_cleveragents/*): Routed to Application-layer services (PlanService, RegistryServices, SyncService, NamespaceService, etc.) - Multi-turn interactions to clients (Task
input-requiredstate,_cleveragents/fs/*,_cleveragents/terminal/*): Forwarded from executing actors back to the connected client — enabling server-hosted agents to access client-local resources
Server Infrastructure: LangGraph Platform
Actor graphs (StateGraphs defined in YAML) are deployed to LangGraph Platform as separate deployments. The server invokes them via RemoteGraph:
sequenceDiagram
participant C as Client
participant S as A2A Server
participant SW as SessionWorkflow
participant RG as RemoteGraph
participant LG as LangGraph Platform
C->>S: message/send {"message": {"role": "user", "parts": [{"text": "Add pagination"}]}}
S->>SW: tell("Add pagination")
SW->>RG: invoke(strategy_actor, state)
RG->>LG: Execute strategy actor graph
LG-->>RG: Strategy result (plan entries)
RG-->>SW: Plan with decisions
SW-->>S: TaskStatusUpdateEvent (plan notification)
S-->>C: TaskStatusUpdateEvent (streaming)
SW->>RG: invoke(execution_actor, state)
RG->>LG: Execute execution actor graph
LG-->>RG: Execution result
RG-->>SW: Completed execution
SW-->>S: TaskArtifactUpdateEvent (tool_call results)
S-->>C: TaskArtifactUpdateEvent (streaming)
Different actors for different plan phases (strategy, execution, estimation) each deploy as separate RemoteGraphs, enabling independent scaling without limiting plan capabilities. The SessionWorkflow orchestrates them the same way it orchestrates local actor graphs, but through the RemoteGraph interface.
Server Infrastructure: Persistence
| Component | Technology | Notes |
|---|---|---|
| Database | PostgreSQL via SQLAlchemy | Same ORM as client (SQLite), different dialect |
| Migrations | Alembic | Schema migrations for server-specific tables (users, tokens, namespace ACLs) |
| Session store | PostgreSQL | Multi-user session persistence with namespace isolation |
| Plan store | PostgreSQL | Plan lifecycle records, decision trees, artifacts |
Server-specific tables (authentication, namespace ACLs, user management) are additive — they extend the shared schema without modifying it.
Server Security Architecture
Authentication: Uses HTTP auth schemes declared in the server's Agent Card (OAuth2, API key, Bearer token). Tokens are validated against the server's user/token store. HTTP transport carries Authorization: Bearer <token> header on every request.
Authorization: Namespace-scoped. Users can only access entities in namespaces they have been granted access to. The local/ namespace is never accessible via server.
Transport security: HTTPS required for all production deployments (TLS termination at Kubernetes ingress).
Client-Side Architecture
The client uses an A2aClient that wraps the A2A Python SDK and routes method calls through a TransportSelector:
# Conceptual client architecture (not literal implementation)
class TransportSelector:
"""Selects transport based on configuration."""
def get_transport(self) -> A2aTransport:
if config.server_url:
return A2aRemoteTransport(url=config.server_url, token=config.server_token)
else:
return A2aStdioTransport(agent_command=["cleveragents", "agent", "serve"])
class A2aClient:
"""Unified client for all A2A communication."""
def __init__(self, transport: A2aTransport):
self.connection = A2AClient(transport)
async def send_message(self, task_id: str, message: str) -> AsyncIterator[Event]:
"""Send a message and stream task events."""
return await self.connection.send_message(Message(
role="user",
parts=[TextPart(text=message)],
), task_id=task_id)
async def plan_status(self, plan_id: str) -> dict:
"""Get plan status via extension method."""
return await self.connection.request("_cleveragents/plan/status", {
"plan_id": plan_id,
})
Evolution of existing source code:
| Current Component | Becomes | Notes |
|---|---|---|
A2aLocalFacade |
Retained | Handles _cleveragents/ dispatch to in-process services; adapts A2A message/send to local workflows |
A2aClient |
Wraps A2A SDK A2AClient |
SDK provides production transport for both stdio and HTTP |
A2aEvent |
Mapped to TaskStatusUpdateEvent / TaskArtifactUpdateEvent |
Event types map to A2A streaming event types |
A2aEventQueue |
Retained for local mode | Delivers events as in-process streaming events |
Local/Server Interchangeability
The transport selector pattern ensures that application code is transport-agnostic. A CLI command works identically regardless of whether the backend is local or remote:
# CLI command — identical in local and server mode
async def plan_status_command(plan_id: str):
client = get_a2a_client() # TransportSelector picks stdio or HTTP
result = await client.plan_status(plan_id)
render_plan_status(result)
The only difference is where computation happens:
| Aspect | Local Mode | Server Mode |
|---|---|---|
| Transport | A2A over stdio | A2A over HTTP |
| Actor execution | In-process LangGraph | LangGraph Platform RemoteGraph |
| Database | SQLite | PostgreSQL |
| Authentication | Bypassed | HTTP auth (Agent Card scheme) + Bearer token |
| File/terminal access | Direct | Via A2A multi-turn interactions (server → client) |
| Namespace resolution | local/ only |
local/ + server namespaces |
Server Deployment
| Component | Technology | Notes |
|---|---|---|
| Container | Docker | Server packaged as container image |
| Orchestration | Kubernetes | Production deployment target |
| Configuration | Helm chart (k8s/) |
Configurable replicas, resource limits, ingress |
| Ingress | Kubernetes Ingress | TLS termination, routing to A2A endpoint |
| Database | PostgreSQL (managed or self-hosted) | External to the application container |
| LangGraph Platform | Managed or self-hosted | External service for graph execution |
| Caching | Redis (optional) | Multi-instance session affinity, rate limiting |
Architectural Principles
-
Hexagonal Architecture (Ports and Adapters): The domain layer defines abstract repository interfaces (ports). The infrastructure layer provides concrete implementations (adapters). This allows swapping SQLite for PostgreSQL, FAISS for Qdrant, or local execution for remote server execution without touching domain logic.
-
Command Query Responsibility Segregation (CQRS): Write operations (plan creation, decision recording, resource modification) flow through command handlers that enforce invariants and emit domain events. Read operations (plan status, decision tree visualization, context queries) use optimized read paths that may bypass the ORM for performance.
-
Event-Driven Architecture: All significant state changes emit domain events through a structured event bus. Events flow through RxPY reactive streams for real-time processing and through the structured logging pipeline for persistence. This enables decoupled observability, audit logging, and future webhook/notification systems.
-
Dependency Injection: All service dependencies are wired through a
DeclarativeContainer(from thedependency-injectorlibrary). This eliminates hidden coupling, enables testing with mock implementations, and supports runtime reconfiguration (e.g., switching providers). -
Convention over Configuration: Default behavior requires zero configuration. For entity registration commands, YAML configuration files are the complete definition (no CLI overrides). For runtime commands (e.g.,
plan use), CLI flags may override entity defaults. Environment variables can be interpolated into configuration files via${ENV_VAR}syntax. The resolution chain depends on context: entity registration uses config-file-only semantics, while runtime commands support per-invocation CLI overrides of entity defaults.
Data Flow: Plan Lifecycle
The following diagram traces data flow through the system during a complete plan lifecycle:
sequenceDiagram
participant U as User
participant C as CLI
participant A as Application
participant D as Domain
participant I as Infrastructure
U->>C: plan use
C->>A: PlanService.create_plan()
A->>D: Plan(phase=strategize)
D->>I: PlanRepository.save(plan)
A->>D: InvariantEnforcer.reconcile()
A->>D: AutonomyCtrl.should_proceed
rect rgb(200, 230, 255)
Note over A,D: Strategize Phase (if auto)
A->>D: StrategyActor.invoke()
D->>I: LangGraph StateGraph.invoke
D->>I: ContextBuilder.build_hot_ctx()
I-->>D: [FAISS/Tantivy results]
D->>I: LLM Provider.generate()
I-->>D: [LLM response]
D-->>A: Decision[]
D->>I: DecisionRepo.save_batch()
end
rect rgb(255, 230, 200)
Note over A,D: Execute Phase
A->>D: SandboxManager.create()
D->>I: GitWorktree.create_worktree()
A->>D: ExecutionActor.invoke(sandbox)
D->>I: Tool calls via SkillRegistry
D-->>A: ChangeSet
end
rect rgb(200, 255, 200)
Note over A,D: Apply Phase
A->>D: Validation.run_all()
D->>I: Subprocess.run(commands)
A->>D: SandboxManager.commit()
D->>I: git merge
end
C-->>U: OK Applied
Technical Stack
!!! adr "Architecture Decision" The technology selections, version constraints, and rationale are defined in ADR-005: Technical Stack.
This section enumerates every technology choice in the CleverAgents stack, organized by functional area. Version constraints are minimum versions; newer compatible versions are acceptable.
Core Runtime
| Technology | Version | Role | Rationale |
|---|---|---|---|
| Python | >= 3.13 | Primary language | Type hint maturity, async/await, pattern matching, performance improvements in 3.13. The entire codebase is Python-only for consistency and LLM ecosystem alignment. |
| Hatchling | >= 1.21.0 | PEP 517 build backend | Lightweight, standards-compliant build system. Produces wheels and sdists without complex configuration. |
| uv | >= 0.8.0 | Package installer and resolver | 10-100x faster than pip. Used in CI, Docker builds, and Nox sessions for dependency installation. Drop-in replacement for pip with full PEP 723 support. |
CLI and Presentation
| Technology | Version | Role | Rationale |
|---|---|---|---|
| Typer | >= 0.9.0 | CLI framework | Built on Click with automatic help generation, type inference from Python type hints, and Rich integration for styled output. Supports nested command groups matching the agents <noun> <verb> pattern. |
| Rich | (transitive via Typer) | Terminal rendering | Provides Panel, Table, Tree, Syntax, Progress, Spinner, and Markdown rendering used by all output formats except plain and structured (json/yaml). |
| Textual | (future) | TUI framework | Built by the Rich maintainers. Provides a reactive terminal UI framework that can be served as a web app via Textual Web, enabling the "single UI codebase" strategy for TUI, Web, and IDE plugin. |
LLM and AI Runtime
| Technology | Version | Role | Rationale |
|---|---|---|---|
| LangChain | >= 0.2.14 | LLM abstraction layer | Provider-agnostic interface for chat models, embeddings, output parsing, prompt templates, and tool calling. The BaseLanguageModel protocol enables swapping providers without changing application code. |
| LangGraph | (transitive) | Stateful workflow orchestration | StateGraph with conditional edges, checkpointing (MemorySaver), and streaming execution. Used for the plan generation graph (load_context -> analyze -> generate -> validate) and auto-debug graph. |
| LangChain Provider Packages | varies | LLM provider integrations | langchain-openai (>= 0.2.0), langchain-google-genai (>= 0.2.0), langchain-anthropic, langchain-groq, langchain-together, langchain-cohere. Each provides a ChatModel implementation. |
| LangChain Community | >= 0.2.14 | Community integrations | FAISS vector store, FakeListLLM/FakeEmbeddings for testing. |
| RxPY | >= 3.2.0 | Reactive stream processing | Subject, BehaviorSubject, ReplaySubject, and operators (map, filter, flat_map, debounce, throttle, scan) for real-time event routing between actors, stream-to-graph bridging, and backpressure management. |
| MCP SDK | >= 1.4.0 | Model Context Protocol | Client SDK for communicating with MCP servers. Enables CleverAgents to discover and invoke tools exposed by any MCP-compliant server. |
Data and Persistence
| Technology | Version | Role | Rationale |
|---|---|---|---|
| SQLite | (system) | Primary database | Zero-configuration, file-based, ACID-compliant. Sufficient for local mode; supports WAL mode for concurrent reads during plan execution. For server mode, the infrastructure layer swaps in PostgreSQL via the same SQLAlchemy interface. |
| SQLAlchemy | (transitive via Alembic) | ORM and database abstraction | Declarative model mapping, session management, Unit of Work pattern, and dialect abstraction enabling database portability. |
| Alembic | >= 1.13.1 | Database migrations | Version-controlled schema migrations with upgrade/downgrade support. Migrations are auto-applied on first run and during agents init. |
| python-ulid | >= 2.7.0 | ID generation | ULID generation for plans, decisions, resources, and correction attempts. ULIDs are lexicographically sortable by creation time, enabling efficient time-range queries without separate timestamp indexes. |
Indexing and Code Intelligence
| Technology | Version | Role | Rationale |
|---|---|---|---|
| Tantivy (via tantivy-py) | configurable | Full-text search | Rust-based search engine providing sub-millisecond full-text search across project resources. Used for keyword-based code search during context building. Alternative: SQLite FTS5 for zero-dependency installations. |
| FAISS (faiss-cpu) | >= 1.7.4 | Vector similarity search | Facebook AI Similarity Search for semantic code search. Indexes embedding vectors generated from project files. Supports approximate nearest neighbor search for fast retrieval during context building. |
| Qdrant | configurable | Vector search (alternative) | External vector database for production deployments requiring horizontal scaling, persistence, and filtering. Connected via qdrant-client. |
| Neo4j | configurable | Knowledge graph | Graph database for structural code relationships (class hierarchies, call graphs, dependency trees). Enables queries like "find all callers of function X" during Strategize. Alternative: rdflib for in-process graph queries. |
| rdflib | >= 7.1.4 | In-process graph store | Python RDF library for lightweight structural code analysis without external dependencies. Stores RDF triples representing code relationships. |
| OpenAI Embeddings | configurable | Vector generation | Default embedding provider (text-embedding-3-small). Alternative providers: Anthropic, local sentence-transformers models via index.embedding.provider config. |
Configuration and Validation
| Technology | Version | Role | Rationale |
|---|---|---|---|
| Pydantic | >= 2.7.0 | Data validation and modeling | All domain models, configuration objects, and API schemas use Pydantic v2 for runtime validation, JSON Schema generation, and serialization. Provides type-safe data boundaries between layers. |
| Pydantic Settings | >= 2.11.0 | Environment configuration | BaseSettings subclasses for environment variable loading with the CLEVERAGENTS_ prefix, .env file support, and nested model resolution. Implements the four-tier configuration resolution chain. |
| Jinja2 | >= 3.1.0 | Template rendering | Used for actor system prompts, YAML configuration interpolation, and prompt template rendering. Supports {{ context.* }} variable expansion with sandboxed execution. |
| TOML | (stdlib tomllib) | Configuration file format | Global configuration uses TOML for its native support of nested tables, which maps naturally to the dot-separated hierarchical key structure. Python 3.13 includes tomllib in stdlib. |
| YAML | (via PyYAML) | Entity configuration format | All entity definitions (actors, skills, tools, actions, resource types, automation profiles) use YAML as the canonical human-authored format. JSON is accepted as valid YAML. |
Testing
| Technology | Version | Role | Rationale |
|---|---|---|---|
| Behave | >= 1.2.6 | BDD feature tests | Gherkin .feature files describe behavioral scenarios in natural language. Step implementations exercise the full application stack. Custom behave-parallel runner enables parallel execution via ProcessPoolExecutor. |
| Robot Framework | >= 7.3.2 | Integration tests | Keyword-driven integration tests for end-to-end CLI workflows. robotframework-pabot enables parallel suite execution. |
| pytest | >= 8.0.0 | Unit tests | Standard Python test framework for unit-level testing with fixtures, parametrization, and plugin support. |
| pytest-asyncio | >= 0.23.0 | Async test support | Enables async def test_* functions for testing async LangGraph workflows and RxPY stream operations. |
| pytest-cov / coverage | >= 7.11.0 | Coverage reporting | Target: 85% line coverage. HTML and XML reports generated by Nox sessions. Coverage gates enforce minimum thresholds in CI. |
| Hypothesis | >= 6.136.6 | Property-based testing | Generates randomized test inputs for invariant verification, particularly useful for testing decision tree operations, merge strategies, and context tier boundaries. |
| ASV (Airspeed Velocity) | >= 0.6.5 | Performance benchmarks | Tracks plan generation latency, CLI startup time, and indexing throughput across commits. Prevents performance regressions. |
Code Quality
| Technology | Version | Role | Rationale |
|---|---|---|---|
| Ruff | >= 0.1.0 | Linting and formatting | Single tool replacing Black, Flake8, isort, and pyupgrade. Rules: E, F, W, B, UP, I, SIM, RUF. Line length: 88. Double quotes. 4-space indent. |
| Pyright | >= 1.1.350 | Static type checking | Strict mode type checking for the entire codebase. Catches type errors at development time rather than runtime. Configured via pyrightconfig.json. |
Infrastructure and Deployment
| Technology | Version | Role | Rationale |
|---|---|---|---|
| Docker | multi-stage | Containerization | Production image uses python:3.13-slim with multi-stage build. Non-root user (appuser, uid 1000). Entrypoint: python -m cleveragents. |
| Helm | (chart) | Kubernetes deployment | Helm chart in k8s/ directory for server mode deployment. Linted and template-tested in CI. |
| Nox | >= 2025.4.22 | Task automation | Session-based task runner for lint, test, build, docs, benchmarks, and coverage. Uses uv as the virtual environment backend for fast session creation. |
| Forgejo CI | (workflow) | Continuous integration | Self-hosted Forgejo instance with GitHub Actions-compatible workflow syntax. Pipeline: lint -> typecheck -> behave (matrix 3.11/3.12/3.13) -> build -> docker -> helm. |
| devcontainer CLI | (optional) | Devcontainer management | Used for building and managing devcontainer-instance resources from .devcontainer/devcontainer.json configurations. Falls back to direct Docker/Podman CLI when not installed, with reduced feature support (e.g., devcontainer features may not be available). See ADR-043. |
Monitoring and Observability
| Technology | Version | Role | Rationale |
|---|---|---|---|
| structlog | >= 24.4.0 | Structured logging | JSON-structured log output with context binding (plan_id, decision_id, actor_name, tool_name). Enables log aggregation, filtering, and correlation across complex plan hierarchies. |
| LangSmith | (optional) | LLM observability | Optional integration for tracing LLM calls, token usage, latency, and cost. Configured via CLEVERAGENTS_LANGSMITH_* environment variables. Syncs to LANGCHAIN_TRACING_V2. |
Additional Libraries
| Technology | Version | Role | Rationale |
|---|---|---|---|
| dependency-injector | >= 4.41.0 | DI container | DeclarativeContainer with Singleton, Factory, and Configuration providers. Wires all services, repositories, and infrastructure components. |
| watchdog | >= 4.0.0 | File system monitoring | Watches project resource directories for changes. Triggers automatic re-indexing when index.auto-reindex is enabled. |
| numpy | >= 2.1.0 | Numerical computing | Vector operations for embedding similarity computation, confidence score aggregation, and distance calculations in the vector store layer. |
| uvicorn | >= 0.30.1 | ASGI server | HTTP server for server mode A2A endpoint. Hosts the A2A JSON-RPC 2.0 server for remote plan execution, namespace resolution, and multi-user collaboration. |
Dependency Injection
!!! adr "Architecture Decision" The DI container design, wiring pattern, and testing strategy are defined in ADR-003: Dependency Injection.
All service dependencies in CleverAgents are wired through a DeclarativeContainer from the dependency-injector library (>= 4.41.0). This container is the single composition root for the entire application. No service instantiates its own dependencies — all are received through constructor injection managed by the container.
Container Structure
The DI container is a DeclarativeContainer subclass that declares all providers:
Singletonproviders for long-lived services: database engine, repository implementations, event bus, index backends, provider registry.Factoryproviders for per-request or per-operation objects: sandbox instances, plan execution contexts, LangGraph state graphs.Configurationprovider for loading and exposing the global TOML configuration and environment variables.
Wiring Pattern
The wiring pattern enforces the layered architecture (see Architecture Overview) by ensuring that dependencies always flow inward:
- The Domain Layer defines repository interfaces as
Protocolclasses (e.g.,PlanRepository,DecisionRepository,ResourceRepository). - The Infrastructure Layer provides concrete implementations (e.g.,
SQLAlchemyPlanRepository,SQLAlchemyDecisionRepository). - The DI container maps protocols to implementations using
SingletonorFactoryproviders. - Application Layer services declare their dependencies as constructor parameters typed to the domain protocols.
- The container auto-wires these dependencies at application startup.
This pattern ensures that domain and application code never imports infrastructure modules directly. Swapping backends (e.g., SQLite → PostgreSQL, FAISS → Qdrant, local execution → remote server) requires only changing the container provider mapping — no application or domain code changes.
Provider Types
| Provider Type | Use Case | Example |
|---|---|---|
Singleton |
Shared stateful services | Database session factory, event bus, index backends |
Factory |
Per-use transient objects | Sandbox instances, execution contexts |
Configuration |
External configuration values | TOML config keys, environment variables |
Testing Support
In test configurations, the container is reconfigured to supply mock or fake implementations:
FakeListLLM/FakeEmbeddingsfrom LangChain Community replace real LLM providers.- In-memory repositories replace SQLAlchemy-backed repositories.
- No-op sandbox strategies replace git worktree or filesystem copy strategies.
The container's override() context manager allows swapping providers for individual test cases without affecting the global configuration.
Container Rules
- All cross-layer dependencies must flow through the DI container. Direct instantiation of infrastructure components in application or domain code is prohibited.
- The container is initialized exactly once at application startup (in the CLI entry point or server bootstrap). It must not be re-initialized during request handling.
- Domain Layer code must never import or reference the
dependency-injectorlibrary. Domain protocols are plain PythonProtocolclasses with no DI framework annotations. - Every new service or repository must be registered in the container before use. Unregistered dependencies cause explicit startup failures, not silent runtime errors.
- A startup self-test verifies that all declared providers can be resolved without errors. This runs as part of the application bootstrap and in CI.
Data Validation
!!! adr "Architecture Decision" The data validation strategy, Pydantic usage, and schema generation approach are defined in ADR-004: Data Validation.
CleverAgents processes data from multiple untrusted or semi-trusted sources: user CLI input, YAML configuration files, LLM-generated tool call arguments, MCP server responses, environment variables, and database records. Every boundary crossing — between layers, between the system and external services, between the user and the application — is an opportunity for invalid data to enter the system and cause subtle downstream failures.
All domain models, configuration objects, and API schemas use Pydantic V2 (>= 2.7.0) for runtime data validation, JSON Schema generation, and serialization. Pydantic Settings (>= 2.11.0) extends this to environment variable loading. Pydantic serves as the type-safe data boundary between all layers.
Domain Models
Every domain entity — Plan, Decision, Action, Resource, Actor, Tool, Skill, Session, Invariant, AutomationProfile, Checkpoint, CorrectionAttempt — is a Pydantic BaseModel subclass. Fields use Python type annotations with Pydantic field validators for business rule enforcement.
Configuration Objects
YAML entity configuration files (actors, skills, tools, actions, resource types, automation profiles, context views) are parsed into Pydantic models. Validation errors are reported with field paths and human-readable messages before any processing occurs. A partially valid configuration must never be applied — YAML files must validate completely before any side effects occur.
Environment and Global Configuration
BaseSettings subclasses from Pydantic Settings handle environment variable loading with the CLEVERAGENTS_ prefix, .env file support, and nested model resolution. This implements the four-tier configuration resolution chain: CLI flag > environment variable > project-scoped config > global config file > built-in default.
JSON Schema Generation
Pydantic models automatically generate JSON Schema definitions, which are used for:
- Tool input/output schema definitions consumed by LLM tool-calling protocols.
- Validation of LLM-generated tool call arguments before execution.
- API request/response schema documentation.
JSON Schema generated from Pydantic models is the canonical schema for tool definitions. Manual schema definitions must not diverge from the model.
Serialization
Pydantic handles serialization to and from JSON, YAML (via dict intermediary), and database column types. Custom serializers are defined for domain-specific types (ULIDs, datetime formats, enum values).
Data Validation Rules
- All data crossing a layer boundary must pass through a Pydantic model. Raw dictionaries or untyped data structures must not propagate between layers.
- Strict mode is used where appropriate to prevent implicit type coercion (e.g., strings silently becoming integers).
- Validation error messages must include the field path and a human-readable explanation suitable for CLI display.
- Pyright in strict mode verifies that Pydantic model field types are consistent across the codebase.
- Schema drift tests verify that JSON Schema generated from Pydantic tool models matches the schemas expected by MCP and LangChain tool-calling protocols.
ACMS (Advanced Context Management System)
!!! adr "Architecture Decision" The ACMS architecture, UKO layer, CRP protocol, and context strategy framework are defined in ADR-014: Context Management (ACMS).
This section provides the complete architectural design of the Advanced Context Management System (ACMS). For the conceptual overview, core data types, CRP skill definition, strategy protocol, and plan lifecycle integration, see Core Concepts > Advanced Context Management System (ACMS).
Architectural Overview
@startuml
skinparam componentStyle rectangle
skinparam defaultFontSize 12
skinparam packageFontSize 13
skinparam packageFontStyle bold
skinparam componentFontSize 11
package "Actor / Skill" as actor #E0F7FA {
component [Issues ContextRequests\nvia Context Request Protocol] as CRP
}
package "Context Assembly Pipeline\n(10-component pluggable)" as pipeline #C8E6C9 {
package "Phase 1: Strategy Orchestration" {
component [StrategySelector] as SS
component [BudgetAllocator] as BA
component [StrategyExecutor] as SE
}
package "Phase 2: Fragment Fusion" {
component [Deduplicator → DepthResolver\n→ Scorer → Packer → Orderer] as Fusion
}
package "Phase 3: Finalization" {
component [PreambleGenerator\nSkeletonCompressor] as Final
}
}
package "Strategies" as strats #E8EAF6 {
component [ARCE\n(graph-aware)] as S1
component [Simple\n(keyword/embed)] as S2
component [Breadth/Depth\nNavigator] as S3
component [Custom\n(user-defined)] as S4
}
package "Backend Abstraction Layer (BAL)" as bal #FFF9C4 {
component [ScopedView\n(Text DB)] as TV
component [ScopedView\n(Vector DB)] as VV
component [ScopedView\n(Graph DB)] as GV
}
package "Physical Data Stores" as stores #E3F2FD {
component [Tantivy /\nSQLite FTS] as TDB
component [FAISS /\nQdrant / Weaviate] as VDB
component [Blazegraph / Jena /\nNeo4j / Stardog\n(UKO triples)] as GDB
}
package "Resource Registry" as rr #F3E5F5 {
component [Physical/virtual resources\nDAG links] as RR
}
CRP -down-> SS
SS -down-> BA
BA -down-> SE
SE -down-> S1
SE -down-> S2
SE -down-> S3
SE -down-> S4
S1 -down-> TV
S1 -down-> VV
S1 -down-> GV
S2 -down-> TV
S2 -down-> VV
S3 -down-> GV
S4 -down-> TV
SE -right[hidden]-> Fusion
Fusion -right[hidden]-> Final
TV -down-> TDB
VV -down-> VDB
GV -down-> GDB
TDB -down-> RR
VDB -down-> RR
GDB -down-> RR
@enduml
Key Design Principles:
- Strategy-agnostic framework: The framework itself has no opinion about how context is assembled. That is the job of strategies. The framework provides the request protocol, the data plumbing, and the Context Assembly Pipeline.
- Dynamic budget: The token budget for context assembly may change on every invocation. Strategies receive the current budget and must respect it. The pipeline's BudgetPacker enforces the budget as a hard ceiling.
- Hierarchical by default: Every context assembly occurs within the context of a plan hierarchy. The system tracks parent context and provides progressive focusing primitives.
- Provenance everywhere: Every piece of context delivered to an actor can be traced back to a specific resource, a specific location within that resource, a specific revision, and a specific UKO node.
- Pluggable Architecture: Every component can be extended or replaced.
- Progressive Enhancement: System works with basic text search, enhances with advanced features.
- Eager Indexing: Indices are built immediately when resources are added and kept continuously up-to-date.
- Agent Awareness: Agents understand available indices through skills.
- Real-time Synchronization: Indices update immediately as code changes.
Universal Knowledge Ontology (UKO)
Design Philosophy
The UKO is designed around a single principle: everything that an actor might want to know about a resource should be representable as a node in the knowledge graph, at any level of detail, with provenance back to the originating resource.
The ontology uses an inheritance hierarchy so that:
- Layer 0 defines universal concepts that apply to any information (code, docs, data, infra). DetailDepth is defined here as a non-negative integer with domain-agnostic semantics: depth 0 is the most minimal representation (just a name/identifier), and each increment reveals progressively more structure, content, and relationships. There is no fixed upper bound — the maximum meaningful depth depends on the domain.
- Layer 1 specializes for domain-specific concepts: general software (
uko-code:— modules, callables, types), documents (uko-doc:— sections, paragraphs, citations), data schemas (uko-data:— tables, columns, constraints), and infrastructure (uko-infra:— services, endpoints, config). Each domain registers a DetailLevelMap that assigns named labels to specific integer depths (e.g.,uko-code:mapsMODULE_LISTING→ 0,SIGNATURES→ 4,FULL_SOURCE→ 9). - Layer 2 specializes for paradigm/format-specific concepts within a domain: object-oriented (
uko-oo:), functional (uko-func:), procedural (uko-proc:) for code. Future extensions could add academic vs. technical for documents, or relational vs. graph for databases. Each paradigm extends its parent domain's DetailLevelMap, potentially inserting new named levels between existing ones (which shifts subsequent integer assignments). - Layer 3 specializes for technology-specific concepts: Python (
uko-py:), TypeScript (uko-ts:), Rust (uko-rs:), Java (uko-java:), and potentially Markdown, PostgreSQL, or others. Language/technology-specific DetailLevelMap extensions are added only when they provide meaningful additional semantics beyond the paradigm level.
This layering means a strategy that operates at Layer 0 can work with any resource — code, documents, databases, infrastructure — while a strategy that operates at Layer 3 can provide language-specific or technology-specific intelligence. The breadth-depth-navigator strategy typically operates at Layers 0-1 (universal graph traversal), while arce leverages all layers. DetailDepth resolution follows the DetailLevelMap inheritance chain: when a named level is requested, the system looks up the name in the most specific map available for the target UKO node type (e.g., uko-py: first), then walks up to the parent map (uko-oo: → uko-code: → uko:) until a match is found. When a raw integer is specified, it is used directly.
Ontology Hierarchy
The UKO defines five namespace prefixes:
| Prefix | IRI | Purpose |
|---|---|---|
uko: |
https://cleveragents.ai/ontology/uko# |
Layer 0: Universal foundation |
uko-code: |
https://cleveragents.ai/ontology/uko/code# |
Layer 1: General software |
uko-oo: |
https://cleveragents.ai/ontology/uko/oo# |
Layer 2: Object-oriented paradigm |
uko-func: |
https://cleveragents.ai/ontology/uko/func# |
Layer 2: Functional paradigm |
uko-proc: |
https://cleveragents.ai/ontology/uko/proc# |
Layer 2: Procedural paradigm |
uko-py: |
https://cleveragents.ai/ontology/uko/py# |
Layer 3: Python-specific |
uko-ts: |
https://cleveragents.ai/ontology/uko/ts# |
Layer 3: TypeScript-specific |
uko-rs: |
https://cleveragents.ai/ontology/uko/rs# |
Layer 3: Rust-specific |
uko-java: |
https://cleveragents.ai/ontology/uko/java# |
Layer 3: Java-specific |
uko-doc: |
https://cleveragents.ai/ontology/uko/doc# |
Layer 1: Documents |
uko-data: |
https://cleveragents.ai/ontology/uko/data# |
Layer 1: Data schemas |
uko-infra: |
https://cleveragents.ai/ontology/uko/infra# |
Layer 1: Infrastructure |
Layer 0: Universal Foundation (uko:)
Every UKO node, regardless of domain, is one of five base classes:
@prefix uko: <https://cleveragents.ai/ontology/uko#> . @prefix owl: <http://www.w3.org/2002/07/owl#> . @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .# ── Base Classes ────────────────────────────────────────────────── uko:InformationUnit a owl:Class ; rdfs:comment "The root of every UKO node. Anything that can appear in an actor's context." .
uko:Container a owl:Class ; rdfs:subClassOf uko:InformationUnit ; rdfs:comment "An information unit that contains other information units (file, module, class, section)." .
uko:Atom a owl:Class ; rdfs:subClassOf uko:InformationUnit ; rdfs:comment "A leaf-level information unit (function body, paragraph, config value)." .
uko:Annotation a owl:Class ; rdfs:subClassOf uko:InformationUnit ; rdfs:comment "Metadata attached to another information unit (comment, docstring, attribute)." .
uko:Boundary a owl:Class ; rdfs:subClassOf uko:InformationUnit ; rdfs:comment "An interface point between containers (export, API endpoint, public method signature)." .
# ── Core Relationships ──────────────────────────────────────────── uko:contains a owl:ObjectProperty ; rdfs:domain uko:Container ; rdfs:range uko:InformationUnit ; rdfs:comment "Parent contains child (e.g., module contains class)." .
uko:references a owl:ObjectProperty ; rdfs:domain uko:InformationUnit ; rdfs:range uko:InformationUnit ; rdfs:comment "Weak reference (e.g., a function mentions a type in a docstring)." .
uko:dependsOn a owl:ObjectProperty ; rdfs:domain uko:InformationUnit ; rdfs:range uko:InformationUnit ; rdfs:comment "Strong dependency (e.g., import, inheritance, call)." .
# ── Content Properties ──────────────────────────────────────────── uko:hasRendering a owl:DatatypeProperty ; rdfs:domain uko:InformationUnit ; rdfs:range xsd:string ; rdfs:comment "A rendered text representation of this node at a specific detail depth. Multiple renderings at different depths may exist for the same node. The depth is indicated by the associated uko:renderingDepth value." .
uko:renderingDepth a owl:DatatypeProperty ; rdfs:domain uko:InformationUnit ; rdfs:range xsd:nonNegativeInteger ; rdfs:comment "The integer detail depth at which the associated uko:hasRendering was produced. Paired with uko:hasRendering via a blank node or reification." .
uko:hasFullContent a owl:DatatypeProperty ; rdfs:domain uko:InformationUnit ; rdfs:range xsd:string ; rdfs:comment "Shorthand for the maximum-depth rendering of this node (complete, unabridged content). Equivalent to uko:hasRendering at the domain's maximum depth." .
# ── Provenance ──────────────────────────────────────────────────── uko:sourceResource a owl:ObjectProperty ; rdfs:domain uko:InformationUnit ; rdfs:comment "The CleverAgents Resource (by ULID) this node was extracted from." .
uko:sourcePath a owl:DatatypeProperty ; rdfs:domain uko:InformationUnit ; rdfs:range xsd:string ; rdfs:comment "Path within the resource (e.g., file path relative to the resource root)." .
uko:sourceRange a owl:DatatypeProperty ; rdfs:domain uko:InformationUnit ; rdfs:range xsd:string ; rdfs:comment "Byte or line range within the source file (e.g., '42:1-87:0')." .
# ── Temporal ────────────────────────────────────────────────────── uko:validFrom a owl:DatatypeProperty ; rdfs:domain uko:InformationUnit ; rdfs:range xsd:dateTime .
uko:validUntil a owl:DatatypeProperty ; rdfs:domain uko:InformationUnit ; rdfs:range xsd:dateTime .
uko:isCurrent a owl:DatatypeProperty ; rdfs:domain uko:InformationUnit ; rdfs:range xsd:boolean .
uko:isRevisionOf a owl:ObjectProperty ; rdfs:domain uko:InformationUnit ; rdfs:range uko:InformationUnit ; rdfs:comment "Links a revised node to its predecessor." .
Layer 1: Domain Specializations
Layer 1 specializes the universal Layer 0 foundation for four domains: general software (uko-code:), documents (uko-doc:), data schemas (uko-data:), and infrastructure (uko-infra:). Each domain defines its own classes, relationships, properties, and a DetailLevelMap mapping named levels to integer DetailDepth values.
General Software (uko-code:):
@prefix uko-code: <https://cleveragents.ai/ontology/uko/code#> .uko-code:Module a owl:Class ; rdfs:subClassOf uko:Container ; rdfs:comment "A source code module (file, package, namespace)." .
uko-code:Callable a owl:Class ; rdfs:subClassOf uko:Atom ; rdfs:comment "Any callable unit (function, method, procedure, lambda)." .
uko-code:TypeDefinition a owl:Class ; rdfs:subClassOf uko:Atom ; rdfs:comment "A type or schema definition (class, struct, interface, enum, typedef)." .
uko-code:TestCase a owl:Class ; rdfs:subClassOf uko:Atom ; rdfs:comment "A test case or test function." .
uko-code:Import a owl:Class ; rdfs:subClassOf uko:Annotation ; rdfs:comment "An import/include/require statement." .
uko-code:hasReturnType a owl:DatatypeProperty ; rdfs:domain uko-code:Callable ; rdfs:range xsd:string .
uko-code:hasParameters a owl:DatatypeProperty ; rdfs:domain uko-code:Callable ; rdfs:range xsd:string ; rdfs:comment "JSON-encoded parameter list." .
uko-code:testsCallable a owl:ObjectProperty ; rdfs:domain uko-code:TestCase ; rdfs:range uko-code:Callable ; rdfs:comment "Links a test case to the callable it tests." .
Documents (uko-doc:) — Layer 1:
The document ontology represents structured and semi-structured documents (articles, technical papers, documentation, reports) as a hierarchy of containers and atoms. A key design principle is semantic awareness: when the system processes a document, it does not merely parse the structural hierarchy (sections, subsections, paragraphs) — it also analyzes the content of each text unit to infer implicit relationships. If a paragraph discusses a concept that is the subject of another section (even without an explicit hyperlink or mention by name), the RDF graph captures that relationship via uko:references or uko-doc:discussesTopic edges. This enables strategies to surface related content that a purely structural traversal would miss.
@prefix uko-doc: <https://cleveragents.ai/ontology/uko/doc#> .# ── Container Classes ────────────────────────────────────────────── uko-doc:Document a owl:Class ; rdfs:subClassOf uko:Container ; rdfs:comment "A complete document (article, report, manual, specification)." .
uko-doc:Part a owl:Class ; rdfs:subClassOf uko:Container ; rdfs:comment "A top-level division of a document (e.g., Part I, Part II)." .
uko-doc:Chapter a owl:Class ; rdfs:subClassOf uko:Container ; rdfs:comment "A chapter within a document or part." .
uko-doc:Section a owl:Class ; rdfs:subClassOf uko:Container ; rdfs:comment "A numbered or titled section within a chapter." .
uko-doc:Subsection a owl:Class ; rdfs:subClassOf uko-doc:Section ; rdfs:comment "A subsection nested within a section (arbitrary depth)." .
# ── Atom Classes ─────────────────────────────────────────────────── uko-doc:Paragraph a owl:Class ; rdfs:subClassOf uko:Atom ; rdfs:comment "A paragraph of prose text." .
uko-doc:CodeBlock a owl:Class ; rdfs:subClassOf uko:Atom ; rdfs:comment "An inline code listing or example within a document." .
uko-doc:Figure a owl:Class ; rdfs:subClassOf uko:Atom ; rdfs:comment "A figure, diagram, or image with an optional caption." .
uko-doc:Table a owl:Class ; rdfs:subClassOf uko:Atom ; rdfs:comment "A tabular data element within a document." .
uko-doc:BlockQuote a owl:Class ; rdfs:subClassOf uko:Atom ; rdfs:comment "A quoted passage from another source." .
uko-doc:ListBlock a owl:Class ; rdfs:subClassOf uko:Atom ; rdfs:comment "An ordered or unordered list." .
# ── Annotation Classes ───────────────────────────────────────────── uko-doc:Footnote a owl:Class ; rdfs:subClassOf uko:Annotation ; rdfs:comment "A footnote or endnote." .
uko-doc:Citation a owl:Class ; rdfs:subClassOf uko:Annotation ; rdfs:comment "A bibliographic citation or reference." .
uko-doc:Comment a owl:Class ; rdfs:subClassOf uko:Annotation ; rdfs:comment "An editorial comment or annotation (e.g., HTML comment, review note)." .
# ── Boundary Classes ─────────────────────────────────────────────── uko-doc:TableOfContents a owl:Class ; rdfs:subClassOf uko:Boundary ; rdfs:comment "The document's table of contents — an interface into the document's structure." .
uko-doc:Index a owl:Class ; rdfs:subClassOf uko:Boundary ; rdfs:comment "A back-of-book index or keyword index." .
uko-doc:Glossary a owl:Class ; rdfs:subClassOf uko:Boundary ; rdfs:comment "A glossary of terms defined in the document." .
# ── Document-Specific Relationships ──────────────────────────────── uko-doc:discussesTopic a owl:ObjectProperty ; rdfs:subPropertyOf uko:references ; rdfs:domain uko:InformationUnit ; rdfs:range uko:InformationUnit ; rdfs:comment "Semantic relationship: this unit discusses a topic that is the subject of another unit. Inferred by the document analyzer even when no explicit link exists." .
uko-doc:cites a owl:ObjectProperty ; rdfs:subPropertyOf uko:references ; rdfs:domain uko:InformationUnit ; rdfs:range uko-doc:Citation ; rdfs:comment "This unit cites a bibliographic reference." .
uko-doc:crossReferences a owl:ObjectProperty ; rdfs:subPropertyOf uko:references ; rdfs:domain uko:InformationUnit ; rdfs:range uko:InformationUnit ; rdfs:comment "Explicit cross-reference (e.g., 'see Section 3.2')." .
uko-doc:precedes a owl:ObjectProperty ; rdfs:domain uko:InformationUnit ; rdfs:range uko:InformationUnit ; rdfs:comment "Reading order: this unit comes before the target unit." .
# ── Document-Specific Properties ─────────────────────────────────── uko-doc:headingLevel a owl:DatatypeProperty ; rdfs:domain uko-doc:Section ; rdfs:range xsd:integer ; rdfs:comment "The nesting depth of this section (1 = top-level, 2 = subsection, etc.)." .
uko-doc:headingText a owl:DatatypeProperty ; rdfs:domain uko-doc:Section ; rdfs:range xsd:string ; rdfs:comment "The title/heading text of this section." .
uko-doc:wordCount a owl:DatatypeProperty ; rdfs:domain uko:InformationUnit ; rdfs:range xsd:integer ; rdfs:comment "Word count of the text content in this unit." .
uko-doc:language a owl:DatatypeProperty ; rdfs:domain uko-doc:Document ; rdfs:range xsd:string ; rdfs:comment "Natural language of the document (e.g., 'en', 'fr')." .
uko-doc:topicKeywords a owl:DatatypeProperty ; rdfs:domain uko:InformationUnit ; rdfs:range xsd:string ; rdfs:comment "JSON-encoded list of topic keywords extracted from this unit." .
Semantic Inference for Documents: When the document analyzer processes a document, it performs semantic analysis beyond pure structural parsing:
- Topic extraction: Each paragraph, section, and subsection is analyzed for key topics. These become
uko-doc:topicKeywordsproperties. - Implicit cross-references: If Paragraph A in Section 2 discusses "authentication flow" and Section 5 is titled "Authentication Architecture", the analyzer creates a
uko-doc:discussesTopicedge from Paragraph A to Section 5 — even though no explicit link exists in the source document. - Citation resolution: Bibliographic citations are linked to the paragraphs that cite them.
- Concept clustering: Paragraphs discussing related concepts across different sections are linked via
uko:references, enabling strategies to pull in contextually relevant content from distant parts of a document.
This semantic awareness means that a breadth-depth-navigator strategy operating on a document resource can follow not just the structural containment hierarchy but also the semantic topic graph, surfacing related sections that an author would need to consider when modifying any part of the document.
Document DetailLevelMap Refinements (per-type rendering at selected depths — see Core Data Types for the authoritative uko-doc: depth map):
| Depth | Named Level | uko-doc:Document |
uko-doc:Section |
uko-doc:Paragraph |
|---|---|---|---|---|
| 0 | TITLE_ONLY |
Document title only | Heading text only | — (not individually listed) |
| 1 | TABLE_OF_CONTENTS_L1 |
Title + top-level section headings | Heading + child section headings | First sentence only |
| 4 | TOC_WITH_SUMMARIES |
All headings + one-sentence abstract per section | Heading + paragraph count + topic keywords + abstract | First two sentences + topic keywords |
| 8 | STRUCTURAL_DETAIL |
Full TOC + figure/table captions + list item headers | Heading + first sentence of each paragraph + all captions | Full text with inline code and links preserved |
| 10 | FULL_CONTENT |
Complete document content | Complete section content including all children | Complete paragraph text with all formatting |
Data Schemas (uko-data:) — Layer 1:
The data schema ontology represents databases, data warehouses, and structured data stores. It models the schema structure (schemas, tables, columns, constraints) and captures query-time relationships (views referencing tables, foreign keys, stored procedures accessing tables). Like the document ontology, the system performs semantic analysis: when a stored procedure references columns from multiple tables, those dependency edges are created automatically.
@prefix uko-data: <https://cleveragents.ai/ontology/uko/data#> .# ── Container Classes ────────────────────────────────────────────── uko-data:Database a owl:Class ; rdfs:subClassOf uko:Container ; rdfs:comment "A database instance containing schemas." .
uko-data:Schema a owl:Class ; rdfs:subClassOf uko:Container ; rdfs:comment "A database schema (namespace for tables, views, etc.)." .
uko-data:Table a owl:Class ; rdfs:subClassOf uko:Container ; rdfs:comment "A database table containing columns." .
uko-data:View a owl:Class ; rdfs:subClassOf uko:Container ; rdfs:comment "A database view (virtual table defined by a query)." .
# ── Atom Classes ─────────────────────────────────────────────────── uko-data:Column a owl:Class ; rdfs:subClassOf uko:Atom ; rdfs:comment "A column within a table or view." .
uko-data:StoredProcedure a owl:Class ; rdfs:subClassOf uko:Atom ; rdfs:comment "A stored procedure or function in the database." .
uko-data:Trigger a owl:Class ; rdfs:subClassOf uko:Atom ; rdfs:comment "A database trigger." .
uko-data:Migration a owl:Class ; rdfs:subClassOf uko:Atom ; rdfs:comment "A schema migration (DDL change script)." .
# ── Annotation Classes ───────────────────────────────────────────── uko-data:Constraint a owl:Class ; rdfs:subClassOf uko:Annotation ; rdfs:comment "A column or table constraint (NOT NULL, UNIQUE, CHECK, etc.)." .
uko-data:Index a owl:Class ; rdfs:subClassOf uko:Annotation ; rdfs:comment "A database index on one or more columns." .
uko-data:ColumnComment a owl:Class ; rdfs:subClassOf uko:Annotation ; rdfs:comment "A COMMENT ON COLUMN annotation in the database." .
# ── Boundary Classes ─────────────────────────────────────────────── uko-data:ForeignKey a owl:Class ; rdfs:subClassOf uko:Boundary ; rdfs:comment "A foreign key constraint — an interface point between tables." .
uko-data:APIEndpoint a owl:Class ; rdfs:subClassOf uko:Boundary ; rdfs:comment "A database-level API endpoint (e.g., PostgREST, GraphQL)." .
# ── Data-Specific Relationships ──────────────────────────────────── uko-data:foreignKeyTo a owl:ObjectProperty ; rdfs:subPropertyOf uko:dependsOn ; rdfs:domain uko-data:Column ; rdfs:range uko-data:Column ; rdfs:comment "Foreign key relationship from this column to a column in another table." .
uko-data:viewReferences a owl:ObjectProperty ; rdfs:subPropertyOf uko:dependsOn ; rdfs:domain uko-data:View ; rdfs:range uko-data:Table ; rdfs:comment "This view's query references columns from the target table." .
uko-data:procedureAccesses a owl:ObjectProperty ; rdfs:subPropertyOf uko:dependsOn ; rdfs:domain uko-data:StoredProcedure ; rdfs:range uko-data:Table ; rdfs:comment "This stored procedure reads from or writes to the target table." .
uko-data:triggerFires a owl:ObjectProperty ; rdfs:subPropertyOf uko:dependsOn ; rdfs:domain uko-data:Trigger ; rdfs:range uko-data:Table ; rdfs:comment "This trigger fires on events on the target table." .
uko-data:migrationAlters a owl:ObjectProperty ; rdfs:subPropertyOf uko:dependsOn ; rdfs:domain uko-data:Migration ; rdfs:range uko-data:Table ; rdfs:comment "This migration modifies the schema of the target table." .
# ── Data-Specific Properties ────────────────────────────────────── uko-data:dataType a owl:DatatypeProperty ; rdfs:domain uko-data:Column ; rdfs:range xsd:string ; rdfs:comment "SQL data type (e.g., 'VARCHAR(255)', 'INTEGER', 'JSONB')." .
uko-data:isNullable a owl:DatatypeProperty ; rdfs:domain uko-data:Column ; rdfs:range xsd:boolean .
uko-data:isPrimaryKey a owl:DatatypeProperty ; rdfs:domain uko-data:Column ; rdfs:range xsd:boolean .
uko-data:rowEstimate a owl:DatatypeProperty ; rdfs:domain uko-data:Table ; rdfs:range xsd:long ; rdfs:comment "Estimated row count from database statistics." .
uko-data:viewDefinition a owl:DatatypeProperty ; rdfs:domain uko-data:View ; rdfs:range xsd:string ; rdfs:comment "The SQL query that defines this view." .
uko-data:procedureBody a owl:DatatypeProperty ; rdfs:domain uko-data:StoredProcedure ; rdfs:range xsd:string ; rdfs:comment "The SQL/PL body of the stored procedure." .
Database DetailLevelMap Refinements (per-type rendering at selected depths — see Core Data Types for the authoritative uko-data: depth map):
| Depth | Named Level | uko-data:Database |
uko-data:Table |
uko-data:Column |
|---|---|---|---|---|
| 0 | SCHEMA_LISTING |
Schema names only | — (not individually listed) | — |
| 1 | TABLE_LISTING |
Schema names + table/view names | Table name only | — |
| 3 | TYPED_COLUMNS |
+ column types + nullability | Column names + types + nullability | Name + type + nullable |
| 5 | RELATIONSHIPS |
+ foreign key graph + row estimates | + primary key + foreign key targets + row estimate | + FK references + constraints |
| 7 | DDL |
Full CREATE TABLE DDL with constraints | Full CREATE TABLE DDL + index definitions | Full column DDL + all constraints |
| 11 | FULL_CATALOG |
Complete DDL + views + procedures + sample data | Full DDL + triggers + sample rows + statistics | Full DDL + value distribution + sample values |
Infrastructure (uko-infra:) — Layer 1:
@prefix uko-infra: <https://cleveragents.ai/ontology/uko/infra#> .# ── Container Classes ────────────────────────────────────────────── uko-infra:Service a owl:Class ; rdfs:subClassOf uko:Container ; rdfs:comment "A deployable service or application." .
uko-infra:ConfigBlock a owl:Class ; rdfs:subClassOf uko:Container ; rdfs:comment "A configuration block or section (e.g., a YAML map, TOML table)." .
uko-infra:DeploymentUnit a owl:Class ; rdfs:subClassOf uko:Container ; rdfs:comment "A deployment unit (e.g., Kubernetes Deployment, Docker Compose service)." .
# ── Atom Classes ─────────────────────────────────────────────────── uko-infra:ConfigKey a owl:Class ; rdfs:subClassOf uko:Atom ; rdfs:comment "A configuration key-value pair." .
uko-infra:EnvironmentVariable a owl:Class ; rdfs:subClassOf uko:Atom ; rdfs:comment "An environment variable definition." .
# ── Boundary Classes ─────────────────────────────────────────────── uko-infra:Endpoint a owl:Class ; rdfs:subClassOf uko:Boundary ; rdfs:comment "A network endpoint (HTTP route, gRPC service, message queue)." .
uko-infra:Port a owl:Class ; rdfs:subClassOf uko:Boundary ; rdfs:comment "A network port binding." .
# ── Infrastructure-Specific Relationships ────────────────────────── uko-infra:connectsTo a owl:ObjectProperty ; rdfs:subPropertyOf uko:dependsOn ; rdfs:domain uko-infra:Service ; rdfs:range uko-infra:Service ; rdfs:comment "This service connects to or depends on the target service." .
uko-infra:exposes a owl:ObjectProperty ; rdfs:domain uko-infra:Service ; rdfs:range uko-infra:Endpoint ; rdfs:comment "This service exposes the target endpoint." .
Layer 2: Paradigm / Format Specializations
Layer 2 refines Layer 1 domains with paradigm- or format-specific specializations. For code: OO, functional, and procedural paradigms. For documents: academic vs. technical. For databases: relational vs. graph. Each may extend the parent domain's DetailLevelMap with additional named levels.
Object-Oriented (uko-oo:):
@prefix uko-oo: <https://cleveragents.ai/ontology/uko/oo#> .uko-oo:Class a owl:Class ; rdfs:subClassOf uko-code:TypeDefinition, uko:Container ; rdfs:comment "An OO class that contains methods and attributes." .
uko-oo:Interface a owl:Class ; rdfs:subClassOf uko-code:TypeDefinition, uko:Boundary ; rdfs:comment "An interface or abstract base class." .
uko-oo:Method a owl:Class ; rdfs:subClassOf uko-code:Callable .
uko-oo:Attribute a owl:Class ; rdfs:subClassOf uko:Atom .
uko-oo:inheritsFrom a owl:ObjectProperty ; rdfs:subPropertyOf uko:dependsOn ; rdfs:domain uko-oo:Class ; rdfs:range uko-oo:Class .
uko-oo:implements a owl:ObjectProperty ; rdfs:subPropertyOf uko:dependsOn ; rdfs:domain uko-oo:Class ; rdfs:range uko-oo:Interface .
Functional (uko-func:):
@prefix uko-func: <https://cleveragents.ai/ontology/uko/func#> .uko-func:PureFunction a owl:Class ; rdfs:subClassOf uko-code:Callable .
uko-func:TypeClass a owl:Class ; rdfs:subClassOf uko-code:TypeDefinition, uko:Boundary .
uko-func:Monad a owl:Class ; rdfs:subClassOf uko-code:TypeDefinition .
Code DetailLevelMap Refinements by Paradigm and Language:
The authoritative DetailLevelMap definitions — with full integer depth assignments and named level descriptions for each domain — are specified in Core Concepts > Advanced Context Management System (ACMS) > Core Data Types > DetailDepth and DetailLevelMap. The tables below show how paradigm-specific (Layer 2) and language-specific (Layer 3) UKO node types map their content at selected depth levels from the parent domain's map. These are illustrative excerpts showing what content each node type contributes at key depths, not exhaustive depth-by-depth definitions.
General Software (uko-code:) — Layer 1 (per-type rendering at selected depths):
| Depth | Named Level | uko-code:Module |
uko-code:Callable |
uko-code:TypeDefinition |
|---|---|---|---|---|
| 0 | MODULE_LISTING |
Module name only | — (not individually listed) | — (not individually listed) |
| 2 | MEMBER_LISTING |
+ names of top-level members | Function name (no signature) | Type name + kind (class/struct/enum) |
| 4 | SIGNATURES |
+ all top-level signatures | Name + parameter types + return type | Name + field types + method signatures |
| 6 | STRUCTURAL_OUTLINE |
+ control flow outline of callables | Signature + control flow outline (branches, loops) | Name + all field + method signatures + inheritance |
| 9 | FULL_SOURCE |
Complete module source | Complete function body | Complete type definition |
Object-Oriented (uko-oo:) — Layer 2 (per-type rendering at selected depths):
| Depth | Named Level | uko-oo:Class |
uko-oo:Method |
uko-oo:Interface |
|---|---|---|---|---|
| 2 | MEMBER_LISTING |
Class name + parent classes | Method name + visibility | Interface name |
| 3 | CLASS_HIERARCHY (OO-inserted) |
+ inheritance chain + interface implementations | — | + method signatures |
| 4 | SIGNATURES |
+ attribute types + method signatures | + params + return type + visibility | + all method signatures with param types |
| 5 | SIGNATURES_WITH_DOCS |
+ class docstring + attribute descriptions | + docstring + side effect hints | + description + implementor count |
| 9+ | FULL_SOURCE |
Complete class definition | Complete method body | Complete interface definition |
Functional (uko-func:) — Layer 2 (per-type rendering at selected depths):
| Depth | Named Level | uko-func:PureFunction |
uko-func:TypeClass |
uko-func:Monad |
|---|---|---|---|---|
| 2 | MEMBER_LISTING |
Function name | Type class name | Monad name |
| 4 | SIGNATURES |
Name + type signature + purity annotation | Name + associated types | Name + bind/return types |
| 6 | STRUCTURAL_OUTLINE |
+ pattern match structure + guard conditions | + all method signatures + laws | + bind/return implementations |
| 9 | FULL_SOURCE |
Complete function definition | Complete type class definition | Complete monad definition |
Procedural (uko-proc:) — Layer 2 (rendering at selected depths):
| Depth | Named Level | Procedural Specifics |
|---|---|---|
| 2 | MEMBER_LISTING |
Function names + global variable names (no types) |
| 4 | SIGNATURES |
Function declarations + global variable types + header file exports |
| 5 | SIGNATURES_WITH_DOCS |
+ function descriptions + global variable purposes + header summaries |
| 6 | STRUCTURAL_OUTLINE |
+ function signatures with parameter names + struct definitions + macro signatures |
| 9 | FULL_SOURCE |
Complete source including all function bodies, macros, and inline assembly |
Layer 3: Technology-Specific Specializations
Layer 3 extends Layer 2 (or Layer 1 directly) with technology-specific refinements — e.g., Python, TypeScript, Rust, Java for code; PostgreSQL, MongoDB for databases; Markdown, LaTeX for documents. Each may insert additional named DetailLevelMap levels into its parent's map.
Language-Specific (Layer 3) DetailLevelMap insertions — examples:
Each language extension may insert additional named levels into its parent paradigm's map, shifting subsequent depths upward to maintain consecutive integer numbering (see the fully expanded effective maps in Core Data Types). The table below summarizes the key additions:
| Language | Inserted Levels | Additional Content at Those Depths |
|---|---|---|
Python (uko-py:) |
DECORATED_SIGNATURES (between SIGNATURES_WITH_DOCS and STRUCTURAL_OUTLINE), TYPE_STUBS (between KEY_LOGIC and NEAR_COMPLETE), WITH_TESTS (beyond FULL_SOURCE) |
Decorator chains (@property, @staticmethod, custom), .pyi stub type annotations, associated test cases |
TypeScript (uko-ts:) |
(extends at SIGNATURES level) | export/default export markers, declare ambient types, generic type parameters, mapped/conditional types |
Rust (uko-rs:) |
(extends at SIGNATURES level) | Visibility modifiers (pub, pub(crate)), lifetime parameters, trait bounds, unsafe markers, #[derive] macros, #[must_use] annotations |
Java (uko-java:) |
(extends at SIGNATURES level) | Package visibility, final/abstract/sealed modifiers, annotation lists (@Override, @Deprecated), checked exceptions |
Not every language requires a Layer 3 extension. If a language's semantics are fully captured by the Layer 1 (uko-code:) and Layer 2 (paradigm) maps, no Layer 3 extension is needed. The system falls back to the nearest parent layer's DetailLevelMap.
The Universal View Guarantee
Because every domain-specific class ultimately inherits from Layer 0 (uko:InformationUnit, uko:Container, etc.), any strategy or query written against Layer 0 automatically works across all domains — not just different programming languages, but across code, documents, databases, and infrastructure alike. For example:
# "Find all containers that depend on this atom" — works for Python classes,
# TypeScript modules, Rust crates, SQL tables, document sections, or config blocks.
SELECT ?container WHERE {
?container a uko:Container .
?container uko:dependsOn <target_atom> .
}
Similarly, a DetailDepth request at Layer 0 is resolved by the most specific DetailLevelMap available in the inheritance chain. A request for depth 3 of a uko:Container will produce a member summary with one-line docstrings for a Python module (MEMBER_SUMMARY), a full table of contents for a document chapter (FULL_TOC), or typed column definitions for a database table (TYPED_COLUMNS) — all through the same universal integer-depth interface, with each domain mapping the integer to its domain-appropriate rendering.
Provenance Contract
Every UKO node carries provenance back to the originating resource:
uko-py:class/AuthManager
uko:sourceResource <resource-ulid-01HXM7...>
uko:sourcePath "src/auth/manager.py"
uko:sourceRange "15:1-87:0"
uko:validFrom "2026-01-15T10:30:00Z"
uko:isCurrent true
This allows the PreambleGenerator pipeline component to generate provenance summaries in the context preamble, and allows actors to trace any piece of context back to its exact source location.
Temporal Data Model and Storage Tiers
Revision-Aware RDF
Each UKO node carries temporal metadata. When code changes, existing nodes are not deleted — instead, their validUntil is set and isCurrent becomes false, and a new node is created with isRevisionOf pointing to the predecessor:
# Before code change: uko-py:class/AuthManager_v1 uko:isCurrent true ; uko:validFrom "2026-01-10T00:00:00Z"^^xsd:dateTime .# After code change: uko-py:class/AuthManager_v1 uko:isCurrent false ; uko:validFrom "2026-01-10T00:00:00Z"^^xsd:dateTime ; uko:validUntil "2026-01-15T10:30:00Z"^^xsd:dateTime .
uko-py:class/AuthManager_v2 uko:isCurrent true ; uko:validFrom "2026-01-15T10:30:00Z"^^xsd:dateTime ; uko:isRevisionOf uko-py:class/AuthManager_v1 .
This temporal chain is what enables the temporal-archaeology strategy to find patterns like "this class was refactored 3 times in the last month" or "this function's signature changed after we updated the auth library."
Three Storage Tiers with Temporal Alignment
| Tier | Content | Retention | Access Pattern | Temporal |
|---|---|---|---|---|
| Hot | Current UKO graph + recent embeddings + active text index | Until resource removed | Direct query via BAL | isCurrent = true only |
| Warm | Recent decision contexts, plan context snapshots | context.tiers.warm.retention-hours (default: 24h) |
Scoped query via plan hierarchy | Current + recently-expired nodes |
| Cold | Archived decision contexts, historical UKO snapshots | context.tiers.cold.retention-days (default: 90d) |
Full historical query | All temporal versions |
Scoped Views and Plan Subgraph Projection
Resource Scope Resolution
When a plan is created (via agents plan use), the system resolves its resource scope:
class ResourceScopeResolver: """Determines which resources a plan can see."""<span style="color: magenta; font-weight: 600;">def</span> <span style="color: cyan; font-weight: 600;">resolve</span>(<span style="color: cyan;">self</span>, plan: <span style="color: cyan;">Plan</span>) -> <span style="color: cyan;">ResourceScope</span>: <span style="color: #888;"># 1. Get all projects this plan targets</span> projects = plan.target_projects <span style="color: #888;"># 2. Collect all resources linked to those projects</span> resources = <span style="color: cyan;">set</span>() <span style="color: magenta; font-weight: 600;">for</span> project <span style="color: magenta; font-weight: 600;">in</span> projects: resources.update(project.linked_resources) <span style="color: #888;"># 3. Expand to include child resources (DAG traversal)</span> expanded = <span style="color: cyan;">set</span>() <span style="color: magenta; font-weight: 600;">for</span> resource <span style="color: magenta; font-weight: 600;">in</span> resources: expanded.add(resource) expanded.update(<span style="color: cyan;">self</span>.registry.get_descendants(resource)) <span style="color: #888;"># 4. Apply context view filters (include/exclude)</span> view = <span style="color: cyan;">self</span>._resolve_view(plan) filtered = <span style="color: cyan;">self</span>._apply_filters(expanded, view) <span style="color: magenta; font-weight: 600;">return</span> ResourceScope(resources=filtered, projects=projects)
UKO Subgraph Projection
Given a resource scope, the system projects only the relevant UKO subgraph:
class UKOSubgraphProjector: """Projects the UKO graph to only include nodes from scoped resources."""<span style="color: magenta; font-weight: 600;">def</span> <span style="color: cyan; font-weight: 600;">project</span>(<span style="color: cyan;">self</span>, scope: <span style="color: cyan;">ResourceScope</span>, graph_backend: <span style="color: cyan;">GraphBackend</span>) -> <span style="color: cyan;">ProjectedGraph</span>: <span style="color: #888;"># Only include UKO nodes whose sourceResource is in scope</span> <span style="color: magenta; font-weight: 600;">return</span> graph_backend.subgraph_by_resources( resource_ulids=[r.ulid <span style="color: magenta; font-weight: 600;">for</span> r <span style="color: magenta; font-weight: 600;">in</span> scope.resources], include_temporal=scope.temporal_scope, )
Scoped Backend Views
Each backend (text, vector, graph) is wrapped in a ScopedView that automatically filters queries to only return results from in-scope resources:
class ScopedBackendView: """Wraps a backend to restrict queries to a resource scope."""<span style="color: magenta; font-weight: 600;">def</span> <span style="color: cyan; font-weight: 600;">__init__</span>(<span style="color: cyan;">self</span>, backend, scope: <span style="color: cyan;">ResourceScope</span>): <span style="color: cyan;">self</span>.backend = backend <span style="color: cyan;">self</span>.scope = scope <span style="color: cyan;">self</span>._resource_filter = {r.ulid <span style="color: magenta; font-weight: 600;">for</span> r <span style="color: magenta; font-weight: 600;">in</span> scope.resources} <span style="color: magenta; font-weight: 600;">def</span> <span style="color: cyan; font-weight: 600;">search</span>(<span style="color: cyan;">self</span>, query, **kwargs): <span style="color: #888;"># Inject the resource filter into the backend query</span> results = <span style="color: cyan;">self</span>.backend.search( query, resource_filter=<span style="color: cyan;">self</span>._resource_filter, **kwargs, ) <span style="color: magenta; font-weight: 600;">return</span> results
This is critical for multi-project isolation: a plan targeting local/api-service never sees resources from local/frontend, even if both projects share the same physical database indices.
Backend Abstraction Layer (BAL)
The BAL provides a uniform interface to heterogeneous data stores. Each backend type has a protocol:
Backend Protocols
class TextBackend(Protocol): def search(self, query: str, *, resource_filter: set[str], limit: int = 20) -> list[TextResult]: ... def index(self, resource_ulid: str, content: str, metadata: dict) -> None: ...class VectorBackend(Protocol): def search(self, embedding: list[float], *, resource_filter: set[str], limit: int = 20, threshold: float = 0.3) -> list[VectorResult]: ... def index(self, resource_ulid: str, chunks: list[EmbeddingChunk]) -> None: ...
class GraphBackend(Protocol): def query_sparql(self, sparql: str, *, resource_filter: set[str]) -> list[dict]: ... def add_triples(self, triples: list[Triple]) -> None: ... def subgraph_by_resources(self, resource_ulids: list[str], include_temporal: TemporalScope) -> ProjectedGraph: ... def traverse(self, start_uri: str, max_hops: int, edge_types: list[str], resource_filter: set[str]) -> SubGraph: ...
Unified Result Types
@dataclass class TextResult: resource_ulid: str path: str line_range: tuple[int, int] content: str score: float
@dataclass class VectorResult: resource_ulid: str uko_node: str content: str similarity: float metadata: dict
Context Assembly Pipeline
The context assembly pipeline is the central orchestrator of context assembly. It receives a ContextRequest, processes it through a chain of pluggable components, and produces a budget-respecting AssembledContext. Every stage of the pipeline is a replaceable plugin — each defined by a Protocol interface, shipped with a default implementation, and configurable at the global, project, or plan scope.
Pipeline Component Model
The pipeline follows the Pipes and Filters architectural pattern, with each filter implemented as a Strategy (GoF) that can be swapped at runtime. Component resolution uses a Chain of Responsibility pattern: plan-level overrides take precedence over project-level, which take precedence over global defaults.
Pipeline Component Resolution Order:
plan scope ──► project scope ──► global scope ──► built-in default (most specific) (least specific)
There are ten pluggable components, grouped into three pipeline phases:
Phase 1 — Strategy Orchestration (selects and runs context strategies):
| # | Component | Protocol | Responsibility |
|---|---|---|---|
| 1 | StrategySelector | StrategySelectorProtocol |
Decides which strategies to invoke and computes confidence scores |
| 2 | BudgetAllocator | BudgetAllocatorProtocol |
Distributes the token budget across selected strategies |
| 3 | StrategyExecutor | StrategyExecutorProtocol |
Controls how strategies are invoked (parallelism, timeouts, error handling) |
Phase 2 — Fragment Fusion (merges strategy outputs into a coherent payload):
| # | Component | Protocol | Responsibility |
|---|---|---|---|
| 4 | FragmentDeduplicator | FragmentDeduplicatorProtocol |
Removes duplicate fragments across strategy outputs |
| 5 | DetailDepthResolver | DetailDepthResolverProtocol |
Resolves conflicts when the same UKO node appears at different depths |
| 6 | FragmentScorer | FragmentScorerProtocol |
Computes composite relevance scores for ranking |
| 7 | BudgetPacker | BudgetPackerProtocol |
Fits scored fragments into the token budget |
| 8 | FragmentOrderer | FragmentOrdererProtocol |
Orders final fragments for coherence in the prompt |
Phase 3 — Context Finalization (produces the final deliverable):
| # | Component | Protocol | Responsibility |
|---|---|---|---|
| 9 | PreambleGenerator | PreambleGeneratorProtocol |
Generates the context preamble (provenance summary, structure map) |
| 10 | SkeletonCompressor | SkeletonCompressorProtocol |
Compresses parent context into a skeleton for child plan inheritance |
Component Protocol Definitions
Each component is defined as a @runtime_checkable Protocol, enabling both structural subtyping (duck typing) and explicit isinstance checks. The protocols use Generic type parameters where applicable, and employ the Template Method pattern — each protocol defines the contract while default implementations provide the algorithm skeleton with overridable hook methods.
# ── Phase 1: Strategy Orchestration ────────────────────────────────@runtime_checkable class StrategySelectorProtocol(Protocol): """Decides which strategies to invoke and with what confidence. Applies request preferences and backend availability filtering."""
<span style="color: magenta; font-weight: 600;">def</span> <span style="color: cyan; font-weight: 600;">select</span>( <span style="color: cyan;">self</span>, strategies: <span style="color: cyan;">Sequence</span>[ContextStrategy], request: ContextRequest, backends: BackendSet, ) -> <span style="color: cyan;">list</span>[<span style="color: cyan;">tuple</span>[ContextStrategy, <span style="color: cyan;">float</span>]]: <span style="color: #888;">"""Returns (strategy, confidence) pairs, sorted by priority. Confidence is 0.0-1.0. Strategies with 0.0 are excluded."""</span> ...@runtime_checkable class BudgetAllocatorProtocol(Protocol): """Distributes the token budget across selected strategies. May use proportional, priority-weighted, or custom allocation schemes."""
<span style="color: magenta; font-weight: 600;">def</span> <span style="color: cyan; font-weight: 600;">allocate</span>( <span style="color: cyan;">self</span>, candidates: <span style="color: cyan;">list</span>[<span style="color: cyan;">tuple</span>[ContextStrategy, <span style="color: cyan;">float</span>]], total_budget: <span style="color: cyan;">int</span>, request: ContextRequest, ) -> <span style="color: cyan;">list</span>[<span style="color: cyan;">tuple</span>[ContextStrategy, <span style="color: cyan;">float</span>, <span style="color: cyan;">int</span>]]: <span style="color: #888;">"""Returns (strategy, confidence, allocated_tokens) triples. Sum of allocated_tokens must not exceed total_budget."""</span> ...@runtime_checkable class StrategyExecutorProtocol(Protocol): """Controls how strategies are invoked — parallelism model, timeout handling, circuit breaking, and error recovery."""
<span style="color: magenta; font-weight: 600;">def</span> <span style="color: cyan; font-weight: 600;">execute</span>( <span style="color: cyan;">self</span>, allocations: <span style="color: cyan;">list</span>[<span style="color: cyan;">tuple</span>[ContextStrategy, <span style="color: cyan;">float</span>, <span style="color: cyan;">int</span>]], request: ContextRequest, backends: BackendSet, plan_context: PlanContext, ) -> <span style="color: cyan;">list</span>[ContextFragment]: <span style="color: #888;">"""Executes strategies and collects all fragments. Must handle strategy failures gracefully (log and continue)."""</span> ...# ── Phase 2: Fragment Fusion ────────────────────────────────────────
@runtime_checkable class FragmentDeduplicatorProtocol(Protocol): """Removes duplicate fragments from the merged strategy output. Deduplication can be identity-based, content-hash-based, or semantic."""
<span style="color: magenta; font-weight: 600;">def</span> <span style="color: cyan; font-weight: 600;">deduplicate</span>( <span style="color: cyan;">self</span>, fragments: <span style="color: cyan;">list</span>[ContextFragment], ) -> <span style="color: cyan;">list</span>[ContextFragment]: <span style="color: #888;">"""Returns deduplicated fragments. When duplicates are found, the fragment with the highest relevance_score is retained."""</span> ...@runtime_checkable class DetailDepthResolverProtocol(Protocol): """Resolves conflicts when the same UKO node appears at different detail depths across strategy outputs."""
<span style="color: magenta; font-weight: 600;">def</span> <span style="color: cyan; font-weight: 600;">resolve</span>( <span style="color: cyan;">self</span>, fragments: <span style="color: cyan;">list</span>[ContextFragment], budget: <span style="color: cyan;">int</span>, ) -> <span style="color: cyan;">list</span>[ContextFragment]: <span style="color: #888;">"""Returns fragments with depth conflicts resolved. Default: keep highest depth that fits within budget."""</span> ...@runtime_checkable class FragmentScorerProtocol(Protocol): """Computes a composite score for each fragment, used for ranking during budget packing. Applies hierarchical weighting, strategy quality weighting, and recency bonuses."""
<span style="color: magenta; font-weight: 600;">def</span> <span style="color: cyan; font-weight: 600;">score</span>( <span style="color: cyan;">self</span>, fragments: <span style="color: cyan;">list</span>[ContextFragment], plan_context: PlanContext, ) -> <span style="color: cyan;">list</span>[ScoredFragment]: <span style="color: #888;">"""Returns fragments annotated with composite scores. ScoredFragment adds a `composite_score: float` field."""</span> ...@runtime_checkable class BudgetPackerProtocol(Protocol): """Fits scored fragments into the token budget using a packing algorithm. Supports depth fallback — if a fragment doesn't fit at its current depth, it can be re-rendered at a lower depth."""
<span style="color: magenta; font-weight: 600;">def</span> <span style="color: cyan; font-weight: 600;">pack</span>( <span style="color: cyan;">self</span>, scored_fragments: <span style="color: cyan;">list</span>[ScoredFragment], budget: <span style="color: cyan;">int</span>, detail_level_maps: DetailLevelMapRegistry, ) -> <span style="color: cyan;">list</span>[ContextFragment]: <span style="color: #888;">"""Returns the subset of fragments that fit within budget, possibly re-rendered at lower depths. Ordered by score."""</span> ...@runtime_checkable class FragmentOrdererProtocol(Protocol): """Orders the packed fragments for optimal coherence in the actor's context window. Ordering may be by relevance, by topological dependency, or by a hybrid coherence heuristic."""
<span style="color: magenta; font-weight: 600;">def</span> <span style="color: cyan; font-weight: 600;">order</span>( <span style="color: cyan;">self</span>, fragments: <span style="color: cyan;">list</span>[ContextFragment], ) -> <span style="color: cyan;">list</span>[ContextFragment]: <span style="color: #888;">"""Returns fragments in the final presentation order."""</span> ...# ── Phase 3: Context Finalization ──────────────────────────────────
@runtime_checkable class PreambleGeneratorProtocol(Protocol): """Generates the preamble prepended to assembled context — a structured summary of what's included, why, and from where."""
<span style="color: magenta; font-weight: 600;">def</span> <span style="color: cyan; font-weight: 600;">generate</span>( <span style="color: cyan;">self</span>, fragments: <span style="color: cyan;">list</span>[ContextFragment], strategies_used: <span style="color: cyan;">list</span>[<span style="color: cyan;">str</span>], budget_used: <span style="color: cyan;">float</span>, max_tokens: <span style="color: cyan;">int</span>, ) -> <span style="color: cyan;">str</span> | <span style="color: magenta; font-weight: 600;">None</span>: <span style="color: #888;">"""Returns the preamble string, or None if preamble is disabled."""</span> ...@runtime_checkable class SkeletonCompressorProtocol(Protocol): """Compresses a parent plan's assembled context into a compact skeleton representation for inheritance by child plans."""
<span style="color: magenta; font-weight: 600;">def</span> <span style="color: cyan; font-weight: 600;">compress</span>( <span style="color: cyan;">self</span>, parent_context: AssembledContext, child_focus: <span style="color: cyan;">list</span>[<span style="color: cyan;">str</span>], skeleton_budget: <span style="color: cyan;">int</span>, ) -> AssembledContext: <span style="color: #888;">"""Returns a compressed version of the parent context that fits within skeleton_budget tokens. Typically reduces all fragments to depth 0-1 (e.g., MODULE_LISTING / TITLE_ONLY)."""</span> ...
Supporting Types
@dataclass(frozen=True) class ScoredFragment: """A ContextFragment annotated with a composite score by the FragmentScorer.""" fragment: ContextFragment composite_score: float # 0.0-1.0 composite ranking score score_components: dict[str, float] # Breakdown: {"relevance": 0.9, "hierarchy": 0.8, ...}@dataclass class PipelineConfig: """Resolved configuration for the context assembly pipeline. Each field holds the concrete plugin instance to use for that stage. Resolved via the scope chain: plan > project > global > built-in."""
strategy_selector: StrategySelectorProtocol budget_allocator: BudgetAllocatorProtocol strategy_executor: StrategyExecutorProtocol fragment_deduplicator: FragmentDeduplicatorProtocol detail_depth_resolver: DetailDepthResolverProtocol fragment_scorer: FragmentScorerProtocol budget_packer: BudgetPackerProtocol fragment_orderer: FragmentOrdererProtocol preamble_generator: PreambleGeneratorProtocol skeleton_compressor: SkeletonCompressorProtocol
Pipeline Orchestrator
The ContextAssemblyPipeline is the top-level orchestrator that wires the ten components together. It implements the Mediator pattern — components do not communicate directly with each other; the pipeline mediates all data flow. The pipeline itself is stateless; all state is carried through the data flowing between stages.
class ContextAssemblyPipeline: """Mediator that orchestrates the 10-stage context assembly pipeline. Each stage is a pluggable component resolved from the scope chain."""<span style="color: magenta; font-weight: 600;">def</span> <span style="color: cyan; font-weight: 600;">__init__</span>(<span style="color: cyan;">self</span>, config: PipelineConfig, strategies: <span style="color: cyan;">Sequence</span>[ContextStrategy]): <span style="color: cyan;">self</span>._config = config <span style="color: cyan;">self</span>._strategies = strategies <span style="color: magenta; font-weight: 600;">def</span> <span style="color: cyan; font-weight: 600;">assemble</span>( <span style="color: cyan;">self</span>, request: ContextRequest, backends: BackendSet, budget: <span style="color: cyan;">int</span>, plan_context: PlanContext, ) -> AssembledContext: <span style="color: #888;"># ── Phase 1: Strategy Orchestration ──────────────────────</span> <span style="color: #888;"># 1. Select strategies</span> candidates = <span style="color: cyan;">self</span>._config.strategy_selector.select( <span style="color: cyan;">self</span>._strategies, request, backends, ) <span style="color: #888;"># 2. Allocate budget</span> allocations = <span style="color: cyan;">self</span>._config.budget_allocator.allocate( candidates, budget, request, ) <span style="color: #888;"># 3. Execute strategies</span> raw_fragments = <span style="color: cyan;">self</span>._config.strategy_executor.execute( allocations, request, backends, plan_context, ) <span style="color: #888;"># ── Phase 2: Fragment Fusion ──────────────────────────────</span> <span style="color: #888;"># 4. Deduplicate</span> deduped = <span style="color: cyan;">self</span>._config.fragment_deduplicator.deduplicate(raw_fragments) <span style="color: #888;"># 5. Resolve depth conflicts</span> resolved = <span style="color: cyan;">self</span>._config.detail_depth_resolver.resolve(deduped, budget) <span style="color: #888;"># 6. Score fragments</span> scored = <span style="color: cyan;">self</span>._config.fragment_scorer.score(resolved, plan_context) <span style="color: #888;"># 7. Pack into budget</span> packed = <span style="color: cyan;">self</span>._config.budget_packer.pack( scored, budget, <span style="color: cyan;">self</span>._detail_level_maps, ) <span style="color: #888;"># 8. Order for coherence</span> ordered = <span style="color: cyan;">self</span>._config.fragment_orderer.order(packed) <span style="color: #888;"># ── Phase 3: Context Finalization ─────────────────────────</span> <span style="color: #888;"># 9. Generate preamble</span> strategies_used = [s.name <span style="color: magenta; font-weight: 600;">for</span> s, _, _ <span style="color: magenta; font-weight: 600;">in</span> allocations] preamble = <span style="color: cyan;">self</span>._config.preamble_generator.generate( ordered, strategies_used, budget_used=<span style="color: cyan;">sum</span>(f.token_count <span style="color: magenta; font-weight: 600;">for</span> f <span style="color: magenta; font-weight: 600;">in</span> ordered) / budget, max_tokens=<span style="color: cyan;">self</span>._preamble_max_tokens, ) <span style="color: magenta; font-weight: 600;">return</span> AssembledContext( fragments=ordered, total_tokens=<span style="color: cyan;">sum</span>(f.token_count <span style="color: magenta; font-weight: 600;">for</span> f <span style="color: magenta; font-weight: 600;">in</span> ordered), budget_used=<span style="color: cyan;">sum</span>(f.token_count <span style="color: magenta; font-weight: 600;">for</span> f <span style="color: magenta; font-weight: 600;">in</span> ordered) / budget, strategies_used=strategies_used, context_hash=<span style="color: cyan;">self</span>._compute_hash(ordered), preamble=preamble, provenance_map={f.uko_node: f.provenance <span style="color: magenta; font-weight: 600;">for</span> f <span style="color: magenta; font-weight: 600;">in</span> ordered}, )
Built-in Default Implementations
Each component ships with a default implementation that provides the behavior described in the current specification. Custom implementations need only implement the relevant Protocol — they need not subclass the default.
Phase 1 defaults:
| Component | Default Class | Algorithm |
|---|---|---|
| StrategySelector | ConfidenceWeightedSelector |
Calls can_handle() on all registered strategies; filters to confidence > 0; applies ContextRequest.preferred_strategies preferences; sorts by confidence * quality_score descending. Uses the Observer pattern to emit StrategySelectionEvent for diagnostics. |
| BudgetAllocator | ProportionalBudgetAllocator |
Allocates budget proportional to confidence * quality_score. Guarantees each strategy receives at least min_useful_budget tokens or is excluded. Implements Flyweight for allocation metadata reuse across re-assembly cycles. |
| StrategyExecutor | ParallelStrategyExecutor |
Runs strategies concurrently via ThreadPoolExecutor. Per-strategy timeout (default: 30s). Uses Circuit Breaker pattern: after 3 consecutive failures, a strategy is temporarily disabled for that plan. Failed strategies log warnings and are excluded from results. |
Phase 2 defaults:
| Component | Default Class | Algorithm |
|---|---|---|
| FragmentDeduplicator | ContentHashDeduplicator |
Groups fragments by UKO node URI; within each group, hashes content to detect duplicates; retains the fragment with the highest relevance_score. Configurable via context.fusion.dedup-strategy: content-hash (default), uko-identity (URI only), or semantic (embedding cosine similarity > 0.95). Implements Strategy (GoF) pattern to swap dedup algorithms. |
| DetailDepthResolver | MaxDepthResolver |
When the same UKO node appears at multiple depths, retains the highest-depth rendering that fits within the remaining budget. Uses a Specification pattern: each fragment is tested against a budget specification, and the highest-depth satisfying fragment wins. |
| FragmentScorer | WeightedCompositeScorer |
Computes composite = relevance_score * strategy_quality * hierarchy_weight * recency_bonus. Hierarchy weight is derived from the fragment's distance to the plan's focus nodes. Recency bonus is 1.0 for current nodes, decaying for older revisions. Scoring weights are configurable. Uses Decorator pattern to compose scoring dimensions. |
| BudgetPacker | GreedyKnapsackPacker |
Sorts by composite score descending; greedily adds fragments until budget is exhausted. When a fragment doesn't fit at its current depth, attempts depth fallback: re-renders at progressively lower depths (e.g., depth 9 → 4 → 0) until it fits or is excluded. Implements Iterator pattern over the depth fallback sequence. |
| FragmentOrderer | RelevanceCoherenceOrderer |
Groups fragments by resource, then orders within each group by containment depth and source position. Groups are ordered by maximum composite score within the group. Configurable via context.fusion.ordering: relevance (strict score order), topological (dependency-aware), relevance-coherence (default hybrid). Implements Template Method with overridable _group_key() and _intra_group_sort() hooks. |
Phase 3 defaults:
| Component | Default Class | Algorithm |
|---|---|---|
| PreambleGenerator | ProvenancePreambleGenerator |
Generates a structured preamble listing: included resources, strategies used, budget utilization, and a compact structure map. Respects context.fusion.preamble-max-tokens limit. Can be disabled via context.fusion.preamble-enabled = false. Uses Builder pattern to construct preamble sections. |
| SkeletonCompressor | DepthReductionCompressor |
Re-renders all parent context fragments at depth 0-1 (e.g., MODULE_LISTING for code, TABLE_OF_CONTENTS_L1 for documents). Prioritizes fragments closer to the child's focus area. Fits within skeleton_budget tokens (derived from skeleton_ratio). Uses Visitor pattern to traverse and re-render fragments by domain type. |
Scope Resolution and Configuration
Pipeline components are resolved through a three-level scope chain. Each scope can override any subset of the ten components; unspecified components inherit from the next broader scope.
class PipelineConfigResolver: """Resolves the effective PipelineConfig for a given context assembly. Implements the Chain of Responsibility pattern across three scopes."""<span style="color: magenta; font-weight: 600;">def</span> <span style="color: cyan; font-weight: 600;">resolve</span>( <span style="color: cyan;">self</span>, plan: Plan | <span style="color: magenta; font-weight: 600;">None</span>, project: Project | <span style="color: magenta; font-weight: 600;">None</span>, global_config: GlobalConfig, ) -> PipelineConfig: <span style="color: #888;"># 1. Start with built-in defaults</span> config = <span style="color: cyan;">self</span>._builtin_defaults() <span style="color: #888;"># 2. Apply global overrides from config.toml</span> config = <span style="color: cyan;">self</span>._apply_scope(config, global_config.pipeline_overrides) <span style="color: #888;"># 3. Apply project-level overrides (if any)</span> <span style="color: magenta; font-weight: 600;">if</span> project <span style="color: magenta; font-weight: 600;">and</span> project.pipeline_overrides: config = <span style="color: cyan;">self</span>._apply_scope(config, project.pipeline_overrides) <span style="color: #888;"># 4. Apply plan-level overrides (if any)</span> <span style="color: magenta; font-weight: 600;">if</span> plan <span style="color: magenta; font-weight: 600;">and</span> plan.pipeline_overrides: config = <span style="color: cyan;">self</span>._apply_scope(config, plan.pipeline_overrides) <span style="color: magenta; font-weight: 600;">return</span> config <span style="color: magenta; font-weight: 600;">def</span> <span style="color: cyan; font-weight: 600;">_apply_scope</span>(<span style="color: cyan;">self</span>, base: PipelineConfig, overrides: <span style="color: cyan;">dict</span>) -> PipelineConfig: <span style="color: #888;">"""Merges overrides into the base config using the Prototype pattern. Only non-None overrides replace the base component."""</span> <span style="color: magenta; font-weight: 600;">return</span> PipelineConfig(**{ field: overrides.get(field, getattr(base, field)) <span style="color: magenta; font-weight: 600;">for</span> field <span style="color: magenta; font-weight: 600;">in</span> PipelineConfig.__dataclass_fields__ })
Pipeline components are registered in TOML configuration:
# ── Global config.toml ──────────────────────────────────────────── [context.pipeline] # Override any pipeline component globally (values are "module:ClassName") strategy-selector = "builtin:ConfidenceWeightedSelector" # default budget-allocator = "builtin:ProportionalBudgetAllocator" # default strategy-executor = "builtin:ParallelStrategyExecutor" # default fragment-deduplicator = "builtin:ContentHashDeduplicator" # default detail-depth-resolver = "builtin:MaxDepthResolver" # default fragment-scorer = "builtin:WeightedCompositeScorer" # default budget-packer = "builtin:GreedyKnapsackPacker" # default fragment-orderer = "builtin:RelevanceCoherenceOrderer" # default preamble-generator = "builtin:ProvenancePreambleGenerator" # default skeleton-compressor = "builtin:DepthReductionCompressor" # default# ── Per-component configuration ──────────────────────────────────── [context.pipeline.strategy-executor] timeout-seconds = 30 max-workers = 4 circuit-breaker-threshold = 3
[context.pipeline.fragment-scorer] relevance-weight = 0.4 hierarchy-weight = 0.3 quality-weight = 0.2 recency-weight = 0.1
[context.pipeline.budget-packer] depth-fallback-steps = [9, 4, 2, 0] # depths to try during fallback min-fragment-tokens = 10 # fragments below this are excluded
Project-level and plan-level overrides use the same key structure, specified via the context view YAML or the agents project context set CLI:
# ── Project-level context view YAML ────────────────────────────────
project: local/api-service
view: strategize
pipeline:
fragment-scorer: "my_extensions.scorers:DomainAwareScorer"
budget-packer: "my_extensions.packers:PriorityPreservingPacker"
Fallback Degradation Path
The pipeline's ConfidenceWeightedSelector (default StrategySelector) implements automatic fallback:
- Try
arce(requires all backends) — if unavailable: - Try
breadth-depth-navigator(requires graph) — if unavailable: - Try
semantic-embedding(requires vector) — if unavailable: - Fall back to
simple-keyword(requires only text search / ripgrep)
This ensures the system always produces context, even when advanced backends are not configured.
Initial Context Assembly
Before the actor's first turn, the system assembles initial context:
class InitialContextAssembler: """Produces the starting context for an actor invocation."""<span style="color: magenta; font-weight: 600;">def</span> <span style="color: cyan; font-weight: 600;">assemble</span>(<span style="color: cyan;">self</span>, plan: <span style="color: cyan;">Plan</span>, actor: <span style="color: cyan;">Actor</span>, token_budget: <span style="color: cyan;">int</span>) -> <span style="color: cyan;">AssembledContext</span>: <span style="color: #888;"># 1. Resolve resource scope</span> scope = <span style="color: cyan;">self</span>.scope_resolver.resolve(plan) <span style="color: #888;"># 2. Get inherited context from parent plan (if any)</span> inherited = <span style="color: cyan;">self</span>._get_inherited_context(plan) <span style="color: #888;"># 3. Create scoped backend views</span> backends = <span style="color: cyan;">self</span>.bal.create_scoped_views(scope) <span style="color: #888;"># 4. Build the initial context request from plan metadata</span> initial_request = <span style="color: cyan;">self</span>._build_initial_request(plan, actor, inherited) <span style="color: #888;"># 5. Run through the Context Assembly Pipeline</span> result = <span style="color: cyan;">self</span>.pipeline.assemble( request=initial_request, backends=backends, budget=token_budget, plan_context=PlanContext( plan=plan, parent_context=inherited, depth_in_tree=plan.depth_in_tree, ), ) <span style="color: magenta; font-weight: 600;">return</span> result
Context Inheritance Mechanism
class PlanContextInheritance: """Manages how child plans inherit and refine parent context. Uses the pipeline's SkeletonCompressor component to produce compact parent context for child plan inheritance."""<span style="color: magenta; font-weight: 600;">def</span> <span style="color: cyan; font-weight: 600;">compute_child_context</span>(<span style="color: cyan;">self</span>, parent_plan, child_plan, parent_context): <span style="color: #888;"># 1. Determine the child's focus from the parent's decisions</span> child_focus = <span style="color: cyan;">self</span>._extract_child_focus(parent_plan, child_plan) <span style="color: #888;"># 2. Compute depth/breadth adjustments</span> depth_delta = child_plan.depth_in_tree - parent_plan.depth_in_tree child_detail = <span style="color: cyan;">self</span>._increase_detail(<span style="color: cyan;">self</span>._avg_detail(parent_context), depth_delta) child_breadth = max(<span style="color: yellow;">1</span>, parent_context.avg_breadth - depth_delta) <span style="color: #888;"># 3. Compute skeleton budget from the child's token budget</span> skeleton_budget = <span style="color: cyan;">int</span>(child_plan.token_budget * <span style="color: cyan;">self</span>.config.skeleton_ratio) <span style="color: #888;"># 4. Use the pipeline's SkeletonCompressor to build the parent skeleton</span> skeleton = <span style="color: cyan;">self</span>.pipeline.skeleton_compressor.compress( parent_context=parent_context, child_focus=child_focus, skeleton_budget=skeleton_budget, ) <span style="color: #888;"># 5. Build the inherited context request with parent skeleton</span> <span style="color: magenta; font-weight: 600;">return</span> ContextRequest( focus=child_focus, breadth=child_breadth, depth=child_detail, depth_gradient=<span style="color: magenta; font-weight: 600;">True</span>, metadata={ <span style="color: #66cc66;">"parent_context_hash"</span>: parent_context.context_hash, <span style="color: #66cc66;">"parent_skeleton"</span>: skeleton, <span style="color: #66cc66;">"inherited_decisions"</span>: <span style="color: cyan;">self</span>._relevant_parent_decisions(parent_plan, child_focus), }, )
Skeleton Context Propagation
The skeleton is a compact, low-depth representation (depth 0-1) of the parent's context window that is passed to every child. This ensures children always have the "big picture" available, even though they focus on a narrow area. The skeleton typically consumes 5-15% of the child's token budget.
Parent's hot context (32K tokens): ┌─ Module: src/auth/ [depth 3/MEMBER_SUMMARY: 2K tokens] ├─ Module: src/core/ [depth 3/MEMBER_SUMMARY: 2K tokens] ├─ Module: src/api/ [depth 3/MEMBER_SUMMARY: 2K tokens] ├─ Module: src/services/ [depth 3/MEMBER_SUMMARY: 2K tokens] └─ ... 12 more modules [depth 3/MEMBER_SUMMARY: 24K tokens]
Child's hot context (32K tokens): ┌─ [SKELETON from parent] [depth 0/MODULE_LISTING: 3K tokens] │ Module: src/auth/ [just name + member names] │ Module: src/core/ [just name + member names] │ Module: src/api/ [just name + member names] │ ... etc. ├─ [CHILD FOCUS AREA] [depth 9/FULL_SOURCE: 25K tokens] │ Class: AuthManager [depth 9: all methods with bodies] │ Class: TokenValidator [depth 9: referenced by AuthManager] │ Class: User [depth 4/SIGNATURES: used as parameter type] └─ [INHERITED DECISIONS] [4K tokens] Decision: "Use async patterns for all auth operations" Decision: "Maintain backward compat with sync callers"
Dynamic Context Window Adaptation
The context window size may change between invocations. Different LLM providers, different models, different configurations, and even dynamic adjustments during a session can alter the available budget. The ACMS handles this seamlessly.
Budget Computation
class ContextBudgetCalculator: """Computes the effective context budget for each assembly cycle."""<span style="color: magenta; font-weight: 600;">def</span> <span style="color: cyan; font-weight: 600;">compute</span>(<span style="color: cyan;">self</span>, actor: <span style="color: cyan;">Actor</span>, plan: <span style="color: cyan;">Plan</span>, conversation_history_tokens: <span style="color: cyan;">int</span>) -> <span style="color: cyan;">int</span>: <span style="color: #888;"># 1. Get the actor's hard context window limit</span> model_limit = actor.model_config.context_window_tokens <span style="color: #888;"># 2. Reserve space for system prompt, history, tools, response</span> reserved = (<span style="color: cyan;">self</span>._count_tokens(actor.system_prompt) + conversation_history_tokens + <span style="color: cyan;">self</span>._estimate_tool_tokens(actor.skills) + actor.model_config.max_output_tokens) <span style="color: #888;"># 3. Apply the soft cap from context view policy</span> soft_cap = <span style="color: cyan;">self</span>._resolve_view(plan).hot_max_tokens <span style="color: magenta; font-weight: 600;">or</span> <span style="color: cyan;">float</span>(<span style="color: #66cc66;">'inf'</span>) <span style="color: #888;"># 4. Calculate available budget</span> effective_budget = max(<span style="color: yellow;">0</span>, min(model_limit - reserved, soft_cap)) <span style="color: magenta; font-weight: 600;">return</span> effective_budget
Adaptive Behavior
When the budget changes between context assembly cycles:
- Budget increase: The pipeline re-runs strategy orchestration (Phase 1) with the expanded budget. The BudgetAllocator distributes the larger budget across strategies, and the BudgetPacker may promote fragments previously rendered at a low depth (e.g., depth 0) to a higher depth (e.g., depth 4 or 9).
- Budget decrease: The pipeline's BudgetPacker re-applies the knapsack with the new budget, potentially reducing fragment depths via its depth fallback mechanism or dropping low-relevance items as ranked by the FragmentScorer.
- Strategies are stateless: Each assembly cycle is independent. The pipeline runs all 10 components from scratch, and strategies receive the current budget and must respect it.
Progressive Refinement Within a Session
As an actor makes tool calls during a session, the conversation grows and the available budget shrinks. The ACMS handles this through a context refresh cycle that triggers re-assembly when the budget changes by more than 30% (configurable via context.budget.refresh_threshold).
Built-in Strategy Catalogue
The ACMS ships with several built-in strategies. The ARCE pipeline is one of them, not the only one.
Strategy: simple-keyword
Basic keyword/regex text search. Works with any backend, any resource type. No graph or vector required. This is the universal fallback. Quality score: 0.3.
Strategy: semantic-embedding
Vector similarity search. Finds semantically related content even without exact keyword matches. Requires a vector backend. Quality score: 0.6.
Strategy: breadth-depth-navigator
The graph-aware strategy that uses the depth/breadth projection system. Works with the UKO graph to provide structurally-aware context at any detail depth. Supports focus items, hop traversal, and detail depth gradients. This is the primary strategy for code-aware context. Quality score: 0.85.
Strategy: arce (Analyze, Retrieve, Contextualize, Execute)
The sophisticated multi-modal pipeline. Combines text, vector, and graph search with intent analysis and actor-type-aware patterns. This is the "kitchen sink" strategy that produces the highest quality results but requires all backends. Quality score: 0.95.
Actor-type-specific graph traversal patterns:
- Strategy actors: Broader, shallower traversal. Emphasis on module boundaries and dependency structures. Prioritizes
uko:Containeranduko:Boundarynodes. - Execution actors: Narrower, deeper traversal. Emphasis on implementation details, function bodies, and test coverage. Prioritizes
uko:Atomnodes. - Estimation actors: Focus on size metrics, complexity indicators, and historical data. Prioritizes temporal data.
Strategy: temporal-archaeology
Searches the cold tier for historical patterns. Useful for understanding "what changed last time we touched this area" or "what decisions were made about this module in the past." Requires graph + cold tier access. Quality score: 0.5.
Strategy: plan-decision-context
Retrieves context from parent and ancestor plan decisions. This is how child plans "remember" what their parent decided and why. Operates on the warm/cold tiers. Quality score: 0.7.
ACMS Extensibility
The ACMS-specific extension points (analyzers, backends, UKO vocabularies, strategies, and pipeline components) are documented in the Extensibility section below. See Extensibility > ACMS Extensions for the full details.
UKO Runtime Services
The UKO runtime is operationalized through three service classes:
-
UKOQueryInterface— Typed interface for ACMS context strategies to query UKO classification data. Providesclassify_resource(uri)returning aClassificationResultwithlayer(0–3),primary_type,source_resource_id, andrelationships. Strategies use this to discover which ontology layer a resource belongs to and what relationships it has. -
UKOInferenceEngine— Semantic analysis service that infers implicit relationships from UKO triples produced by domain analyzers. Infers three relationship types:uko:implicitSiblingOf(co-occurrence),uko:implicitContains(URI prefix containment), anduko:implicitDependsOn(URI reference in object values). Inferred triples are stored with confidence0.7to distinguish them from deterministic extractions. -
UKOGraphPersistence— Serializes and restores UKO graph state via JSON or in-memory backends. Used for graph persistence across sessions and for test isolation.
The UKOIndexer.index_graph() method runs inference via UKOInferenceEngine and populates uko:layer triples for all four ontology layers during indexing.
Real-time Index Synchronization
The system maintains index freshness through immediate, proactive updates. The UKOIndexer produces UKO triples from resources using pluggable analyzers, and simultaneously indexes into text and vector backends:
class UKOIndexer: """Produces UKO triples from resources using pluggable analyzers."""<span style="color: magenta; font-weight: 600;">def</span> <span style="color: cyan; font-weight: 600;">index_resource</span>(<span style="color: cyan;">self</span>, resource: <span style="color: cyan;">Resource</span>): <span style="color: #888;"># 1. Determine the best analyzer for this resource</span> analyzer = <span style="color: cyan;">self</span>.analyzers.get_for_resource(resource) <span style="color: #888;"># 2. Produce UKO triples using the most specific vocabulary</span> triples = analyzer.analyze(resource) <span style="color: #888;"># 3. Add provenance</span> <span style="color: magenta; font-weight: 600;">for</span> triple <span style="color: magenta; font-weight: 600;">in</span> triples: <span style="color: cyan;">self</span>._add_provenance(triple, resource) <span style="color: #888;"># 4. Store in graph backend</span> <span style="color: cyan;">self</span>.graph_backend.add_triples(triples) <span style="color: #888;"># 5. Also index into text and vector backends</span> <span style="color: cyan;">self</span>.text_indexer.index(resource, triples) <span style="color: cyan;">self</span>.vector_indexer.index(resource, triples)
Index Lifecycle
| Stage | Trigger | Action | Result |
|---|---|---|---|
| Resource added | agents resource add / agents project link-resource |
Immediate full indexing | All indices ready for instant search |
| Code changed | File modification detected | Immediate incremental update | Indices reflect latest state |
| Resource removed | agents resource remove / agents project unlink-resource |
Immediate cleanup | Stale data removed, UKO nodes marked historical |
| Maintenance | Scheduled | Full reindex, consistency check | Indices verified and optimized |
Fallback to Traditional Search
When advanced features are unavailable, the system gracefully degrades. The pipeline's ConfidenceWeightedSelector (default StrategySelector component) automatically routes to simpler strategies:
- Try
arce(requires all backends) -> if unavailable: - Try
breadth-depth-navigator(requires graph) -> if unavailable: - Try
semantic-embedding(requires vector) -> if unavailable: - Fall back to
simple-keyword(requires only text search / ripgrep)
ACMS Performance Characteristics
Assembly Latency
| Operation | Target Latency | Notes |
|---|---|---|
| Resource scope resolution | < 10ms | In-memory set operations |
| Scoped backend view creation | < 5ms | Filter injection |
Strategy can_handle check (all) |
< 20ms | Simple capability checks |
simple-keyword assembly |
< 100ms | Text search |
semantic-embedding assembly |
< 200ms | Vector search |
breadth-depth-navigator assembly |
< 500ms | Graph traversal + materialization |
arce assembly |
< 1s | Multi-modal search + ranking |
temporal-archaeology assembly |
< 2s | Cold tier queries |
| Fragment fusion pipeline (phases 2-3) | < 100ms | Dedup + depth resolution + scoring + knapsack + ordering + preamble |
| Total initial assembly | < 2s | Parallel strategy execution |
| Incremental refresh | < 500ms | Re-compression only |
Indexing and Query Performance
| Metric | Performance |
|---|---|
| Text indexing speed | 10,000 files/minute |
| Vector indexing speed | 1,000 files/minute (with GPU) |
| Graph indexing speed | 5,000 files/minute |
| Text search query | < 100ms for 1M files |
| Vector search query | < 200ms for 10M embeddings |
| Graph traversal query | < 500ms for 3-hop queries |
Scalability Guarantees
- 100K+ file projects: The scoping mechanism ensures strategies never see the full dataset. Scoped views filter at the backend level.
- Deep plan hierarchies (10+ levels): Skeleton compression ensures parent context overhead stays bounded (10% per level, multiplicatively compressed).
- Concurrent plans: Each plan gets independent ScopedBackendViews. No cross-plan interference.
- Dynamic budgets: The knapsack algorithm is O(n) in the number of fragments. Re-compression is O(1) per fragment.
- Maximum scale tested: 10M files, 1B+ triples, 100M+ embeddings.
Progressive Enhancement Path
Organizations can adopt ACMS features progressively. At each stage, existing resources are reindexed to take advantage of new capabilities:
| Stage | Features | Requirements | Benefit |
|---|---|---|---|
| Basic | Text search, file watching | ripgrep, sqlite | Instant exact-match search |
| Semantic | Vector embeddings, similarity search | Embedding model, vector DB | Instant semantic similarity search |
| Structural | UKO graph, relationship queries | Graph database, language analyzers | Instant relationship queries |
| Intelligent | ML-driven ranking, automated analysis | GPU, training data | Instant intelligent suggestions |
| Custom | Domain-specific ontologies, custom analyzers | Domain expertise | Instant domain-aware intelligence |
Summary of Timing:
- Indexing: Eager (happens immediately when resources are added/changed)
- Searching: Instant (because indices are pre-computed and ready)
- Sandboxing: Lazy (only when execution needs to modify a resource)
- Context Assembly: Real-time (but fast because it queries ready indices)
This design ensures agents never wait for index building during execution, providing a responsive and predictable experience even on massive codebases.
Storage and Persistence
!!! adr "Architecture Decision" The hybrid storage model, database strategy, and migration approach are defined in ADR-019: Storage and Persistence.
This section defines the complete storage strategy for every persistent concept in CleverAgents, specifying the storage backend, data format, lifecycle management, and migration strategy for each entity type.
Storage Architecture Overview
CleverAgents uses a hybrid storage model combining a relational database for structured data, the filesystem for configuration files and artifacts, and specialized indexes for search operations. The storage layer is accessed exclusively through repository interfaces defined in the domain layer, ensuring that storage backends can be swapped without affecting application logic.
@startuml
skinparam componentStyle rectangle
skinparam defaultFontSize 12
skinparam packageFontSize 13
skinparam packageFontStyle bold
package "Storage Architecture" as SA {
package "Relational Database" as RDB #E3F2FD {
component [Plans\nDecisions\nProjects\nResources\nSessions\nActors (db)\nInvariants\nChanges\nCheckpoint Metadata\nCorrection Attempts\nAudit Log] as Tables
}
package "Filesystem" as FS #C8E6C9 {
component [Config YAML Files] as Cfg
component [Sandbox Worktree Files] as Sbx
component [Artifact Storage] as Art
}
package "Search Indexes" as IDX #F3E5F5 {
component [Text (Tantivy)] as TI
component [Vector (FAISS)] as VI
component [Graph (Neo4j)] as GI
component [Checkpoint Store\n(filesystem)] as CS
}
}
@enduml
Entity Storage Map
The following table specifies where each CleverAgents concept is persisted, in both local and server modes:
| Entity | Local Storage | Server Storage | Format | Identifier |
|---|---|---|---|---|
| Plans | SQLite plans table |
Server PostgreSQL | Relational rows | ULID (plan_id) |
| Decisions | SQLite decisions table |
Server PostgreSQL | Relational rows + JSON context_snapshot |
ULID (decision_id) |
| Projects | SQLite projects table |
Server PostgreSQL (for non-local/ namespaces) |
Relational rows | Namespaced name |
| Resources | SQLite resources table + resource_links table |
Server PostgreSQL | Relational rows + DAG edges | ULID (resource_id) |
| Resource Types | SQLite resource_types table + YAML config files |
Server PostgreSQL + synced YAML | Relational rows | Namespaced name (built-ins unnamespaced) |
| Actions | SQLite actions table + YAML config files |
Server PostgreSQL + synced YAML | Relational rows | Namespaced name |
| Actors | SQLite actors table + YAML config files |
Server PostgreSQL + synced YAML | Relational rows + file reference | Namespaced name |
| Skills | SQLite skills table + YAML config files |
Server PostgreSQL + synced YAML | Relational rows + file reference | Namespaced name |
| Tools | SQLite tools table + YAML config files |
Server PostgreSQL + synced YAML | Relational rows + file reference | Namespaced name |
| Sessions | SQLite sessions table + session_messages table |
Server PostgreSQL | Relational rows | ULID (session_id) |
| Invariants | SQLite invariants table |
Server PostgreSQL | Relational rows | Auto-generated ID |
| Automation Profiles | SQLite automation_profiles table + YAML config files |
Server PostgreSQL + synced YAML | Relational rows | Namespaced name (built-ins unnamespaced) |
| Changes / ChangeSets | SQLite changes table |
Server PostgreSQL | Relational rows | ULID per change |
| Checkpoints | Filesystem (<data-dir>/checkpoints/<plan_id>/) + SQLite checkpoint_metadata table |
Server object storage + PostgreSQL metadata | Git refs or filesystem snapshots + metadata rows | ULID (checkpoint_id) |
| Correction Attempts | SQLite correction_attempts table + archived artifacts on filesystem |
Server PostgreSQL + object storage | Relational rows + file archives | ULID (correction_attempt_id) |
| Artifacts | Filesystem (<data-dir>/artifacts/<plan_id>/) |
Server object storage (S3-compatible) | Raw files (code, documents, images, etc.) | Filesystem path relative to plan |
| Context Indexes (Text) | Filesystem (<data-dir>/index/text/) |
Server-managed index cluster | Tantivy index files or SQLite FTS5 | Index directory per project |
| Context Indexes (Vector) | Filesystem (<data-dir>/index/vector/) |
Server-managed FAISS/Qdrant cluster | FAISS index files or Qdrant collections | Index directory per project |
| Context Indexes (Graph) | Neo4j database or rdflib files | Server-managed Neo4j instance | Graph triples | Graph database per project |
| Configuration | TOML file (<data-dir>/config.toml) |
N/A (server has own config) | TOML | Single file |
| Logs | Filesystem (<data-dir>/logs/) |
Server-managed log aggregation | Structured JSON (structlog) | Rotated log files |
| Audit Logs | SQLite audit_log table |
Server PostgreSQL | Relational rows | Auto-incrementing ID |
Database Schema Design
The relational database follows a normalized design with foreign key constraints enforcing referential integrity. The schema is version-controlled through Alembic migrations.
Key design decisions:
-
ULID primary keys: All time-series entities (plans, decisions, checkpoints, correction attempts) use ULID primary keys. This provides natural time-ordering without separate timestamp indexes, collision-free generation without coordination, and URL-safe string representation.
-
JSON columns for semi-structured data: Fields like
context_snapshot,alternatives_considered,artifacts_produced, andresource_bindingsare stored as JSON text columns. This avoids over-normalization for data that is always read and written as a unit, while still being queryable via SQLite'sjson_extract()or PostgreSQL'sjsonboperators. -
Soft delete pattern: Entities that support archival (actions, actors, skills) use a
statecolumn with values likeavailable,archived. No rows are physically deleted except during explicitagents initreset or retention policy enforcement. -
Optimistic concurrency control: The
updated_attimestamp column on mutable entities serves as a version check. Update operations includeWHERE updated_at = <previous_value>to detect concurrent modifications. On conflict, the operation fails with an explicit error rather than silently overwriting.
Core tables (SQLite DDL):
-- Plans table: the fundamental unit of orchestration CREATE TABLE plans ( plan_id TEXT PRIMARY KEY, -- ULID parent_plan_id TEXT REFERENCES plans(plan_id), root_plan_id TEXT NOT NULL REFERENCES plans(plan_id), action_name TEXT NOT NULL, -- namespaced action name phase TEXT NOT NULL DEFAULT 'strategize', -- action|strategize|execute|apply state TEXT NOT NULL DEFAULT 'queued', -- queued|processing|errored|complete|cancelled attempt INTEGER NOT NULL DEFAULT 1, automation_profile_name TEXT NOT NULL, effective_profile_snapshot TEXT NOT NULL, -- JSON: frozen profile at creation arguments TEXT, -- JSON: action arguments project_names TEXT NOT NULL, -- JSON array of project names strategy_actor_name TEXT, execution_actor_name TEXT, estimation_actor_name TEXT, invariant_actor_name TEXT, created_by TEXT, -- session ID or user identity created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f', 'now')), updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f', 'now')), completed_at TEXT, error_message TEXT, error_traceback TEXT, cost_estimate_usd REAL, cost_actual_usd REAL, token_count_input INTEGER DEFAULT 0, token_count_output INTEGER DEFAULT 0 );CREATE INDEX idx_plans_parent ON plans(parent_plan_id); CREATE INDEX idx_plans_root ON plans(root_plan_id); CREATE INDEX idx_plans_phase_state ON plans(phase, state); CREATE INDEX idx_plans_action ON plans(action_name); CREATE INDEX idx_plans_created ON plans(created_at);
-- Decisions table: the decision tree CREATE TABLE decisions ( decision_id TEXT PRIMARY KEY, -- ULID plan_id TEXT NOT NULL REFERENCES plans(plan_id), parent_decision_id TEXT REFERENCES decisions(decision_id), decision_type TEXT NOT NULL, -- prompt_definition|invariant_enforced| -- strategy_choice|subplan_spawn| -- subplan_parallel_spawn|implementation_choice question TEXT NOT NULL, chosen_option TEXT NOT NULL, alternatives_considered TEXT, -- JSON array of alternative options confidence_score REAL, -- 0.0-1.0 rationale TEXT, context_snapshot TEXT NOT NULL, -- JSON: hot_context_hash, relevant_resources, -- actor_state_ref downstream_plan_ids TEXT, -- JSON array of child plan ULIDs artifacts_produced TEXT, -- JSON array of artifact references is_correction BOOLEAN DEFAULT FALSE, corrects_decision_id TEXT REFERENCES decisions(decision_id), superseded_by TEXT REFERENCES decisions(decision_id), sequence_number INTEGER NOT NULL, -- ordering within this plan created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f', 'now')) );
CREATE INDEX idx_decisions_plan ON decisions(plan_id); CREATE INDEX idx_decisions_parent ON decisions(parent_decision_id); CREATE INDEX idx_decisions_type ON decisions(decision_type); CREATE INDEX idx_decisions_superseded ON decisions(superseded_by) WHERE superseded_by IS NOT NULL;
-- Resources table: the resource registry CREATE TABLE resources ( resource_id TEXT PRIMARY KEY, -- ULID name TEXT, -- namespaced name (NULL for auto-discovered children) resource_type_name TEXT NOT NULL, -- namespaced type name classification TEXT NOT NULL, -- physical|virtual description TEXT, properties TEXT, -- JSON: type-specific properties location TEXT, -- physical resources only content_hash TEXT, -- for equivalence tracking sandbox_strategy TEXT, -- override per resource created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f', 'now')), updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f', 'now')) );
CREATE UNIQUE INDEX idx_resources_name ON resources(name) WHERE name IS NOT NULL; CREATE INDEX idx_resources_type ON resources(resource_type_name); CREATE INDEX idx_resources_classification ON resources(classification);
-- Resource DAG: parent/child relationships CREATE TABLE resource_links ( parent_id TEXT NOT NULL REFERENCES resources(resource_id), child_id TEXT NOT NULL REFERENCES resources(resource_id), link_type TEXT DEFAULT 'contains', -- contains|references|derived_from created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f', 'now')), PRIMARY KEY (parent_id, child_id) );
CREATE INDEX idx_resource_links_child ON resource_links(child_id);
-- Project-resource linkage CREATE TABLE project_resources ( project_name TEXT NOT NULL, resource_id TEXT NOT NULL REFERENCES resources(resource_id), read_only BOOLEAN DEFAULT FALSE, linked_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f', 'now')), PRIMARY KEY (project_name, resource_id) );
-- Checkpoint metadata (actual data on filesystem) CREATE TABLE checkpoint_metadata ( checkpoint_id TEXT PRIMARY KEY, -- ULID plan_id TEXT NOT NULL REFERENCES plans(plan_id), decision_id TEXT REFERENCES decisions(decision_id), checkpoint_type TEXT NOT NULL, -- pre_write|post_step|manual resource_id TEXT REFERENCES resources(resource_id), filesystem_path TEXT NOT NULL, -- relative path within checkpoint dir size_bytes INTEGER, created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f', 'now')) );
CREATE INDEX idx_checkpoints_plan ON checkpoint_metadata(plan_id);
-- Correction attempts CREATE TABLE correction_attempts ( correction_attempt_id TEXT PRIMARY KEY, -- ULID plan_id TEXT NOT NULL REFERENCES plans(plan_id), original_decision_id TEXT NOT NULL REFERENCES decisions(decision_id), new_decision_id TEXT REFERENCES decisions(decision_id), mode TEXT NOT NULL, -- revert|append guidance TEXT NOT NULL, archived_artifacts_path TEXT, -- filesystem path to archived originals state TEXT NOT NULL DEFAULT 'pending', -- pending|executing|complete|failed created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f', 'now')), completed_at TEXT );
CREATE INDEX idx_corrections_plan ON correction_attempts(plan_id);
-- Audit log for apply operations CREATE TABLE audit_log ( id INTEGER PRIMARY KEY AUTOINCREMENT, event_type TEXT NOT NULL, -- plan_applied|plan_cancelled|resource_modified| -- correction_applied|config_changed plan_id TEXT, project_name TEXT, actor_name TEXT, user_identity TEXT, details TEXT NOT NULL, -- JSON: event-specific details created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f', 'now')) );
CREATE INDEX idx_audit_event ON audit_log(event_type); CREATE INDEX idx_audit_plan ON audit_log(plan_id) WHERE plan_id IS NOT NULL; CREATE INDEX idx_audit_created ON audit_log(created_at);
Filesystem Layout
The data directory follows a deterministic layout. All paths are relative to core.data-dir (default: ~/.cleveragents):
~/.cleveragents/
├── config.toml # Global configuration
├── cleveragents.db # SQLite database (WAL mode)
├── cleveragents.db-wal # WAL file (auto-managed by SQLite)
├── cleveragents.db-shm # Shared memory file (auto-managed)
├── logs/
│ ├── cleveragents.log # Current log file (structured JSON)
│ ├── cleveragents.log.1 # Rotated log files
│ └── ...
├── cache/
│ ├── models/ # Cached model artifacts
│ ├── tools/ # Cached tool downloads
│ └── templates/ # Compiled Jinja2 templates
├── backups/
│ ├── 2026-02-08T12-30-00/ # Timestamped backup snapshots
│ └── ...
├── checkpoints/
│ ├── 01HXR1C1D2E3F4G5H6I7.../ # Per-plan checkpoint directories
│ │ ├── chk_01HXR1E1.tar.gz # Compressed checkpoint snapshots
│ │ └── ...
│ └── ...
├── artifacts/
│ ├── 01HXR1C1D2E3F4G5H6I7.../ # Per-plan artifact directories
│ │ ├── src/routes/health.py # Generated/modified artifacts
│ │ └── ...
│ └── ...
├── index/
│ ├── text/
│ │ ├── local_api-service/ # Per-project Tantivy indexes
│ │ └── ...
│ ├── vector/
│ │ ├── local_api-service/ # Per-project FAISS indexes
│ │ └── ...
│ └── graph/
│ └── ... # rdflib stores or Neo4j connection info
├── sessions/
│ └── ... # Session state files (if needed beyond DB)
└── contexts/
└── ... # Exported/cached context snapshots
Data Lifecycle and Retention
| Data Type | Retention Policy | Cleanup Trigger |
|---|---|---|
| Plans (active) | Indefinite until the plan reaches applied or cancelled state |
Manual deletion |
| Plans (terminal) | Governed by core.backup.retention-days for archives |
Automatic cleanup daemon |
| Decisions | Retained as long as parent plan exists | Cascade delete with plan |
| Checkpoints | sandbox.checkpoint.max-per-plan per plan; oldest pruned first (keeping first + most recent) |
After apply or on retention limit |
| Correction archives | core.backup.retention-days after plan reaches applied state |
Automatic cleanup daemon |
| Logs | core.log.retention-days |
Daily rotation + age-based cleanup |
| Backups | core.backup.retention-days |
Age-based cleanup |
| Cache | No automatic retention; manual cleanup via agents diagnostics --cleanup |
User-initiated |
| Search indexes | Rebuilt on resource add/remove; no explicit retention | Re-index on change |
| Audit logs | Indefinite (critical for compliance) | Manual archival only |
Migration Strategy
Database schema changes are managed through Alembic migrations with the following rules:
- Forward-only in production: Downgrade scripts are provided but only used in development. Production deployments only move forward.
- Auto-apply on startup: When
CLEVERAGENTS_AUTO_APPLY_MIGRATIONSis set (default in development), migrations run automatically on first database access. In production, migrations are triggered byagents initor explicit upgrade commands. - Backward compatibility window: Each migration maintains backward compatibility with the previous schema version for at least one minor release cycle, allowing rolling upgrades in server mode.
- Data migration separation: Schema DDL changes and data transformations are kept in separate migration steps to enable rollback of data changes independently.
Observability
!!! adr "Architecture Decision" The observability strategy, structured logging, metrics, and distributed tracing are defined in ADR-025: Observability and Logging.
CleverAgents implements a comprehensive observability strategy across three pillars — logging, metrics, and tracing — designed to provide full visibility into plan execution, actor behavior, tool invocations, and resource access. Every significant operation in the system is observable, enabling debugging of complex multi-plan hierarchies and performance optimization.
Structured Logging
All logging uses structlog with JSON output format. Every log entry includes contextual fields that enable correlation across plan hierarchies:
import structloglogger = structlog.get_logger()
# Every log entry automatically includes bound context logger = logger.bind( plan_id="01HXR1C1D2E3F4G5H6I7J8K9L0", phase="execute", actor_name="anthropic/claude-3.5-sonnet", session_id="01HXM2A6K1P2E9Q9D4GQ7J4S7Z", )
# Example: tool invocation logging logger.info( "tool_invoked", tool_name="local/write-file", skill_name="local/file-ops", resource_id="01HXR1A1B2C3D4E5F6G7H8J9K0", resource_type="git-checkout", input_schema_hash="sha256:a1b2c3...", duration_ms=142, writes=True, checkpoint_created=True, )
Log levels and their semantics:
| Level | Usage | Examples |
|---|---|---|
DEBUG |
Internal state transitions, context building details, LLM prompt construction | "Building hot context: 12 files, 8,420 tokens", "Decision confidence computed: 0.87" |
INFO |
Significant lifecycle events, phase transitions, tool completions | "Plan entered execute phase", "Tool write-file completed in 142ms", "Validation passed: pytest" |
WARNING |
Degraded operations, approaching limits, non-fatal issues | "Budget at 82% ($4.10/$5.00)", "Checkpoint pruned: exceeded max-per-plan", "Provider fallback: openai -> anthropic" |
ERROR |
Operation failures, invariant violations, unrecoverable tool errors | "Validation failed: pytest exited 1", "Invariant violated: API compatibility", "LLM provider error: rate limited" |
Log correlation: Every log entry within a plan execution context includes plan_id, root_plan_id, parent_plan_id, phase, state, and attempt. This enables tracing a single decision through the entire plan hierarchy using standard log querying tools (e.g., jq '.plan_id == "01HXR1C1..."').
Event System
CleverAgents emits domain events for every significant state change. Events flow through two channels:
- RxPY Reactive Stream: Real-time event stream for in-process subscribers (TUI updates, progress bars, streaming output). Uses
Subjectfor fan-out distribution. - Persistent Event Log: All events are written to the
audit_logtable for post-hoc analysis and compliance.
Event taxonomy:
from enum import Enum from pydantic import BaseModel from datetime import datetimeclass EventType(StrEnum): # Plan lifecycle events PLAN_CREATED = "plan.created" PLAN_PHASE_CHANGED = "plan.phase_changed" PLAN_STATE_CHANGED = "plan.state_changed" PLAN_APPLIED = "plan.applied" PLAN_CANCELLED = "plan.cancelled" PLAN_ERRORED = "plan.errored" PLAN_ESTIMATION_COMPLETE = "plan.estimation_complete" # emitted after Strategize when estimation_actor is set
<span style="color: #888;"># Decision events</span> DECISION_CREATED = <span style="color: #66cc66;">"decision.created"</span> DECISION_APPROVED = <span style="color: #66cc66;">"decision.approved"</span> DECISION_CORRECTED = <span style="color: #66cc66;">"decision.corrected"</span> DECISION_SUPERSEDED = <span style="color: #66cc66;">"decision.superseded"</span> <span style="color: #888;"># Invariant events</span> INVARIANT_RECONCILED = <span style="color: #66cc66;">"invariant.reconciled"</span> INVARIANT_VIOLATED = <span style="color: #66cc66;">"invariant.violated"</span> INVARIANT_ENFORCED = <span style="color: #66cc66;">"invariant.enforced"</span> <span style="color: #888;"># Actor events</span> ACTOR_INVOKED = <span style="color: #66cc66;">"actor.invoked"</span> ACTOR_COMPLETED = <span style="color: #66cc66;">"actor.completed"</span> ACTOR_ERRORED = <span style="color: #66cc66;">"actor.errored"</span> ACTOR_ESCALATED = <span style="color: #66cc66;">"actor.escalated"</span> <span style="color: #888;"># confidence below threshold</span> <span style="color: #888;"># Tool events</span> TOOL_INVOKED = <span style="color: #66cc66;">"tool.invoked"</span> TOOL_COMPLETED = <span style="color: #66cc66;">"tool.completed"</span> TOOL_ERRORED = <span style="color: #66cc66;">"tool.errored"</span> TOOL_RETRIED = <span style="color: #66cc66;">"tool.retried"</span> <span style="color: #888;"># Resource events</span> RESOURCE_ACCESSED = <span style="color: #66cc66;">"resource.accessed"</span> RESOURCE_MODIFIED = <span style="color: #66cc66;">"resource.modified"</span> RESOURCE_INDEXED = <span style="color: #66cc66;">"resource.indexed"</span> <span style="color: #888;"># Correction events</span> CORRECTION_APPLIED = <span style="color: #66cc66;">"correction.applied"</span> <span style="color: #888;"># Configuration events</span> CONFIG_CHANGED = <span style="color: #66cc66;">"config.changed"</span> <span style="color: #888;"># Entity lifecycle events</span> ENTITY_DELETED = <span style="color: #66cc66;">"entity.deleted"</span> <span style="color: #888;"># Authentication events (server-mode only)</span> AUTH_SUCCESS = <span style="color: #66cc66;">"auth.success"</span> AUTH_FAILURE = <span style="color: #66cc66;">"auth.failure"</span> <span style="color: #888;"># Sandbox events</span> SANDBOX_CREATED = <span style="color: #66cc66;">"sandbox.created"</span> SANDBOX_COMMITTED = <span style="color: #66cc66;">"sandbox.committed"</span> SANDBOX_ROLLED_BACK = <span style="color: #66cc66;">"sandbox.rolled_back"</span> CHECKPOINT_CREATED = <span style="color: #66cc66;">"checkpoint.created"</span> CHECKPOINT_RESTORED = <span style="color: #66cc66;">"checkpoint.restored"</span> <span style="color: #888;"># Context events</span> CONTEXT_BUILT = <span style="color: #66cc66;">"context.built"</span> CONTEXT_QUERY_EXECUTED = <span style="color: #66cc66;">"context.query_executed"</span> <span style="color: #888;"># Context tier lifecycle events</span> TIER_PROMOTED = <span style="color: #66cc66;">"tier.promoted"</span> TIER_DEMOTED = <span style="color: #66cc66;">"tier.demoted"</span> TIER_EVICTED = <span style="color: #66cc66;">"tier.evicted"</span> <span style="color: #888;"># Validation events</span> VALIDATION_STARTED = <span style="color: #66cc66;">"validation.started"</span> VALIDATION_PASSED = <span style="color: #66cc66;">"validation.passed"</span> VALIDATION_FAILED = <span style="color: #66cc66;">"validation.failed"</span> VALIDATION_FIX_ATTEMPTED = <span style="color: #66cc66;">"validation.fix_attempted"</span> VALIDATION_FIX_SUCCEEDED = <span style="color: #66cc66;">"validation.fix_succeeded"</span> VALIDATION_FIX_EXHAUSTED = <span style="color: #66cc66;">"validation.fix_exhausted"</span> <span style="color: #888;"># Session events</span> SESSION_CREATED = <span style="color: #66cc66;">"session.created"</span> SESSION_MESSAGE_SENT = <span style="color: #66cc66;">"session.message_sent"</span> <span style="color: #888;"># Cost events</span> BUDGET_WARNING = <span style="color: #66cc66;">"budget.warning"</span> BUDGET_EXCEEDED = <span style="color: #66cc66;">"budget.exceeded"</span>
class DomainEvent(BaseModel): """Base event model emitted by all domain operations.""" event_type: EventType timestamp: datetime # UTC; defaults to now(UTC) correlation_id: str # ULID linking related events in one request chain plan_id: str | None = None root_plan_id: str | None = None session_id: str | None = None actor_name: str | None = None project_name: str | None = None user_identity: str | None = None # Authenticated user; None in local (unauthenticated) mode details: dict # Event-type-specific payload
Event emission pattern: Domain services emit events through an injected EventBus interface. The infrastructure layer provides two implementations:
from typing import Protocol import rx from rx.subject import Subjectclass EventBus(Protocol): def emit(self, event: DomainEvent) -> None: ... def subscribe(self, event_type: EventType, handler: Callable) -> None: ...
class ReactiveEventBus: """In-process event bus using RxPY for real-time distribution.""" def init(self): self._subject = Subject() self._subscriptions: dict[EventType, list[Callable]] = {}
<span style="color: magenta; font-weight: 600;">def</span> <span style="color: cyan; font-weight: 600;">emit</span>(<span style="color: cyan;">self</span>, event: DomainEvent) -> <span style="color: cyan;">None</span>: <span style="color: #888;"># Push to reactive stream for real-time subscribers</span> <span style="color: cyan;">self</span>._subject.on_next(event) <span style="color: #888;"># Persist to audit log</span> <span style="color: cyan;">self</span>._persist_audit(event) <span style="color: #888;"># Dispatch to type-specific handlers</span> <span style="color: magenta; font-weight: 600;">for</span> handler <span style="color: magenta; font-weight: 600;">in</span> <span style="color: cyan;">self</span>._subscriptions.get(event.event_type, []): handler(event) <span style="color: magenta; font-weight: 600;">def</span> <span style="color: cyan; font-weight: 600;">subscribe</span>(<span style="color: cyan;">self</span>, event_type: <span style="color: cyan;">EventType</span>, handler: <span style="color: cyan;">Callable</span>) -> <span style="color: cyan;">None</span>: <span style="color: cyan;">self</span>._subscriptions.setdefault(event_type, []).append(handler) <span style="color: cyan;">@property</span> <span style="color: magenta; font-weight: 600;">def</span> <span style="color: cyan; font-weight: 600;">stream</span>(<span style="color: cyan;">self</span>) -> rx.Observable: <span style="color: #888;">"""Raw observable stream for advanced RxPY operators."""</span> <span style="color: magenta; font-weight: 600;">return</span> <span style="color: cyan;">self</span>._subject
LLM Call Tracing
Every LLM invocation is traced with full context for debugging and cost tracking:
# Trace record for each LLM call
class LLMTrace(BaseModel):
trace_id: str # ULID
plan_id: str
decision_id: str | None
actor_name: str
provider: str # openai, anthropic, google, etc.
model: str # gpt-4o, claude-3.5-sonnet, etc.
prompt_tokens: int
completion_tokens: int
total_tokens: int
cost_usd: float
latency_ms: int
temperature: float
tool_calls: list[str] # tool names invoked by the LLM
context_hash: str # hash of the context window contents
context_refs: list[str] # resource IDs referenced in context
streaming: bool
retry_count: int
error: str | None
timestamp: datetime
LangSmith integration: When CLEVERAGENTS_LANGSMITH_ENABLED is true, all LLM traces are additionally forwarded to LangSmith for visualization, comparison, and cost analysis. The integration maps CleverAgents plan IDs to LangSmith run tags, enabling filtering by plan hierarchy in the LangSmith UI.
Metrics Collection
CleverAgents collects operational metrics for performance monitoring and capacity planning. Metrics are emitted as structured log entries (for local mode) and optionally exported to Prometheus (for server mode):
| Metric | Type | Description |
|---|---|---|
plan.duration_seconds |
Histogram | Total wall-clock time per plan, labeled by phase |
plan.cost_usd |
Counter | Cumulative API cost per plan |
plan.decisions_count |
Counter | Number of decisions per plan |
plan.child_plans_count |
Counter | Number of child plans spawned |
actor.invocation_duration_ms |
Histogram | Per-actor invocation latency |
actor.token_usage |
Counter | Token counts by provider and model |
tool.invocation_duration_ms |
Histogram | Per-tool invocation latency |
tool.error_rate |
Counter | Tool invocation failures by tool name |
context.build_duration_ms |
Histogram | Context building time by tier (hot/warm/cold) |
context.tokens_used |
Gauge | Current token usage in hot context |
index.query_duration_ms |
Histogram | Index query latency by backend (text/vector/graph) |
sandbox.operation_duration_ms |
Histogram | Sandbox create/commit/rollback time |
validation.duration_seconds |
Histogram | Per-validation tool invocation execution time |
validation.pass_rate |
Counter | Validation pass/fail counts (from passed field in return JSON) |
Diagnostic Dashboard
The agents diagnostics command provides a real-time health check combining all observability data:
- System health: Config file readability, database accessibility, disk space, provider API key validation.
- Index health: Text index status, vector index dimensionality and count, graph store connectivity.
- Active plan overview: Running plans, queued plans, resource utilization.
- Cost summary: Current session cost, budget utilization, provider-level breakdown.
- Performance summary: Average plan duration, tool call latency percentiles, context build times.
Security Model
!!! adr "Architecture Decision" Sandbox isolation is covered in ADR-015: Sandbox and Checkpoint. The server-mode security model is defined in ADR-023: Server Mode.
CleverAgents implements a defense-in-depth security model addressing five concerns: sandbox isolation, access control, prompt injection mitigation, secret management, and audit logging. The security model differs between local mode (single-user, trusted environment) and server mode (multi-user, potentially untrusted environment).
Sandbox Isolation
The sandbox is the primary safety mechanism preventing untested changes from reaching production resources. Every plan's Execute phase runs within an isolated sandbox unless explicitly disabled by the automation profile (require_sandbox: false).
Sandbox strategies and their security properties:
| Strategy | Isolation Level | Resource Type | Mechanism | Rollback |
|---|---|---|---|---|
git_worktree |
Process-level filesystem isolation | Git repositories | Creates a separate git worktree on a plan-specific branch. Changes are confined to the worktree directory. Merge back to main requires explicit apply. | git checkout -- . or worktree deletion |
copy_on_write |
Process-level filesystem isolation | Filesystems with native CoW support (e.g., BTRFS, ZFS) | Leverages the filesystem's native copy-on-write capability to create a lightweight snapshot. The filesystem preserves original blocks when edits occur, requiring no explicit copy. Only available on CoW-capable filesystems. | Restore from snapshot |
filesystem_copy |
Process-level filesystem isolation | All writable filesystems | Creates an explicit full copy of the resource directory (using cp or equivalent). Works on any writable filesystem regardless of CoW support, at the cost of duplicating data upfront. Original files are never modified during execution. |
Delete the copy directory |
overlay |
Process-level filesystem isolation | Filesystems supporting overlay mounts | Uses an overlay filesystem (e.g., OverlayFS) to layer changes on top of the original. Writes go to the upper layer; the lower layer remains untouched. | Remove the overlay mount |
transaction_rollback |
Database transaction isolation | Databases | Opens a database transaction with SERIALIZABLE isolation level. All changes are staged within the transaction. Commit on apply; rollback on cancel/error. |
ROLLBACK |
none |
No isolation | Any | Changes are applied directly. Requires require_sandbox: false in the automation profile. Only appropriate when external safety mechanisms exist. |
No automatic rollback |
Sandbox security invariants:
- No cross-sandbox access: Each plan's sandbox is isolated. Plan A cannot read or write Plan B's sandbox contents. The sandbox path is derived deterministically from the plan ULID, preventing path traversal.
- Read-only source guarantee during Execute: The original project resources are never modified during Execute. All tool write operations target the sandbox copy.
- Atomic apply: The Apply phase is an atomic operation — either all sandbox changes are committed to the real resources, or none are. For git-based resources, this uses git merge. For database resources, this uses transaction commit.
- Sandbox cleanup: Sandbox directories are cleaned up according to
sandbox.cleanuppolicy. Sensitive data (API responses, LLM outputs) is purged from the sandbox on cleanup.
Prompt Injection Mitigation
In server mode, where user-provided content may flow into LLM prompts, CleverAgents implements several mitigations:
-
Input sanitization: User-provided text in action arguments, invariant text, and session prompts is sanitized before inclusion in LLM prompts. HTML entities, control characters, and known injection patterns are escaped or rejected.
-
Prompt boundary markers: System prompts and user content are separated by clear boundary markers that the LLM is instructed to recognize. The system prompt explicitly states that content between
[USER_CONTENT_START]and[USER_CONTENT_END]markers is user-provided and should not be treated as system instructions. -
Output validation: LLM outputs that are used as tool invocations are validated against the tool's JSON Schema before execution. This prevents the LLM from being tricked into invoking tools with malicious parameters.
-
Tool capability restrictions: Tools declare their capabilities (
read_only,writes,checkpointable,side_effects). The execution engine enforces these declarations — a tool declared asread_onlycannot invoke write operations even if the LLM requests it. -
Unsafe tool gating: Tools marked as
unsafe(e.g., arbitrary shell execution, network access) are blocked unless the automation profile explicitly setsallow_unsafe_tools: true. In server mode, only administrators can create automation profiles that allow unsafe tools.
Secret Management
Secrets (API keys, database credentials, authentication tokens) are managed through a layered approach:
-
Environment variables (preferred): Provider API keys (
OPENAI_API_KEY,ANTHROPIC_API_KEY, etc.) and the server token (CLEVERAGENTS_SERVER_TOKEN) are read from environment variables. This is the recommended approach for both local and CI environments. -
Configuration file (fallback): API keys can be stored in
config.tomlunder theprovider.*section. The config file should have restrictive permissions (chmod 600). Theagents diagnosticscommand warns if config file permissions are too permissive. -
Secret masking in logs: All log output and CLI display automatically masks values that match known secret patterns (keys starting with
sk-,sk-ant-,tok_, etc.). Masked values are replaced with***REDACTED***. The masking is applied at the structlog processor level, ensuring no secret leaks through any log path. -
Secret masking in LLM context: Before constructing LLM prompts, the context builder scans for patterns matching known secret formats and replaces them with
[REDACTED]. This prevents accidental exposure of secrets in LLM training data (for providers that use customer data for training, which CleverAgents discourages). -
Server mode credential isolation: In server mode, each user's API keys are stored encrypted in the server database using AES-256-GCM with a per-user key derived from the server's master secret. Keys are decrypted only at the moment of LLM invocation and are never transmitted to the client CLI.
Exception Handling and Error Classification
All exceptions surfacing through the CLI are processed by the cleveragents.core.error_handling module, which provides three guarantees: classification, redaction, and safe wrapping.
Error classification: Every exception is mapped to an HTTP-like numeric error code via the exception class hierarchy. The mapping uses the exception's MRO (Method Resolution Order) to find the most specific match:
| Range | Category | Examples |
|---|---|---|
| 400-range | Client / input errors | ValidationError → 422, ResourceNotFoundError → 404, AuthenticationError → 401, RateLimitError → 429 |
| 500-range | Server / infrastructure errors | DatabaseError → 522, NetworkError → 524, ProviderError → 520, ConfigurationError → 525 |
Unknown exceptions (not inheriting from CleverAgentsError) default to code 500 (INTERNAL).
Secret redaction in error output: Both the exception message and its details dict are redacted before display. Redaction delegates to cleveragents.shared.redaction (the single redaction implementation shared with structlog processors and CLI output). This ensures:
- Key names matching sensitive substrings (
api_key,password,secret,token,credential,auth, etc.) have values fully replaced with***REDACTED***. - String values are scanned for known secret patterns (OpenAI keys, Anthropic keys, JWT tokens, GitHub PATs, GitLab PATs, Bearer tokens).
- Nested dicts and lists are recursively redacted.
- Error details always enforce redaction (
show_secrets=False) regardless of the globalshow_secretsflag.
Safe wrapping of unexpected exceptions: The wrap_unexpected function converts bare Exception instances into CleverAgentsError with a generic, user-safe message ("An unexpected error occurred"). Internal stack details are captured in the error's details dict for diagnostics but are not shown to the user. If the exception is already a CleverAgentsError, it is returned unchanged with optional context merged into a copy of its details.
CLI integration: The top-level error handlers in cli/main.py use classify_error for CleverAgentsError exceptions and wrap_unexpected + classify_error for bare exceptions. CLI output includes the error code and category name for consistent, machine-parseable error reporting:
Error [422] VALIDATION_FAILED: name is required
Error [500] INTERNAL: An unexpected error occurred
See docs/reference/error_handling.md for the complete API reference and exception hierarchy.
Audit Logging
All security-relevant operations are recorded in the audit_log table with the following events:
| Event Type | Trigger | Details Captured |
|---|---|---|
plan_applied |
agents plan apply |
Plan ID, project names, files changed, lines added/removed, resources modified, apply duration, user identity |
plan_cancelled |
agents plan cancel |
Plan ID, reason, project names, cancelled phase, cancelled processing state, last completed step, subplan count, sandbox refs, changeset ID, resources pending cleanup |
resource_modified |
Tool write operations during Execute | Resource ID, modification type, sandbox path, tool name |
correction_applied |
agents plan correct |
Correction attempt ID, original decision ID, mode, guidance |
config_changed |
agents config set |
Key, old value (masked if secret), new value (masked if secret), user identity |
entity_deleted |
agents <entity> delete/remove |
Entity type, entity name, cascade effects |
session_created |
agents session create |
Session ID, actor name, user identity |
auth_success |
Server mode login | User identity, IP address, token prefix |
auth_failure |
Server mode failed login | Attempted identity, IP address, failure reason |
Audit log retention: Audit logs are never automatically deleted. In server mode, audit logs can be exported for compliance archival using server administration commands. In local mode, audit logs are preserved in the SQLite database indefinitely and are included in backup snapshots.
Extensibility
!!! adr "Architecture Decision" The extensibility model and plugin architecture are grounded in the layered design defined in ADR-001: Layered Architecture and the configuration system in ADR-024: Configuration System.
CleverAgents is designed as an extensible platform where every major subsystem supports custom implementations. The extensibility model follows the Open/Closed Principle — the system is open for extension through well-defined interfaces, but closed for modification of core behavior.
Plugin Architecture Overview
@startuml
skinparam componentStyle rectangle
skinparam defaultFontSize 12
skinparam packageFontSize 13
skinparam packageFontStyle bold
skinparam componentFontSize 11
package "Extension Points" as EP {
package "User-Facing Extensions" as UFE #C8E6C9 {
component [Custom Tools\n(YAML/Python)] as CT
component [Custom Skills\n(YAML)] as CS
component [Custom Actors\n(YAML/Graph)] as CA
}
package "Integration Extensions" as IE #E3F2FD {
component [MCP Servers\n(stdio/SSE)] as MCP
component [Agent Skills\nStandard] as ASS
component [Custom LLM\nProviders] as CLP
}
package "Core Registries" as CR #F3E5F5 {
component [Tool Registry\nSkill Registry\nActor Registry\nProvider Registry] as REG
}
package "Infrastructure Extensions" as INF #FFF9C4 {
component [Custom Resource\nTypes (YAML)] as CRT
component [Custom Sandbox\nStrategies (Python)] as CSS
component [Custom Index\nBackends] as CIB
}
package "ACMS Pipeline Extensions" as ACMS #E8EAF6 {
component [Custom UKO\nAnalyzers / Vocabularies] as UKO
component [Custom Context\nStrategies] as CCS
component [Custom Pipeline\nComponents (10 slots)] as CPC
}
CT -down-> REG
CS -down-> REG
CA -down-> REG
MCP -down-> REG
ASS -down-> REG
CLP -down-> REG
REG -down-> CRT
REG -down-> CSS
REG -down-> CIB
REG -down-> UKO
REG -down-> CCS
REG -down-> CPC
}
@enduml
Tool Extensibility
Tools are the atomic unit of execution and the primary extension point. There are five ways to add tools:
-
Custom Python tools: Define a tool with inline Python code in a YAML configuration file. The code runs in a sandboxed execution context with access to the plan context, resource bindings, and checkpoint API.
# File: tools/my-custom-tool.yaml tool: name: local/analyze-complexity description: "Compute cyclomatic complexity for Python files" source: custominput_schema: type: object properties: file_path: { type: string } required: [file_path]
output_schema: type: object properties: complexity: { type: integer } functions: { type: array }
capability: writes: false checkpointable: false
resource_slots: - name: source type: git-checkout binding: contextual
code: | import ast content = ctx.resources["source"].read(params["file_path"]) tree = ast.parse(content) # ... complexity analysis ... return {"complexity": total, "functions": results} -
MCP server tools: Any MCP-compliant server can expose tools to CleverAgents. Skills reference MCP servers by their transport configuration:
# File: skills/kubernetes-ops.yaml skill: name: local/kubernetes-ops description: "Kubernetes cluster management tools" mcp_servers: - transport: stdio command: npx args: ["-y", "@anthropic/mcp-kubernetes"] -
Agent Skills Standard tools: Tools organized in standard folder structures are auto-discovered and registered:
skill: name: local/project-tools agent_skills_dirs: - ./agent-skills/ -
Built-in tools: Core file operations (
read_file,write_file,edit_file,delete_file,move_file,list_files,search_files), plan operations (create-subplan), and system operations are provided as built-in tools grouped into built-in skills. -
Anonymous inline tools: Tools can be defined inline within actor graph nodes or skill definitions for one-off operations that don't need global registration.
Tool lifecycle hooks: Every tool follows a four-phase lifecycle — discover (find available capabilities), activate (prepare for use, acquire connections), execute (perform the operation), deactivate (release resources). The tool registry manages these lifecycle transitions.
Skill Extensibility
Skills compose tools into capability collections. Skills support:
- Tool aggregation: Reference named tools from the Tool Registry
- Inline anonymous tools: Define tools directly in the skill YAML
- Skill inclusion: Include other skills by reference (composability)
- MCP server aggregation: Expose tools from one or more MCP servers
- Agent Skills Standard: Expose tools from Agent Skills Standard directories
Actor Extensibility
Actors can be extended through:
- YAML-defined agents: Single LLM actors with custom system prompts, temperature settings, tool bindings, and capability constraints.
- YAML-defined graphs: LangGraph topologies with multiple actors and tool nodes connected by edges, conditional routing, and parallel execution groups.
- Provider extension: New LLM providers can be added by implementing the
AIProviderInterfaceprotocol and registering with theProviderRegistry. The registry uses auto-discovery to detect installedlangchain-*packages.
from typing import Protocolclass AIProviderInterface(Protocol): """Protocol for LLM provider implementations."""
<span style="color: cyan;">@property</span> <span style="color: magenta; font-weight: 600;">def</span> <span style="color: cyan; font-weight: 600;">provider_name</span>(<span style="color: cyan;">self</span>) -> <span style="color: cyan;">str</span>: ... <span style="color: cyan;">@property</span> <span style="color: magenta; font-weight: 600;">def</span> <span style="color: cyan; font-weight: 600;">capabilities</span>(<span style="color: cyan;">self</span>) -> ProviderCapabilities: ... <span style="color: magenta; font-weight: 600;">def</span> <span style="color: cyan; font-weight: 600;">create_chat_model</span>( <span style="color: cyan;">self</span>, model: <span style="color: cyan;">str</span>, temperature: <span style="color: cyan;">float</span> = <span style="color: yellow;">0.7</span>, **kwargs, ) -> BaseChatModel: ... <span style="color: magenta; font-weight: 600;">def</span> <span style="color: cyan; font-weight: 600;">create_embedding_model</span>( <span style="color: cyan;">self</span>, model: <span style="color: cyan;">str</span>, **kwargs, ) -> BaseEmbeddings: ...
Custom Resource Types
New resource types extend CleverAgents to manage any kind of resource:
# File: resource-types/docker-registry.yaml resource_type: name: local/docker-registry description: "Docker container registry" classification: physical user_addable: truecli_args: - name: registry_url type: string required: true - name: repository type: string required: true - name: tag type: string default: "latest"
sandbox_strategy: none # Docker images are immutable handler: docker_registry_handler
child_types: - type: local/docker-image relationship: contains auto_discover: true
auto_discovery: enabled: true strategy: list_tags
Custom Index Backends
The indexing layer is abstracted behind three interfaces (text, vector, graph). Custom implementations can be registered for each:
class TextIndexBackend(Protocol): """Interface for full-text search backends.""" def index_document(self, project: str, doc_id: str, content: str, metadata: dict) -> None: ... def search(self, project: str, query: str, limit: int) -> list[SearchResult]: ... def remove_document(self, project: str, doc_id: str) -> None: ... def rebuild_index(self, project: str) -> None: ...class VectorIndexBackend(Protocol): """Interface for vector similarity search backends.""" def index_embedding(self, project: str, doc_id: str, embedding: list[float], metadata: dict) -> None: ... def search_similar(self, project: str, query_embedding: list[float], limit: int, min_relevance: float) -> list[VectorResult]: ... def remove_embedding(self, project: str, doc_id: str) -> None: ...
class GraphIndexBackend(Protocol): """Interface for knowledge graph backends.""" def add_triple(self, project: str, subject: str, predicate: str, obj: str) -> None: ... def query(self, project: str, sparql: str) -> list[dict]: ... def remove_triples(self, project: str, subject: str | None, predicate: str | None, obj: str | None) -> None: ...
New backends are registered via configuration:
# config.toml [index.text] backend = "custom" custom_module = "my_extensions.elasticsearch_backend" custom_class = "ElasticsearchTextIndex"
[index.text.custom_options] hosts = ["http://localhost:9200"] index_prefix = "cleveragents"
Custom Sandbox Strategies
The sandbox layer supports custom isolation strategies for specialized resource types:
class SandboxStrategy(Protocol):
"""Interface for sandbox isolation strategies."""
def create(self, plan_id: str, resource: Resource) -> SandboxRef: ...
def read(self, ref: SandboxRef, path: str) -> bytes: ...
def write(self, ref: SandboxRef, path: str, content: bytes) -> Change: ...
def diff(self, ref: SandboxRef) -> DiffView: ...
def commit(self, ref: SandboxRef) -> None: ...
def rollback(self, ref: SandboxRef) -> None: ...
def checkpoint(self, ref: SandboxRef, checkpoint_id: str) -> None: ...
def restore_checkpoint(self, ref: SandboxRef, checkpoint_id: str) -> None: ...
def cleanup(self, ref: SandboxRef) -> None: ...
Custom strategies are mapped to resource types via the resource type configuration's sandbox_strategy field, or globally via sandbox.strategy config.
ACMS Extensions
The ACMS provides five categories of extension points: UKO analyzers, index backends, context strategies, UKO vocabularies, and Context Assembly Pipeline components.
Domain and Language-Specific Analyzers
Analyzers are pluggable components that parse resources and produce UKO triples. They are organized by domain (code, documents, data, infrastructure) and then by specific technology within each domain:
PluginSystem: analyzers: # --- Source Code Analyzers (Layer 1: uko-code + Layer 2/3) --- python: class: "PythonAnalyzer" features: [AST parsing, Type inference via mypy, Import resolution, Docstring extraction] typescript: class: "TypeScriptAnalyzer" features: [TSC-based parsing, Type extraction, Module resolution, JSDoc parsing] rust: class: "RustAnalyzer" features: [rust-analyzer integration, Lifetime analysis, Trait resolution, Macro expansion] custom_dsl: class: "CustomDSLAnalyzer" config: grammar: "path/to/grammar.peg" semantic_rules: "path/to/rules.yaml"<span style="color: #888;"># --- Document Analyzers (Layer 1: uko-doc) ---</span> <span style="color: cyan; font-weight: 600;">markdown</span>: <span style="color: cyan; font-weight: 600;">class</span>: <span style="color: #66cc66;">"MarkdownAnalyzer"</span> <span style="color: cyan; font-weight: 600;">features</span>: [<span style="color: #66cc66;">Heading hierarchy, Link extraction, Code block detection, Topic inference, Cross-reference resolution</span>] <span style="color: cyan; font-weight: 600;">restructuredtext</span>: <span style="color: cyan; font-weight: 600;">class</span>: <span style="color: #66cc66;">"ReStructuredTextAnalyzer"</span> <span style="color: cyan; font-weight: 600;">features</span>: [<span style="color: #66cc66;">Directive parsing, Role resolution, Cross-reference resolution, Index extraction</span>] <span style="color: cyan; font-weight: 600;">html</span>: <span style="color: cyan; font-weight: 600;">class</span>: <span style="color: #66cc66;">"HTMLDocumentAnalyzer"</span> <span style="color: cyan; font-weight: 600;">features</span>: [<span style="color: #66cc66;">Semantic HTML parsing, Heading extraction, Link graph, Readability scoring</span>] <span style="color: #888;"># --- Data Schema Analyzers (Layer 1: uko-data) ---</span> <span style="color: cyan; font-weight: 600;">postgresql</span>: <span style="color: cyan; font-weight: 600;">class</span>: <span style="color: #66cc66;">"PostgreSQLAnalyzer"</span> <span style="color: cyan; font-weight: 600;">features</span>: [<span style="color: #66cc66;">Schema introspection, DDL extraction, Foreign key graph, View dependency analysis, Stored procedure parsing, Statistics collection</span>] <span style="color: cyan; font-weight: 600;">mysql</span>: <span style="color: cyan; font-weight: 600;">class</span>: <span style="color: #66cc66;">"MySQLAnalyzer"</span> <span style="color: cyan; font-weight: 600;">features</span>: [<span style="color: #66cc66;">Schema introspection, DDL extraction, Foreign key graph, Trigger parsing</span>] <span style="color: cyan; font-weight: 600;">sqlite</span>: <span style="color: cyan; font-weight: 600;">class</span>: <span style="color: #66cc66;">"SQLiteAnalyzer"</span> <span style="color: cyan; font-weight: 600;">features</span>: [<span style="color: #66cc66;">Schema introspection, DDL extraction, Virtual table detection</span>] <span style="color: #888;"># --- Infrastructure Analyzers (Layer 1: uko-infra) ---</span> <span style="color: cyan; font-weight: 600;">docker_compose</span>: <span style="color: cyan; font-weight: 600;">class</span>: <span style="color: #66cc66;">"DockerComposeAnalyzer"</span> <span style="color: cyan; font-weight: 600;">features</span>: [<span style="color: #66cc66;">Service graph, Port mapping, Volume resolution, Environment variable extraction</span>] <span style="color: cyan; font-weight: 600;">kubernetes</span>: <span style="color: cyan; font-weight: 600;">class</span>: <span style="color: #66cc66;">"KubernetesAnalyzer"</span> <span style="color: cyan; font-weight: 600;">features</span>: [<span style="color: #66cc66;">Resource parsing, Service dependency graph, ConfigMap/Secret references, Ingress routing</span>]
Index Backend Providers
backends:
graph:
- { name: "blazegraph", class: "BlazegraphBackend", features: ["SPARQL", "reasoning"] }
- { name: "neo4j", class: "Neo4jBackend", features: ["Cypher", "APOC", "GDS"] }
vector:
- { name: "faiss", class: "FaissBackend", features: ["GPU acceleration", "HNSW"] }
- { name: "qdrant", class: "QdrantBackend", features: ["filtering", "payloads"] }
embedders:
- { name: "openai", class: "OpenAIEmbedder", models: ["text-embedding-3-large"] }
- { name: "local", class: "LocalEmbedder", models: ["all-MiniLM-L6-v2"] }
Adding a New UKO Vocabulary (e.g., for Terraform)
@prefix uko-tf: <https://cleveragents.ai/ontology/uko/infra/terraform#> .
uko-tf:TerraformModule a owl:Class ; rdfs:subClassOf uko-infra:ConfigBlock, uko:Container . uko-tf:Resource a owl:Class ; rdfs:subClassOf uko:Container . uko-tf:Variable a owl:Class ; rdfs:subClassOf uko-infra:ConfigKey . uko-tf:Output a owl:Class ; rdfs:subClassOf uko:Boundary . uko-tf:provisionsOn a owl:ObjectProperty ; rdfs:subPropertyOf uko:dependsOn ; rdfs:domain uko-tf:Resource ; rdfs:range uko-infra:Service .
Adding a New Context Strategy
class MyDomainStrategy: name = "my-namespace/domain-strategy" capabilities = StrategyCapabilities( uses_graph=True, uses_text=True, uko_levels=["uko:", "uko-tf:"], resource_types=["*"], supports_depth_breadth=True, quality_score=0.8, )<span style="color: magenta; font-weight: 600;">def</span> <span style="color: cyan; font-weight: 600;">can_handle</span>(<span style="color: cyan;">self</span>, request, backends): <span style="color: magenta; font-weight: 600;">if</span> any(<span style="color: #66cc66;">"terraform"</span> <span style="color: magenta; font-weight: 600;">in</span> f <span style="color: magenta; font-weight: 600;">for</span> f <span style="color: magenta; font-weight: 600;">in</span> request.focus): <span style="color: magenta; font-weight: 600;">return</span> <span style="color: yellow;">0.9</span> <span style="color: magenta; font-weight: 600;">return</span> <span style="color: yellow;">0.0</span> <span style="color: magenta; font-weight: 600;">def</span> <span style="color: cyan; font-weight: 600;">assemble</span>(<span style="color: cyan;">self</span>, request, backends, budget, plan_context): <span style="color: #888;"># Custom logic...</span> ...
Register via config:
[context.strategies.custom]
"my-namespace/domain-strategy" = "my_package.strategies.MyDomainStrategy"
Replacing Pipeline Components
Any of the 10 Context Assembly Pipeline components can be replaced by implementing the corresponding Protocol and registering the implementation. Components can be overridden at three scopes (plan > project > global), with more specific scopes taking precedence.
class MyCustomScorer: """A domain-aware fragment scorer that boosts infrastructure fragments when the plan focuses on deployment tasks."""<span style="color: magenta; font-weight: 600;">def</span> <span style="color: cyan; font-weight: 600;">score</span>(<span style="color: cyan;">self</span>, fragments: <span style="color: cyan;">list</span>[ContextFragment], plan_context: PlanContext) -> <span style="color: cyan;">list</span>[ScoredFragment]: scored = [] is_deploy = <span style="color: #66cc66;">"deploy"</span> <span style="color: magenta; font-weight: 600;">in</span> plan_context.plan.description.lower() <span style="color: magenta; font-weight: 600;">for</span> f <span style="color: magenta; font-weight: 600;">in</span> fragments: base_score = f.relevance_score * <span style="color: yellow;">0.4</span> + f.hierarchy_weight * <span style="color: yellow;">0.3</span> <span style="color: magenta; font-weight: 600;">if</span> is_deploy <span style="color: magenta; font-weight: 600;">and</span> <span style="color: #66cc66;">"uko-infra:"</span> <span style="color: magenta; font-weight: 600;">in</span> f.uko_uri: base_score *= <span style="color: yellow;">1.5</span> <span style="color: #888;"># Boost infrastructure fragments</span> scored.append(ScoredFragment(**vars(f), composite_score=base_score)) <span style="color: magenta; font-weight: 600;">return</span> sorted(scored, key=<span style="color: magenta; font-weight: 600;">lambda</span> s: s.composite_score, reverse=<span style="color: magenta; font-weight: 600;">True</span>)
Register via TOML config (global scope):
[context.pipeline]
fragment-scorer = "my_extensions.scorers:MyCustomScorer"
Or via project-level context view YAML:
project: local/api-service
view: strategize
pipeline:
fragment-scorer: "my_extensions.scorers:MyCustomScorer"
The following table shows which Protocol each pipeline slot implements and what the default implementation provides:
| Pipeline Slot | Protocol | Default Implementation | Design Pattern |
|---|---|---|---|
strategy-selector |
StrategySelectorProtocol |
ConfidenceWeightedSelector |
Strategy |
budget-allocator |
BudgetAllocatorProtocol |
ProportionalBudgetAllocator |
Strategy |
strategy-executor |
StrategyExecutorProtocol |
ParallelStrategyExecutor |
Circuit Breaker |
fragment-deduplicator |
FragmentDeduplicatorProtocol |
ContentHashDeduplicator |
Strategy |
detail-depth-resolver |
DetailDepthResolverProtocol |
MaxDepthResolver |
Specification |
fragment-scorer |
FragmentScorerProtocol |
WeightedCompositeScorer |
Decorator |
budget-packer |
BudgetPackerProtocol |
GreedyKnapsackPacker |
Iterator |
fragment-orderer |
FragmentOrdererProtocol |
RelevanceCoherenceOrderer |
Template Method |
preamble-generator |
PreambleGeneratorProtocol |
ProvenancePreambleGenerator |
Builder |
skeleton-compressor |
SkeletonCompressorProtocol |
DepthReductionCompressor |
Visitor |
Extension Points Summary
| Extension Point | Registration Mechanism | Configuration Format | Discovery |
|---|---|---|---|
| Custom Tools | agents tool add --config <file> |
YAML | Manual registration |
| MCP Server Tools | Referenced in skill YAML mcp_servers section |
YAML | Auto-discovered from MCP server |
| Agent Skills Standard | Referenced in skill YAML agent_skills_dirs section |
Folder structure | Auto-discovered from directory |
| Custom Skills | agents skill add --config <file> |
YAML | Manual registration |
| Custom Actors | agents actor add --config <file> |
YAML | Manual registration |
| Custom Resource Types | agents resource type add --config <file> |
YAML | Manual registration |
| Custom LLM Providers | Python package installation + ProviderRegistry auto-discovery | Python module | Auto-discovered via langchain-* package naming |
| Custom Index Backends | config.toml index.*.custom_module |
TOML + Python module | Configuration-driven |
| Custom Sandbox Strategies | Resource type sandbox_strategy field |
YAML + Python module | Configuration-driven |
| Custom Automation Profiles | agents automation-profile add --config <file> |
YAML | Manual registration |
| Custom Invariant Actors | Standard actor registration + assignment via --invariant-actor |
YAML | Manual assignment |
| Custom UKO Analyzers | config.toml context.uko.analyzers.custom.* |
TOML + Python module | Configuration-driven |
| Custom UKO Vocabularies | Turtle/RDF files + analyzer registration | RDF/Turtle + TOML | Configuration-driven |
| Custom Context Strategies | config.toml context.strategies.custom.* |
TOML + Python module | Configuration-driven |
| Pipeline: StrategySelector | config.toml context.pipeline.strategy-selector or project/plan YAML |
TOML/YAML + Python module | Configuration-driven (scope chain: plan > project > global > built-in) |
| Pipeline: BudgetAllocator | config.toml context.pipeline.budget-allocator or project/plan YAML |
TOML/YAML + Python module | Configuration-driven (scope chain) |
| Pipeline: StrategyExecutor | config.toml context.pipeline.strategy-executor or project/plan YAML |
TOML/YAML + Python module | Configuration-driven (scope chain) |
| Pipeline: FragmentDeduplicator | config.toml context.pipeline.fragment-deduplicator or project/plan YAML |
TOML/YAML + Python module | Configuration-driven (scope chain) |
| Pipeline: DetailDepthResolver | config.toml context.pipeline.detail-depth-resolver or project/plan YAML |
TOML/YAML + Python module | Configuration-driven (scope chain) |
| Pipeline: FragmentScorer | config.toml context.pipeline.fragment-scorer or project/plan YAML |
TOML/YAML + Python module | Configuration-driven (scope chain) |
| Pipeline: BudgetPacker | config.toml context.pipeline.budget-packer or project/plan YAML |
TOML/YAML + Python module | Configuration-driven (scope chain) |
| Pipeline: FragmentOrderer | config.toml context.pipeline.fragment-orderer or project/plan YAML |
TOML/YAML + Python module | Configuration-driven (scope chain) |
| Pipeline: PreambleGenerator | config.toml context.pipeline.preamble-generator or project/plan YAML |
TOML/YAML + Python module | Configuration-driven (scope chain) |
| Pipeline: SkeletonCompressor | config.toml context.pipeline.skeleton-compressor or project/plan YAML |
TOML/YAML + Python module | Configuration-driven (scope chain) |