Skip to content

Adapters

An adapter is what runs an agent's "thinking" step. This page covers the adapter types, when to pick each, and the operator‑side configuration that goes with them.

For the conceptual background, see ../concepts/the-mental-model.md and ../board/configuring-an-agent.md (the board user's view of changing adapters). For the risk profile of each adapter (what it can read, where it runs, network reach), read adapter-isolation.md alongside this page.

How an adapter is selected

For each heartbeat run, the engine picks the adapter in this order (engine.ExecuteHeartbeatRun):

  1. The default sentinel resolves first. If adapter_type is the literal default, the engine swaps it for the company's agent_default_adapter_type (and adopts the company's agent_default_adapter_config when the agent's own config is empty) before any of the steps below. This happens server‑side, so a remote worker never sees default. See the default sentinel below.
  2. Provider binding wins. If the agent has a provider_id set, the engine dispatches through that company provider (enforcing the enabled flag and a cross‑company guard) — this is the llm path.
  3. Else the adapter_type if it is one of the seven registry types below.
  4. Else the default llm adapter. An empty adapter_type resolves to llm. If no provider is configured at all, the run fails.

Adapter types at a glance

The factory registry holds seven types; llm is the implicit default when adapter_type is empty.

Adapter Runs When to pick it
llm (default) An HTTP call to a provider (Anthropic, OpenAI, Perplexity, OpenAI‑compatible, Bedrock). The engine renders the full heartbeat prompt (issue + timeline + roster + manager) before the call. Default. Simplest, most agents.
claude_local The Claude Code CLI (claude) as a host subprocess. Session ID persisted across heartbeats. Filesystem/shell/coding agents on a host with ~/.claude auth. Trusted single‑tenant only.
codex_local The OpenAI Codex CLI (codex exec) as a host subprocess. Distinct JSONL stream; same session model. Same as claude_local with GPT‑style models; needs ~/.codex auth.
claude_sandbox The same claude CLI inside a hardened Docker container on the egress‑allowlist network. Production‑safe local‑CLI execution; preferred over claude_local for multi‑tenant.
codex_sandbox The same codex CLI inside a hardened Docker container. Production‑safe Codex execution; preferred over codex_local.
docker A container per heartbeat with the prompt + context piped in. Custom runtime images, per‑agent isolation, third‑party tools.
lambda An HTTP POST to AWS Lambda or any HTTP endpoint that speaks the payload contract. Agent logic hosted elsewhere — Function URLs, Cloud Run, self‑hosted inference.
remote Enqueues a task and polls until a registered trimus-worker (or a custom worker per REMOTE_WORKERS.md) claims, runs, and posts the result back. Run execution on a different host — e.g. server in Docker but local‑CLI agents need a host's ~/.claude / ~/.codex.
default (sentinel) Not a runtime — resolved at dispatch to the company's agent_default_adapter_type. Portable agents and exported templates that should follow the receiving instance's posture instead of hardcoding one adapter.

You can mix adapters within a company — an llm triage agent routing work to a claude_sandbox engineer, or a remote agent dispatching to a worker host that runs Claude locally there.

The default sentinel

default is not a real adapter — it is a dispatch‑time pointer to whatever the company is configured to use. Setting an agent's adapter_type to default means "run me with this company's agent_default_adapter_type," resolved fresh on every heartbeat:

  • The concrete type is read from the company default; if the agent's own adapter_config is empty ({}), the company's agent_default_adapter_config is adopted too (a non‑empty agent config is never overwritten).
  • Resolution is server‑side and one‑shot: the concrete adapter is used for both local factory dispatch and any remote payload, so a worker never receives the literal default.
  • A company default of default is rejected (it would resolve to itself); an empty or somehow‑still‑default value degrades to llm.

This is what makes agents and exported template/portability bundles portable: a bundle authored on a hosted instance (remote) imports cleanly into a local instance and follows its default instead of forcing the original adapter. The built‑in onboarding templates ship their agents as default for exactly this reason.

llm adapter

The standard path. Reads the agent's adapter_config JSONB:

{
  "provider": "anthropic",
  "model": "claude-opus-4-6",
  "api_key": "(populated from the provider, NOT pasted directly here)",
  "base_url": "(optional override)",
  "max_tokens": 4096,
  "temperature": 0.7
}

In practice, you don't fill this in by hand — the Configuration → Provider form on the agent page builds it for you. Read this only if you're curious or scripting agent setup via the JSON API.

Operator-side concerns:

  • Add the matching provider first (providers.md).
  • Heartbeats hit the vendor on every run. Costs scale linearly with run count.

claude_local adapter

Spawns the Claude Code CLI (claude by default) on the same host that runs trimus-server. Each heartbeat is a separate CLI invocation, with session-id persistence across runs so the conversation continues.

Required on the host

  • The claude binary on PATH (or specify a full path in the agent's adapter config).
  • Filesystem permissions for the OS user running trimus-server to read and write the agent's working directory.

Working directory resolution

Per heartbeat, the CLI is spawned with a specific CWD. Order:

  1. project.working_dir — if the issue is attached to a project with a working_dir set, that's the CWD. Operator-managed.
  2. <agent-home>/work/ — fallback if no project working_dir. Auto-created.

This is what lets you point a project at /repos/myproject and have every claude_local agent working on issues in that project run inside the repo.

Session persistence

The Claude session ID is saved in postgres after each run. The next run resumes the session, so the conversation has continuity. Side effects:

  • A model upgrade requires a session reset. The CLI rejects a resume if the model changed. Click Clear Session in the agent header.
  • A stale session can produce confused output. Same fix.

Agent configuration knobs

On the agent's Configuration tab → Heartbeat Config (Claude):

Field Notes
Claude binary path claude if on PATH, otherwise /full/path/to/claude.
Claude flags Extra CLI flags. Common: --allowed-tools to restrict tool set, --add-dir to surface extra directories.
System prompt The agent's role prompt.
Heartbeat interval Seconds between ticks.

Operator gotchas

  • Multi-host installs: each Trimus instance must have its own claude binary. If you're running trimus-server on multiple boxes with the same database, the box that picks up a run is the one that needs the CLI. For deterministic placement, use remote workers (remote-workers.md) with tags.
  • Auth: Claude Code uses its own auth (API key or subscription). Not configured by Trimus. Whoever runs the CLI must be logged in. Most teams set ANTHROPIC_API_KEY in the trimus-server's environment, which the CLI picks up.
  • Concurrency: each heartbeat spawns a CLI process. Multiple in-flight claude_local agents = multiple subprocesses. Plan host CPU/RAM accordingly.
  • Output capture: the CLI's stdout is streamed into the run's event log and rendered as the issue's Live Transcript. If your CLI version produces unexpected output formats, you'll see them raw — file an issue.

docker adapter

Runs each heartbeat inside a container. The container gets the agent's prompt + issue context on stdin and produces a reply on stdout.

Required on the host

  • Docker daemon reachable from trimus-server.
  • The image you reference is pullable (or pre-pulled).

Agent configuration

On the agent's Configuration → Heartbeat Config (Docker):

Field Notes
Image myorg/my-agent:latest or whatever.
Pull policy always / if-not-present / never. if-not-present is sane.
Mount points Host:container path pairs. Common: mount the agent's home so the container can read skills.
Heartbeat interval Seconds.

Use cases

  • Pre-built agent images that bundle their own LLM client + tools.
  • Tight isolation: the container can't read other agents' files.
  • Testing custom agent logic without affecting the main process.

Gotchas

  • Image bloat. Each agent's image is pulled independently. Share base layers ruthlessly.
  • Networking. The container needs egress to its LLM provider. Default DockerConfig network is none; point Network at the trimus-egress-allowlist network for SSRF‑safe egress.

Hardening

The docker adapter applies these by default (see adapter-isolation.md for the full contract): --read-only root + writable /tmp tmpfs, --cap-drop ALL, --security-opt no-new-privileges, --pids-limit (default 100, via TRIMUS_DOCKER_PIDS_LIMIT or per‑agent pids_limit), and --memory-swap pinned to --memory. Ship a seccomp/AppArmor profile with TRIMUS_DOCKER_SECURITY_OPTS=seccomp=/etc/trimus/seccomp.json.

claude_sandbox and codex_sandbox adapters

These run the same claude / codex CLIs as the *_local adapters, but inside a hardened Docker container on the trimus-egress-allowlist network instead of as a bare host subprocess. They are the production‑safe way to use the local CLIs: they keep session persistence and CWD‑aware resume, but the agent can no longer read the host filesystem or inherit DATABASE_URL / TRIMUS_ENCRYPTION_KEY from the host environment.

Required on the host

  • A Docker daemon reachable from trimus-server.
  • The trimus-egress-allowlist network (created automatically at boot).
  • A container image that carries the relevant CLI.

Hardening

Container root is read‑only with a writable /tmp, host env is not inherited (only the curated provider/context vars), --pids-limit is governed by TRIMUS_SANDBOX_PIDS_LIMIT (default 100), and all the Track‑B flags (cap‑drop, no‑new‑privileges, memory‑swap parity) apply. With the egress DNS resolver enabled (TRIMUS_EGRESS_DNS_ADDR), the container resolves only allow‑listed hostnames; everything else is NXDOMAIN and recorded for review. See adapter-isolation.md.

Prefer the sandbox adapters in production

For any multi‑tenant or internet‑exposed deployment, choose claude_sandbox / codex_sandbox over claude_local / codex_local. The behaviour is the same; the blast radius of a prompt‑injection or CLI bug is far smaller.

lambda adapter

POSTs the heartbeat payload to an HTTP endpoint and parses the response. The endpoint can be AWS Lambda, Cloud Run, a custom internal service — anything that speaks the protocol.

Agent configuration

On the agent's Configuration → Heartbeat Config (Lambda):

Field Notes
Endpoint URL https://lambda.example.com/trimus-agent. Where heartbeats POST.
Auth header Typically Authorization: Bearer <token> so the endpoint can verify the calling Trimus instance.

Protocol

POST body: JSON with agent_id, issue (title/body/status/etc.), context (recent comments, activity), and prompt (the assembled system prompt).

Response body: JSON with reply_text (becomes the comment / parsed for actions). See internal/engine/adapters/lambda.go for the exact schema.

Use cases

  • Bedrock / Vertex agents that ride on those platforms' SDKs.
  • Custom agents written in non-Go languages.
  • Federated setups: agents running in a different security domain than Trimus itself.

codex_local adapter

Added in v2.7.0. Sibling of claude_local for the OpenAI Codex CLI. Spawns codex exec as a subprocess and parses its JSONL output stream. Session IDs are persisted across heartbeats the same way Claude's are.

{
  "adapter_type": "codex_local",
  "adapter_config": {
    "model": "gpt-5-codex",
    "sandbox": "workspace-write",
    "dangerously_bypass_approvals_and_sandbox": true,
    "timeout_sec": 600,
    "max_turns_per_run": 10
  }
}
Field Default Description
model (CLI default) Codex model slug (e.g. gpt-5-codex).
sandbox workspace-write One of read-only, workspace-write, danger-full-access.
dangerously_bypass_approvals_and_sandbox true Skip per-command approval prompts (parity with claude_local's dangerously_skip_permissions).
timeout_sec 300 Hard wall-clock cap per heartbeat.
max_turns_per_run 10 Advisory — Codex itself doesn't expose a native max-turns flag, but Trimus records it for parity.

Same operator setup story as claude_local: the user that runs the Trimus service needs a working codex CLI on PATH and a valid ~/.codex/ auth directory.

remote adapter

Dispatches the heartbeat to an external worker process that connects back to Trimus over the documented worker protocol. The server queues a row in remote_tasks; the worker polls POST /api/workers/tasks/claim, runs the agent locally with whatever local adapter it's configured for, and POSTs the result back.

{
  "adapter_type": "remote",
  "adapter_config": {
    "local_adapter_type": "claude_local",
    "tags": ["darwin", "claude_local"],
    "timeout_seconds": 600
  }
}
Field Default Description
local_adapter_type llm What adapter the worker runs internally on the task.
tags [] Worker must possess ALL these tags to claim the task. Empty means any worker.
timeout_seconds 600 How long the server waits before giving up.

The full wire protocol is at REMOTE_WORKERS.md. The canonical worker contract is served at GET /api/workers/operating-prompt and rendered on the /admin/workers UI. An end-to-end smoke-test script with curl + jq lives at remote-worker-smoke-test.md.

Choosing per-agent

Mix freely. Typical company shape:

  • Triage agentllm (cheap, fast, no filesystem needed).
  • Engineer agentsclaude_local (filesystem + shell).
  • Researcher agentsllm with a Perplexity provider.
  • Designer agentsllm, expensive model.
  • CEO agentllm, expensive model, low heartbeat frequency.

The adapter is a per-agent property, not company-wide.

Switching an agent's adapter

Agent detail page → Properties → Adapter type. Pick the new type, save, then fill out the new adapter's config on the Configuration tab. Switching clears parts of the existing config that don't apply (e.g., switching llmclaude_local nulls out the provider/model fields).

The agent's run history is preserved — heartbeat_runs records the adapter that ran each turn, so swapping doesn't make old runs ambiguous.

Where to next