Sponsio — Agent Safety Lifecycle Companion
SkillAI & modelsInstall, observe, tune, and enforce Sponsio: a runtime contract layer for LLM agents that blocks unsafe tool calls and scores output quality against declared rules. Use when the user wants to set up / add / install Sponsio, add guardrails or runtime safety to an LLM agent, generate or refine a sponsio.yaml, audit tool configurations for risks (data leaks, unguarded writes, missing confirmations), explain or review existing contracts, check what Sponsio would have blocked (`sponsio report`), move from observe to enforce mode, or debug why a contract is (or isn't) firing. Triggers on phrases like "set up sponsio", "add sponsio", "install sponsio", "add guardrails", "monitor my agent", "harden my agent", "audit my agent", "generate contracts", "explain my sponsio.yaml", "sponsio report", "flip to enforce", "false positive", "why is this rule firing".
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Sponsio — Agent Safety Lifecycle Companion skill
What this skill tells your AI
The instructions your AI receives, as published by sponsiolabs/sponsio in sponsio/skills/sponsio/SKILL.md and read by ahel’s review.
Sponsio is a Python/TypeScript runtime safety layer for LLM agents: it evaluates deterministic contracts against each tool call and can block (enforce) or just log (observe) violations. The engine is deterministic-only. This skill covers the full lifecycle — first-time setup, contract authoring/review, observe-mode tuning, and flipping to enforce — by orchestrating Sponsio's CLI and explaining its output in plain language.
This skill does NOT reimplement Sponsio's logic; it calls the CLI and interprets results.
When to use this skill
Dispatch by what the user is trying to do. Pick ONE workflow and follow it; do not run multiple workflows in one turn.
| User is… | → Workflow |
|---|---|
| Setting up Sponsio for the first time in a project ("add sponsio", "install sponsio", "add guardrails") | W1 — Initial setup (just dispatch sponsio init) |
| Handing you a codebase and asking "what could go wrong?" / wants a fresh contract file from scratch / has a policy doc to encode | W2 — Audit & refine |
| Authoring contracts for a Claude Code / OpenClaw plugin or a bare MCP server (input is a plugin manifest, not source code) | W2b — Plugin / MCP contracts |
| Tightening rules that apply to Task-spawned subagents (Cursor / Claude Code) — they lack user context and need stricter privileges than the main agent | W2c — Subagent privilege boundary |
| Tuning the IDE's OWN host-plugin library (Claude Code's Bash / Read / Write / MCP gating; Cursor likewise) — different from the user's project sponsio.yaml | hand off to the sponsio-claude-code:configure skill (or the cursor analog). Don't reimplement here. |
| Has Sponsio running in observe mode and wants to review violations, tune thresholds, silence false positives | W3 — Tune in observe |
| Ready to ship — wants to move from observe to enforce, needs regression confidence | W4 — Flip to enforce |
| Sponsio errored, a rule isn't firing when it should, a rule is firing when it shouldn't | W5 — Troubleshoot |
| Just ran their agent and asks "did anything slip through?" / wants NEW contracts proposed from a run's traces ("check my last run", self-evolving rulebook) | W6 — Discover missing contracts from traces |
Do NOT trigger for: general LLM-safety discussions not tied to a specific codebase; non-agent code review (linting, correctness).
Prerequisites (run silently before any workflow)
sponsio --version
- Not found → install:
pip install sponsio(orpip install -e ".[all]"from a local clone). - For
--llminference, check:OPENAI_API_KEY/ANTHROPIC_API_KEY/GEMINI_API_KEY/GOOGLE_API_KEY. Absent → still proceed; AST-based extraction and all of W3/W4/W5 work with zero keys.
Editing contract YAML — write rules by file
Sponsio contract YAMLs split into two trust zones with different write rules. Pick the right zone before any edit; the runtime's self-modify pack enforces the host-zone rules, but cross-zone slips are still a config-correctness bug we'd rather avoid up-front.
Zone A — project YAML (you may add additively)
Path: <project>/sponsio.yaml — the file sponsio onboard writes
into the user's repo. This evolves through every onboard / scan
cycle. Adding new contracts via Edit (extending
old_string) is the supported workflow.
Three legal write modes:
-
Add a new contract —
Editwithnew_stringextendingold_string(the invariant:old_string ⊆ new_string):old_string = the verbatim tail of the existing file ending at the last contract entry (or the last `contracts:` line if the list is empty) new_string = old_string + "\n - <new contract YAML block>" -
Tune an existing pack-shipped rule — never edit the rule directly; append a
customized:entry:customized: - match: { desc: "<the shipped rule's desc, exact>" } A: "<extra assumption that narrows when it fires>" # to relax # or args: [...] # to retune thresholds # or disabled: true # to silence (last resort) -
Run
sponsio scanfor bulk additions from code / policy, merges additively and writes atomically:sponsio scan <paths> -o ./sponsio.yaml --append
Zone B — host bucket + plugin bundle YAMLs (user-only — never write directly)
Paths:
~/.sponsio/plugins/{{HOST_BUCKET}}/sponsio.yaml(this host's runtime library)~/.sponsio/plugins/{{HOST_BUCKET_SUBAGENT}}/sponsio.yaml(this host's subagent library)~/.sponsio/plugins/<plugin-id>/sponsio.yaml(per-plugin / per-MCP-server bundle — github, filesystem, my-plugin, …)
These files govern your own future tool calls. The runtime
self-modify pack blocks every Edit / Write / MultiEdit you'd attempt
against them — for the host bucket because rewriting your own rules
is privilege escalation, and for the per-plugin bundles for the same
reason (they constrain the plugin tools you'll call later). The
{{HOST_BUCKET}} placeholders above are baked in at skill install
time (_host_cursor, _host_claude_code, _host_openclaw, …) so
each host only loses write access to its own bucket.
You must NOT use Edit, Write, MultiEdit, or Bash with
shell redirects (>, >>, tee, sed -i, cp, mv, rm, dd)
on any path under ~/.sponsio/plugins/. The legitimate update
paths are:
- CLI (you can run via
Bash):sponsio plugin install <name> # copy a fresh bundled starter sponsio plugin scan --apply # regenerate per-plugin bundle sponsio plugin show <name> # surface what's loaded - Hand-edit by the user in their text editor.
For customizations the user agreed to during a tuning conversation, do
NOT ghostwrite the YAML they should paste. Contract content in
Zone B has exactly four legitimate sources: bundle libraries
(sponsio plugin install), CLI extraction (sponsio plugin scan,
sponsio scan, sponsio onboard), the user's own keystrokes, and
customized: blocks the user authors themselves. An LLM-composed
snippet has none of those provenances — it's configuration with no
audit trail.
The flow:
- Restate the user's intent in plain English and identify the
shipped rule it affects.
sponsio plugin show <id>prints thedesc:of every loaded rule; quote a desc verbatim from that output if you need to refer to one — do NOT compose YAML around it. - Tell the user the file path and describe the change in words
("add a
customized:entry besidecontracts:whosematch.descmatches the rule you want to silence, withdisabled: true"). Point them at the existing pack's syntax for reference. Let them write the YAML themselves. - When they say they're done, run
sponsio validate --config ~/.sponsio/plugins/<id>/sponsio.yamland help debug if it doesn't parse.
Forbidden write modes (universal)
Writeon any contract YAML above — overwrites the whole file in one go; bypasses the additive evidence even when the result happens to be a superset.Editon a Zone-B path — denied by the self-modify pack regardless of additive intent.Editon a Zone-A path wherenew_stringdoes NOT containold_string— a modification or deletion masquerading as an edit; treat as forbidden.MultiEditon any contract YAML — same shape asWrite.Bashwith shell write operators (>,>>,tee,sed -i,cp,mv,rm,dd) targeting any of these paths — the runtime blocks Zone B; treat Zone A the same way.
Why this matters
The user's invariant: adding contracts is always allowed; modifying or deleting existing ones is not. Following this protocol makes that invariant easy to see at the diff level (old_string ⊆ new_string). When the user audits your edits later, "was anything removed or changed?" reduces to a string-containment check.
If you genuinely need to remove a rule
You don't. Use customized: ... disabled: true (an additive edit that silences the rule) or ask the user to delete it by hand. The agent never has authority to remove its own contracts.
Pattern generality — match operator intent, not demo data
When the operator's NL says "block public gists" or "cap files at 3", write the rule against the operator's literal intent. Do not infer file extensions, content types, naming conventions, or path structures from sample data, demo fixtures, or examples you've seen — unless the operator explicitly named them.
Concrete: a regex like (\.md"\s*:.*?){4,} matches only keys ending in .md, which means a 4-file gist of .json / .txt / .csv / extension-less keys passes freely. If the operator said "cap files at 3", the right form is (\".+?\"\s*:\s*\{){4,} — any 4+ keys. Same principle for path globs (/work/notes/.*\.md vs /work/notes/.+), arg_field_has value patterns, and match: selectors.
If the operator's intent IS demo-specific ("block any .md dump from this notes plugin"), keep the narrow pattern but record the assumption in the contract desc: so a later reviewer sees the constraint instead of inferring a bug.
The flip side — don't broaden past what the operator named
The operator's NL also has a verb scope. When a policy says "no destructive verbs against AWS" the agent must not emit \baws\s+ (matches every aws invocation including aws s3 ls / aws sts get-caller-identity). The regex must require both the provider and a destructive-verb token from the policy.
Concrete:
| Policy phrase | Wrong (overbroad) | Right (verb-anchored) |
|---|---|---|
| "no destructive AWS calls" | \baws\s+ | \baws\s+(rds|s3api|ec2)\s+(delete-|terminate-)\w+\b |
| "no destructive gcloud calls" | \bgcloud\s+ | \bgcloud\s+\w+\s+(delete|destroy|drain)\b |
| "no destructive Railway control plane" | api\.railway\.app | (curl|http|wget)[^|;&]*-X\s+DELETE[^|;&]*\bapi\.railway\.app\b |
| "no DROP TABLE" | DROP | \bDROP\s+TABLE\b |
The shipped packs already follow this convention (see sponsio:incident/cursor-railway-wipe). When you extract from a policy doc, mirror the pack style: every command-shape regex is (host or tool prefix) AND (destructive verb the operator named). Bare provider prefixes are false-positive factories — every routine aws s3 ls in a CI script becomes a violation.
If the operator named providers exhaustively in prose ("Railway, Fly, Render, Supabase, Vercel, Cloudflare, Heroku, AWS, GCP"), keep all named providers, each anchored to its own verb set. Do not collapse them into a single broad alternation that drops the verb anchor.
W1 — Initial setup
Goal: from "project has no Sponsio" to "agent runs under observe mode with a sane contract file".
The CLI now has a one-shot wizard that covers the common path —
detect framework + IDEs, ask which to install, dispatch
sponsio onboard / sponsio host install / sponsio skill install accordingly, then verify. W1 is just orchestrating
that wizard, not reimplementing it.
Steps
-
Run the wizard.
sponsio init # interactive TTY sponsio init --apply 'framework=<name>;ides=<ide>:<level>,...;mode=observe' # non-interactivePicks string format:
framework=<one of langgraph / langchain / crewai / openai / anthropic / claude_agent / openai_agents / google_adk / vercel_ai / mcp / none>.noneis "bare loop, genericguard.guard_before/afterwiring"; an emptyframework=skips the onboard step entirely.ides=<ide>:<level>,...per-IDE pick.<level>isnone/skill(drop SKILL.md only) /full(host hooks + SKILL.md, the canonical "protect this IDE" pick).mode=observe|enforce. Default observe; enforce flips are W4's job.
Use
sponsio init --plan '<picks>'first to surface the exact commands the wizard will run, especially when you're non-interactive. Surfacesponsio init's output to the user verbatim — the panel + preview + recap blocks are designed to be readable as-is. -
Patch the agent entry file (only when
framework≠"").sponsio onboard . --emit-context > /tmp/sponsio-onboard-context.jsonRead the JSON. Pick the entry file in this priority:
entry_file_candidateshas exactly one strong match.- Else dedupe
tool_inventory[*].filepath— one file, use it. - Else stop and ask the user.
If the file already imports
from sponsio.<adapter>(Python) orfrom "@sponsio/sdk"(TS), it's already wrapped — skip. Otherwise splicewrap_snippetfrom the JSON: imports + guard construction at the top, wrap site adapted to the file's actual idiom (canonicalcreate_react_agent(model, guard.wrap(tools))if present, else adapt — e.g.tools_by_name = guard.wrap(TOOLS).tools_by_namefor a name-keyed dispatch loop). Show the diff before writing. -
Verify.
sponsio doctor sponsio validate --config sponsio.yamlSurface every warning / fail line verbatim. If
sponsio doctorFAILED (not warned, failed), stop here — don't let the user run their agent thinking the install is healthy when it isn't. -
Explain observe mode explicitly: "Nothing is blocked on day 1. Every contract is still evaluated; violations are logged to
~/.sponsio/sessions/<agent_id>/*.jsonl. Usesponsio report --since 24hafter a day of real traffic to see what would have been blocked. When you're ready to flip, that's W4."
Beyond W1 — pointers
Onboarding is just install + wire + verify. These are SEPARATE workflows, not extensions of W1:
- Author tighter contracts from a policy doc / threat model / actually-scanned codebase → W2 (audit & refine).
- Tune host-plugin libraries (Bash / Read / Write / MCP gating
in this Claude Code or Cursor session) → invoke the
sponsio-claude-code:configureskill (or the cursor analog). That skill ownssponsio plugin scan/sponsio plugin append/ per-MCP-server library generation. W1 doesn't duplicate it. - Move from observe to enforce → W4.
- Something doesn't fire / fires wrong → W5.
Choosing the write target — Zone A vs Zone B
sponsio init's axes already encode the destination decision:
- Axis 1 (framework wrap, picked at non-empty / non-
none) → writes<project>/sponsio.yaml. This is Zone A — governs the LLM agent the user is wiring Sponsio INTO. You may Edit/Write this file;sponsio validate --config <path>after. - Axis 2 (per-IDE level=full) → writes
~/.sponsio/plugins/_host_<ide>/sponsio.yamlviasponsio host install. This is Zone B — governs the HOST agent (i.e. you, Claude Code / Cursor). Edit/Write/ MultiEdit are denied by the self-modify pack; the only way to extend it from inside the host issponsio plugin append.
If a user later asks you to add rules and the destination is ambiguous (project rules vs host-IDE rules), ask:
"These rules — should they govern (a) the LLM agent your project deploys (Zone A:
<project>/sponsio.yaml), or (b) my own tool calls inside this IDE (Zone B:~/.sponsio/plugins/_host_<ide>/ sponsio.yaml)?"
Two destinations need two files; conflating them silently breaks one or both.
Adding rules to Zone B (host bucket) — sponsio plugin append
When a Zone-B addition is the right call, you can't Edit/Write the host yaml directly. Use the append CLI:
# 1. Write the new rules to a staging file OUTSIDE Zone B (project
# root is fine). Never name it sponsio.yaml — collides with
# project-config.
sponsio plugin append --from <staging-path> --target <bucket> --dry-run
sponsio plugin append --from <staging-path> --target <bucket>
rm <staging-path> # delete after success
plugin append is structurally additive: it rejects anything
beyond contracts: entries — no customized:, no
disabled:, no desc collisions, no top-level keys. Tell the
user up front you'll be running this on their behalf so they're
not surprised when it appears.
Auto-selected packs
sponsio onboard uses simple, conservative heuristics:
| Pack | Auto-included when… | Notes |
|---|---|---|
sponsio:core/universal | Always | Empty stub, kept so existing include: lines don't error. |
sponsio:core/runaway | Framework runs a multi-step loop (langgraph/crewai/…) | token budget, delegation depth, loop detection; no LLM calls |
sponsio:capability/shell | A tool name matches {bash, shell, exec, execute, execute_command, run_command, run_shell, run_bash, terminal, subprocess} | Auto-fills tool_rename: if the user's tool name isn't the canonical exec |
sponsio:capability/filesystem | A tool name matches {read, read_file, open_file, write, write_file, edit, edit_file, apply_patch, patch_file, ...} | Auto-fills tool_rename: and workspace: |
sponsio:incident/openclaw | Never auto-included — opt-in only | CVE-derived rules for a specific vendor incident |
sponsio packs lists all shipped packs with live rule counts and include specs.
Do NOT
- Do NOT edit
sponsio/contracts/*.yamlinside the installed package — those are the shipped packs; they're read-only. Adjustments go in the user'ssponsio.yamlviacustomized:orcontracts:. - Do NOT flip
mode: enforceduring W1. The whole point of observe mode is to find false positives before they break production.
W2 — Audit & refine (from scratch, or deepen an existing yaml)
Goal: produce or improve a sponsio.yaml from code / policy docs / traces, and explain every contract in plain language.
Routing rules between layers
A policy document can mix two kinds of rules that land in different YAMLs. Misrouting them (writing host rules to the project file, or agent rules to the host bucket) means the rule loads but never fires.
Classify each rule before writing:
| Signal | → Project YAML (./sponsio.yaml) | → Host YAML (~/.sponsio/plugins/{{HOST_BUCKET}}/sponsio.yaml) | → Per-plugin YAML (~/.sponsio/plugins/<plugin-id>/sponsio.yaml) |
|---|---|---|---|
| Subject of the rule | "the loan agent must…", "the chatbot should…" | "Cursor / Claude Code / OpenClaw must not…" (host's own tools) | "the GitHub MCP server must not…" (a specific plugin/MCP) |
| Tool names mentioned | tool names from the user's project tool inventory | host primitives — Bash, Edit, Write, Read, exec | namespaced — mcp__github__*, mcp__filesystem__*, … |
| Path form | paths relative to the project (src/...) | absolute / ~/... paths outside the user's project | paths the named MCP server operates on |
| Domain language | AML, KYC, refund, PII, approval, faithfulness, hallucination | shell, git, file system, FS primitives | the plugin's domain (issues, PRs, repos for github; pages, paths for filesystem) |
| Trigger | the agent under development is the subject | the IDE / coding agent is the subject | a specific plugin or MCP server is the subject |
Process:
- Score each rule by the signals above.
- Write each rule to its target YAML using the additive-only protocol in the previous section.
- For genuinely ambiguous rules ("PII must not leak"), ask the user which agent the rule is about before picking a target.
- After writing per-plugin rules, run
sponsio plugin show <plugin-id>and surface the digest verbatim so the user sees what's now active.
The default failure mode is to dump everything into the file you were already editing. Cross-layer leakage is a worse user error than the extra clarification round.
Decide which sources to use
Sponsio contracts come from four sources, mixable in one yaml:
| # | Source | What it is | Command |
|---|---|---|---|
| 1 | Shipped packs | Pre-built, parameterized rule sets (sponsio:core/universal, sponsio:capability/shell, …) | Hand-add include: [sponsio:<spec>] — or W1's onboard does it automatically |
| 2 | Extraction | AST + optional LLM inference from your code / policy docs. For traces: read the trace yourself (session JSONL or an exported trace), propose candidates, and confirm each with sponsio check --trace | sponsio scan <paths> [--llm] [--policy <doc>]; trace mining is agent-driven + sponsio check --trace <file> --config <cand.yaml> --agent <id> |
| 3 | User input | An NL sentence or a structured dict the user writes | Hand-edit sponsio.yaml; validate a single NL string with sponsio validate "<NL>" |
| 4 | Pattern library | Deterministic parameterized templates (rate_limit, must_precede, arg_blacklist, …) — full list via sponsio patterns | sponsio patterns to browse; hand-write the YAML entry |
Match the user's input to the source(s):
- "Explain / review my
sponsio.yaml" → source 1 and/or others already applied; jump to "Explain contracts" below. - "Scan my agent code" → source 2, code-only.
- "We have a security policy document" → source 2, add
--policy <doc> --llm. - "I know the pattern I want but not the syntax" → source 4, then show them the yaml entry.
If ambiguous: ask ONE question — "(a) scan your code, or (b) extract from a policy document?"
Run scan (when extraction is needed)
You ARE the LLM. Use the agent-mediated path — Sponsio collects deterministic inputs, you do the cognition, Sponsio validates. No --llm / API key needed.
# 1. Sponsio dumps the deterministic inputs (AST tool inventory, policy
# docs, existing yaml) as JSON:
sponsio scan <PATHS> --agent <AGENT> [--policy <doc>] --emit-context
# 2. Sponsio prints the contract-authoring prompt template:
sponsio prompt scan
Read both, apply the prompt to the JSON in your own context, and produce contract YAML entries. Source-tag every entry you author with source: agent-extracted so later tooling can distinguish them from heuristic rules.
Decide the write target by intent BEFORE writing, using the W1 step-4 (A) vs (B) test. Write path differs by destination:
If destination is project YAML — <project>/sponsio.yaml
Write via Edit/Write, then validate:
sponsio validate --config ./sponsio.yaml
If destination is host bucket — ~/.sponsio/plugins/{{HOST_BUCKET}}/sponsio.yaml
Edit / Write / MultiEdit on this path are denied by Zone B's self-modify pack. Use sponsio plugin append — a structurally-additive CLI that writes through validated Python:
-
Write the YAML you produced (above) to a transient staging file outside Zone B (e.g.
<project>/.sponsio.staging.yamlor/tmp/sponsio-host-<timestamp>.yaml). Never name itsponsio.yaml— that collides with project-config and triggers the wrong-destination anti-pattern. -
Dry-run the merge to confirm what would land:
sponsio plugin append --from <staging-path> \ --target {{HOST_BUCKET}} --dry-run -
Apply. The CLI rejects anything beyond additive
contracts:entries — nocustomized:, nodisabled:, no desc collisions with existing rules, no top-levelruntime:/include:smuggling. It validates the merged file via the loader before declaring success.sponsio plugin append --from <staging-path> --target {{HOST_BUCKET}} -
Delete the staging file once
plugin appendreports success — keeping it around invites accidental re-apply.
Shortened here. Read the whole file on GitHub.
Signals
- GitHub stars
- 440
- Forks
- 25
- Last commit
- Sep 2026
ahel review
K1binfo
installs-packagesK4blow
destructive-scoped
Automated review, not a security audit. Ruleset v1+k2.
Advanced
- Catalog kind
- skill
- Gateway key
sponsio-sponsiolabs- Source
- github.com/sponsiolabs/sponsio