API Reference
Agent Runs
Observability and a server-side safety backstop for your autonomous agents. Reliant does not run your agent loop — you do. You report each step; Reliant records the full trace and enforces two brakes: the budget (steps / tokens / USD) and the kill switch. Agent Guard (argument validation, action policy, human approval) is an optional layer on top.
The loop model
your loop Reliant
───────── ───────
create / fetch the Agent ─────▶ config: budgets, kill switch, tools (schema + policy)
reliant.agentRun() ─────▶ creates the run, returns a handle
┌─ LLM turn
│ handle.logStep(llm_turn) ──▶ stores it, accrues tokens/cost, checks the budget
│ handle.guardTool(name,args) ─▶ validates args + policy → allow | block | escalate
│ run the tool (if allow)
│ handle.logStep(tool_call) ──▶ stores it
│ if (handle.halted) break ◀── budget exhausted or kill switch
└─ repeat
handle.end() ─────▶ closes the run, returns the totals
All routes below authenticate with the X-Reliant-Key header and are scoped to the project that key belongs to. The base URL is https://reliant.api.br (or your own host — see Run Reliant Locally).
Plans: the plain trace (agentRun + logStep) is available on every tier. As soon as a tool declares requires_approval or a non-empty policy, or the agent sets approval_webhook_url, Agent Guard applies and requires starter or above.
Create an agent
An agent is the reusable config; a run is one execution of it. POST /agents upserts by name within the project — calling it again with the same name updates the config and reactivates a soft-deleted agent.
POST https://reliant.api.br/agents
Headers:
Content-Type: application/json
X-Reliant-Key: rel_...
{
"name": "support-agent",
"description": "Answers tickets and issues refunds",
"max_steps": 40,
"max_tokens": 200000,
"max_usd": 5,
"kill_switch": false,
"approval_webhook_url": "https://your-app.com/hooks/reliant",
"webhook_secret": "whsec_...",
"approval_timeout_seconds": 3600,
"tools": [
{
"name": "search_kb",
"args_schema": {
"type": "object",
"required": ["query"],
"properties": { "query": { "type": "string" } },
"additionalProperties": false
}
},
{
"name": "issue_refund",
"args_schema": {
"type": "object",
"required": ["order_id", "amount_usd"],
"properties": {
"order_id": { "type": "string" },
"amount_usd": { "type": "number" }
},
"additionalProperties": false
},
"requires_approval": true,
"on_violation": "escalate",
"policy": {
"max_value": { "field": "amount_usd", "limit": 500 },
"rate_limit": { "max": 20, "period": "daily" },
"safe_result": { "status": "refund_pending_review" }
}
}
]
}
| Field | Type | | Description |
|---|
| name | string | required | Unique per project. |
| max_steps / max_tokens / max_usd | number | optional | Budget backstop. A run halts when spend reaches any of these. Omit for no cap. |
| kill_switch | boolean | optional | When true, every run of this agent halts on its next call and no new runs start. |
| approval_webhook_url | string | optional | Fired with a signed agent.approval_requested payload on every escalation. |
| webhook_secret | string | optional | HMAC-SHA256 key for the X-Reliant-Signature header on the webhook. |
| approval_timeout_seconds | number | optional | Pending approvals past this age flip to expired (checked lazily on read). |
| tools[].name | string | required | Must match the tool name your loop passes to guard-tool. |
| tools[].args_schema | object | required | A valid JSON Schema. Proposed tool args are validated against it. |
| tools[].requires_approval | boolean | optional | Every call to this tool escalates to a human. Default false. |
| tools[].on_violation | enum | optional | block (default) or escalate — what a policy violation does. |
| tools[].policy | object | optional | Action policy — see the policy fields table below. |
const agent = await reliant.agents.create({
name: 'support-agent',
maxSteps: 40,
maxTokens: 200_000,
maxUsd: 5,
tools: [
{ name: 'search_kb', argsSchema: { type: 'object', required: ['query'],
properties: { query: { type: 'string' } }, additionalProperties: false } },
{ name: 'issue_refund',
argsSchema: { type: 'object', required: ['order_id', 'amount_usd'],
properties: { order_id: { type: 'string' }, amount_usd: { type: 'number' } },
additionalProperties: false },
requiresApproval: true,
onViolation: 'escalate',
policy: { max_value: { field: 'amount_usd', limit: 500 },
rate_limit: { max: 20, period: 'daily' },
safe_result: { status: 'refund_pending_review' } } },
],
})
// reliant.agents.list() / get(id) / update(id, params) / delete(id)
Start a run
POST https://reliant.api.br/agent-runs
{
"agent": "support-agent", // id or name
"goal": "Resolve ticket #8421",
"external_id": "ticket-8421",
"max_usd": 2 // per-run override of the agent budget
}
{
"run_id": "clyy...",
"agent_id": "clxx...",
"status": "running",
"budget": { "max_steps": 40, "max_tokens": 200000, "max_usd": 2 }
}
If the agent's kill switch is on, this returns 423 and no run is created.
const run = await reliant.agentRun({
agent: 'support-agent',
goal: 'Resolve ticket #8421',
externalId: 'ticket-8421',
maxUsd: 2,
})
run.id // 'clyy...'
run.budget // { max_steps, max_tokens, max_usd }
run.halted // false — becomes true when the server says stop
run.haltReason // null | 'budget_steps' | 'budget_tokens' | 'budget_usd' | 'kill_switch'
Log a step
Call this for each llm_turn, each executed tool_call, and the final_output. Reliant creates approval_gate steps itself on escalation — you do not log those.
POST https://reliant.api.br/agent-runs/{run_id}/steps
{
"type": "llm_turn",
"output": [ ... ],
"tokens": 1840,
"cost_usd": 0.021,
"latency_ms": 900
}
| Field | Type | | Description |
|---|
| type | enum | required | llm_turn | tool_call | final_output | approval_gate |
| tool_name | string | optional | For tool_call steps. |
| input / output | any | optional | Recorded verbatim in the trace. |
| validation_errors / retry_log | any | optional | Your own retry bookkeeping, if any. |
| tokens / cost_usd | number | optional | Accrued into the run total and checked against max_tokens / max_usd. |
| latency_ms | number | optional | Wall time for the step. |
{
"ok": true,
"halt": false,
"halt_reason": null,
"budget_remaining": { "steps": 39, "tokens": 198160, "usd": 1.979 }
}
When halt is true, stop the loop — further logStep / guard-tool calls return 409. The SDK also flips run.halted / run.haltReason.
| halt_reason | | | Meaning |
|---|
| budget_steps | | | Step count reached max_steps. |
| budget_tokens | | | Accrued tokens reached max_tokens. |
| budget_usd | | | Accrued cost reached max_usd. |
| kill_switch | | | The agent's kill switch was turned on. |
Guard a tool call
Call this before dispatching any tool. On allow nothing is stored — you log the tool_call step after running it. On block / escalate the attempt is stored (as a tool_call or approval_gate step) and counts toward the step budget.
POST https://reliant.api.br/agent-runs/{run_id}/guard-tool
{
"tool_name": "issue_refund",
"args": { "order_id": "o_991", "amount_usd": 120 },
"context": { "confidence": 0.9 }
}
Order of checks
| # | | | Check |
|---|
| 1 | | | Kill switch — block + halt. |
| 2 | | | Schema — args validated against args_schema. Failure → block with validation_errors + retry_guidance. Unregistered tool → passes (tool_registered: false). |
| 3 | | | Policy — confidence_min, max_value, allowed_values, blocked_values, active_hours. Violation → block or escalate per on_violation. |
| 4 | | | Rate limit — per hourly/daily/monthly bucket. Over → block / escalate. |
| 5 | | | requires_approval — always escalate. |
Response
{
"decision": "allow",
"validated": true,
"tool_registered": true,
"validated_args": { "order_id": "o_991", "amount_usd": 120 },
"halt": false,
"halt_reason": null,
"budget_remaining": { "steps": 38, "tokens": 198160, "usd": 1.979 }
}
{
"decision": "block",
"validated": true,
"tool_registered": true,
"validation_errors": [ { "path": "/amount_usd", "message": "must be number", "keyword": "type", "params": {} } ],
"retry_guidance": "The arguments you produced for the tool \"issue_refund\" did not match its schema. ...",
"policy_rule": "max_value",
"policy_reason": "\"amount_usd\" (1200) exceeds the limit of 500",
"safe_result": { "status": "refund_pending_review" },
"step_index": 7,
"halt": false,
"halt_reason": null,
"budget_remaining": { "steps": 37, "tokens": 198160, "usd": 1.979 }
}
{
"decision": "escalate",
"validated": true,
"tool_registered": true,
"approval_id": "clzz...",
"policy_rule": "requires_approval",
"policy_reason": "tool requires human approval for every call",
"safe_result": { "status": "refund_pending_review" },
"step_index": 7,
"halt": false,
"halt_reason": null,
"budget_remaining": { "steps": 37, "tokens": 198160, "usd": 1.979 }
}
On block: do not run the tool; feed retry_guidance back to the model. On escalate: wait on approval_id (next section) before proceeding.
Policy fields (tools[].policy)
| Field | Shape | | Rule |
|---|
| confidence_min | number 0..1 | optional | context.confidence must be ≥ this. |
| max_value | { field, limit } | optional | args[field] must be a number ≤ limit (dot-path supported). |
| allowed_values | { field, values[] } | optional | args[field] must be one of values. |
| blocked_values | { field, values[] } | optional | args[field] must not be one of values. |
| active_hours | { start, end, days? } | optional | HH:MM server-local window; days is 0–6 (Sun–Sat). |
| rate_limit | { max, period } | optional | Max calls per hourly | daily | monthly bucket. |
| safe_result | any | optional | Echoed back on block / escalate so the agent has a safe value to use. |
Approvals
On an escalation Reliant creates an approval_gate step and a pending AgentApproval, and fires the signed webhook if the agent has one.
GET /agent-runs/{run_id}/approvals?status=pending
GET /agent-runs/{run_id}/approvals/{approval_id}
POST /agent-runs/{run_id}/approvals/{approval_id}/approve { "decided_by": "ana@acme.com" }
POST /agent-runs/{run_id}/approvals/{approval_id}/deny { "decided_by": "ana@acme.com" }
# project-wide inbox (what the dashboard uses)
GET /agent-approvals?status=pending&agent_id=...&limit=50
POST /agent-approvals/{approval_id}/approve
POST /agent-approvals/{approval_id}/deny
POST /agent-approvals/sweep # bulk-expire due approvals
{
"id": "clzz...",
"run_id": "clyy...",
"agent_id": "clxx...",
"step_index": 7,
"tool_name": "issue_refund",
"args": { "order_id": "o_991", "amount_usd": 1200 },
"reason": "\"amount_usd\" (1200) exceeds the limit of 500",
"status": "pending", // pending | approved | denied | expired
"decided_by": null,
"decided_at": null,
"expires_at": "2026-09-10T15:00:00.000Z",
"created_at": "2026-09-10T14:00:00.000Z"
}
Expiry is lazy — a due approval flips to expired on the next read, so waitForApproval resolves without a cron. The webhook payload is agent.approval_requested with header X-Reliant-Signature: sha256=<hmac of the raw body with webhook_secret>.
// in the loop — poll until resolved
const verdict = await run.waitForApproval(guard.approval_id, { pollMs: 3000, timeoutMs: 600_000 })
// verdict.status: 'approved' | 'denied' | 'timeout'
if (verdict.status === 'approved') {
const args = verdict.validated_args ?? guard.validated_args
// dispatch, then run.logStep({ type: 'tool_call', ... })
}
// out of band — resolve from your own tooling
const { approvals } = await reliant.listAgentApprovals({ status: 'pending' })
await reliant.resolveAgentApproval(approvalId, 'approve', 'ana@acme.com')
Framework adapters
The SDK ships wrappers so you don't hand-write the guard/dispatch/log dance. Both call guardTool + logStep for you.
import { guardAnthropicToolUses, wrapAISDKTools } from 'reliant-js'
// Anthropic — pass the content array from messages.create
const { toolResults, halted, blocked, pendingApprovals } = await guardAnthropicToolUses(
run,
resp.content,
{ dispatch: (name, args) => myTools[name](args), onEscalate: 'wait' },
)
messages.push({ role: 'user', content: toolResults })
if (halted) break
// Vercel AI SDK — wraps the tools object; each execute() self-guards
const guardedTools = wrapAISDKTools(myTools, run, { onEscalate: 'wait' })
await generateText({ model, tools: guardedTools, prompt })
onEscalate: 'wait' polls the approval and dispatches if approved; 'block' (default) returns the guidance immediately and leaves the approval_id for you to resolve out of band. Adapters do not emit llm_turn steps — add those yourself if you want them in the trace.
End a run
POST https://reliant.api.br/agent-runs/{run_id}/end
{ "status": "completed", "summary": "Refund issued" } // status: 'completed' | 'failed'
{
"id": "clyy...",
"status": "completed",
"halt_reason": null,
"steps": 12,
"tokens": 40213,
"usd": 0.48,
"started_at": "2026-09-10T14:00:00.000Z",
"ended_at": "2026-09-10T14:03:11.000Z"
}
end never clobbers a halt — if the run already stopped, it returns the existing summary unchanged.
Inspect runs
GET /agent-runs?agent_id=...&status=running&page=1&limit=20
GET /agent-runs/{run_id} # run + steps[] in order — the full trace
Listing is clipped to your plan's retention window. Each step in the detail response carries index, type, tool_name, input/output, validation_errors, policy_decision/policy_reason, and tokens/latency_ms/cost_usd.
Live stream
A WebSocket pushes run events as they happen (this is what the dashboard uses; there is no SDK helper — open a raw socket):
wss://reliant.api.br/agent-runs/stream?key=rel_...&agent_id=<optional>
# event types
connected | run.started | step | run.halted | run.completed | run.failed
approval.requested | approval.resolved
Durable checkpoint & resume
Push opaque loop state so a fresh process can recover a run after a crash. Reliant stores it verbatim and never executes anything.
PUT /agent-runs/{run_id}/checkpoint { "state": { ... }, "cursor": 12 }
GET /agent-runs/{run_id}/checkpoint
GET /agent-runs/{run_id}/resume # run status + checkpoint + any pending approval
await run.saveCheckpoint(myOpaqueState, cursor) // after each step or turn
// in a new process
const { run, status, checkpoint, pendingApproval } = await reliant.resumeAgentRun(runId)
if (status !== 'running') { /* already finished / halted */ }
else { /* rehydrate from checkpoint.state and keep going on the same handle */ }
Replay
A dry-run that answers “would this schema/policy change break past traffic?”. It re-validates every recorded tool_call and re-runs Agent Guard policy against a modified tool config. No LLM calls; no writes to the run — only the report is saved. llm_turn steps are not re-executed.
POST /agent-runs/{run_id}/replay
{
"agent_id": "clxx...", // optional — replay against a different agent's tools
"tools": [
{ "name": "issue_refund", "policy": { "max_value": { "field": "amount_usd", "limit": 100 } } }
]
}
GET /agent-runs/{run_id}/replays
GET /agent-replays/{replay_id}
{
"total_steps": 12,
"tool_calls_evaluated": 5,
"unchanged": 3,
"newly_blocked": 1,
"newly_allowed": 0,
"escalation_changes": 1,
"validation_now_failing": 0,
"skipped": 0
}
const { report } = await reliant.replayAgentRun(runId, {
tools: [{ name: 'issue_refund', policy: { max_value: { field: 'amount_usd', limit: 100 } } }],
})
// reliant.listAgentReplays(runId) / reliant.getAgentReplay(replayId)