Architecture
Repo layout and the shape of the agent
Minara Agent is a TypeScript/Node.js AI agent. The turn loop runs on
the Vercel AI SDK (ai, streamText) over multiple LLM providers
(Anthropic, OpenAI, xAI, OpenRouter). It uses a custom in-house
skill + tool registry for its own tools instead of MCP.
Docker-deployable, CLI plus HTTP gateway, SQLite-backed state.
Stack: Node ≥ 24, TypeScript 5.7 (ESM), ai (Vercel AI SDK) +
@ai-sdk/* provider adapters, better-sqlite3, ioredis,
playwright, vitest. Package manager: [email protected].
Architecture diagram
Repo layout
src/
app.ts # createApp() orchestrator, calls the app/ wiring builders
app/ # Private wiring builders (bootstrap, llm, mcp, messaging, ...)
gateway/
cli.ts # `pnpm dev` entry (REPL)
server.ts # `pnpm serve` entry (HTTP)
skills-cli.ts # `minara skills add/list/upgrade/remove`
core/
tool-registry.ts # ToolRegistry + PermissionTier + tool sets
prompt-builder.ts # Assembles system prompt (skill catalog + fragments)
agent/
runtime.ts # The LLM turn loop (streamText + stopWhen)
tools/ # Tool factories, createXxxTools() returns ToolEntry[]
_shared/
sandbox.ts # resolveInSandbox, every file tool uses this
result.ts # ok() / err() / errFromThrow() envelope
skills/
types.ts # DomainSkill interface
registry.ts # SkillRegistry + requires_env gating
builtin/<id>/SKILL.md # Builtin skills, SKILL.md packages, auto-discovered
external/ # Vendored SKILL.md packages
learning/ # Methodology store + seeds
memory/ # Agent memory + audit log
config/
load-env.ts # First-line `.env` loader via process.loadEnvFile()
docs-src/ # Authoritative markdown source
docs/ # This documentation site (Fumadocs)
.env.example # Canonical template, commit but never put real secretsCore flow
-
Entrypoint (
gateway/cli.tsorgateway/server.ts) importsconfig/load-env.tsas its first line to populateprocess.env, then callscreateApp()fromapp.ts. -
createApp()runs the wiring builders underapp/in order, which instantiate:ToolRegistry: all tools, permission-tiered.SkillRegistry: all builtin plus external domain skills, gated byrequires_env.- The turn loop (
agent/runtime.ts): orchestrates skill activation, tool calls, prompt assembly, and state persistence. - SQLite state (WAL + FTS5) for memory, sessions, audit log.
-
Each turn, the runtime:
- Pulls active skills from
SkillRegistry(auto-activated or user-pinned). - Builds the system prompt via
prompt-builder.ts, combining the skill catalog and prompt fragments. - Calls the LLM via
streamTextwith the set of tools allowed by currently active skills and the current permission tier policy. - Routes tool calls through the permission hook pipeline (kill switch, daily spend cap, tier gating).
- Persists the turn to SQLite (history plus audit log).
- Pulls active skills from
Why not MCP
MCP adds an extra process boundary, schema-transformation layer, and separate permission model. For an agent where we control every tool, the in-house registry:
- Gives us typed tool handlers with direct access to shared state (SQLite, Minara client, sandbox).
- Lets us enforce permission tiers from a single
BeforeToolCallHook. - Keeps the audit log exact (tool name, args, reasoning, outcome, no cross-process translation).
- Avoids serialization overhead on every call.
MCP is still supported for third-party servers. See MCP_SERVERS
in Environment Variables.