Tools
A3S Code keeps a registry of available tools. toolNames() returns the current
session tool surface. That surface is assembled from the workspace capability
set plus session-level integrations, so a non-local workspace can intentionally
hide tools it cannot service.
Tool activity reaches clients through the session event stream, so a UI can render starts, output, errors, and completion without parsing terminal text.
Tool Surface
Use toolNames() / toolDefinitions() in tests or application diagnostics when
a workflow depends on a specific tool being visible. Tool visibility is not a
security grant. Model-selected tool calls inside send, run, and stream
pass through active-skill restrictions, permission policy, confirmation, hooks,
budget, queue/timeouts, cancellation, recursive-call protection, output
sanitization, artifact limits, and workspace path checks. Direct SDK calls such
as session.tool(...) are host control-plane calls with a different explicit
policy, described below.
Use activeTools() for a different question: which tool calls are currently
running in an active operation.
One Invocation Kernel
The runtime attaches an origin to every invocation:
Model sub-runs created by a host-direct Skill, Task, or custom tool start again
with the Agent origin. A public custom tool can make nested calls only through
InvocationRuntime, which always creates the governed nested origin. This
prevents one direct call to arbitrary extension code from becoming a reusable
authority token.
All origins use the same invocation kernel. Pre-hooks can block them; budget checks run before side effects; queue/timeout, cancellation, recursive invocation protection, post-hooks, and security-provider output sanitization remain active. A nested call cannot fall back to a raw registry while a scoped invoker is installed.
The host-direct policy is not an end-user authorization system. The embedding application must authenticate and authorize a user before translating their request into a direct SDK helper. Closing the session cancels an in-flight host-direct tool through the session cancellation scope.
Bounded Tool Contracts
Governed agent, nested, and session calls validate arguments against each tool's cached JSON Schema before confirmation or side effects. Tools also publish per-invocation scheduling capabilities: read-only, idempotent, resumable, cancellation-safe, pagination support, maximum parallelism, and output kind.
read, ls, glob, Git log/list/diff, and web_fetch return explicit
continuation cursors or offsets. Shell capture is byte-bounded across both
streams, retains the beginning and end with exact accounting, and still
exposes stdout and stderr separately to a sandbox host. A command deadline
includes both output draining and child.wait(), so closing both pipes cannot
evade it. Timeout and cancellation terminate the complete Unix process group.
Large file changes replace full before/after event payloads with bounded
previews and unified diff text plus hashes, sizes, and artifact references.
Repository-context modes
Use read.files when the relevant paths are already known and one bounded
response is cheaper than several tool turns:
The shared byte budget includes headers and the continuation text. Results
stay in request order, and one unreadable member does not discard successful
members. When metadata.batch.truncated is true, copy
metadata.batch.continuation into the next call's files value; its offsets
and remaining limits resume without repeating completed lines.
grep.output_mode selects the smallest useful result shape:
Built-in workspace backends perform non-content scans without constructing
discarded match text. S3 results set metadata.search.truncated and warn when
the backend's object scan limit makes totals or paths incomplete.
glob keeps backend relevance or recency order by default. Set sort: "path"
before cursor pagination when stable lexical pages are required.
For exact-string changes, call edit with dry_run: true to return the same
before/after diff metadata without writing. Then apply the edit with
expected_replacements set to the previewed count, and optionally add
max_replacements as an independent upper bound. Dry runs advertise read-only
capability and are safe for batch parallelization.
batch accepts at most 32 calls and applies at most 16-way concurrency. It
fans out only when every child declares safe read-only, idempotent behavior;
mutating and unknown tools are serialized. A partial batch identifies failed
indices while treating the orchestration as completed, so callers retry only
failed items. parallel_task has the same 32-task bound and settles cancelled
children before publishing terminal state.
parallel_task requires 2-32 independent foreground branches. Each branch
accepts agent, description, prompt, optional max_steps, and optional
output_schema; background is not a parallel-branch parameter. The optional
min_success_count is valid only with allow_partial_failure: true and must be
between 1 and the submitted task count. Provider and child runtimes own typed
retry policy; the fan-out layer never replays a branch based on error text.
web_search reports complete, partial, or failed in metadata. Its default
path executes native APIs first, conventional HTTP/RSS engines only when the
combined generic quality floor is not met, and headless engines only when both
earlier tiers remain insufficient. Browser discovery and pool creation are
therefore lazy. Session-scoped closed/open/half-open circuit state skips known
quota, permission, rate-limit, transport, repeated-empty, and timeout failures
without retrying them on every request; Retry-After is retained. An explicit
engines argument runs only the requested tiers. Empty results with engine
errors are failures rather than successful empty searches. Timeout,
cancellation, invalid-argument, partial-failure, and rate-limit errors carry
structured error kinds, while tier decisions, quality signals, engine outcome,
attempt duration, and retry context remain available in metadata.
web_fetch also preserves failure semantics instead of deriving retry advice
from rendered messages. Request and response-body I/O failures, HTTP 408, and
HTTP 5xx responses use the typed transport kind. HTTP 429 uses
rate_limited and carries a parsed Retry-After delay when the server supplies
one; the outer tool deadline uses timeout. Other HTTP status failures remain
ordinary status errors unless the runtime has typed evidence that retrying is
safe.
Structured Output with generate_object
The generate_object tool asks the configured LLM for a JSON value, validates
the response against a JSON Schema, and returns the validated value only on a
zero-exit result. Root objects, arrays, enums, constants, composition keywords,
and local $ref definitions are supported. The active timeout starts after
model-generation admission, is forwarded to clients that accept an active
transport budget, and remains in force across bounded schema repairs. It works
in two ways:
- Agent-driven: The LLM sees
generate_objectin its tool list and calls it autonomously when structured output is needed. - Direct call: Your application calls
session.tool('generate_object', ...)to bypass model-driven tool selection. The tool itself still calls the configured LLM.
Parameters
Modes
- tool: Forces a synthetic tool whose parameters are the schema when the provider supports forced tool calls.
- prompt: Appends schema instructions to the prompt. It is useful as a prompt-only fallback, but depends more on model compliance.
- auto: Selects forced-tool mode when available, otherwise prompt mode.
- strict: Uses provider-native strict JSON Schema when supported, otherwise safely falls back to forced-tool or prompt mode.
- json: Uses provider-native JSON-object mode when supported, otherwise safely falls back to forced-tool or prompt mode.
Every resolved mode retains the provider-facing response schema as host-only
validation metadata; that metadata is never serialized as an extra provider
request field. Rust composite clients can use
structured::is_complete_streamed_value(...) to accept only a complete JSON
value that validates against this schema, including endpoints that omit a
terminal stream event. They can also inspect
LlmClient::has_distinct_non_streaming_transport() before treating a blocking
call as an independent fallback instead of replaying the same streaming
failure mode under another method name.
Streaming
When called through session.stream(), partial objects are emitted as
tool_output_delta events. Snapshots are rate-limited to one event per 100 ms;
objects over the event budget emit byte accounting instead of duplicating the
full value in the stream:
Repair Loop
If the LLM output fails schema validation, the tool automatically retries by feeding the validation errors back to the model. This handles edge cases like missing required fields or wrong enum values without application-level retry logic.
Direct Tool Calls
SDK callers can call deterministic tools directly:
Direct calls execute inside the session workspace and should be treated as privileged host operations. They do not claim the session's single-flight conversation lease because they do not update transcript history.
For delegated child work, use SDK helpers over the same core tools:
Automatic subagent delegation also uses these core tools. autoParallel: false
disables only automatic parallel fan-out; it does not remove parallel_task or
session.tasks(...).
Programmatic Tool Calling
PTC is the next step beyond a single direct call. The program tool runs a sandboxed JavaScript script in an embedded QuickJS VM. The script defines async function run(ctx, inputs) and replaces repeated model-tool turns with one bounded program.
Instead of spending LLM turns on:
the model can ask program to run a script:
Run it through the SDK helper with either inline source or a workspace-relative .js or .mjs file path:
session.program(...) is equivalent to session.tool('program', { type: 'script', language: 'javascript', ... }) but uses SDK-native naming. When allowedTools / allowed_tools is omitted, the script can call every registered tool except program. Provide an allow-list when a workflow should run with a smaller capability surface.
The QuickJS VM receives no filesystem, network, subprocess, or environment permissions. The only useful capabilities are the ctx methods wired back to A3S Code tools. PTC returns a readable ToolResult.output and structured data in ToolResult.metadataJson. Keep large raw output out of the prompt; summarize findings, evidence refs, risks, and suggested next actions.
In a3s code, PTC is also used by DynamicWorkflowRuntime. Recursive
program, dynamic_workflow, and parallel_task calls are kept out of the
default TUI PTC allow-list. When a dynamic workflow needs local parallel
subagents, it schedules a Flow step named parallel_task; the TUI host runs
the native parallel_task implementation outside QuickJS. After /login,
dynamic workflow PTC steps may call ctx.tool("runtime", ...) when a host
runtime tool is registered in the session.
Dynamic workflows default structured generation to single-flight. Set
limits.maxConcurrentGenerations to 2-4 only when independent
generate_object steps should fan out; each admitted step receives a client
fork bound to its exact run and step identity. Providers that cannot fork a
session remain single-flight. The Rust helper
dynamic_workflow::recover_dynamic_workflow_step_output(...) can recover a
completed durable step only when the run id, original query, and step id all
match. It does not act as a cross-run query cache and never promotes an
incomplete step.
Tool Results
ToolResult includes name, output, exitCode, and optional metadataJson.
Direct session.tool(...), session.program(...), session.git(...),
session.writeFile(...), session.ls(...), session.editFile(...), and
session.patchFile(...) calls return ToolResult. The typed read/search/shell
helpers return simpler values: readFile, grep, and bash return strings,
while glob returns a string array. Long outputs should be summarized before
they are fed back to the model.
Verification
Use verification commands to turn "done" into evidence: