@flowdot.ai/guardian-agent
v0.2.2
Published
TypeScript reference implementation of the guardian-agent spec: a runtime supervisor for tool-using LLM agents (audit log, tool-permission scoping, HITL approval gates, emergency-stop).
Maintainers
Readme
@flowdot.ai/guardian-agent
TypeScript reference implementation of the guardian-agent spec. A runtime supervisor for tool-using LLM agents: tamper-evident audit log, tool-permission policy, HITL approval gates, emergency-stop, plus a runtime-safety layer (honeytokens, capability tripwires, per-class rate limits, two-key operator gates, heartbeat) and offline analysis tools.
Status: v0.1.0 on npm · tracks SPEC v0.5 · v0.10 feature milestone hit · interface stabilizing toward v1.0
npm install @flowdot.ai/guardian-agentNote on versioning: milestone labels in this README (v0.1, v0.2, …, v0.10) refer to feature milestones in the ROADMAP, not semver. The package's npm semver lives in
package.json. Until the public API freezes for v1.0, expect minor-version churn — pin a specific minor in production.
The Python reference implementation lives at flowdot-llc/guardian-agent. This repository is the parallel TypeScript implementation. Both conform to the same versioned spec; the spec is the canonical contract, not either implementation.
Why a second language
Python serves the research and evaluation ecosystem (LangChain, AutoGen, MCP Python clients, eval labs). TypeScript serves the production-runtime ecosystem — Node servers, Electron apps, TypeScript MCP clients, LangChain.js, and the broader JS agent tooling. The same spec, in both places, is how a single supervisory contract reaches both worlds.
Cross-language interop is real and intended:
- An audit log written by the Python implementation can be read and verified by the TypeScript implementation, and vice versa.
- A
permissions.yamlis honored identically by both. - A gate callback URL hosted by one can be invoked by the other.
- An
estoptriggered in one produces an audit event identical in structure to one triggered in the other.

What's included
The library bundles three concentric layers. Each is independently usable; together they form the canonical supervisor.

Trust foundation (v0.1 – v0.7):
- Audit log — hash-chained JSONL, optionally ed25519-signed. Every tool call gets a structured record.
guardian-verifyCLI confirms chain + signature integrity. - Tool-permission policy — HMAC-signed YAML policy with
once/session/forever/bannedscopes and glob-matched tool names. Model-awarewhenclauses (model.provider,model.id,attribution_path). - HITL approval gate — four reference adapters: CLI prompt, async webhook, programmatic callback, LiveKit data channel. Custom
GateOptionSetlets consumers define their own button sets. - Emergency stop —
EStopLocalfor single-process deployments,EStopHubmiddleware + poller for hub-coordinated deployments (HTTP 423 Locked).
Runtime safety layer (v0.8 – v0.9):
- External chain attestation (SPEC §11) — periodically publish chain heads to an external append-only store. Closes the "compromised runtime forges its own log" gap. Reference adapters:
httpAttestor,nullAttestor. Fail-soft on attestor outage. - Honeytokens (SPEC §12) — consumer-supplied value patterns + phantom tool names. Zero false positives by construction. Library ships no default tokens.
- Capability tags + Yellow-line tripwires (SPEC §13) — tag tools with classes (
credential,network-egress,write, ...) and define combination rules. v0.x ships Yellow-only (audit-row, no behavior change); Red-line auto-stop ships after real-surface telemetry calibrates thresholds. - Per-capability rate limits (SPEC §14) —
MultiRateLimiterwith conservative defaults (credential=2/s, delete=1/s, network-egress=5/s). - Two-key operator authorization (SPEC §15) — suspend dispatch pending fresh operator confirmation. Library defines the suspend/resume contract; consumers wire the transport.
- Dead-man's heartbeat (SPEC §16) — soft warn + hard E-stop on missed liveness signals. Opt-in (default OFF).
Offline analysis tools (v0.10):
guardian-baseline(SPEC §17) — descriptive statistics on audit streams.--checkflags σ-deviations. Reports only; not a runtime tripwire.guardian-correlator(SPEC §18) — overlapping sessions + args-hash collisions + sequence-similarity matches across multiple audit logs for the same agent_id.
Demos
Three minimal demos show the supervisor enforcing a single primitive end-to-end. Each one is a deterministic tsx script — anyone with this repo can reproduce these on their machine by cloning and running npm install && npm run demo:N.
Demo 1 — Tamper-evident audit log
Hash-chained + ed25519-signed audit records detect any post-hoc edit, down to a single byte. (examples/demo/demo-1-tamper.ts)

Demo 2 — HITL approval gate
A tool tagged requiresOperatorConfirmation: true suspends dispatch and asks a configured operatorGate for a decision. Denial means the tool body never executes. (examples/demo/demo-2-gate.ts)

Demo 3 — Honeytoken catches exfiltration
Planted tokens that never appear in real workflows. Any tool call whose args contain one is, by construction, an attack. Hit triggers x_honeytoken_triggered + emergency-stop, and the E-stop is sticky — subsequent calls also throw. (examples/demo/demo-3-honeytoken.ts)

Run them yourself:
npm install
npm run demo:1 # tamper-evident audit log
npm run demo:2 # HITL approval gate
npm run demo:3 # honeytoken catches exfiltrationQuickstart
import {
AuditLogWriter,
EStopLocal,
GuardianRuntime,
} from '@flowdot.ai/guardian-agent';
const audit = new AuditLogWriter({
path: './audit.jsonl',
agentId: 'agent_demo',
sessionId: 'sess_quickstart',
});
const estop = new EStopLocal({ audit });
const runtime = new GuardianRuntime({
agentId: 'agent_demo',
sessionId: 'sess_quickstart',
audit,
estop,
});
// Wrap any tool function — MCP, LangChain.js, native async fn:
const listAccounts = runtime.tool(
async (broker: string) => [
{ id: 'acct_001', broker, balanceUsd: 12_345.67 },
],
{ name: 'list_accounts', capabilities: ['read'] },
);
// Your agent code calls these as normal. The runtime intercepts every call,
// records tool_call → policy_check → tool_result in the audit log.
const accounts = await listAccounts('schwab');
// Hit the kill switch from anywhere — another async context, a signal, an
// HTTP endpoint:
await estop.press({ reason: 'operator manual halt', initiator: 'operator' });
// Clean shutdown — flushes audit, attestation if configured:
await runtime.close();Adding the v0.8 safety layer
import {
AuditLogWriter,
EStopLocal,
GuardianRuntime,
httpAttestor,
defineHoneytokenSet,
} from '@flowdot.ai/guardian-agent';
const audit = new AuditLogWriter({
path: './audit.jsonl',
agentId: 'agent_demo',
sessionId: 'sess_quickstart',
// v0.8: external attestation
attestor: httpAttestor({ url: 'https://attestor.example/v1/heads' }),
attestEvery: 100,
});
const estop = new EStopLocal({ audit });
const runtime = new GuardianRuntime({
agentId: 'agent_demo',
sessionId: 'sess_quickstart',
audit,
estop,
// v0.8: honeytokens
honeytokens: defineHoneytokenSet('production', [
{ id: 'fake-aws', pattern: /AKIA[0-9A-Z]{16}/ },
{ id: 'recovery-key', value: 'fd_recovery_canary_REPLACE_ME' },
], ['delete_account_unsafe']),
// v0.8: capability rules (Yellow-only)
capabilityRules: [
{
id: 'exfil-shape',
combination: ['credential', 'network-egress', 'write'],
window_ms: 60_000,
level: 'yellow',
},
],
});v0.9: operator confirmation + heartbeat
import {
GuardianRuntime,
callbackOperatorGate,
HeartbeatMonitor,
} from '@flowdot.ai/guardian-agent';
const runtime = new GuardianRuntime({
agentId: 'agent_demo',
sessionId: 'sess_quickstart',
audit,
estop,
operatorGate: callbackOperatorGate(async (req) => {
// Show req to a real human (UI modal, IPC, webhook) and return their decision.
const approved = await operatorUI.prompt(req);
return { decision: approved ? 'approved' : 'denied', operator_id: 'alice' };
}),
operatorTimeoutMs: 5 * 60_000,
});
const sensitiveTool = runtime.tool(
async () => doDangerousThing(),
{
name: 'wire_transfer',
capabilities: ['network-egress', 'credential'],
requiresOperatorConfirmation: true,
operatorConfirmationReason: 'sensitive_action',
},
);
// Heartbeat — opt-in. Surface MUST call heartbeat() from its main loop.
const heartbeat = new HeartbeatMonitor({
softMs: 30_000,
hardMs: 90_000,
audit,
estop,
});
heartbeat.start();
setInterval(() => heartbeat.heartbeat(), 10_000);Offline analysis
# Produce a per-agent_id statistical baseline
node dist/cli/guardian-baseline.js ~/.flowdot/audit/cli.jsonl
# Check a new session against the saved baseline
node dist/cli/guardian-baseline.js ~/.flowdot/audit/cli.jsonl --check --sigma 3
# Cross-surface correlation
node dist/cli/guardian-correlator.js \
~/.flowdot/audit/cli.jsonl:cli \
~/.flowdot/audit/mcp.jsonl:mcp \
--out ~/.flowdot/audit/correlations.jsonlSee examples/quickstart.ts for a runnable version.
What it is NOT
It is deliberately small. Not an agent harness. Not a platform. Not a workflow builder. Not an observability dashboard. Not a model evaluation suite. It is the supervisor primitive only.
Some things the library deliberately does NOT do:
- Ship default honeytokens. Library shipping plausible-looking fake credentials gets picked up by secret scanners + creates support load. Consumers register their own.
- Promote Red-line capability rules without telemetry. Yellow-only until real-surface data shows zero organic fires.
- Use baselines as runtime tripwires. Statistical anomaly detection is descriptive output, not a gate. Operator decides what to do.
- Reason about agent intent. Every primitive is a deterministic predicate over inputs.
Relationship to FlowDot
FlowDot's commercial platform — hub, CLI, native Electron app, mobile, MCP server — uses this library directly. FlowDot's flowdot-cli and mcp-server supervisors are thin per-surface glue around the library's GuardianRuntime + AuditLogWriter + EStopLocal. The runtime-safety layer (attestation / honeytokens / capability tripwires / two-key / heartbeat) is wired through both surfaces.
The library itself is independent. Other Node-shaped agent projects can adopt the same supervisor primitives without depending on FlowDot's commercial stack.
Project status & roadmap
Pre-alpha. Releases track the Python implementation milestone-for-milestone:
- v0.1 – v0.7 ✅ Audit log + signatures + policy + gates + estop + model-aware policy.
- v0.8 ✅ External attestation, honeytokens, capability tags + Yellow-line, per-capability rate limits.
- v0.9 ✅ Two-key operator auth, dead-man's heartbeat.
- v0.10 ✅ Offline
guardian-baseline+guardian-correlatortools. - v0.11+ — Red-line capability auto-stop (after Yellow telemetry calibration). Python port. Cross-language conformance corpus.
- v1.0 — Stable API, conformance suite in both languages, at least one production deployment outside FlowDot, published red-team study.
Full plan: ROADMAP.md. Canonical spec: SPEC.md.
Verification + testing posture
- 539 tests passing, 100% line + branch + function coverage on the library.
- Negative-corpus harness replays real production audit logs (
~/.flowdot/audit/{cli,mcp}.jsonl) through every safety detector at default thresholds; required outcome is zero false positives, and the bar is met. - No false E-stops, ever is a hard rule. Any mechanism that could E-stop a session ships with thresholds calibrated against real-workload data.
License
AGPL-3.0-or-later. See LICENSE.
Dual licensing. FlowDot LLC, as sole copyright holder of this code, also licenses it under proprietary terms for use inside its own commercial products (@flowdot.ai/cli, @flowdot.ai/mcp-server, etc.). This is the standard open-core arrangement and does not affect downstream users — your obligations under AGPL-3.0 are exactly as written in LICENSE. If you want a commercial license for your own (non-FlowDot) use, contact [email protected].
Citation
Mousseau, E. (2026). @flowdot.ai/guardian-agent: TypeScript reference
implementation of the guardian-agent spec. v0.10.
https://github.com/flowdot-llc/guardian-agent-ts