Skip to content

Server Side Agent

diegokoes edited this page Jul 5, 2026 · 1 revision

Server-Side Agent

The agent (packages/agent) is the deployed app's reasoning layer. It is what the web Chat talks to, so a teammate can analyze a ticket or structure a doc from the browser without running a local agent of their own.

Is this "the SDK for Claude Code"? No.

There are two integration surfaces, and only one is Claude-specific:

  • The MCP server (packages/mcp) is vendor-neutral. It exposes tachý's 35 tools over the Model Context Protocol. Any MCP client drives it: Claude Code, Codex CLI, VS Code Copilot, or an organization's own model. There is no Claude dependency on this path.
  • The server-side agent (packages/agent) uses the Claude Agent SDK (@anthropic-ai/claude-agent-sdk). That is a headless programmatic library: the same engine that powers Claude Code, run in-process on the server, not the Claude Code CLI. It is Claude-specific.

So the SDK is Claude-coupled but headless and server-side; it is not "only for Claude Code," and it is not the path a local agent takes. It is a thin wrapper: the only LLM-vendor coupling in the whole project is one import in packages/agent/src/index.ts, consumed only by packages/api/src/routes/agent.ts. Everything else is provider-neutral. Swap the agent out and the rest of tachý is unchanged.

How a turn runs

startTurn(prompt, cfg) (packages/agent/src/index.ts) drives the SDK's query() against a freshly spawned tachý MCP subprocess:

  • System prompt is the claude_code preset with CLAUDE.md appended, plus a web-chat note that reinterprets "ask before saving" for the approval UI (the review box is the approval, so the agent proposes and calls the write tool directly rather than asking in prose first).
  • settingSources: [] ignores any local ~/.claude config, so server behavior is deterministic and independent of the host machine.
  • Tools come only from the tachý MCP server. Read tools are on the allowedTools allowlist and auto-run. Every built-in file/shell/web tool is on disallowedTools. Anything else falls through to canUseTool.
  • The turn exposes an event stream (text, tool_use, approval_request, approval_resolved, result, error) that the API route relays to the browser over SSE.

resume carries a prior sessionId so a chat is multi-turn.

The approval flow

canUseTool classifies every tool call (packages/agent/src/tools.ts):

  • read → allowed immediately.
  • denied (a built-in, or anything not prefixed mcp__tachy__) → refused with a message.
  • write (save_*, update_*, add_*, post_private_note, record_analysis_run, or any unknown tachý tool) → the turn emits an approval_request and blocks on a promise. The browser shows the exact tool input as editable JSON; the user approves (optionally editing the input) or denies via POST /api/agent/approve. Only then does the tool run.

An unknown tachý tool is treated as a write (approval-gated), never silently run. When a turn ends with an approval still outstanding, it is auto-denied so the SDK loop can unwind cleanly.

Model, effort, and cost policy

This applies only to the bundled agent; it adds no coupling to the MCP/core path, so an organization driving the tools with its own model is unaffected. All three settings come from Runtime Settings (DB wins over env) and are snapshotted into the MCP subprocess env at spawn.

  • Default model is claude-sonnet-5 (the "use Sonnet by default" cost guidance), overridable via the agent_model setting or TACHY_AGENT_MODEL.
  • Allowed-models policy. allowed_models (or TACHY_ALLOWED_MODELS, comma-separated) is an optional allowlist: a disallowed or unset model is clamped to the first entry (effectiveModel()). Empty means no restriction.
  • Effort. agent_effort (or TACHY_AGENT_EFFORT) sets the reasoning-effort level, low through max, default medium.
  • Cost surfacing. recordRun computes an estimated USD cost from the run's model plus token counts into analysis_runs.meta.estimated_cost_usd. Audit only, no enforcement.

Auth and attribution

The agent authenticates with ANTHROPIC_API_KEY, or the server's Claude Code login if that is unset. The MCP subprocess inherits the API env (DB URL, source tokens) plus the logged-in user's email as TACHY_USER_EMAIL, so saved entries are attributed to the person in the Chat, not to a shared service account.

Security specifics are consolidated in Security Model.

Clone this wiki locally