Guardrails
Guardrails classify every tool call an agent makes into an action class — execute autonomously, route to human approval, or block — using deterministic, non-LLM guard expressions.
Overview
A guardrail is a standalone, versioned resource, separate from IAM policies: a policy decides whether a caller may invoke an endpoint; a guardrail decides whether an agent may take this action, with these arguments, in this context, without a human signing off. It maps tool calls to action classes (A/B/C/D) and gates class-B autonomy behind guard expressions evaluated after the model produces the call and before it executes.
Class-C actions route into the approvals queue, guards read spend from usage metering, and expressions use the JSON Logic evaluator that orchestrations use. Tools, agents and projects each carry a guardrail_ids list, so a guardrail attaches at any of those scopes; the strictest decision wins.
See the Permissions Reference for the IAM action strings for this module.
Related Tutorials
- Gate a Dangerous Tool with Guardrails - Step 4 (Write the guardrail)
- Gate a Dangerous Tool with Guardrails - Step 5 (Dry-run every decision)
- Gate a Dangerous Tool with Guardrails - Step 10 (A failing guard: the tripwire)
- Gate a Dangerous Tool with Guardrails - Step 12 (Raise the floor for the whole project)
Data Model
Guardrail
| Field | Type | Description |
|---|---|---|
id | string | Public identifier prefixed with guard_ |
project_id | string | ID of the owning project |
name | string | Human-readable name |
description | string | Optional description |
version | integer | Incremented on every document write; prior versions are archived |
document | object | The action-class document (see below) |
context_tool_id | string | Optional tool the platform calls at evaluation time to fetch fresh guardrail context |
context_mode | string | null | How tool-fetched context combines with the caller-supplied context: merge (default) or replace. null when explicitly cleared |
created_at | string | ISO 8601 creation timestamp |
updated_at | string | ISO 8601 last-updated timestamp |
The document:
| Field | Type | Description |
|---|---|---|
class | string | object | A class literal ("A" | "B" | "C" | "D") or a JSON Logic expression returning one — see Classification |
default_class | string | Applied when the class expression returns anything other than a valid class (a missing key, null, a typo). Defaults to C (fail-closed) |
guard | object | A single JSON Logic expression; when the call classifies as B, it must evaluate truthy to execute autonomously. Compose multiple conditions with { "and": [...] } |
escalate | boolean | When true, a failing guard routes to approval instead of tripping fail-closed |
GuardrailVersion
An immutable archive of a guardrail's configuration at one version; same shape and engine as AgentVersion.
| Field | Type | Description |
|---|---|---|
id | string | guard_ver_-prefixed public ID of the archived version |
guardrail_id | string | The guard_-prefixed guardrail this version belongs to |
version | integer | The archived version number |
config | object | The versioned surface at that version — today { document } and nothing else |
label | string | Optional human tag, e.g. pre-tightening; null when unset |
created_by | string | Public ID of the user whose action produced this version; null when there was none |
created_at | string | ISO 8601 timestamp |
Only the document is versioned; name, description and the context binding are metadata. One version number denotes one policy, which is what an evaluation record cites.
Key Concepts
Attachment
A guardrail attaches through the guardrail_ids list on one of three resources; each scope can carry several:
- Project — every tool call by every agent in the project.
- Agent — every tool call the agent makes.
- Tool — that tool wherever it is used, by any agent.
Adding an id can only tighten the outcome, so it needs the carrying resource's update permission (tools:UpdateTool, agents:UpdateAgent, projects:UpdateProject). Removing an id can loosen posture, so it additionally requires guardrails:DetachGuardrail.
Every applying guardrail evaluates and the strictest decision wins, ordered blocked > tripwire > route_to_approval > execute; where several classify the same call as B, all their guards must pass. Composition is order-independent; A is the identity. One guardrail_evaluation record is written per guardrail evaluated.
Action Classes
| Class | Meaning | Behavior |
|---|---|---|
| A | Read-only / harmless | Always execute; logged to the activity feed |
| B | Autonomous with a guard | Execute iff the guard passes; a failing guard trips fail-closed (or routes to approval — see Tripwires) |
| C | Human sign-off | Files an ApprovalItem (origin: tool_call); executes only on approval |
| D | Forbidden | The call is blocked at dispatch; the model receives a blocked tool result and continues its turn |
A class expression returning anything other than "A" / "B" / "C" / "D" resolves to default_class (default C). Mapping autonomy grades onto classes: Layers are concerns, not autonomy levels.
Class C uses the approvals queue's return-pending mechanics: the tool result is { "status": "pending_approval", "approval_id": …, "expires_at": … } and the turn completes. Approval starts a continuation generation with the frozen (or edited) arguments; rejection executes nothing; expiry ends the chain unless the agent opts in (Agents → Approval Expiry).
The document may carry expires_in (seconds): the sign-off window for a class-C approval it files (default 24h). When several guardrails apply, the strictest guardrail's expires_in wins, in agent tool dispatch and the orchestration tool node alike.
Classification
class is a literal ({ "class": "C" } always requires sign-off) or a single JSON Logic expression returning the class, evaluated over the same namespaces as guards (args.* / context.* / runtime.*). There is no rule list; anything other than a valid class falls through to default_class.
To gate several tools differently, create a guardrail per tool and attach each to its tool rather than branching on runtime.tools.name. This example classifies a budget update B below a threshold and C at or above it:
{
"default_class": "C",
"class": { "if": [{ "<": [{ "var": "args.amount" }, 500] }, "B", "C"] },
"guard": { "<=": [{ "var": "args.amount" }, { "var": "context.max_daily_budget" }] }
}
Guards and Guardrail Context
class and guard are single JSON Logic expressions (the evaluator orchestration mappings use); compose with if/and/or/!, not arrays. Every var resolves against three namespaces:
| Namespace | Source |
|---|---|
args.* | The proposed call's arguments (post preset-merge — the same frozen arguments an approval item records) |
context.* | The effective guardrail context — application-owned, see below |
runtime.* | Platform-computed values (fixed catalog below); reserved — never writable by the caller or the context tool |
The caller passes a free-form guardrail_context object on the generation request or orchestration-run start; the platform never interprets it. A guardrail may also name a context_tool_id, an ordinary tool in its own project (400 TOOL_NOT_FOUND otherwise) the platform calls immediately before classifying each gated call, so long-parked runs read fresh context. context_mode combines the two: merge (default) shallow-merges top-level keys over the caller-supplied object, the tool's value winning; replace substitutes it entirely.
The context tool is called on every gated call, uncached, with the proposed call as its input, nested under one key:
{ "call": { "action": "<action>", "tool": { "id": "tool_…", "name": "…" }, "args": { … } } }
call.args are the guard's args.*: preset parameters applied, approval justification fields removed. The shape is the same on every gate site (agent turn, client tool, orchestration tool node, direct call or pipeline step) and in a dry run. Its only top-level key is call, so the input never supplies the action a builtin context tool reads its operation from: set that in preset_parameters.action. A tool that ignores its input works unchanged. http tools receive the input as the request body.
Use it for rules about the call's target entity, which runtime.* does not key by argument — the context tool reads call.args.optimization_id and returns both figures:
{
"and": [
{ ">=": [{ "var": "context.hours_since_last_change" }, 24] },
{ "<": [{ "var": "context.retunes_7d" }, 3] }
]
}
call.argsare model output. The tool runs under the calling agent's credentials (same project scoping and secret resolution); validate the arguments before using them in a lookup. Its result never enters the model context.- Bind it to the tools it governs, not to a whole agent: every gated call pays one tool call and a fail-closed timeout risk (
SOAT_GUARDRAIL_CONTEXT_TIMEOUT_MS, default 5000 ms), and a read-heavy agent makes dozens of reads per turn. - Fail-closed. An inaccessible tool, an error, a timeout or a non-object result contributes no
context.*keys.
The runtime.* catalog is a grammar, leaves only:
runtime.action the request fact; no module, no window
runtime.<module>.<identity> runtime.tools.name
runtime.<module>.<metric>.<window> runtime.tools.tool_calls.24h
A module answers about the entity of that module in the current call: projects the project, agents the calling agent, tools the tool being called, guardrails the guardrail being evaluated, orchestrations the current orchestration run. Windows 1h / 24h / 7d / 30d are rolling and end at evaluation time; total is the entity's lifetime — all-time for a tool, "this run so far" for orchestrations. The served combinations:
| Module | Identity | Metrics | Windows |
|---|---|---|---|
projects | id | tool_calls tokens cost_usd errors | 1h 24h 7d 30d |
guardrails | tool_calls | 1h 24h 7d 30d | |
agents | id | tool_calls tokens cost_usd | 1h 24h 7d 30d |
tools | id name | tool_calls errors | 1h 24h 7d 30d total |
orchestrations | node_attempt | tool_calls tokens cost_usd | total |
Any other path — a non-leaf (runtime.tools.tool_calls), an unserved combination (runtime.tools.tokens.24h) — is refused at write time.
| Metric | Type | Reads |
|---|---|---|
tool_calls | integer | tool_execution events: one per outbound tool call, whatever the target answered |
errors | integer | tool_execution events whose outcome is error or timeout |
tokens | integer | Billable tokens (input + output + cached) of the entity's usage events |
cost_usd | number | Cost of the entity's usage events, every meter |
Every metric is read live from the usage meter at evaluation time. An entity that has done nothing yet reads 0; a key whose entity the call does not have — agents.* on an orchestration tool node, orchestrations.* outside a run, tools.* on an inline tool — is unresolvable, and so is a failing query.
guardrails.tool_calls.* counts the executions of the calls this guardrail released: every guardrail that applied to a call and let it through is recorded on its tool_execution event. Attach one guardrail to a set of tools (every write, say) and one key caps the set, with no tool list in the expression. A pipeline passes the guardrails that released it to each step.
A cost key resolves to null when its spend cannot be priced. SUM(cost_usd) ignores unpriced events, so a window that metered LLM usage and priced none of it would pass every ceiling. The cost_usd keys report null instead, which fails the guard: the same verdict a cost_usd quota answers with QUOTA_UNENFORCEABLE, cleared once the models carry price book rows. Not triggered by a window with no LLM usage (reads 0), unpriced embeddings (their rate is deployment configuration, not a tenant price row), or a partly priced window (any priced LLM event clears it; unpriced events count as zero). The partly priced gap is reported as a quota_unpriced exception on the project's cost_usd quota naming the rows to price.
Two windows at once, and a failure circuit breaker, in one guard:
{
"class": "B",
"guard": {
"and": [
{ "<": [{ "var": "runtime.tools.tool_calls.24h" }, 50] },
{ "<": [{ "var": "runtime.tools.tool_calls.1h" }, 10] },
{ "<": [{ "var": "runtime.tools.errors.1h" }, 3] }
]
}
}
Fail-closed at both ends. At write time, a var outside the three namespaces, or a runtime.* key outside the catalog, is rejected with 400. At evaluation time, a context.* key absent from the effective context, a context-tool failure or timeout, or an unresolvable runtime.* provider fails closed: in class the result is default_class; in guard it counts as a failed guard and tripwire semantics apply.
Variable casing. guardrail_context (and a dry-run's args) keys pass through verbatim, with no snake↔camel conversion; snake_case is recommended (it matches the runtime.* catalog): { "var": "context.max_daily_budget" } reads a supplied max_daily_budget.
Missing keys. JSON Logic coerces an absent var to a falsy, zero-ish value, so { "<": [{ "var": "args.amount" }, 500] } is true when args.amount is absent. Test presence explicitly when that must not reach the permissive branch: { "and": [{ "var": "args.amount" }, { "<": [{ "var": "args.amount" }, 500] }] }.
Tripwires and escalate
A failing class-B guard is a tripwire: by default it aborts the action and files an exception. escalate: true routes the call to the approvals queue instead.
escalate is per-guardrail: a failing guard yields tripwire without it and route_to_approval with it; tripwire outranks route_to_approval in the decision ordering.
Per-run spend ceilings
runtime.orchestrations.tokens.total and runtime.orchestrations.cost_usd.total expose the current orchestration run's cumulative metered spend, live (runtime.orchestrations.tool_calls.total its tool calls), so a ceiling trips mid-run on the call that crosses it, where a project-windowed guard would barely move.
Give the ceiling itself as guardrail_context (or a context tool) so one guardrail serves every run:
- CLI
- SDK
- curl
soat create-guardrail \
--name "Per-run token ceiling" \
--document '{
"class": "B",
"guard": {
"<": [
{ "var": "runtime.orchestrations.tokens.total" },
{ "var": "context.action_token_ceiling" }
]
}
}'
const { data, error } = await soat.guardrails.createGuardrail({
body: {
name: 'Per-run token ceiling',
document: {
class: 'B',
guard: {
'<': [
{ var: 'runtime.orchestrations.tokens.total' },
{ var: 'context.action_token_ceiling' },
],
},
},
},
});
if (error) throw new Error(JSON.stringify(error));
curl -X POST https://api.example.com/api/v1/guardrails \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"name": "Per-run token ceiling",
"document": {
"class": "B",
"guard": {
"<": [
{ "var": "runtime.orchestrations.tokens.total" },
{ "var": "context.action_token_ceiling" }
]
}
}
}'
Attach it to the tools the run dispatches; the guard fails before the tool runs. Swap tokens.total for cost_usd.total to cap dollars.
- Fail-closed outside a run. The keys are unresolvable when no run is in scope (they do not read as
0), so a per-run ceiling attached at project scope trips on plain agent calls too; attach at tool scope unless that is intended. - Metering granularity is the resolution. Counters advance as each provider call is metered (usage coverage), so a ceiling trips on the first gated call after it is crossed; a single over-budget call can still complete.
Client Tools
Client tools are gated at the requires_action handoff: class A and a passing B hand the call to the client; class C files the approval item first and hands off only on approval; class D blocks the handoff; a tripwire aborts before anything reaches the client. The platform cannot observe what the client does after.
Orchestration tool nodes
An orchestration tool node is gated at dispatch with no agent in scope, so it composes only the project + tool scopes (agentId/generationId are null on the evaluation identity and audit record). The strictest decision is enacted as:
- A / passing B — the tool executes with the (cleaned) node inputs.
- C — the run parks on the node with a
requires_actionoftype: "approval", filing anApprovalItem(origin: node). On approval the node re-dispatches with the frozen (or edited) arguments without re-evaluating the guardrail; on rejection or expiry the tool never runs and only a matching decision edge (condition: "rejected"/"expired") follows. - D / tripwire — a routable
blockedoutcome, not a run failure: the node records a{ status, reason }artifact and branches by label, so an edge conditioned onblocked(ortripwire) routes to a fallback path. An unlabeled success edge does not follow a blocked node.
Direct calls and pipeline steps
A tool-scoped guardrail also gates the dispatches with no agent and no orchestration graph: POST /api/v1/tools/{tool_id}/call, every step of a pipeline tool, a trigger whose target is a tool, an ingestion rule converter, an eval tool scorer, and a tool_id embedded in a message. These compose project + tool scope only, and none can await a decision:
- A / passing B — the call runs with the cleaned arguments.
- C / D / tripwire — refused with
422 TOOL_DISPATCH_FAILED, whosemetacarries thetool_idand theoutcomethat settled it. Inside a pipeline the step's ownPIPELINE_STEP_FAILEDnames the settled step.
A gated pipeline is adjudicated before its first step runs, so a refusal never leaves half a pipeline applied. Reach an approval-gated tool through an agent or an orchestration, which can park on the sign-off.
Not gated: a guardrail's own context fetch, and an approved call (the filing guardrail already classified it and a human signed off on those exact arguments).
Running a tighter posture in one project
There is no override resource: attach a tighter guardrail at the project scope ({ "class": "C" } forces sign-off on every call its agents make) or at one tool's scope. Stricter-wins means it can only tighten; other projects are untouched.
Versioning
The policy uses the append-only archive that backs agent versions. Version 1 is written on create; every write that changes the document increments version and archives a GuardrailVersion. Approval items, activity entries and exceptions record the governing version.
Three writes archive nothing: a metadata-only edit (name, description, context_tool_id, context_mode); re-writing the document already held (compared structurally); restoring the version already live. version_label on a create or update annotates the version that write archives and is not itself a change.
A write may name the version it is changing (expected_version, or an If-Match header) and is refused with 409 VERSION_CONFLICT when the resource has moved on — see Concurrent Writes.
| Operation | Endpoint |
|---|---|
| List versions, newest first | GET /api/v1/guardrails/{guardrail_id}/versions |
| Fetch one version | GET /api/v1/guardrails/{guardrail_id}/versions/{version} |
| Roll back to a version | POST /api/v1/guardrails/{guardrail_id}/versions/{version}/restore |
Restore appends. Restoring v1 of a guardrail at v2 writes v1's document as v3, so records citing v2 still resolve. It runs through the ordinary update path (re-validated), takes an optional label, and rolls back only the policy; name, description and the context binding are untouched.
Attachments reference the id, not a version, so a document edit takes effect everywhere at once; dry-run it first. There is no release/canary layer: splitting traffic across two policies would under-enforce one.
Deletion
A guardrail cannot be deleted while attached: DELETE /api/v1/guardrails/{guardrail_id} returns 409 listing the tools, agents and projects whose guardrail_ids reference it. Each must be detached first (guardrails:DetachGuardrail, see Attachment). A dangling reference met at evaluation time fails closed as class C.
Dry-run Evaluation
POST /api/v1/guardrails/{guardrail_id}/evaluate runs the full evaluation pipeline (class, guard, context tool per context_mode, live runtime.*) against caller-supplied args and guardrail_context, and returns the evaluation record a real call would produce. Nothing executes or is filed. Pass an optional tool_id to resolve runtime.tools.*; the context tool receives the same call input a real call sends, built from args and tool_id. An unresolvable runtime.* key fails closed as at runtime. Use it before attaching a document, or before editing a widely attached one.
Evaluation Audit Record
Every evaluation writes a guardrail_evaluation activity entry and stamps the generation/run record:
{
"kind": "guardrail_evaluation",
"guardrail_id": "guard_V1StGXR8Z5jdHi6B",
"guardrail_version": 3,
"scope": "tool",
"tool": "update-budget",
"action": "update-budget",
"class": "B",
"decision": "execute",
"guard_result": true,
"context_source": "merged",
"context_snapshot": {
"args.amount": 450,
"context.max_daily_budget": 500,
"context.cost_ceiling": 1000,
"runtime.projects.cost_usd.24h": 812.4
},
"agent_id": "agent_V1StGXR8Z5jdHi6B",
"orchestration_run_id": "orch_run_V1StGXR8Z5jdHi6B",
"generation_id": "gen_V1StGXR8Z5jdHi6B"
}
tool/actionname the call being classified; both arenullfor a call with no tool in scope.decisionis one ofexecute|route_to_approval|blocked|tripwire.classis the resolved class; when theclassexpression returned an invalid value it is the applieddefault_class.scoperecords where this guardrail was attached:project|agent|tool. One record per applying guardrail; the enacteddecisionis the strictest across them.context_sourcerecords where the effective context came from:caller|tool|merged|none.guard_resultis the guard expression's boolean outcome;nullwhen the document has no guard or the call did not classify asB.context_snapshotis a flat map of only the vars the evaluation referenced, keyed by fully-qualified path and frozen at evaluation-time value; unreferenced (possibly sensitive) context is never recorded.
Evaluations that changed the call's outcome (route_to_approval, blocked, tripwire; not execute) are also mirrored into the audit log as a platform-originated entry (action: guardrails:Evaluate, detail.kind: guardrail_evaluation); a route_to_approval entry also carries the filed approval_id.
Formation resource
A guardrail formation resource (GuardrailResourceProperties) takes name, description, class, default_class, guard, escalate, context_tool_id, context_mode: the fields of Create a guardrail with document flattened to top-level properties. context_tool_id may be a { "ref": "ResourceName" } to a tool resource in the same template, and a tool or agent resource can attach it via guardrail_ids: [{ "ref": "ResourceName" }]. class/default_class/guard/escalate are recombined into one document write on every create/update, so an update omitting one drops it (matching PATCH /api/v1/guardrails/{guardrail_id}'s full-replace semantics for document).
Who may act on a guardrail
Every route that acts on one guardrail — read it, change it, delete it, dry-run
it, or read and restore its versions — is authorized against that guardrail's
SRN, srn:<project_id>:guardrail:<guardrail_id>, not against the project. A
policy may therefore name the guardrails it covers:
{
"statement": [
{
"effect": "Allow",
"action": ["guardrails:GetGuardrail", "guardrails:EvaluateGuardrail"],
"resource": ["srn:proj_V1StGXR8Z5jdHi6B:guardrail:guard_V1StGXR8Z5jdHi6B"]
}
]
}
Refusals keep the shapes IAM defines: a read the caller may not perform is 404 (a guardrail it may not see does not announce itself), a write or an evaluation is 403, and a credential scoped to another project is 403 API_KEY_PROJECT_SCOPE. A write on a resource in a project none of the caller's policies name is 404 too — the same answer their read would get, so a refusal never confirms existence across a tenant boundary.
Listing guardrails stays project-scoped: GET /api/v1/guardrails asks whether the caller may list guardrails in a project at all, so a policy that names individual guardrails grants no listing.
Examples
Create a guardrail
Class B below 500, C at or above; executes autonomously only while 24h spend stays under 1000.
- CLI
- SDK
- curl
soat create-guardrail \
--name "Budget Update Guardrail" \
--document '{
"default_class": "C",
"class": { "if": [{ "<": [{ "var": "args.amount" }, 500] }, "B", "C"] },
"guard": { "<": [{ "var": "runtime.projects.cost_usd.24h" }, 1000] }
}'
import { SoatClient } from '@soat/sdk';
const soat = new SoatClient({
baseUrl: 'https://api.example.com',
token: 'sk_...',
});
const { data, error } = await soat.guardrails.createGuardrail({
body: {
name: 'Budget Update Guardrail',
document: {
default_class: 'C',
class: { if: [{ '<': [{ var: 'args.amount' }, 500] }, 'B', 'C'] },
guard: { '<': [{ var: 'runtime.projects.cost_usd.24h' }, 1000] },
},
},
});
if (error) throw new Error(JSON.stringify(error));
curl -X POST https://api.example.com/api/v1/guardrails \
-H "Authorization: Bearer <admin-token>" \
-H "Content-Type: application/json" \
-d '{
"name": "Budget Update Guardrail",
"document": {
"default_class": "C",
"class": { "if": [{ "<": [{ "var": "args.amount" }, 500] }, "B", "C"] },
"guard": { "<": [{ "var": "runtime.projects.cost_usd.24h" }, 1000] }
}
}'
Dry-run a guardrail before attaching
Preview the decision the guardrail above would make; nothing executes or is filed:
- CLI
- SDK
- curl
soat evaluate-guardrail \
--guardrail-id guard_V1StGXR8Z5jdHi6B \
--args '{"amount": 450}'
const { data, error } = await soat.guardrails.evaluateGuardrail({
path: { guardrail_id: 'guard_V1StGXR8Z5jdHi6B' },
body: { args: { amount: 450 } },
});
if (error) throw new Error(JSON.stringify(error));
curl -X POST https://api.example.com/api/v1/guardrails/guard_V1StGXR8Z5jdHi6B/evaluate \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{ "args": { "amount": 450 } }'
The response is the would-be evaluation record: class B, passing guard, runtime.projects.cost_usd.24h resolved live:
{
"class": "B",
"decision": "execute",
"guard_result": true,
"context_source": "none",
"context_snapshot": {
"args.amount": 450,
"runtime.projects.cost_usd.24h": 812.4
}
}
Attach a guardrail
Attach to a tool (below), to an agent (soat update-agent --agent-id agent_01 --guardrail-ids …) or to a project (soat update-project --project-id proj_01 --guardrail-ids …); see Attachment.
- CLI
- SDK
- curl
soat update-tool \
--tool-id tool_01 \
--guardrail-ids guard_V1StGXR8Z5jdHi6B guard_9f3Kd2Lm0PqRsT4u
const { data, error } = await soat.tools.updateTool({
path: { tool_id: 'tool_01' },
body: { guardrail_ids: ['guard_V1StGXR8Z5jdHi6B', 'guard_9f3Kd2Lm0PqRsT4u'] },
});
if (error) throw new Error(JSON.stringify(error));
curl -X PATCH https://api.example.com/api/v1/tools/tool_01 \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"guardrail_ids": ["guard_V1StGXR8Z5jdHi6B", "guard_9f3Kd2Lm0PqRsT4u"]}'
Pass guardrail context on a generation
The application supplies the context.* values; a context_tool_id's output is combined over this object per context_mode.
- CLI
- SDK
- curl
soat create-agent-generation --wait true \
--agent-id agent_01 \
--messages '[{"role":"user","content":"Raise the campaign budget to 450"}]' \
--guardrail-context '{"max_daily_budget": 500, "cost_ceiling": 1000}'
const { data, error } = await soat.agents.createAgentGeneration({
path: { agent_id: 'agent_01' },
query: { wait: true },
body: {
messages: [{ role: 'user', content: 'Raise the campaign budget to 450' }],
guardrail_context: { max_daily_budget: 500, cost_ceiling: 1000 },
},
});
if (error) throw new Error(JSON.stringify(error));
curl -X POST https://api.example.com/api/v1/agents/agent_01/generate?wait=true \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"messages": [{ "role": "user", "content": "Raise the campaign budget to 450" }],
"guardrail_context": { "max_daily_budget": 500, "cost_ceiling": 1000 }
}'
List archived versions
- CLI
- SDK
- curl
soat list-guardrail-versions --guardrail-id guard_V1StGXR8Z5jdHi6B
const { data, error } = await soat.guardrails.listGuardrailVersions({
path: { guardrail_id: 'guard_V1StGXR8Z5jdHi6B' },
});
if (error) throw new Error(JSON.stringify(error));
curl -X GET https://api.example.com/api/v1/guardrails/guard_V1StGXR8Z5jdHi6B/versions \
-H "Authorization: Bearer <admin-token>"