-
Notifications
You must be signed in to change notification settings - Fork 0
Educational Guide
๐ฌ๐ง English ยท ๐ฎ๐น Italiano
Generated from docs/educational-guide.md โ edit that file in the repository, not this page.
Leggi in ๐ฎ๐น Italiano
This guide explains the architectural principles and implementation details needed to build a modern multi-agent harness like TSUKA. It covers both universal components (found in tools like Claude Code, OpenCode, or Aider) and specific design choices made in this project, highlighting practical traps encountered during development.
๐ก How to read this guide: The 10 milestones in ยง2 are ordered by increasing complexity: each module is self-contained and serves as the foundation for the next. If you are building your own harness, follow them in sequence; if you want to understand TSUKA's architecture, jump directly to the topic of interest.
A Large Language Model (LLM) on its own is a pure function: text in
An agentic harness is the application that wraps the model, providing it with observation capabilities, execution powers, and persistent memory:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโ HARNESS โโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โ
โ REPL โโโบ Agentic Loop โโโบ LLM Provider (HTTP Streaming) โ
โ โฒ โ โ
โ โ โผ โ
โ UI โโโ Tool Registry โโโบ Permissions โโโบ Execution (fs, sh)โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
The fundamental insight behind every harness:
The language model never executes actions directly.
The model declares its intent to call tools (tool calling). The harness intercepts and validates the request, executes the action in a controlled environment, gathers the output, and injects it back into history as a new message.
Intelligence belongs to the model; execution authority and safety belong entirely to the harness. This is why permission governance (Milestone 4) lives inside the harness: it is the only place capable of intercepting and validating actions before execution on the OS.
| Term | Definition |
|---|---|
| Tool | A native utility or system function the model can request to execute (e.g. file reading, web search, shell execution). |
| Tool Call | A structured payload (typically JSON) emitted by the model specifying the tool name and argument dictionary. |
| History | The ordered sequence of conversation messages (user, assistant, tool) sent to the LLM on each request to maintain operational context. |
| Context Window | The maximum token limit the model can process in a single request. The most critical and constrained resource. |
| Character / Agent | In TSUKA every Character is an Agent: a declarative JSON configuration combining operational capabilities (Role) and communication style (Trait). |
โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ
โ 1. REPL & โ โโโบ โ 2. Agentic โ โโโบ โ 3. Tool โ
โ Streaming โ โ Loop โ โ Registry โ
โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โ
โ 6. Live ANSI โ โโโ โ 5. Context โ โโโ โโโโโโโโผโโโโโโโโ
โ & Repaint โ โ Budgeting โ โ 4. Permissionโ
โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โ System โ
โ โโโโโโโโโโโโโโโโ
โผ
โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ
โ 7. Multi- โ โโโบ โ 8. Model โ โโโบ โ 9. Tool Self-โ โโโบ 10. Packaging &
โ Agent โ โ Tiers โ โ Authoring โ Distribution
โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ
Code references: src/core/provider.ts, src/cli/index.ts, src/cli/input.ts
Start with a clean interactive read-eval-print loop that captures user input and forwards it to an OpenAI-compatible endpoint (/v1/chat/completions).
Standardizing on OpenAI API compatibility is a massive architectural win: local inference engines like Ollama, llama-server (llama.cpp), Unsloth Studio, vLLM, and cloud gateways like OpenRouter all speak this exact protocol. A single LLMProvider class covers all backends.
Streaming responses via Server-Sent Events (SSE) is crucial for usability: without it, users stare at a frozen cursor for tens of seconds during long generation cycles.
Code references: src/core/agent.ts
The execution core follows the ReAct (Reason + Act) pattern across four sequential steps:
- Context Dispatch: send conversation history and available tool definitions to the LLM.
-
Output Evaluation: if the response contains
tool_calls, suspend text output and execute the requested tools. -
Result Injection: append tool outputs to the conversation history as messages with
role: "tool". - Recursive Turn: re-invoke the model with the enriched history until it returns a plain text response.
The Agent facade coordinates the loop without owning every invariant itself:
conversation history, tool rounds, token calibration, ReAct state, and reasoning-trace
persistence live in focused modules (conversationHistory.ts, toolRound.ts,
tokenCalibration.ts, reactState.ts, reasoningTrace.ts). The public contract stays
stable while each responsibility remains independently testable.
โโโโโโโโโโโโโโโโโโโโโโโโ
โ User Input / Prompt โ
โโโโโโโโโโโโฌโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโ
โ Send History + Tool Schemas โโโโโโโโโโโโโโโโ
โโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโ โ
โ โ
โผ โ
[ Model Evaluation ] โ
โ โ
Emitted โ โ
tool calls? โโโโโโโโโโ No โโโโโโโโโโโ โ
โ โ โ
Yes โผ โ
โ โโโโโโโโโโโโโ โ
โผ โ Final โ โ
โโโโโโโโโโโโโโโโโโโ โ Response โ โ
โ Execute Tools โ โโโโโโโฌโโโโโโ โ
โ (Sandbox & FS) โ โ โ
โโโโโโโโโโฌโโโโโโโโโ โ โ
โ โ โ
โผ โ โ
โโโโโโโโโโโโโโโโโโโ โ โ
โ Append results โ โ โ
โ with role: tool โโโโโโโโโโโโโโโโ โ
โโโโโโโโโโฌโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
-
Round Ceiling (
MAX_TOOL_ROUNDS): compact models can fall into infinite tool-calling loops. Enforce a strict safeguard (TSUKA defaults to 15 rounds inAgent.DEFAULT_MAX_TOOL_ROUNDS, configurable viamaxToolRounds). -
History Integrity: providers reject payloads where
tool_callentries lack matchingtoolresponse messages. Any history pruning must strictly preserve these pairs.
Code references: src/tools/registry.ts, src/tools/index.ts, tools_schemas/*.json
Tools expand faster than any other subsystem. Treat them as modular plugins:
-
TypeScript Implementation: each file in
src/tools/impl/exports the execution logic and is dynamically imported at launch. -
JSON Schema Specifications: tool descriptions and parameter validations live in standalone JSON files under
tools_schemas/.
src/tools/impl/read_file.ts โโโบ Execution Logic (TypeScript)
tools_schemas/read_file.json โโโบ Description & Parameters (JSON Schema)
Separating code from schemas enables rapid prompt engineering: refining tool descriptions to guide model routing does not require recompiling application code.
Code references: src/safety/permissions.ts
To protect the host system, every tool declares an explicit risk level:
| Level | Operational Behavior | Examples |
|---|---|---|
SAFE |
Executed immediately without interruption. |
read_file, list_dir, web_search
|
RESTRICTED |
Prompts user for approval ([y/N/always]). |
write_file, delete_file, edit_file
|
DANGEROUS |
Always prompts per action; bypass disabled. | execute_command |
Three complementary defense layers:
-
Workspace Sandboxing: restricts filesystem operations to
workspaceRoot. - I/O Bounds: caps file reading to 5 MB and command outputs to 50 KB.
-
Credential Redaction: filters sensitive environment variables (
KEY|SECRET|TOKEN|PASSWORD) before prompt injection.
Code references: src/core/agent.ts, src/core/thinkParser.ts, src/core/memory.ts
The context window is your scarcest computational resource. TSUKA manages it via four mechanisms:
-
Token-Driven Pruning (
pruneHistory): cuts history based on actual token limits (maxHistoryTokens) rather than message counts, with dynamic runtime calibration againstusage.prompt_tokens. -
Reasoning Isolation: extracts
<think>reasoning chunks for live display but strips them from persistent history to save context. -
Persistent Shared Memory: structured storage (
memory/memory.json) storing facts, conventions, and lessons across sessions with weighted OR keyword search and score-based eviction. -
Resumable Traces (
/continue): long reasoning paths are persisted tomemory/thinking/*.md, allowing explicit resumption of interrupted tasks.
Code references: src/core/logSink.ts, src/core/agent.ts (AgentEvents), src/tui/, src/cli/stream.ts, src/cli/interrupt.ts
When starting an agent harness, it is tempting to scatter console.log calls everywhere to monitor tools, memory, or the ReAct loop. This works for a basic terminal, but quickly becomes a dead end as the user interface evolves:
- If a tool prints directly to stdout during a turn, it breaks live text streaming.
- If you build a full-screen interactive terminal dashboard (TUI), a single stray
console.logcorrupts the screen buffer. - If you later expose the agent via a Web UI or headless background server, those logs stay trapped on the server stdout instead of reaching the user.
A truly modular harness core (Core, Memory, Tools) never prints directly to the terminal. All output is routed through two decoupled channels:
-
Conversation Channel (
AgentEvents): during streaming generations, the agent emits typed events to any listening frontend (onChunkfor incoming text chunks,onStatsfor speed and tokens,onEventfor tool lifecycle states). -
Diagnostic Channel (
logSink): all internal utility modules send warnings, errors, and operational notices to an injectable sink (logSink.log(),logSink.warn(),logSink.error()).
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ CORE AGENTIC ENGINE โ
โ (Zero console.log โ pure reusable logic) โ
โโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโ
โ Streaming events โ Logs & warnings
โผ (AgentEvents) โผ (logSink)
โโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโ
โ TUI Dashboard โ โ CLI REPL โ
โ Full-screen app โ โ Classic terminal โ
โ (npm run tui) โ โ interface โ
โโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโ
The same boundary applies to authorization requests: PermissionManager decides
whether a request is allowed and serializes concurrent prompts, but it knows nothing
about menus or terminals. CLI and TUI inject a PermissionPromptHandler; in a
headless context without a renderer, non-SAFE operations are denied by default.
Workflow escalation tools similarly request execution through the
WorkflowDispatcher contract instead of importing command handlers from a specific UI.
Thanks to this decoupling, TSUKA powers two completely different interfaces using the exact same underlying engine:
-
Full-Screen TUI (
src/tui/): subscribes toAgentEventsto update the chat feed,<think>reasoning containers, file explorer, and live telemetry, while routinglogSinkdiagnostics into pop-up notification modals. -
Classic CLI (
src/cli/): receives the same events to display continuous token streams and repaints syntax-highlighted Markdown on completion.
In both interfaces, pressing Esc or Ctrl+X aborts generation immediately via an AbortController signal, preserving the conversation state without killing the process.
Code references: roles/, traits/, characters/, teams/, src/cli/commands/
In TSUKA, agents are entirely declarative:
ROLE (roles/) ร TRAIT (traits/) = CHARACTER / AGENT (e.g. @geordi, @worf, @pike)
- Role: technical skills and allowed tools.
- Trait: tone and communication style.
- Character: named agent preset linking role and trait.
-
orchestrated(recommended): a supervisor dynamically assigns each turn viaroute_next. -
round-robin: cyclical turn-taking across team members. -
pipeline: assembly line with objective acceptance loops (src/core/loop.ts). -
hybrid: periodic discussion and voting rounds (cast_vote).
Dynamically plans, recruits agents from all 24 characters, and executes objectives with concurrent PARALLELO blocks isolated via AsyncLocalStorage and conflict-aware filesystem merges.
Code references: src/core/modelProfile.ts, src/tools/registry.ts
Local models range from 1B to 70B parameters. Instead of guessing capabilities from model filenames, TSUKA runs objective benchmarks (/benchmark):
- Tests instruction following, JSON generation, and function calling.
- Assigns an empirical tier (
SMALL,MEDIUM,LARGE). - Filters tools by combining Active Role
$\times$ Measured Model Tier.
Code references: src/tools/impl/createTool.ts, src/core/mcp/ (types.ts, stdioTransport.ts, client.ts, adapter.ts, connectMcpServers.ts)
A mature agent harness cannot remain confined to its initial static tool set. TSUKA supports two complementary extension pathways:
Agents equipped with development permissions can author new JavaScript/TypeScript tools on the fly:
-
Opt-in Shape Validation: self-authoring is disabled by default. With
selfAuthoringEnabled: true,node:vmchecks module shape and timeout but is not a security sandbox; creation and loaded custom tools are always DANGEROUS. -
Risk Capped: generated tools can only be assigned
SAFEorRESTRICTEDtiers (neverDANGEROUS). - Core Protection: native system tools cannot be overwritten, and automated backups are preserved in the workspace.
To connect the agent with complex external services (GitHub repositories, SQLite databases, web browsers, external filesystems) without writing bespoke TypeScript libraries, TSUKA implements the open Model Context Protocol (MCP).
Rather than taking on heavy third-party SDKs, TSUKA features a native, zero-dependency implementation (~400 lines in src/core/mcp/):
-
Standard I/O Transport (
stdioTransport.ts): launches configured servers fromtsuka.config.jsonas child processes communicating overstdin/stdout. -
JSON-RPC 2.0 Handshake (
client.ts): performs theinitializehandshake and queries available tools viatools/list. -
Adapter Registration (
adapter.ts): registers remote tools intoToolRegistrywith themcp__<server>__<tool>prefix, using remote JSON schemas directly for validation. -
Safety & Fault Isolation: MCP tools inherit full
PermissionManagergating (RESTRICTEDby default with interactive approval). Crashed or unresponsive MCP servers emit diagnostics vialogSinkwithout blocking the harness. Each runtime awaits cleanup of its own child processes; a synchronous exit hook handles abrupt termination.
Code references: src/core/apphome.ts
TSUKA resolves configurations hierarchically:
-
Local Project (
.tsuka/): configurations initialized viatsuka initoverride global defaults. -
Global App Home (
appHome): fallback to system-wide characters, teams, and settings.
Code references: src/core/runtime.ts, src/core/agent.ts, src/core/provider/, src/tools/, src/core/memory/
As an agent harness scales beyond 80 test suites, maintainability becomes paramount (Directives 8, 9, and 10 in AGENTS.md):
-
Unified Composition Root (
createHarnessRuntime): Initializing configuration, providers, registries, and permissions ad-hoc across CLI and TUI leads to behavioral drift. A single factory inruntime.tswires the entire system and provides an idempotentclose()method for deterministic shutdown (terminating MCP processes and flushing memory). -
Isolating Agent Invariants:
Agentis no longer a sprawling monolith. Token calibration (tokenCalibration.ts), conversation pruning (conversationHistory.ts), tool invocation lifecycles (toolRound.ts), state machine transitions (reactState.ts), and reasoning trace persistence (reasoningTrace.ts) are decomposed into sharp, testable units. -
Strict Layer Contracts: The ReAct engine never inspects provider wire payloads or file schema paths directly. By coding against
IToolRegistryand encapsulating OpenAI payloads inprovider/wireFormat.tsandprovider/streamAccumulator.ts, providers and tools can be swapped out cleanly without touching agent logic. -
Decoupled Memory Codec & Storage: In
src/core/memory/, fact serialization, normalization, summary derivation, and deduplication live incodec.ts, while atomic file writes (via.tmp+renameSync) and corruption recovery backups (.corrupt-<timestamp>) live instorage.ts.JsonMemoryBackendis purely responsible for RAM state orchestration.
| Feature | Universal Pattern | TSUKA Distinctive Implementation |
|---|---|---|
| Agentic Loop | ReAct function calling | Token-budgeted pruning with dynamic server window discovery |
| Tool System | JSON Schema definitions | Adaptive Tier Pruning based on /benchmark capability fingerprinting |
| Multi-Agent | Fixed prompt chaining | Dynamic Goal Orchestrator + 4 Team Strategies + Run Blackboard |
| Verification | Self-reported completion | Objective acceptance criteria (loop.ts) with anti-stall signatures |
| Safety | User prompts | 3-tier risk system + serialized async permission queues + workspace jail |
-
String Replacement Metacharacters:
String.prototype.replaceinterprets$&in replacement strings; always use() => replacementfunctions in file-editing tools. -
CJS / ESM Dynamic Imports: transpiled dynamic
import()behaves differently betweentsxdev mode and compiled dist builds. Test both! -
Token Streaming Measurement: counting raw stream chunks produces erratic metrics; enable
stream_options: { include_usage: true }. - Index Shifts During Pruning: slicing history by numerical indices breaks when pruning occurs mid-run; always track message object identities.
-
Unambiguous File Mutations:
write_fileacceptsappendonly as a boolean and rejects strings, numbers, andnull;edit_filerejects empty targets while preserving empty replacements for intentional deletion. -
Configuration Recovery: invalid
tsuka.config.jsonbytes are preserved in a collision-safe backup before defaults are restored atomically; failed recovery blocks later persistence instead of overwriting evidence. -
Canonical, Not Lexical, Jails: normalized-prefix checks do not stop symlinks or junctions. TSUKA resolves the root, target, or nearest existing ancestor with
realpath, permits internal links only, and deduplicates real directories during bounded recursive scans. -
HTML Entities in Terminal Rendering: Markdown parsers convert quotes into HTML entities (
'); decode them before ANSI terminal output. - Accidental Credential Leaks: system diagnostic tools can inadvertently leak environment variables; apply proactive redaction masks.
- Hidden Local Server Queues: a local model that appears frozen is often waiting in a single-slot inference queue. Always provide visual status and timeouts.
- Oversized Tool Arguments: passing entire files inline breaks small model JSON generation. Design tools to support chunking or file paths.
- History Poisoning from Malformed JSON: never save raw invalid JSON tool calls to history; sanitize and repair them before persisting.
-
Test Suite Memory Isolation: automated tests must never write to the real user
memory.json. Always redirect test stores to temporary test environments.
For detailed architectural specifications, consult the System Architecture and Multi-Agent Workflows.
TSUKA v0.8.1 ยท Repository ยท Issues ยท MIT License