Tools & providers

Two capability surfaces sit under the daemon: a registry of ~110 built-in tool wrappers it executes on behalf of any client, and the LLMProvider abstraction that routes completions to a local model or to the claude / codex CLIs. Everything runs through the daemon — clients never open a socket to the cloud themselves.

The tool registry

Every executable capability — read a file, run a shell command, open a PR, tail pod logs, sample a Parquet file — is registered exactly once as a Tool and exposed through a single registry + executor. The pieces live under src/daemon/tools/:

A single registration serves two callers: the LLM (via the tool-call protocol) and daemon controllers (via kind: 'tool' tasks). Same schema, same execution, same approval flow.

The daemon is the only process that executes tools. The CLI, the MCP servers, and the IDE workbench request execution over the Unix-socket JSON-RPC — they never run a tool in-process. This keeps DB access, approval gating, and streaming progress in one place. See architecture.
approval gate

Read-only tools (git_status, file_read, search_grep) run ungated. Mutating tools fire an Approve / Skip / Edit gate with a diff or command preview before execute(); destructive actions (force-push, k8s_delete, git_reset --hard) add an extra confirm. Some tools are risk-tiered — low-risk shell commands auto-run in auto-accept mode, medium and high always gate.

Categories

Tool IDs follow a domain_verb_noun convention (a handful of deeper namespaces use domain:sub:action). One row per capability domain, with real IDs drawn from src/daemon/tools/builtins/:

CategoryWhat it doesExample tools
fileRead, write, edit, move, copy, stat files on disk.file_read · file_write · file_edit
gitLocal git — status/log/diff read, plus staged commits, branches, push/pull, rebase.git_status · git_diff · git_commit
shellRun commands: one-shot, detached (streamed), or a scripted pipeline; set cwd.shell_exec · shell_exec-detached
httpGeneric HTTP client — requests, file up/download, websocket.http_request · http_download · http_websocket
webWeb search and page fetch for research flows.web_search · web_fetch
ghGitHub via the gh CLI + REST/GraphQL — issues, PRs, runs, releases, repos, projects.gh_pr_create · gh_issue_view · gh_run_list
k8sWraps kubectl — get/describe/logs plus gated apply/delete/exec.k8s_get · k8s_logs · k8s_apply
pkgPackage managers — install/add/remove, audit, outdated (npm/pip/cargo/go auto-detect).pkg_install · pkg_audit · pkg_outdated
sshRun commands on remote hosts and copy files, using the system ssh client.ssh_exec · scp:upload · scp:download
testRun the detected test framework and parse failures; coverage and watch modes.test_run · test_coverage · test_watch
notifyOutbound messaging — always gated with the rendered message + destination.notify_slack · notify_email · notify_teams
searchLocal filesystem search — glob, content grep, directory listing, recently-modified.search_grep · search_glob · search_recent
graphQuery the indexed code-knowledge graph — semantic ANN, entity lookup, structural query.graph_search · graph_entity · graph_query
dbData-driver introspection over SQL engines, on-disk files (DuckDB), and the KV store.db_sql_sample · db_file_describe · db_kv_scan
dataHigher-level data analysis — lineage tracing and schema-drift detection.data_lineage · data_schema-drift
codeStructural code intel — locate a class, find its references/fields, walk migrations, scan ORMs.code_class_locate · code_class_references · code_orm_scan
cloudAWS / GCP / Azure CLI wrappers — reads auto-run, mutations gate. Registers only when the CLI is on PATH.cloud_aws_s3_ls · cloud_gcp_run_deploy · cloud_az_vm_list

A related docs family (docs_retrieve, docs_project_context, docs_summary_get) surfaces indexed documentation to the same callers.

Categories are gated on capability: cloud, k8s and similar CLI wrappers register only when their binary is detected on PATH, so the LLM's tool list stays uncluttered on machines that don't have them.

LLM providers

All model interaction goes through one interface, LLMProvider, defined in src/shared/types.ts. Application code never imports an SDK directly — it holds an LLMProvider and calls its methods:

Two implementations back it:

CliProvider — how cloud calls happen
# claude: structured output via native --json-schema
$ claude --print --output-format json --json-schema '<inline>'
# codex: structured output via --output-schema
$ codex exec --output-schema <tmpfile> --json
# auth + quota belong to the CLI's own OAuth session — no keys on our side
Cloud LLM access happens only through the locally-installed claude and codex CLI binaries, wrapped by CliProvider. There are no API keys stored on our side and no direct REST calls to Anthropic / OpenAI / Gemini / Mistral from the daemon process. Direct REST providers must never be reintroduced — auth and quota stay with the user's CLI OAuth sessions.

Structured output

src/agent/providers/structured-output.ts gives providers a schema-validated JSON path. It compiles the caller's JSON Schema with ajv (draft 2020-12, singleton compiled-validator cache) and validates every response. On a validation mismatch the helper re-issues the completion with the errors appended to the conversation, retrying up to maxAttempts (default 3) so the model can self-correct.

The provider's wire layer enforces the schema natively where it can — Ollama via format, the CLIs via --json-schema / --output-schema — and ajv re-validates as a defensive backstop. A provider without a native structured-output API sets its capability flag off and throws a clear "not supported" error, so callers gate on the flag at config time.

Never Promise.all anything that reaches an LLM provider — cloud (CliProvider) or local Ollama embed / complete. Always drive it with a serial for…of loop and sequential awaits. Parallel fan-out to a provider blows quota, races the local model, and reorders streamed output.

Where next