Tools schema
Declare your agent's tools and choose sandbox or passthrough per tool.
tools_schema is the OpenAI-style list of capabilities your agent
advertises. The platform uses it to validate tool calls, type-check
arguments, and decide whether to simulate (sandbox) or forward
(passthrough) each call.
Shape
{
"tools_schema": [
{
"name": "<string>",
"description": "<string>",
"input_schema": { /* JSON Schema */ },
"output_schema": { /* JSON Schema, optional */ },
"default_execution_mode": "sandbox" | "passthrough",
"passthrough_binding": {
"endpoint_id": "<UUID>",
// OR
"endpoint_name": "<unique name within your org>",
"tool_name": "<tool as discovered on the endpoint>"
}
}
]
}Fields
| Field | Required | Notes |
|---|---|---|
name | yes | ^[A-Za-z_][A-Za-z0-9_\-]{0,127}$. Becomes the URL segment in POST /api/odyssey-proxy/tools/{tool_name}. Unique per agent. |
description | no | Free-form. |
input_schema | yes | JSON Schema for the arguments object. |
output_schema | no | JSON Schema for the response. The simulator validates its output against this. |
default_execution_mode | no (default sandbox) | sandbox or passthrough. |
passthrough_binding | required if passthrough | { tool_name, endpoint_id } or { tool_name, endpoint_name }. |
ledger_write_policy | no (default record_only) | Passthrough only. record_only / adapter / none — see Writing ledger updates. |
ledger_adapter | required if adapter | Declarative rule mapping the live response into ledger updates. |
Pick one of endpoint_id (UUID) or endpoint_name (unique per org) —
not both.
Sandbox mode (default)
The proxy hands the call to the platform's simulator:
- Failure-rule check — if any rule matches, return the rule's payload
tagged
source = "injected". - Otherwise generate a response against the seeded world state, the
behavior instructions, and the tool's I/O schemas. Tagged
source = "odyssey". - If validation fails after retries, returns
source = "error".
Passthrough mode
Set default_execution_mode: "passthrough" to forward the call to a
registered tool endpoint. Requires:
- An endpoint of type
mcp_server,http_openapi, orcodein your org. - A
tool_namethat exists on that endpoint's discovered tool list.
Tagged source = "passthrough" on success, source = "transport_error"
on a live-hop failure.
Picking an endpoint type
| Tool body | Pick |
|---|---|
| Calls one upstream with an OpenAPI spec, no reshaping | http_openapi |
| Calls an MCP-server tool you already host | mcp_server |
| Does post-processing (HTML strip, XML parse, response reshape, multi-call orchestration) | code |
If the tool body does anything beyond a raw HTTP call (regex strip,
parsing, dict reshape), choose code — passthrough is a verbatim
forward and won't run that post-processing.
Code-endpoint passthrough
Ship your local Python tool body as a code endpoint via the SDK:
from pipelines.odyssey import tool_endpoints
from research_agent import tools_network
endpoint = tool_endpoints.create_code(
name="research-tools",
functions=[tools_network.search_wikipedia, tools_network.fetch_url],
requirements=["httpx>=0.27", "trafilatura"],
org_id=42,
api_key=os.environ["PIPELINES_API_KEY"],
)Then bind by name:
{
"name": "search_wikipedia",
"input_schema": {"type": "object", "properties": {"query": {"type": "string"}}, "required": ["query"]},
"default_execution_mode": "passthrough",
"passthrough_binding": {
"endpoint_name": "research-tools",
"tool_name": "search_wikipedia"
}
}If a tool needs a secret (API key for the upstream it calls), pass
env_vars to create_code — names of org credentials the platform
decrypts into the sandbox at runtime. See
Secrets via env_vars → org credentials.
Full SDK reference: Tool endpoints from local modules.
Writing ledger updates from passthrough
A passthrough tool returns live data, but by default that data doesn't
become part of the simulated world-state ledger — so a later
sandbox-mode tool can't "see" it. Set ledger_write_policy to control
this:
| Policy | Effect |
|---|---|
record_only (default) | The live result shows in the trace but doesn't change the ledger. |
adapter | Mirror the live result into the ledger via a ledger_adapter (below). |
none | Don't write the result to the ledger at all. |
Use adapter when a passthrough tool returns state that downstream
simulated tools should read (e.g. a live get_order whose result a
later simulated refund_order depends on).
The ledger_adapter
A declarative rule — no code — that turns the tool's JSON response into
ledger updates. It requires a ledger schema
declaring the entity_type you target.
{
"name": "get_order",
"default_execution_mode": "passthrough",
"passthrough_binding": { "endpoint_name": "orders-api", "tool_name": "get_order" },
"ledger_write_policy": "adapter",
"ledger_adapter": {
"op": "update",
"entity_type": "order",
"id_from": "$.order_id",
"field_map": { "status": "$.status", "total": "$.amount" },
"flags": ["fetched_order:{order_id}"]
}
}| Field | Required | Notes |
|---|---|---|
op | no (default update) | add, update, remove, or set_flag. |
entity_type | entity ops | A type declared in your ledger_schema. |
id_from | entity ops | JSONPath into the response for the entity id (e.g. $.order_id). |
field_map | no | { declared_field: jsonpath } — pulls fields from the response onto the entity. |
flags | required for set_flag | List of flag templates; {name} placeholders interpolate from request args plus {id}. |
Path + template rules to know when authoring one:
id_fromandfield_mapvalues are JSONPath against the parsed JSON response.- If
id_frommatches nothing (or a non-scalar), the adapter no-ops for that call — you can't key an entity without an id. - A
field_mappath that matches nothing skips just that field. - A flag template with an unresolved
{placeholder}drops that flag. set_flagwrites no entity, so it must omitentity_type/id_from/field_map.
Adapters are best-effort: a malformed or non-matching adapter is a no-op, never an error that fails the agent's tool call.
Sandbox vs passthrough cheatsheet
| Tool | Pick |
|---|---|
| Read-only external API (Wikipedia, weather, web search) | passthrough |
| Mutates internal state your agent owns (DB writes, refunds, marks) | sandbox |
| Stateful tools that downstream simulated tools read from | sandbox — the simulator needs to own that state |
| Real production-money mutators | sandbox unless you have a non-prod clone of the upstream registered |
When in doubt, start sandbox. Flip to passthrough later via
PUT /api/agents/{id} (or the SDK) without changing agent code.
Validation
The platform rejects schemas with:
tools_schemanot a list, or non-object entries.- Missing
name/input_schema. - Invalid
namepattern or duplicates within the agent. default_execution_modeother thansandbox/passthrough.- Passthrough without
passthrough_binding, bothendpoint_idandendpoint_nameset, or a binding pointing at a missing / unsupported / cross-org endpoint. endpoint_namethat doesn't resolve to any endpoint in the agent's org.ledger_write_policy: "adapter"without aledger_adapter, or anadapterwhoseentity_typeisn't a declared ledger schema entity.
Failures return 422 with a per-tool message.