guardrails (planned)
Status: planned, design draft. No
guardrailssurface exists on the ambientstackboneclient yet — this page sketches the intended shape so creators and reviewers can align before the PRD is written. Subject to change. For the safety tools that ship today, see What exists today below.
A future stackbone.guardrails surface will own the pre-flight and
post-flight checks around model calls, tool invocations and the work a
durable workflow performs. Right now creators hand-roll these as
if-statements around stackbone.ai.chat.completions.create(...), inside
a deep-agent tool, or before a
side-effect in a durable workflow step. Or they
skip them entirely. A platform surface would centralise the policies, make
them versioned and reviewable (the same way prompts are), and emit unified
violation events across both durable agent sessions and durable workflow
runs.
What exists today
The safety primitives that already ship cover two of the three boundaries a guardrail would, so reach for these until the surface lands:
- Tool-input validation, natively. Every LangChain tool you author with
tool(...)declares a Zodschema. LangChain validates the model's tool arguments against it before your tool function runs, so malformed input never reaches your code. A future guardrail "schema-validation" policy would extend this to model completions and HTTP responses, not replace it at the tool boundary. - Human-in-the-loop, durably. When you need a person to approve a
risky action, call
requestApproval()from@stackbone/sdk/workflow. It pauses the workflow run durably, writes a row to the approvals inbox, and races the human decision against a timeout. A guardrailreviewverdict (below) would defer to exactly this primitive.
import { z } from '@stackbone/sdk';
import { requestApproval } from '@stackbone/sdk/workflow';
// Inside a workflow body — the live pattern a `review` verdict would reuse.
const decision = await requestApproval({
token: input.approvalToken,
topic: 'high-risk-action',
payload: { summary: input.summary },
title: 'Approve this action',
timeout: '24h',
fallback: 'reject',
});
if (decision.status !== 'approved') {
return { performed: false, reason: decision.status };
}Why a guardrails surface would exist
Safety, compliance and quality checks share a common shape:
- Take a piece of text (user prompt, model completion, tool argument, tool result).
- Run one or more policies against it.
- Return a verdict —
allow,redact,block, orreview— with a reason. - Emit a violation event when the verdict is not
allow.
Without a single surface, every agent and every workflow re-implements
that wiring around its own model calls, tool boundaries and steps. With
it, the dashboard could show "this run was blocked because policy X
flagged Y" the same way it shows approvals from
requestApproval() today, and the platform could
roll out built-in policies (PII, prompt injection, schema validation)
without each agent re-vendoring them.
Planned API shape
The primitive is check(...), called from the ambient stackbone client
wherever a boundary needs a verdict: inside a deep-agent tool, or
inside a workflow "use step":
import { stackbone } from '@stackbone/sdk';
// Hypothetical: this surface does not exist yet.
const verdict = await stackbone.guardrails.check({
text: userInput,
// 'pre_tool' / 'post_tool' map to a deep-agent tool's call boundary.
stage: 'pre_llm', // 'pre_llm' | 'post_llm' | 'pre_tool' | 'post_tool'
policies: ['pii', 'prompt_injection', 'topic:finance'],
});
if (verdict.data?.action === 'redact') {
// verdict.data.text is the redacted version; verdict.data.spans
// lists what was replaced and why.
}The block and review actions short-circuit, and the idiom differs by
where you are:
Inside a deep-agent tool, a
blockverdictthrows. The runtime surfaces the failure on the session and the model never sees a forbidden result.if (verdict.data?.action === 'block') { throw new Error(`blocked_by_guardrail: ${verdict.data.reason}`); }Inside a durable workflow step, you short-circuit before the side-effect — return early, or hand a
reviewverdict torequestApproval()for a human gate.if (verdict.data?.action === 'review') { const decision = await requestApproval({ /* … */ }); if (decision.status !== 'approved') return { performed: false }; }
Policy identifiers are organization-scoped. The control plane owns the catalog: built-in policies for PII, profanity, prompt injection and schema validation, plus custom policies declared per organization.
A higher-level wrapper around the model client is on the table —
something like stackbone.guardrails.wrap(stackbone.ai, { policies }) —
but the primitive check(...) is the first call the SDK needs to expose.
Capability gate
A reserved capability identifier (likely guardrails.basic) will gate
the surface. Until the contract bump, callers will see not_implemented.
The capability identifier is not pinned yet — the PRD will lock it.
Open questions
- Where do policies live: in
stackbone.config.ts(workspace-wide), per agent, in the dashboard, or some combination? - Does
post_llmredaction return the redacted text outright, or the original plus a list of spans to redact so callers can decide? - How do creators share policy implementations across agents
(
@stackbone/guardrails-pii-style packages) vs. keep them organization- private? - Do guardrails emit violation events into the run timeline, into a
dedicated
guardrailsstream, or both?
These will be answered in the feature PRD. This page is the surface sketch only.
Where to go next
ai— the model client (stackbone.ai.chat.completions.create(...)) guardrails would wrap.approval— the live HITL surface areviewverdict defers to today.- Agents & sessions: deep-agent tools and the Zod
schemathat already validates tool input. - Workflows — durable steps, the other boundary guardrails would protect.
prompts— the catalog model (named-by-key + versioned) guardrail policy definitions would reuse.