Skip to main content

Architecture

System map of Acolyte’s runtime flow, module boundaries, and extension points.

First-class concepts

Every concept below is modeled as an explicit entity with typed contracts, its own module, and clear boundaries — not buried as implementation details.

  • Sessions — persistent conversation context with history and state
  • Tasks — state-machined units of work with stable IDs and per-task scoping
  • Lifecycle phases — resolve, prepare, generate, finalize as separate modules
  • Effects — lifecycle-owned side effects applied per-tool-result via callback
  • Tools — typed definitions with categories, schemas, and output contracts
  • Skills — declarative prompt extensions with metadata and tool restrictions
  • Memory sources — pluggable memory tiers (session, project, user) with pipeline stages
  • Protocol — typed RPC messages with request correlation and lifecycle envelopes

System flow

CLI → client → server → lifecycle → model + tools
  • execution model: one active task per session, with ordered queued tasks
  • yielding: lifecycle only yields at safe checkpoints (never mid-step)

TUI

React tree → reconciler → TUI DOM → serialize → terminal output
  • custom React reconciler for terminal rendering
  • details: see TUI

Daemon flow

client → rpc server → task queue → lifecycle worker
  • rpc server: accepts requests, exposes task/status streams, and routes to queue/lifecycle
  • task queue: enforces ordering, capacity, and cancellation boundaries
  • lifecycle worker: executes accepted tasks through lifecycle phases

Session flow

  • create or resume: resolve target session from ID prefix or active session
  • lock: acquire session lock to prevent concurrent modification
  • persist: save session state at checkpoints during chat
  • details: see Sessions

Task flow

accept → queue → run → complete|fail|cancel
  • accept: validate request and assign task_id
  • queue: hold until runnable under queue policy
  • run: execute lifecycle for active task
  • complete|fail|cancel: emit terminal state and persist task outcome
  • details: see Tasks

Tool layering

lifecycle → budget → cache → toolkit → registry
  • budget: step-budget check inlined into tool execution
  • cache: per-task reuse layer for read-only and search tool results
  • toolkit: domain tool definitions (file-toolkit, code-toolkit, git-toolkit, shell-toolkit, web-toolkit, checklist-toolkit, memory-toolkit, skill-toolkit)
  • registry: toolkit registration and agent-facing tool surface
  • details: see Tooling

Lifecycle flow

resolve → prepare → generate → finalize
  • resolve: pick model and policy (sync, not a full phase)

  • prepare: build inputs, context, and tools

  • generate: run model + tool calls (one pass, effects applied per-tool-result)

  • finalize: accept lifecycle signal, persist outputs, emit final response

  • model-host protocol: model may explicitly signal done/no_op/blocked; host validates against runtime state

  • host/model boundary: host provides runtime structure; model decides how to complete the task

  • scheduling: yield checks happen between lifecycle decisions, never mid-step

  • details: see Lifecycle

Memory engine

Memory Engine
  → Memory Pipeline (ingest → normalize → commit)
  → Memory Toolkit (search, add, remove) — on-demand access
  • Memory Engine composes source strategy, pipeline stages, and distill behavior to provide continuity across turns
  • pipeline seams: normalization is strategy-injectable behind registry contracts
  • on-demand access: the model uses memory toolkit tools (memory-search, memory-add, memory-remove) to access and manage memory at runtime instead of upfront injection
  • integration: commit is best-effort background work at finalize; memory access is on-demand via toolkit
  • details: see Memory

Dependency injection

  • No container, no decorators — dependencies are passed as typed parameters with defaults from appConfig.
  • Toolkit input — toolkit factory functions take a single ToolkitInput object containing per-request runtime data (e.g. createFileToolkit(input: ToolkitInput)). Output budgets are defined locally in each tool.
  • Defaults at the edge — library modules accept injected params; composition roots (cli-command-registry, server-chat-runtime, cli-chat) read appConfig and pass values down.
  • Tests inject directly — tests pass config through the new params instead of mutating appConfig.

Contracts

  • error handling: tools emit failures/error codes; lifecycle surfaces them for the model to decide
  • step budget: inlined into tool execution; blocks calls when budget is exhausted
  • protocol: transport contract is transport-agnostic; see docs/protocol.md

Observability and state

  • observability: lifecycle emits ordered debug events per request (calls, tool results, effect decisions, summaries, errors). Events are dual-written to logfmt (~/.acolyte/daemons/server.log) and SQLite (~/.acolyte/trace.db); the CLI queries SQLite for indexed trace lookups
  • runtime config: loaded from user/project config
  • state ownership: chat/session state and memory are persisted outside lifecycle and passed in as inputs
  • task trace: RPC emits task-state transitions with stable task_id:
accepted → queued → running → completed|failed|cancelled