Skip to content

Latest commit

 

History

History
239 lines (207 loc) · 20.1 KB

architecture.md

File metadata and controls

239 lines (207 loc) · 20.1 KB

Architecture

OpenKyrozen is a modular monolith. Its Python package contains the implementations; root scripts are launchers. The Bubble Tea client remains in the tui Go package.

CLI / TUI JSONL / web REST and SSE / MCP
                  │
                  ▼
       AgentRuntime.chat(session, message)
                  │
   preparation → response recovery → action rounds → completion
                  │
                  ▼
       capability and approval gates → ExecutionReceipt
                  │
                  ▼
       provider / workspace tools / repository ports
                  │
                  ▼
       SDKs / subprocess / browser / SQLite / optional Chroma

Composition and ownership

openkyrozen.app.bootstrap.build_application constructs storage, adapters, feature services and the runtime. Application.close releases owned worker and browser resources. Importing core modules or server:app creates no database, client, worker or terminal UI. The web factory openkyrozen.interfaces.web.app.create_app(application=None) accepts an injected application, or creates one during startup. Web chat and MCP retain request serialization while using explicit sessions; they do not swap agent globals.

Owner State
Application Configuration, authoritative store, provider configuration, shared usage and feature services
Workspace Root, bound tool adapters, graph, GitHub client and launch context
AgentSession Actor, conversation, durable task scope, interaction controller, learning feedback and subagents
Turn Response state, callbacks, capability token, selected model and request usage
ConversationHistory Versioned private turn entries and provider/display projections, scoped by actor, interaction workspace and actual session ID
EventsRepository Transactional journal append, idempotent entry IDs, expected-head checks and private record reads over the existing SQLite events table

Session identity includes actor, interaction scope, session ID and workspace root. Personal memory retains its existing global workspace scope. Source snapshots and project history use the source scope. Changing projects selects another adapter owner; existing sessions retain their original workspace. Context variables carry session, event, approval and execution state through provider threads without global mutation.

ConversationHistory records user input, each received provider response, correlated typed tool results, interaction references and turn outcomes. Entry payloads are versioned defensive JSON snapshots; private rows are not returned by generic event, SSE or interface APIs. The runtime records before the next dispatch and stops if a journal write fails. Workspace snapshot nodes carry a private journal checkpoint; rollback appends a branch-selection entry and preserves the abandoned branch. Legacy message lists are imported with provenance without parsing their text. Saved provider continuations remain bound to their original provider/model and cannot replay tools when that binding no longer validates.

The provider projection restores typed responses and correlated tool replies, and fails if a native call/reply group or continuation binding is incomplete. The display projection renders each completed turn once. Context compaction remains a derived message view and never rewrites the journal. Context usage reports estimated, content-free component attribution, a stable-prefix fingerprint, newly introduced history, retransmission and duplicate-entry counts; provider-reported input totals remain separate from those estimates.

Preparation freezes the learned-artifact snapshot and one bounded default memory packet for the turn. Periodic learning runs in the detached worker; input-dependent learning is queued on the existing executor after turn completion. Neither runs during foreground preparation. Initial prompts and action-round continuations reuse the frozen snapshots; an explicit search_memory call remains an additional retrieval.

Prompt profiles share execution and safety instructions; compact mode changes only tool-description discovery. Native providers receive typed schemas, while text-only providers retain the Action compatibility adapter. Triggered bug-fix and Git workflows are versioned built-in skills selected during preparation and frozen in the turn's learned-guidance snapshot. Runtime capability and approval checks remain authoritative even when no skill matches.

routing.router resolves one immutable RoutingDecision for each turn. Local classification and optional System One advice can select only configured models, the existing coder/researcher profiles, and planning needs derived from valid complexity. The decision has no capability or protocol fields; a routing-advice failure falls back to normal local selection.

Under context pressure, older complete native call/result groups keep their call identity while oversized result strings are shortened; the newest group remains intact. An accepted plan summary bound to the active Working Plan lives in fixed prompt state, so conversation compaction cannot discard its current bounds or tasks.

Package responsibilities

Package Responsibility
app Configuration, resources, composition and lifecycle
agent Session runtime, coordinated turn phases, parsing, prompts, planning, execution, modes and subagents
routing Complexity/model routing, System One choices, Decision Assist, calibration and optional transports
providers Existing provider contract, metadata registry, transport adapters, fallback and usage
tools Workspace-bound filesystem, shell, Git, web, browser, graph and GitHub adapters
tasks Models, evidence-based completion, recovery, workers and scheduling
memory Retrieval, scoped claims and the optional vector-index adapter
learning Feature dispatch and implementations, evidence, candidates, canaries, promotion, rollback and detached worker
security Capabilities, permissions, command protections, credentials and untrusted input
workspace Launch context, project graph and history
persistence SQLite connections/schema and repositories for events, history, tasks, memory, learning, usage and catalogs
skills, plugins Existing loading, registry and lifecycle behavior
updates Verified downloads, package updates and Go/TUI installation
interfaces Rich CLI, Python JSONL backend, web feature routes/templates and MCP

Compatible providers share the OpenAI transport; native Responses, Anthropic, Google, Azure, Bedrock, Perplexity and Ollama transports retain their existing contracts. The Go UI splits its model, startup, events, update/input handling, navigation, settings, layout and views inside the same package and Bubble Tea model.

V3 subsystem ownership contract

This contract covers V3 foundation issue #227, requirements R236–R249. Each row names one package owner, not a new class to scaffold. The existing components are the migration starting points on V2; the linked issues implement the V3 behavior. In particular, native AgentEngine execution, separate Working/Strategic Plans, typed provider responses and typed runtime events are separate implementation work, not capabilities established by ownership names alone. The structured provider foundation is described in providers.

Requirement / subsystem Package owner and existing components Responsibility and authority boundary V3 implementation
R236 AgentEngine agent: runtime.AgentRuntime, turn phases in preparation, action_rounds, completion Sole foreground model/tool loop coordinator; calls provider, planning and executor contracts. Does not translate provider wire formats or bypass authorization. #238
R237 PlanningService tasks: engine.TaskManager, models, ports.TaskStore; agent.planner currently bridges textual plans Owns Working Plan and Strategic Plan transitions, validation and completion evidence. Working Plans are mutable; Strategic Plans require user approval and material revisions require reapproval. Storage persists transitions; the engine requests them. #241, #242, #245, #246
R238 ToolRegistry tools: manifest.ToolRegistry, ToolManifest, registry, adapters.ToolAdapters Owns definitions, schemas, metadata and executor registration. Supplies the catalog to providers and execution; registration or discovery never grants permission. Extend this registry rather than adding a competing catalog. #232
R239 ToolExecutor agent: executor, types.ExecutionReceipt Owns authorized invocation and receipts. Uses registry definitions, security decisions and bound tool implementations; denied or failed calls cannot become successful effects. Does not own permission policy or plan transitions. #233, #236
R240 SecurityPolicy security: capabilities, tool_policy, permissions, permission_gate Owns capability and approval authorization. Effective permissions stay within configured, session and delegated bounds; model output, routing advice and catalog entries cannot approve a call. The executor enforces the decision. #261
R241 ProviderAdapter providers: base.LLMProvider, provider transport modules, factory, calls Owns native request/response translation, streaming and provider errors. Receives conversation/tool contracts; returns model results and usage. Never executes model-requested tools or mutates plans. #228, #229, #230, #231
R242 ContextManager agent: models.AgentSession, context, compaction.ContextState Owns active conversation representation and compaction. Uses provider token/usage information and plan snapshots; preserves call/result identity and active plans. Does not promote memories or independently change plan state. #247, #248
R243 Task/Plan Store persistence: tasks.TasksRepository, events.EventsRepository, store.EventStore, database Owns durable task/plan records and revision events. Implements feature-owned storage ports and atomic writes/migrations. PlanningService owns transition semantics; storage cannot infer user approval or completion from prose. #244, #264
R244 RoutingService routing: router, policy, models, choices Owns model/provider/reasoning/resource selection. Reads provider capabilities, complexity and resource bounds; returns a routing decision. Cannot grant capabilities, invoke tools or mark tasks complete. #251
R245 SubagentManager agent: subagents.SubAgentManager, delegation, delegation_runtime Owns child orchestration, budgets, isolation and lifecycle. Uses the shared engine/executor and security bounds; child authority can only narrow parent authority. Results require evidence/review before parent acceptance. #254, #258
R246 MemoryService memory: service.MemoryBank, retrieval, claims, ports Owns retrieval/storage semantics and scoped claims. Uses injected SQLite/vector ports; Chroma remains derived. Retrieved content is evidence/context, never execution authority or an approved plan. #252
R247 LearningService learning: engine.LearningEngine, dispatcher, evidence/evaluation/promotion modules Owns post-turn evidence, evaluation and promotion. Uses memory, skill and storage contracts; promotion requires evidence. Learning cannot change foreground execution authority or approve its own tool calls. #253
R248 Jev/System One routing: system_one, decision_assist, transport, kev Advisory only: proposes routing/ranking/review choices within RoutingService. Never owns execution authority, permission approval, plan acceptance or completion. Remote/local transports stay behind injected boundaries. #251
R249 Interfaces interfaces: CLI, TUI JSONL, web REST/SSE and MCP adapters; tui renders the Go client Consume/render runtime events and submit user input, approvals and cancellation. Do not implement model/tool loops, plan transitions or completion rules. Typed event migration replaces existing surface shapes through explicit adapters. #255, #256

Dependency direction and enforcement

app.bootstrap is the composition root: it constructs concrete adapters and injects feature services, callbacks and ports. Existing method binding is retained during migration; it is not a license to move semantic decisions into presentation code. Interfaces call the runtime/service contracts; feature code emits events through agent.ports.EventSink and requests approval through Approval, rather than importing interface implementations. Concrete provider, tool, SQLite, vector and routing transports remain behind the injected boundaries.

Runtime observers receive immutable agent.events.RuntimeEvent records with per-turn sequence numbers and user/workspace/session plus request/call/task correlation IDs. The legacy on_event callback is a compatibility projection; it cannot mutate canonical payloads or change authorization, execution, usage, or task status. CLI/TUI/Web adapters preserve their established wire fields. Text bypasses legacy control parsing only when the request used native tool schemas; the foreground loop's no-tools textual protocol remains on its compatibility path until AgentEngine migration. Tool-call delta events expose call identity and fragment length but redact argument text because observers include remote UI surfaces; those deltas are progress metadata, not replayable invocation input.

Permitted feature dependencies follow the responsibilities above: agent coordinates tasks, tools catalog/model contracts, security, provider contracts, routing, memory and learning; tasks owns its models/storage ports; memory owns memory models/storage/vector ports; learning consumes memory, skill and storage contracts. Shared persistence models are allowed; concrete repositories belong at composition. tools uses security/contracts and adapter-specific libraries inside adapter modules; providers uses provider configuration/contracts and SDKs inside transport modules; routing reads provider metadata and security/resource bounds; persistence implements storage without importing interfaces or coordinating agent execution. New dependencies must preserve these authority limits and avoid import-time cycles.

Tool authority is the intersection of configured, surface, interaction-mode, parent, and request capabilities; a surface override or renewed token can only narrow that bound. AgentRuntime.execute_call resolves canonical names and creates an ApprovalRequest fingerprinted over call identity, arguments, workspace/session, and the registered tool policy before asking the interface for approval. The executor rechecks registration, scope, and authority after an approval wait, then crosses an explicit legacy argument adapter only for tools not yet migrated to object arguments. Approval is not plan acceptance or effect evidence. Delegated children may write only their assigned files through workspace file tools; shell, Git, browser, plugin, and other unconfined mutation tools stay with the parent.

scripts/check_architecture.py enforces the existing static import policy across agent, tasks, memory and learning: no legacy root implementation imports, no interface package-root or descendant imports, and no listed concrete adapters or external adapter libraries. Absolute and relative imports are checked, including function-local imports and concrete adapters reached through statically named import re-exports (including aliased and chained re-exports). Existing entry adapters (memory.vector, learning.worker and learning.benchmark) retain their explicit exemption from core adapter bans. Import-time statement bodies (including branches, try/else/finally, with, loops, match and class bodies) contribute cycle edges. Function bodies and type-only TYPE_CHECKING / typing.TYPE_CHECKING branches do not; their runtime else branches still do. check(root=...) accepts a temporary source root for regressions; check() and the script entry point still check this repository.

This is a static import guard, not an authority proof: it does not inspect dynamic imports, dynamically assigned exports, injected callables or every semantic dependency listed above. Runtime safety and ownership tests remain necessary. Each linked implementation issue must extend contract/authority tests as it migrates behavior; this issue does not claim the whole V3 architecture is already implemented.

Ports and durable data

The inbound chat contract is AgentRuntime.chat(session, message, *, clear_tasks=False, profile=None, memory_context=None, on_event=None, approve=None) -> str. Providers expose structured LLMProvider.chat_response and get_capabilities, with checked chat tuple compatibility and existing text-only chat_stream. Small feature-owned protocols describe memory/vector, task, learning, scheduling, history and interaction storage; event and approval boundaries are callables. Concrete adapters are supplied at composition. Foreground, durable, MCP and subagent actions share the executor and produce the same ExecutionReceipt; external response shapes remain unchanged.

SQLite remains authoritative. Its existing schema version, tables, IDs, locking, conditional task claims and history head checks are retained. Task writes and learning fingerprints live in repositories. Chroma is a derived index: retrieval falls back to SQLite on failure and the index can be rebuilt without losing durable records. Claim implementations belong to memory; learning retains delegating methods for callers.

Safety and validation

Ask and Plan retain read/network capability intersections. Fast and Decision Assist cannot approve execution or expand capabilities. Tool output, memory, downloaded pages and skill text remain untrusted. Learning promotion and rollback retain evidence gates, and detached workers retain their scope and singleton ownership.

make check includes package dependency checks for import cycles, legacy internal imports and forbidden core-to-interface/adapter dependencies. tests/test_architecture.py also checks inert imports, actor/session/workspace isolation, concurrent turn ownership, and vector fallback/rebuild. Run the full Python and Go suites, workflow acceptance, installed wheel/sdist smoke and Docker persistence smoke as documented in development. A blocked check is not a pass. See the refactor validation record for this migration’s results.

See tool inventory, self-learning and Jev Decision for product contracts.