Tools & approvals
Agents act on your knowledge base through tools: typed functions with a Zod input schema. Reads and reversible edits run freely; outbound and irreversible actions pause the run for your approval; and every credential a tool needs is acquired through a broker rather than handed out ambiently. This page covers the tool surface, the approval model, and the two ways a run talks back to you mid-flight.
Every tool is a registry entry of the shape:
{ description: string, schema: ZodType, // validated input requires?: string[], // capability reach (see the broker below) needsApproval: boolean | ((args) => boolean), alwaysGate?: boolean, // trust floor: gate under every policy execute(args, { sql, runId?, caps? }): ToolResult}The full, always-current catalog lives in the agent tools reference. The sections below cover the shape of it.
The knowledge base surface
Section titled “The knowledge base surface”The kb tools let an agent search, read, and edit pages. Editing never touches the filesystem: the agent submits Markdown, and the OKF codec diffs it into node commands that land through the same command model as a human edit.
| Tool | What it does |
|---|---|
kb.search {q, kinds?} |
Hybrid search, top 10 results. |
kb.grep {pattern, regex?, limit?} |
Exact (ILIKE, metacharacters literal) or regex (~*) scan over every bullet. Returns page › node-id: text lines plus a total count, for exhaustive queries that ranked search cannot answer. |
kb.read {page, depth? 0-2} |
The page as OKF markdown (node ids as <!--nid--> comments) plus labels and backlinks. depth follows [[links]] breadth-first, up to 10 pages. |
kb.node.read {node, depth?} |
One node’s subtree as an OKF fragment. Node ids come from kb.read or kb.grep. |
kb.expand {directory?} |
One directory level at a time, for PageIndex-style traversal. |
kb.corpus {maxChars?} |
A bounded, title-ordered dump of every readable node, bypassing retrieval. |
kb.edit {page, okf} |
Submit full-page OKF. The codec parses it, re-adopts current node ids by position and text, and lands one applyOkf command (a node-level diff; the page is created when missing). |
kb.node.edit {node, okf} |
Replace one node’s subtree with an OKF fragment (exactly one root bullet), scoped through the same id-adoption diff. |
kb.create {title, dir?, okf?} |
Create a page, ensuring its directory by slug. |
kb.append / kb.remove |
Append or remove a bullet. kb.remove is destructive, so it gates. |
Tasks, memory, capture, and connectors
Section titled “Tasks, memory, capture, and connectors”Beyond the kb surface, agents reach the rest of the ops spine and the comms domains:
- Tasks:
tasks.list {section?},tasks.create {title, section='up-next'},tasks.done {id},tasks.doneLog {since?}, over the task queue. - Memory:
memory.propose {kind, text}files a proposal, andmemory.consolidate {}runs the deterministic dedup pass. See memory. - Capture:
ingest.capture {url, title, markdown, screenshotFileId?, source?}files extracted content and feeds the knowledge ingestion compiler. - Calendar, mail, and CRM: outbound comms tools like
mail.send,im.send, and calendar writes act as the run’s user against mail, calendar, and CRM. These gate (see below). - Files: agents attach and read files and blobs through the same content-addressed store the rest of the product uses.
- Heartbeat:
ops.heartbeat {}runs the deterministic reminder and calendar-prep scan.
Approval gates
Section titled “Approval gates”An approval gate pauses a run before an outbound or irreversible action and files a needs-confirm task card, so you decide before anything leaves the system. Gating composes two inputs:
-
The definition’s policy
approvalPolicymapsnone/outbound/alltonever/auto/always. Underautoa tool gates only if its ownneedsApprovalsays so;alwaysgates every tool;nevergates nothing. -
The per-tool trust floor
A tool marked
alwaysGatepauses under every policy.os.execis the canonical example:os.exec {command, cwd?}runs/bin/sh -cwith a 15s timeout and an 8k output cap, always gates, and is disabled entirely unlessSUBSPACE_OS_EXEC=1(orPROVIDERS=fake). It also takes an effect claim so a crash-replayed call refuses the silent re-run.
When a call gates, the pausing transaction writes the approval row, the task card, and a
status flip to paused together. Deciding is also one commit: the decision, the card
resolution, the flip back to running, and the resume notice land atomically. You can
Confirm or Dismiss from the task queue with no special UI, or Approve or
Deny inline on the ⚑ node in the run tree. A denied
call returns the tool result denied by user; the model sees it and continues, so a denial
steers the run rather than killing it.
The capability broker
Section titled “The capability broker”Credentials are acquired, never ambient. The broker holds the master key, decrypts
ops.secrets rows on demand, caches handles by ciphertext (so rotating a secret is a row
update, picked up on the next acquire), and enforces per-run grants.
-
Tools declare what they reach
A tool’s
requires: string[]names the capabilities it needs, for examplemail.send,im.send,web.fetch,slack.send,google.oauth,llm.anthropic. -
Definitions grant reach
A definition’s
capsnarrows what its tools may touch. Absent, the grant set is the union of the allowlisted tools’requires, so namingcapsonly ever restricts reach. -
The runtime enforces before approval
A missing grant is denied as the tool result (
capability denied: …) before any approval row is written, so an ungranted tool never even reaches the approval prompt. -
Every acquire is audited
ToolContext.capsgives connector-grade tools a grant-checkedacquire(). Each acquire logsacquired <cap>intoops.step_logs, visible in the run tree’s step detail.
Recognized environment variables (ANTHROPIC_API_KEY, SLACK_BOT_TOKEN,
GOOGLE_CLIENT_ID/SECRET, DEEPGRAM_API_KEY, and more) seed their secret rows once at
boot, insert-only and reported by name, so a rotated row is never clobbered by a stale env
var. See security and trust for the broker’s role in the
trusted/worker plane split, and the environment variables reference
for the full list.
human.ask: the agent asks you
Section titled “human.ask: the agent asks you”human.ask {question, options?, context?, timeout?, default?} hands a decision back to you
mid-run. It is ungated (asking is not an outbound action). The tool files a
human-question needs-confirm card scoped to the invoking node’s page and suspends on a
durable hook at zero cost.
You answer from either surface: the task card renders options as answer buttons (Dismiss
answers “dismissed without an answer”), or the run tree’s question node offers the option
buttons plus a free-text input and an Answer button. The answer string becomes the tool
result. With a timeout, the hook races a durable sleep and expiry returns default (the
card auto-resolves as expired).
Steer: inject a message mid-run
Section titled “Steer: inject a message mid-run”A live run’s anchor row carries a Steer this run… input. An owner or admin message rides
the reusable steer:<runId> hook and appends as a user message between LLM steps, including
while an LLM call or an approval wait is active. Steering lets you redirect a run without
killing it: correct a misread instruction, add a constraint, or point it at the right page,
and the next model step sees your message in the history.