Tools and sub-agents
The built-in tools an agent run can use, the web tools contract shared by every codai agent, the standalone /v1/tools and /v1/research endpoints, and how spawn_agent fans work out to typed sub-agents.
Inside an agent run — or a chat completion with x-codai-server-tools: 1 — the model gets a set of gateway-executed tools. It calls them like any function tool; the gateway runs every call of a turn in parallel and feeds the results back. You never see the tool traffic unless you read the run's steps.
Built-in tools
| Tool | What it does | Available when |
|---|---|---|
http_fetch | Fetch a URL server-side. | Your key's tool level allows HTTP (l1_http or higher). |
json_query | Query a JSON document. | Same as http_fetch. |
exec_python | Run Python in a sandbox — the "code interpreter" capability. | Sandbox enabled on the gateway. |
read_artifact | Read back a large tool result the gateway spilled to storage instead of inlining. | Spilling enabled and at least one tool active. |
memory_recall, memory_remember | Long-term memory scoped to your account. | Your plan includes persistent memory. |
use_skill | Load one of your skills into the loop. | At least one skill visible to the key. |
web_search, web_fetch | Verified web search and page reading, same contract as every other surface (below). | Web search configured on the gateway. |
| MCP tools | Tools exposed by the MCP servers attached to your key. | Tool level is not none. |
delegate_to_model | codai's internal single-completion router across the models you may call. | Always, when ≥ 2 models are allowed. |
spawn_agent | A typed sub-agent with its own context and tool loop. | See below. |
Tool level is a property of your key. none disables every executed tool; l1_http allows network reads; l3_sandbox (cloud sandbox: shell, Python, files) and l4_browser unlock the heavier sub-agent roles.
spawn_agent
When server-side tools are on, the model also gets spawn_agent. Unlike delegate_to_model (one completion), a spawned child runs its own loop with its own context. The model picks a role; the gateway picks the tier, tool level and step budget for it.
Prop
Type
| Role | Tier | Max steps | Max tool level | Purpose |
|---|---|---|---|---|
explore | small | 6 | l1_http | Read-only facts. The default. |
research | small | 8 | l1_http | web_search + http_fetch, returns a sourced summary. |
code | large | 12 | caller's level (up to l4_browser) | Implement and verify; returns diff or output. |
verify | small | 6 | l3_sandbox | Run checks, report PASS/FAIL per check. |
summarize | small | 3 | l1_http | Digest one source. |
Semantics:
- Parallel by design. Every
spawn_agentcall in one assistant turn runs concurrently, and concurrently with the turn's other tool calls. The fan-out is capped per turn (default 4); calls past the cap receive{"error":"delegation_fanout_capped"}as their tool result. - Children never spawn. A child has
spawn_agentremoved, runs on the parent's alias withmax_tokens: 4096, and bills as a continuation of the parent turn — it does not start a new task. - Cost ceiling. Child spend counts toward the request's cost ceiling (default 5 USD); once reached, further spawns return
{"error":"request_cost_ceiling"}. - Result shape. The child's answer comes back to the parent as an ordinary
toolmessage prefixedSUB-AGENT[<role>] RESULT:. - Progress frames. On a streamed chat completion, the gateway emits vendor frames inside the SSE delta so a UI can render live sub-agent cards:
{"codai":{"event":"subagent_started","id","role","task"}}and{"codai":{"event":"subagent_done","id","role","ok","preview"}}. Ignore them if you only want text. - Invalid arguments →
{"error":"invalid_arguments"}; a child that fails →{"error":"sub_agent_failed"}.
spawn_agent is injected only when the request carries no client-side tools of its own — if you send tools, you own the loop and the gateway stays out.
Web tools: one contract everywhere
codai has three web tools, and every agent surface exposes exactly the same ones — same names, same JSON schemas, same limits, same gateway endpoints and the same text rendering for the model. The single source is packages/agent-core/spec/tools-web.json; a parity test fails the build when a surface drifts from it.
| Tool | Endpoint | What it does |
|---|---|---|
web_search | POST /v1/tools/search | Verified search: up to 10 results, rendered as [S1]..[Sn] lines the model cites. |
web_fetch | POST /v1/tools/fetch | One page as clean markdown, optionally windowed by focus; the text is fenced as untrusted content. |
deep_research | POST /v1/research, GET /v1/research/{id} | Several searches + page reads → a report where every claim cites [Sn], or a JSON object with output_schema. |
| Surface | How it shows up |
|---|---|
| VS Code extension (0.7.0+) | codai_web_search, codai_web_fetch, codai_deep_research in Copilot agent mode |
| Desktop and the Android app | Built-in agent tools with the same names |
Headless CLI (codaid run) | Same tools in the hosted agent |
Hosted MCP (https://ai.codai.ro/mcp) | web_search, web_fetch, deep_research |
| SDKs | tools.search, tools.fetch, research.create / retrieve / wait / run |
| Server-side agent loop (this page) | web_search, web_fetch (no nested research) |
Rules every surface follows:
- The gateway is the only search backend for a signed-in user. A 4xx answer (rules refusal, bad arguments, rate limit) is final: the model sees an
ERROR <code>: <message>line and nothing retries through another provider or an LLM. langandcountryare left unset unless the user asked for a market; the gateway infers them from the query.published_atis never made up: when a page has no known publish date the result shows none.crawled_atis when codai fetched it.- Google-grounded rows, when the automatic fallback runs, are a separate block rendered as
[G1]..[Gn]with the attribution line.
Pricing
Web tools are not model calls, so they write no usage row; each call is metered on its own and charged in EUR from your wallet:
| Call | Price |
|---|---|
web_search, depth: fast | $3 per 1,000 |
web_search, depth: deep | $8 per 1,000 |
web_fetch | $0.50 per 1,000 |
deep_research job | $0.20 per job, plus the job's own model tokens |
The first 1,000 calls per user per UTC month are free, and calls made inside a codai agent turn (any surface where codai is the model) are never charged. Errors and cache hits are never charged.
Standalone search and fetch — /v1/tools
The same search and fetch engines are exposed as plain endpoints, so a client-side agent can use them without going through a model. Both need only your codai_ key, count against your per-minute / per-hour request limits (X-RateLimit-* headers, 429 rate_limit_exceeded), and do not create a usage row — they are metered as web tool calls (see pricing).
POST /v1/tools/search
Prop
Type
Any date filter (freshness, published_after, published_before) drops pages whose publish date is unknown — a crawl date is never used in its place. Query operators work too: site:, -site:, filetype:, intitle:, inurl:, "exact phrase", -term, A OR B, after:, before:, lang:xx.
curl https://ai.codai.ro/v1/tools/search \
-H "Authorization: Bearer $CODAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "query": "react compiler release", "include_domains": ["react.dev"], "published_after": "2026-01-01" }'Response — every result is verified: the snippet was found on the live page before it was returned.
{
"results": [
{
"title": "React Compiler v1.0",
"url": "https://react.dev/blog/…",
"snippet": "React Compiler is now stable …",
"source": "docs",
"verified": true,
"published_at": "2026-02-10T00:00:00Z",
"crawled_at": "2026-10-18T07:12:44Z"
}
],
"provider": "codai",
"took_ms": 412,
"cached": false,
"coverage": 1,
"sources_tried": ["docs", "index"],
"degraded": [],
"intent": "docs"
}published_at is null when the page has no known publish date; page_age is a legacy alias kept only when it is known. degraded lists real source failures only; informational pipeline codes go to notes. If the search service is unreachable the gateway answers 503 search_unavailable (type: server_error) — retry later; nothing was charged.
POST /v1/tools/fetch
Prop
Type
curl https://ai.codai.ro/v1/tools/fetch \
-H "Authorization: Bearer $CODAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "url": "https://nodejs.org/en/blog/release/v24.0.0", "max_chars": 4000, "focus": "ESM loader hooks" }'{
"url": "https://nodejs.org/en/blog/release/v24.0.0",
"final_url": "https://nodejs.org/en/blog/release/v24.0.0",
"title": "Node.js 24.0.0 (Current)",
"byline": "The Node.js Project",
"description": "…",
"canonical": "https://nodejs.org/en/blog/release/v24.0.0",
"lang": "en",
"content_type": "text/html",
"content": "## Module customization hooks\n\n…",
"chars": 3811,
"truncated": true,
"took_ms": 688,
"cached": false
}Limits: 10 s timeout, 3 MB body cap, at most 3 redirects, and an SSRF guard on every hop — private, loopback and credential-bearing URLs, or non-HTTP schemes, are 400 bad_request. Accepted content types are HTML, plain text, JSON, XHTML, Markdown and XML; a non-2xx upstream is 502 upstream_error with details: { status, url }. Fetches are cached for 15 minutes, searches for 10.
The fetched text is third-party content. Agent surfaces wrap it in an untrusted fence and tell the model never to follow instructions found inside it; do the same if you feed it to a model yourself.
Deep research — /v1/research
POST /v1/research starts an asynchronous job: codai plans several searches, reads the best pages and writes a report where every claim cites [Sn], followed by the sources. It takes 20 s to a few minutes. Full reference: Research API.
Prop
Type
const job = await codai.research.run({ question: 'Which EU countries require B2B e-invoicing in 2026?', max_sources: 15 });
console.log(job.report, job.sources);These endpoints fetch from the public internet on your behalf. Do not pass them user-supplied URLs you have not validated yourself — the SSRF guard protects codai's network, not your intent.
Last updated: