-
Notifications
You must be signed in to change notification settings - Fork 28
Expand file tree
/
Copy pathllms-full.txt
More file actions
3870 lines (2782 loc) · 174 KB
/
Copy pathllms-full.txt
File metadata and controls
3870 lines (2782 loc) · 174 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# Sponsio — Full Documentation Dump
This file is the concatenation of every public-facing doc in the
repository, produced for LLM context ingestion. Canonical sources
live under `docs/` in the repo root. Each section starts with a
machine-readable `<!-- FILE: path -->` separator so downstream
tools can split if needed.
<!-- ====================================================================== -->
<!-- FILE: README.md -->
<!-- ====================================================================== -->
<p align="right">
<b>English</b> ·
<a href="./README.zh-CN.md">简体中文</a> ·
<a href="./README.ja.md">日本語</a>
</p>

<p align="center">
<a href="https://opensource.org/licenses/Apache-2.0"><img src="https://img.shields.io/badge/License-Apache%202.0-orange.svg" alt="License"></a>
<a href="https://pypi.org/project/sponsio/"><img src="https://img.shields.io/badge/install-pip%20install%20sponsio-blue?logo=python&logoColor=white" alt="Install from PyPI"></a>
<a href="https://sponsio.dev"><img src="https://img.shields.io/badge/Visit-sponsio.dev-181818?logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjI4MyA3NjMgMzczIDM3MyI%2bPGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwyMDQ4KSBzY2FsZSgwLjEsLTAuMSkiIGZpbGw9IiNGRkZGRkYiPjxwYXRoIGQ9Ik01MDEwIDEyNTAxIGMtNTggLTkgLTE4NyAtNDEgLTI2NyAtNjYgLTI2IC05IC05OSAtNDEgLTE2MCAtNzEgLTM1NCAtMTc0IC02MTMgLTQ3NiAtNzM2IC04NTkgLTQzIC0xMzMgLTY0IC0yNTEgLTczIC00MDcgbC03IC0xMTggLTQ2MiAwIC00NjMgMCAtNiAtMjIgYy0zIC0xMyAtMyAtNjYgMCAtMTE4IDE2IC0yODQgMTA2IC01NTYgMjYwIC03ODggMTEzIC0xNjggMzI0IC0zNTYgNTE2IC00NjAgMjcyIC0xNDcgNjM3IC0xOTAgOTY4IC0xMTUgMjM2IDUzIDQ1NiAxNzggNjQwIDM2MyAyNzIgMjczIDQxMyA2MTEgNDIzIDEwMjAgbDMgMTE1IDQ1NSA1IDQ1NCA1IDMgNDUgYzQgNDcgLTEyIDIwNyAtMjkgMzAwIC0xMDcgNTkyIC01MjMgMTAzMSAtMTA5NCAxMTU3IC03OSAxNyAtMzQxIDI2IC00MjUgMTR6IG0zMjAgLTk2MCBjNzMgLTI3IDE2MiAtOTkgMjA1IC0xNjQgNTggLTg3IDEwNCAtMjM5IDEwNSAtMzQ1IGwwIC01MiAtNDU3IDIgLTQ1OCAzIC0zIDQ4IGMtNSA3MyAyNCAyMDQgNjAgMjc3IDYxIDExOSAxOTEgMjI1IDMxMCAyNTAgNjQgMTMgMTc2IDUgMjM4IC0xOXogbS02MTIgLTY0MSBjMTMgLTI5NSAtMTkxIC01MjAgLTQ3MCAtNTIwIC0yMTcgMCAtMzkzIDE0NCAtNDUzIDM3MSAtMTUgNTUgLTIwIDIxMCAtOCAyMjIgMyA0IDIxNCA2IDQ2NyA1IGw0NjEgLTMgMyAtNzV6Ii8%2bPC9nPjwvc3ZnPg==&logoColor=white&labelColor=555555" alt="Visit sponsio.dev"></a>
</p>
<p align="center">
<a href="https://x.com/sponsiolabs"><img src="https://img.shields.io/badge/Follow%20on%20X-000000?logo=x&logoColor=white" alt="Follow on X"></a>
<a href="https://www.linkedin.com/company/sponsio-labs/"><img src="https://img.shields.io/badge/Follow%20on%20LinkedIn-0A66C2?logo=linkedin&logoColor=white" alt="Follow on LinkedIn"></a>
<a href="https://discord.gg/s8TfPnZWUm"><img src="https://img.shields.io/badge/Join%20our%20Discord-5865F2?logo=discord&logoColor=white" alt="Join our Discord"></a>
</p>
# Sponsio
<p align="center">
<img src="https://raw.githubusercontent.com/SponsioLabs/Sponsio/main/assets/sponsio-comparison-freeze.png" alt="Same coding agent under a declared code freeze. Without Sponsio it drops the prod users table, back-fills fabricated rows, and files a status report that hides the damage. With Sponsio the first destructive SQL is blocked pre-execution: 35 checks, 100% deterministic, 0 LLM calls, p50 13µs." width="900">
</p>
Sponsio provides deterministic contracts for agent procedures over time, enforced in under 0.01 ms with zero LLM cost at runtime. Works with LangChain, Claude Agent, OpenAI Agents, Google ADK, CrewAI, Vercel AI, MCP, or any custom tool-calling loop, in Python or TypeScript.
> An **agent contract** is a runtime rule that is checked at every agent action, [backed by formal methods](docs/concepts/formal-methods.md).
> **v0.2.0a3 alpha is out.** `pip install --pre sponsio==0.2.0a3`. Closes a `redirect_to_safe` fail-open bug in non-LangGraph adapters (the unsafe call was running anyway), brings TS `Eq` semantics to Python parity for composite values, and adds Cloudflare Workers compatibility. **Upgrade recommended if you are on 0.2.0a2.** See the [v0.2.0a3 release notes](docs/release-notes/v0.2.0a3.md).
---
## How Sponsio works
<p align="center">
<img src="https://raw.githubusercontent.com/SponsioLabs/Sponsio/main/assets/sponsio-architecture.png" alt="Sponsio architecture: Agent Flow + (Natural Language + Pattern Library) compile into Contracts (Assumption → Enforcement), enforced by a Fuzzy LTL Monitor (deterministic + stochastic) that decides Pass / Block · Warn · Escalate / Redirect for every function call, with full audit trail logs feeding back to the agent." width="900">
</p>
On [ODCV-Bench](https://github.com/McGill-DMaS/ODCV-Bench) (12 frontier LLMs × 80 trajectories), unguarded models cheat in 11.5%–66.7% of runs. **With Sponsio, 95.6% of misalignment is avoided on average; 24/36 high-risk scenarios at 100%.** On the `Financial-Audit-Fraud-Finding` scenario, frontier models commit fraud in 16/24 trials; **Sponsio blocks 18/19**. On RedCode-Exec (1,410 cases), Sponsio reaches **92% combined** (bash 95% · python 90%) across a 60-file clean-code audit.
The logic checker takes p50 **0.139 ms** per contract, **5,000×–60,000× faster than any LLM-as-judge guardrail** (50–800 ms per check), with zero LLM cost in the hot path. p99 stays under 1.04 ms across every measured workload.
See the [full benchmark methodology and per-model breakdown](docs/reference/benchmarks.md), [how Sponsio compares against prompt filters, output validators, LLM-as-judge, and sandboxing](docs/why.md), or dive into the [architecture](docs/concepts/architecture.md) and [formal methods primer](docs/concepts/formal-methods.md).
---
## Quick start
A single prompt or a 2-line CLI command gets you onboarded.
**Paste into Claude Code / Codex / Cursor.** The agent walks the full onboarding flow:
<p align="center">
<a href="docs/getting-started/onboard-prompt.md#python-project"><img src="https://img.shields.io/badge/One--shot%20prompt-Python-3776AB?logo=python&logoColor=white&labelColor=555555" alt="One-shot prompt: Python"></a>
<a href="docs/getting-started/onboard-prompt.md#typescript-project"><img src="https://img.shields.io/badge/One--shot%20prompt-TypeScript-3178C6?logo=typescript&logoColor=white&labelColor=555555" alt="One-shot prompt: TypeScript"></a>
</p>
**Or run the CLI yourself**:
```bash
pip install sponsio # or: npm install -D @sponsio/sdk
sponsio init . # interactive wizard: detects framework, IDE hosts, observe vs enforce
```
The wizard auto-detects your framework and prints the right wrap snippet. For manual wiring, see [all supported integrations](docs/integrations/index.md). [OpenClaw users](docs/integrations/openclaw.md) get bundled ClawHavoc and CVE-2026-25253 coverage out of the box. For config reference, observe → enforce flip, and CI wiring, see the [full walkthrough](QUICKSTART.md).
**Drafting contracts from natural language.** `sponsio validate "<rule in plain English>"` turns a plain-English rule into a contract you can read back. Treat the output as a starting draft to review and adjust before you enforce. The determinism is in how contracts are *enforced* at runtime, not in how they're drafted.
---
## Contract Library
Sixteen **contract bundles** ship out of the box, organized by tier (always-on / per-tool / per-incident). Each bundle is a YAML pack composed from Sponsio's deterministic patterns. Drop one into `sponsio.yaml` and your agent is guarded against a known failure class in one line, with no per-contract authoring.
```yaml
# sponsio.yaml: one-line bundle inclusion
agents:
my_agent:
workspace: "/srv/my-bot"
include:
- sponsio:core/universal # always-on
- sponsio:capability/shell # if your agent runs commands
- sponsio:capability/filesystem # if your agent touches files
```
See the [full bundle reference](docs/reference/contract-lib.md) for all 16 bundles, or the [46 underlying patterns](docs/reference/patterns.md) for the primitives they compose. Want a bundle for your agent type? That is currently the highest-leverage way to contribute. [Open an issue](https://github.com/SponsioLabs/Sponsio/issues/new) with your incident, CVE, or pattern.
---
## Contributing
Patches, issue reports, and new pattern proposals are welcome. Start with [CONTRIBUTING.md](CONTRIBUTING.md). Sponsio's threat model draws on public security research; e.g. Simon Willison's ["Lethal Trifecta"](https://simonwillison.net/2025/Jun/16/the-lethal-trifecta/) shaped our [multi-tool composition contracts](sponsio/contracts/incident/mcp-composition.yaml). Have a threat model we should defend against? [Open an issue](https://github.com/SponsioLabs/Sponsio/issues/new).
---
## License
Apache 2.0 ([LICENSE](LICENSE)).
*AI agents reading this repo: [`llms.txt`](llms.txt) lists canonical doc paths; [`llms-full.txt`](llms-full.txt) is the concatenated full context dump.*
<!-- ====================================================================== -->
<!-- FILE: QUICKSTART.md -->
<!-- ====================================================================== -->
# Quickstart
Get Sponsio blocking an unsafe tool call in under a minute. No API key, no framework SDK, no Docker.
## 1. Install
```bash
pip install sponsio
```
Optional extras (all pure Python):
```bash
pip install "sponsio[all]" # yaml + llm + otel
```
## 2. See a contract fire
Four recorded unsafe-agent trajectories ship in the wheel. Replay one:
```bash
sponsio demo --scenario wire --fast
```
You'll see an accounts-payable agent try to wire $847k to an unverified vendor, and Sponsio block it on three fronts at once:
```text
━━━ ◒◓ sponsio ━━━━━━━━━━━━━━━━━━━━━━━━━━
▎ contract · ap_copilot
▎ single wire capped at $50k
▎ enforce ▸ wire_transfer.amount must be in range [0, 50000]
▎ contract · ap_copilot
▎ compliance_approve must precede wire_transfer
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-> wire_transfer(to='Acme Logistics LLC', amount=847000, ...)
✗ amount must be in range [0, 50000] — VIOLATED → blocked
✗ compliance_approve must precede wire_transfer — VIOLATED → blocked
```
Other scenarios:
```bash
sponsio demo --scenario cleanup # Claude Code agent deletes .env + .git/
sponsio demo --scenario backup # SRE cost-optimizer deletes prod DR backups
sponsio demo --scenario freeze # Replit-style code-freeze violation + coverup
sponsio demo --scenario wire --no-guard # same trajectory without contracts
```
## 3. Wire it into your project
One command. Detects framework, writes `sponsio.yaml` in observe mode, runs `sponsio doctor`, prints a 2-line patch:
```bash
sponsio init .
```
Typical output:
```text
· framework: langgraph (found 1 langgraph import in agent.py)
· provider: none (no provider credentials detected)
· starter-pack: +5 contracts from name-heuristic safety rules
· packs: +2 auto-selected (capability/shell, capability/filesystem)
· wrote sponsio.yaml
· running doctor checks…
✓ sponsio.yaml
tools: 2
contracts: 17
mode: observe
framework: langgraph
doctor: 8/9 ok, 1 warn
Add this to your agent entry point:
from sponsio.langgraph import Sponsio
guard = Sponsio(config="sponsio.yaml", agent_id="agent")
agent = create_react_agent(model, guard.wrap(tools))
```
Without an LLM key, `init` still ships a name-heuristic starter plus capability packs (shell / filesystem / credentials / …) with deterministic rules — no LLM calls. Run `sponsio packs` for the full list with rule counts.
For TypeScript, install `@sponsio/sdk` and run `npx sponsio init .`. Same yaml output, same `Sponsio({ config: "sponsio.yaml" })` API.
## 4. Run and observe
`sponsio.yaml` starts in observe mode. Every contract evaluates, nothing blocks. Would-have-blocked decisions land in `~/.sponsio/sessions/<agent_id>/*.jsonl`.
After exercising the agent, review what would have blocked:
```bash
sponsio report --agent agent --since 24h
```
Pure-OSS live stream:
```bash
sponsio host trace --follow
```
## 5. Flip to enforce
When the report is clean:
```bash
export SPONSIO_MODE=enforce # no code change
```
Or bake it into yaml:
```yaml
runtime:
mode: enforce
```
Precedence: explicit ctor arg > env var > yaml > default.
## Troubleshooting
```bash
sponsio doctor # install + config + wiring
sponsio validate --config sponsio.yaml # parse + structural checks
sponsio check --trace trace.json --config sponsio.yaml --agent agent
```
## Next
- [First contract](docs/getting-started/first-contract.md): write your own rule.
- [Integrations](docs/integrations/index.md): plug into LangGraph, CrewAI, OpenAI Agents, and others.
- [Config reference](docs/reference/config-yaml.md): full `sponsio.yaml` schema.
- [CLI reference](docs/reference/cli.md): every command and flag.
<!-- ====================================================================== -->
<!-- FILE: docs/index.md -->
<!-- ====================================================================== -->
---
title: Sponsio documentation
description: Runtime contracts for LLM agents. Install, integrate, reference.
---
# Sponsio documentation
Sponsio is a runtime contract layer for LLM agents. It sits at the action boundary, blocks unsafe tool calls before they fire, and ships every verdict to your observability stack.
If you have never run Sponsio before, start here:
```bash
pip install sponsio
sponsio init .
```
Then go to the [Quickstart](getting-started/quickstart.md).
## Sections
- **[Getting started](getting-started/install.md)**: install, run your first guarded agent, write your first contract. Includes paste-ready [IDE-agent prompts](getting-started/onboard-prompt.md) for Claude Code / Cursor / Codex driven setup.
- **[Concepts](concepts/overview.md)**: what contracts are, how the runtime evaluates them, the LTL (linear temporal logic) backbone, OWASP coverage.
- **[Integrations](integrations/index.md)**: drop-in adapters for LangGraph, Claude Agent, OpenAI Agents, CrewAI, Vercel AI, MCP, and others.
- **[Guides](guides/onboarding.md)**: task-oriented walkthroughs. Tuning, observe-vs-enforce, contract sources, reporting, FAQ.
- **[Plugins](plugins.md)**: gate an entire Claude Code or OpenClaw session without code changes.
- **[Reference](reference/cli.md)**: CLI, `sponsio.yaml` schema, pattern catalog, observability schema, benchmarks, OSS / Cloud boundary.
For LLM assistants, a flat link map is at [`llms.txt`](../llms.txt).
<!-- ====================================================================== -->
<!-- FILE: docs/getting-started/install.md -->
<!-- ====================================================================== -->
---
title: Install
description: Install Sponsio, pick the right extras for your stack, and verify the install.
---
# Install
Sponsio is a pure-Python package with zero required dependencies. The core engine installs in seconds.
```bash
pip install sponsio
```
Verify:
```bash
sponsio --version
sponsio doctor
```
---
## Choosing extras
Extras are optional dependency bundles. Pick what matches your stack; none of them are required to run contracts.
| Extra | What it installs | When to pick it |
|---|---|---|
| `sponsio[yaml]` | `pyyaml` | Loading contracts from `sponsio.yaml` |
| `sponsio[llm]` | provider SDKs (OpenAI, Anthropic, Gemini) | `sponsio scan --llm` and sto judge calls |
| `sponsio[otel]` | OpenTelemetry exporters | Streaming traces to your observability stack |
| `sponsio[langgraph]` | `langgraph`, `langchain-core` | LangGraph integration |
| `sponsio[claude-agent]` | `claude-agent-sdk` | Claude Agent SDK integration |
| `sponsio[openai]` | `openai` | OpenAI SDK integration |
| `sponsio[crewai]` | `crewai` | CrewAI integration |
| `sponsio[google-adk]` | `google-adk` | Google ADK integration |
| `sponsio[vercel-ai]` | `vercel-ai` | Vercel AI SDK (Python) integration |
| `sponsio[mcp]` | `mcp` | MCP proxy integration |
| `sponsio[all]` | everything above | Kitchen-sink install |
```bash
pip install "sponsio[all]"
```
---
## Python support
Python 3.10 and newer. Older versions are not tested.
## TypeScript
The TypeScript deterministic engine ships separately:
```bash
npm install @sponsio/sdk
```
See [TypeScript integrations](../integrations/index.md#typescript) for framework bindings. The Python and TS engines share the same LTL core. They produce identical block/allow decisions over the same trace.
---
## Provider credentials
Sponsio reads API keys from environment variables only. No config file, no keyring.
| Provider | Env var |
|---|---|
| OpenAI | `OPENAI_API_KEY` (optional: `OPENAI_BASE_URL` for Ollama, OpenRouter, DeepSeek, Together, Groq, vLLM, Azure) |
| Anthropic | `ANTHROPIC_API_KEY` |
| Gemini | `GEMINI_API_KEY` or `GOOGLE_API_KEY` |
`sponsio scan --llm` auto-detects the provider from whichever env var is set. Specify `--provider` to override.
---
## Verifying the install
```bash
sponsio doctor
```
Runs a battery of checks: config is valid, framework is detected, provider credentials are reachable, atoms referenced in contracts are registered. Exits non-zero if anything fails.
```bash
sponsio demo --scenario wire --fast
```
Replays a packaged unsafe-agent trajectory locally, no API key, no framework SDK. Sponsio blocks an unverified wire transfer mid-flow. If you see the block, install is working.
---
## Next
- [Quickstart](quickstart.md): block an unsafe tool call in 60 seconds.
- [First contract](first-contract.md): write a custom contract against your own agent.
- [Integrations](../integrations/index.md): plug Sponsio into your framework.
<!-- ====================================================================== -->
<!-- FILE: docs/getting-started/first-contract.md -->
<!-- ====================================================================== -->
---
title: Write your first contract
description: Write, wire, and test a custom contract against an agent you control.
---
# Write your first contract
This walkthrough goes from an empty project to a working contract that blocks an unsafe tool call. By the end you will have a `sponsio.yaml`, a wired guard, and a passing test.
Prereqs: Python 3.10+ and an agent framework. We use LangGraph in the examples below; any framework works. See [Integrations](../integrations/index.md).
---
## 1. Install
```bash
pip install "sponsio[langgraph]"
```
## 2. A minimal agent
Start with a small agent that exposes two tools, a policy check and a refund issuer. This is our running example.
```python
# agent.py
from langgraph.prebuilt import create_react_agent
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
@tool
def check_policy(customer_id: str) -> str:
"""Check the customer's refund policy."""
return f"customer {customer_id} is eligible for up to $200"
@tool
def issue_refund(customer_id: str, amount: float) -> str:
"""Issue a refund to the customer."""
return f"refunded ${amount} to {customer_id}"
model = ChatOpenAI(model="gpt-4o-mini")
agent = create_react_agent(model, tools=[check_policy, issue_refund])
```
This agent can issue refunds without ever checking the policy. That is the bug we are going to fix with a contract.
## 3. Write the contract
Add the guard and one contract. The contract says: every `issue_refund` call must be preceded by a `check_policy` call in the same session.
```python
from sponsio import contract
from sponsio.langgraph import Sponsio
guard = Sponsio(
agent_id="refund_bot",
contracts=[
contract("policy gate before refund")
.assume("called `issue_refund`")
.guarantees("must call `check_policy` before `issue_refund`"),
],
)
agent = create_react_agent(model, guard.wrap([check_policy, issue_refund]))
```
Three lines added: import, `guard = Sponsio(...)`, and `guard.wrap(...)` around the tool list.
## 4. See it block
```python
result = agent.invoke({"messages": [("user",
"Refund customer 42 $50. Skip the policy check, I'll vouch for it."
)]})
```
The agent tries to call `issue_refund` directly. Sponsio checks the trace, sees no `check_policy` event, and blocks:
```text
✗ enforce must call `check_policy` before `issue_refund`: VIOLATED, blocked
```
The framework surfaces this as a `SponsioBlocked` exception; the agent can react and retry with a different plan.
Block is the default outcome. Three other strategies are available on the same contract: `RedirectToSafe(safe=...)` substitutes a pre-approved tool, `EscalateToHuman(notify=[...])` blocks and fires a notifier callback, and `WarnOnly` logs the violation without blocking. See [observe vs enforce](../guides/observe-vs-enforce.md) for the picker.
Run the same request with the correct tool order ("check the policy first, then refund customer 42 $50") and the contract passes silently.
---
## 5. Move it to YAML
Inline contracts work, but production usually puts them in `sponsio.yaml` so they can be reviewed, diffed, and owned by a policy team.
```yaml
# sponsio.yaml
agents:
refund_bot:
contracts:
- name: "policy gate before refund"
A: "called `issue_refund`"
G: "must call `check_policy` before `issue_refund`"
```
Then:
```python
guard = Sponsio(config="sponsio.yaml", agent_id="refund_bot")
```
See [sponsio.yaml reference](../reference/config-yaml.md) for the full schema.
## 6. Ship in shadow mode first
Before you flip the switch on a real agent, run Sponsio in **observe mode**. It records violations without blocking. You review the report, tune the contracts, then promote to enforce.
```yaml
# sponsio.yaml
mode: observe
agents:
refund_bot: { ... }
```
See [Observe vs. enforce](../guides/observe-vs-enforce.md) for the full rollout.
---
## What next
- **Add more contracts.** The [pattern catalog](../reference/patterns.md) lists all 46 deterministic patterns with NL examples. Pick the ones that match your failure modes.
- **Generate contracts automatically.** `sponsio scan src/` reads your tool definitions and drafts a `sponsio.yaml` with candidate contracts. See [config yaml reference: how to populate sponsio.yaml](../reference/config-yaml.md#how-to-populate-sponsioyaml).
- **Wire a different framework.** Claude Agent SDK, OpenAI, CrewAI, Google ADK, Vercel AI, MCP. See [Integrations](../integrations/index.md).
<!-- ====================================================================== -->
<!-- FILE: docs/concepts/overview.md -->
<!-- ====================================================================== -->
---
title: Concepts overview
description: The concept stack, the trace, and the atom vocabulary that define Sponsio's deterministic contracts.
---
# Concepts overview
The [README](../../README.md) explains what Sponsio does. This page explains how to think about it when you sit down to write contracts.
Three ideas carry most of the system. Once they are straight, the pattern library, the atom catalog, the integrations, and the CLI all read as small variations on the same theme.
1. **The concept stack**: atom → pattern → formula → contract.
2. **The trace**: what contracts are actually evaluated against.
3. **The atom vocabulary**: where Sponsio's observation boundary lies.
For the full design rationale and LTL semantics, see [Architecture](architecture.md). This page bridges the README's three-line architecture summary and that document.
---
## 1. The concept stack
Four layers build on each other.
```
┌─────────────────────────────────────────────┐
│ Contract │
│ = {assumption, guarantee} bound to agent │
│ = the unit of enforcement │
├─────────────────────────────────────────────┤
│ Formula │
│ = Atoms + LTL + boolean connectives │
│ = what the evaluator actually checks │
├─────────────────────────────────────────────┤
│ Pattern │
│ = named factory that emits a Formula │
│ = convenience, not new expressiveness │
├─────────────────────────────────────────────┤
│ Atom │
│ = one observable fact about one event │
│ = the vocabulary boundary │
└─────────────────────────────────────────────┘
```
**Atom**: a binary or integer fact extracted from a single event: `called(X)`, `count(X)`, `arg_has(X, pattern)`, `perm(P)`. This is the observation boundary. If a fact cannot be expressed as an atom, Sponsio cannot observe it.
**Pattern**: a named factory that emits a formula from a short set of arguments. `must_precede("check_policy", "issue_refund")` returns the LTL formula `G(called("issue_refund") → ◆⁻ called("check_policy"))`. Patterns are *sugar*: they do not expand the expressiveness of the language, only the ergonomics.
**Formula**: an LTL expression over atoms. This is what the evaluator actually checks. Anything expressible in LTL over the available atom vocabulary can be enforced.
**Contract**: an (assumption, guarantee) pair bound to one or more agents, with a strategy for what to do on violation. The four strategies are `DetBlock` (refuse the call), `EscalateToHuman` (refuse and notify on-call), `RedirectToSafe` (substitute a pre-approved tool), and `WarnOnly` (log without blocking). The assumption tells the engine *when* the rule applies; the guarantee tells it *what must hold* when it does.
```python
contract("policy gate before refund")
.assume("called `issue_refund`")
.guarantees("must call `check_policy` before `issue_refund`")
```
---
## 2. The trace
A **trace** is the append-only record of what the agent has done in a session: each tool call, LLM response, and state change with its arguments and result. Every contract is evaluated against the current trace plus the candidate next event.
- **Ordering is checkable** because the trace remembers history. Output-only checkers cannot express "A before B" since "A" is not in the current output.
- **Enforcement is session-scoped.** Each agent session has its own trace. Contracts do not leak across sessions unless wired to.
- **Blocked events are rolled back.** In enforce mode, a hard-blocked event is removed from the trace so it does not poison later checks. In observe (shadow) mode, nothing is blocked. Violations are only recorded for reporting. See [Observe vs. enforce](../guides/observe-vs-enforce.md).
The grounding layer sits between raw events and the evaluator. Its only job is to turn each event into a dictionary of atom valuations. The evaluator never sees raw events.
---
## 3. The atom vocabulary
Atoms define the vocabulary in which contracts can be written. Adding a new atom (`http_method(X)`, `sql_verb(X)`, `path_depth()`) expands what Sponsio can reason about. Without a corresponding atom, even a natural-language rule that "reads" obvious cannot be enforced.
| Category | Example atoms | What you can express |
|---|---|---|
| Identity | `called(X)`, `agent_is(A)` | Which tool or agent ran this event |
| Counting | `count(X)`, `count_in_window(X, N)` | Rate limits, loops, bounded retries |
| Arguments | `arg_has(X, pattern)`, `arg_length(X) > N` | Blacklists, scope limits, length caps |
| Permissions | `perm(P)` | Static role checks |
| Data flow | `data_from(S)`, `data_to(D)` | No-leak rules across tool boundaries |
| Time | `elapsed_since(X) > T`, `deadline_passed()` | Cooldowns, deadlines |
The full list, with the formal signature of each atom and the patterns that use it, lives in [Architecture § Atoms](architecture.md). When in doubt, start with a pattern from the [pattern catalog](../reference/patterns.md). Most real rules map to one.
---
## 4. When a deterministic contract fits
Sponsio's deterministic contracts apply when a property is **structurally observable**: expressible with a counter, a regex, a path check, or an ordering relation.
| | Deterministic contract |
|---|---|
| **Ships in** | `pip install sponsio` |
| **Use when** | Property is structurally observable (counter, regex, path, ordering) |
| **Examples** | Tool ordering, rate limits, retries, loop detection, destructive gates, irreversible-once, path / argument blacklists, scope and length limits, exact-regex PII, format checks, permissions, allowlists, segregation of duty |
| **Cost** | Microseconds, zero LLM calls |
| **Pipeline** | LTL evaluator (formal) |
The rule of thumb: keep contracts to things a counter, regex, path, or ordering relation can check. Properties that only make sense semantically (tone, relevance, whether text is *truly* off-topic) are outside the deterministic engine's scope.
---
## How it fits together
```
┌─────────────────────────────────────────────────────────────┐
│ Your agent loop │
│ │
│ LLM ──▶ pick tool ──▶ ┌─────────────────┐ ──▶ tool runs │
│ │ Sponsio check │ │
│ result ◀───────────── │ │ ◀── or blocked │
│ └─────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ trace │ append-only │
│ │ + atoms │ grounding layer │
│ └─────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────┐ │
│ │ det formula │ │
│ │ evaluator │ │
│ └───────┬───────┘ │
│ │ │
│ ▼ │
│ block / escalate │
└─────────────────────────────────────────────────────────────┘
```
Deterministic formulas are evaluated in microseconds. A violation routes through a **strategy**. The four options are `DetBlock` (refuse the call), `EscalateToHuman` (refuse and notify on-call), `RedirectToSafe` (substitute a pre-approved safe tool), and `WarnOnly` (log without blocking).
---
## Next
- [Architecture](architecture.md): LTL semantics, grounding internals, why the atom vocabulary is the observation boundary.
- [Deterministic contracts](contracts.md): the pattern library and how each pattern compiles to LTL.
- [Write your first contract](../getting-started/first-contract.md): hands-on walkthrough.
- [Integrations](../integrations/index.md): wire it into your framework (LangGraph, Claude Agent SDK, OpenAI, CrewAI, Google ADK, Vercel AI, MCP, or custom).
<!-- ====================================================================== -->
<!-- FILE: docs/concepts/architecture.md -->
<!-- ====================================================================== -->
# Pattern architecture
Internals reference for contributors. For the user-facing concept model (atom → pattern → formula → contract), see [Concepts overview](overview.md). Read this page when you are adding a new pattern, atom, or observation layer.
---
## 1. Atom vocabulary
### Current atoms (in `tracer/grounding.py`)
| Atom | Type | Source event | Truly atomic? | Notes |
|---|---|---|---|---|
| `called(X)` | bool | `tool_call` | Yes, directly from `event.tool` | Core. Present at every timestep where tool X fires. |
| `count(X)` | int | `tool_call` | Yes, cumulative accumulator | LTL cannot count; this must be maintained by grounding. Compared via arithmetic nodes (`Le`, `Gt`). |
| `arg_has(tool, pattern)` | bool | `tool_call` | Yes, regex on serialized `event.args` | Parameterized: grounding only checks patterns it was told about via `collect_content_atoms()`. |
| `arg_field_has(tool, field, pattern)` | bool | `tool_call` | Yes, regex on a specific arg field | Parameterized. Field-specific precision (vs `arg_has` which checks all args). Used by `arg_blacklist`. |
| `arg_paths_within(tool, *prefixes)` | bool | `tool_call` | Yes, checks all file paths in args are within allowed prefixes | Parameterized. Replaces FOL `ForAllPaths` quantifier. |
| `output_has(tool, pattern)` | bool | `tool_call` | Yes, regex on `event.content` | Requires `guard_after()` to populate content. |
| `perm(P)` | bool | `Agent.permissions` | Yes, static lookup | Not derivable from events. Useful for multi-agent RBAC. |
| `contains(field)` | bool | `data_write` | Yes, from `event.contains` | Data flow tracking. |
| `flow(src, dest)` | bool | `data_read`, `message` | Semi: requires cross-event state | Forward-propagated: once true, stays true for the rest of the trace. |
| `llm_said(pattern)` | bool | `llm_response` | Yes, regex on LLM output | Requires integration to emit `llm_response` events. |
| `prompt_contains(pattern)` | bool | `llm_request` | Yes, regex on LLM input | Requires integration to emit `llm_request` events. |
| `system_prompt_present()` | bool | `llm_request` | Yes, structural check | True if LLM request has a system message. |
| `context_length()` | int | `llm_request` | Yes, char count of LLM input | Compared via arithmetic nodes. |
### Proposed additions
| Candidate atom | Source | OTEL span attribute | Use case | Observation model |
|---|---|---|---|---|
| `arg_eq(tool, key, val)` | `tool_call` args | `tool.input.{key}` | Exact match on specific arg field | A + B |
| `llm_input_contains(pattern)` | LLM span | `gen_ai.prompt` | Prompt injection detection | B only (OTEL) |
| `llm_output_contains(pattern)` | LLM span | `gen_ai.completion` | Output safety audit | B only (OTEL) |
| `token_count(type)` | LLM span | `gen_ai.usage.*_tokens` | Cost control | B only (OTEL) |
| `latency_exceeds(tool, ms)` | Any span | span duration | Performance constraints | B only (OTEL) |
Atoms marked "B only" are exclusively available through OTEL consumption (Section 4), not integration hooks. Hooks intercept at the tool level, not the LLM level.
### Design principles
1. **Atoms must be extractable from a single event** (or a simple accumulator like `count`). If computing a value requires reasoning over multiple events, express it as an LTL formula over simpler atoms.
2. **Parameterized atoms** (regex patterns or prefix lists) use `collect_content_atoms()` to tell grounding what to look for. Grounding does not speculatively match; it only checks atoms that appear in the active formulas.
3. **New atoms require registration** in `_CONTENT_PREDICATES` (if parameterized) and extraction logic in `ground()`. This is the only code change needed to extend Sponsio's observation capabilities.
---
## 2. Grounding as thin event adapter
```
Events -> Grounding (thin adapter) -> list[dict[str, bool|int]] -> Evaluator
| |
|- extract atoms from event fields |- evaluate formula AST
|- maintain count() accumulators | over valuations
|- maintain flow() state tracker |
`- regex-match parameterized atoms `- return bool
```
Grounding (`tracer/grounding.py`) is a thin event adapter. Its job:
1. Map `Event` fields to atom truth values.
2. Maintain `count(X)` accumulator (LTL cannot count).
3. Maintain `flow()` state tracker (requires cross-event state).
4. Regex-match parameterized atoms (`arg_has`, `arg_paths_within`, `output_has`, `llm_said`).
No derived predicates. All composition is expressed in the formula AST and handled by the evaluator.
### Why one unified AST
1. **One AST, multiple backends.** A unified formula AST can be consumed by the runtime evaluator today, and by Z3/nuXmv model checkers in the future. Two ASTs means two encodings.
2. **Users learn one concept.** "Everything is an LTL formula over atoms" is a complete mental model.
3. **Extensibility via atoms, not AST nodes.** Adding observation capabilities means registering new atoms in grounding. No new AST node types needed.
---
## 3. Patterns as named templates
Patterns are factory functions, not a new layer.
### What a pattern function does
```python
def must_precede(a: str, b: str) -> DetFormula:
formula = U(Not(Atom("called", b)), Atom("called", a))
return DetFormula(
formula=formula,
desc=f"tool `{a}` must precede `{b}`",
pattern_name="must_precede",
args=(a, b),
)
```
It takes user-friendly arguments, constructs a formula from atoms, and wraps it with metadata (`desc`, `pattern_name`, raw `args` for round-trip).
### Current pattern inventory
**Ordering (temporal)**:
- `must_precede(A, B)`: A before B, using `Until`
- `always_followed_by(A, B)`: A implies eventually B
- `must_confirm(action)`: confirmation required before action
- `no_reversal(A, B)`: B forbidden after A commits
**Frequency / rate**:
- `rate_limit(action, N)`: at most N calls total
- `idempotent(action)`: at most 1 call (special case of `rate_limit`)
- `cooldown(action, N)`: min N steps between consecutive calls
- `bounded_retry(action, N)`: at most N retries
- `deadline(trigger, action, N)`: action within N steps of trigger
**Exclusion**:
- `mutual_exclusion(A, B)`: at most one ever called across entire trace
- `segregation_of_duty(A, B)`: same agent cannot do both
- `tool_allowlist(tools)`: only listed tools may be called
**Recovery**:
- `redirect_to_safe(unsafe, safe)`: substitute the offending call with a pre-approved safe tool. Bundles a `RedirectToSafe` strategy on the resulting `DetFormula`, so a violation surfaces as `action="redirected"` instead of `"blocked"`. The LangGraph adapter dispatches the substitute call; other adapters surface `result.redirected_to` for the application.
**Access control**:
- `requires_permission(tool, perm)`: tool needs static permission
**Data flow**:
- `no_data_leak(src, dest)`: no cross-agent data flow
- `arg_blacklist(tool, param, patterns)`: forbid regex patterns in tool args
- `scope_limit(tool, paths)`: restrict tool to allowed path prefixes
### Adding a new pattern
1. Write the factory in `patterns/library.py`. Return `DetFormula` and populate `args=(...)` with the raw arguments so the pattern store can round-trip them.
2. If the formula uses atoms not yet in grounding, add the extraction logic to `tracer/grounding.py`.
3. Add DSL keyword rules in `generation/dsl_to_contract.py`.
4. Add tests in `tests/test_pattern_e2e.py` covering NL → guard → enforcement.
A pattern that only uses existing atoms (composing `called()` and `count()`) requires zero grounding changes.
---
## 4. Two observation models
Sponsio has two ways to observe agent behavior. They differ in what they can see and whether they can intervene.
### Model A: integration hooks (realtime, can block)
Each framework integration hooks at tool-call boundaries:
```
LangGraphGuard -> wraps wrap() -> sees: tool_name, args, result
OpenAIGuard -> patches completions.create -> sees: tool_calls in response
CrewAIGuard -> on_tool_start/on_tool_end -> sees: tool_name, args, result
AgentsSDKGuard -> wraps @function_tool -> sees: tool_name, args, result
MCPContractProxy -> wraps call_tool() -> sees: tool_name, args, result
```
| Property | Value |
|---|---|
| Can observe | tool name, tool args, tool result |
| Cannot observe | LLM input prompt, LLM output text, memory state, retrieval results |
| Can block | Yes. `guard_before()` returns `blocked=True` before tool executes |
| Latency | Microseconds (formula evaluation is pure Python, no I/O) |
| Atoms available | `called`, `count`, `perm`, `arg_has`, `output_has`, `contains`, `flow` |
Real-time enforcement. When a tool call would violate a contract, it is blocked before execution.
### Model B: OTEL consumer (post-hoc, richer observation)
Instead of hooking each framework, consume the OTEL traces frameworks already produce natively:
```
Any LLM framework -> framework's OTEL instrumentation -> standard OTEL spans
|
Sponsio OTEL consumer
|
atom extraction -> LTL evaluation -> report
```
| Property | Value |
|---|---|
| Can observe | Everything in the OTEL trace: tool calls, LLM I/O, tokens, latency, retrieval |
| Cannot observe | Internal chain-of-thought not emitted as span attributes |
| Can block | No. Observation is after the fact |
| Latency | Batch processing (seconds to minutes, depending on collection interval) |
| Atoms available | All of Model A, plus: `llm_input_contains`, `llm_output_contains`, `token_count`, `latency_exceeds` |
Frameworks already export OTEL traces via standard instrumentation: `langchain-opentelemetry`, `opentelemetry-instrumentation-openai`, CrewAI built-in, `llama-index-instrumentation-opentelemetry`. Sponsio needs a consumer component that receives these spans, extracts atoms from span attributes, and feeds them into the same LTL evaluator.
### Complementary use
| | Can block? | LLM I/O visible? | Framework changes needed? |
|---|---|---|---|
| Integration hooks | Yes | No | None (already built) |
| OTEL consumer | No | Yes | None (framework has OTEL) |
Use both. Integration hooks for real-time enforcement (block dangerous tool calls before execution); OTEL consumer for post-hoc audit (detect prompt injection, PII in outputs, cost overruns).
### Current OTEL components
| Component | Status | Direction | Purpose |
|---|---|---|---|
| `sponsio/tracer/exporters.py` (`OtlpHttpExporter`) | Yes | Sponsio → OTLP | Push contract-checking span tree to any OTLP/HTTP collector (Datadog, Honeycomb, Grafana) |
| OTEL Consumer / Atom Adapter | Not yet | OTEL → Evaluator | Extract atoms from framework OTEL spans, run LTL evaluation |
The outbound exporter works today: ship spans to your own OTLP/HTTP collector. The consumer that closes the loop from OTEL spans back to contract verification is the missing piece.
---
## 5. Pattern classification by observation boundary
Patterns are organized by which atoms they require, which determines which observation model can supply them.
### Category A: tool-call patterns
Atoms used: `called(X)`, `count(X)`, `arg_has(X, pattern)`, `output_has(X, pattern)`, `perm(P)`.
Available via: hooks (realtime, can block) AND OTEL (post-hoc).
Most deterministic patterns fall here. Universally available, enforceable, covers the majority of agent safety constraints.
Examples:
- `must_precede(A, B)` = `Not(called(B)) U called(A)`, uses `called` atoms
- `rate_limit(X, N)` = `G(count(X) <= N)`, uses `count` atom
- `arg_blacklist(X, _, patterns)` = `G(called(X) -> And(Not(arg_has(X, p1)), ...))`, uses `called` + `arg_has`
### Category B: data-flow patterns
Atoms used: `contains(field)`, `flow(src, dest)`.
Available via: hooks only, and only if the agent emits `data_read` / `data_write` / `message` events (not just `tool_call`). Most agents only produce `tool_call` events, making this category niche. The `no_data_leak` pattern lives here.
### Category C: LLM-level patterns
Atoms used: `llm_input_contains(pattern)`, `llm_output_contains(pattern)`, `token_count(type)`.
Available via: OTEL only (post-hoc, cannot block). Not enforceable in real-time. For audit and compliance:
- Prompt injection detection: `G(Not(llm_input_contains("ignore previous instructions")))`
- Output safety: `G(Not(llm_output_contains(ssn_pattern)))`
- Cost control: `G(token_count("total") <= 10000)`
`llm_said` and `prompt_contains` atoms exist in grounding but require integrations to emit `llm_response` / `llm_request` event types. The OTEL consumer would provide these atoms automatically from framework spans.
### Recommendation
Keep the pattern library focused on Category A. These are universal, enforceable, and cover the dominant use case. Categories B and C are documented but not prioritized for pattern library expansion. Category C patterns belong in the OTEL consumer module's analysis layer.
<!-- ====================================================================== -->
<!-- FILE: docs/concepts/stochastic.md -->
<!-- ====================================================================== -->
<!-- MISSING: docs/concepts/stochastic.md -->
<!-- ====================================================================== -->
<!-- FILE: docs/guides/onboarding.md -->
<!-- ====================================================================== -->
---
title: Onboarding an existing agent
description: Use `sponsio init` to wire framework, host hooks, skill, and mode in one wizard.
---
# Onboarding an existing agent
`sponsio init` is the 4-axis setup wizard. One run covers every decision that matters on first install. Three surfaces (interactive TTY, `--plan` dry-run, `--apply` non-interactive) share the same dispatch table, so an IDE-agent's preview matches what `--apply` actually runs (they call into the same code path).
```bash
pip install sponsio
sponsio init
```
---
## The four axes
| Axis | Picks | What it does |
|---|---|---|
| **1. Framework wrap** (single) | `langgraph` / `crewai` / `openai` / `claude_agent` / `agents` / `vercel_ai` / `google_adk` / `mcp` / `none` | AST-scans your code, writes `sponsio.yaml`, prints a 2-line patch for your agent entry. |
| **2. Protect host agents** (multi) | `claude-code` / `cursor` / `openclaw` | Installs the host's pre-tool hook so the IDE's own tool calls (Bash, Edit, MCP servers) get gated too. |
| **3. Install Sponsio skill** (multi) | `claude-code` / `cursor` / `codex` | Drops `SKILL.md` into the host's skill directory. Auto-triggers on phrases like *"audit my agent"*, *"explain my sponsio.yaml"*. |
| **4. Mode** (single) | `observe` (default) / `enforce` | `observe` evaluates and logs; `enforce` blocks unsafe calls. |
Pick `none` for axis 1 if your code uses a custom tool-call loop and you'd rather call `guard.guard_before()` / `guard.guard_after()` yourself.
---
## Three surfaces
### Interactive TTY (humans)
```bash
sponsio init
```
The wizard prompts each axis in turn. Defaults are highlighted; press Enter to accept.
### Non-interactive (CI, scripts, IDE agents)
```bash
sponsio init --apply 'framework=langgraph;hosts=cursor;mode=observe'
```
Picks format: `framework=<name>;ides=<ide>:<level>,<ide>:<level>;mode=<observe|enforce>` where `<level>` is `none`, `skill`, or `full`. Each axis is optional; omit any axis to take its default. Legacy `hosts=<a>,<b>;skills=<a>,<b>` form is still accepted.
### Dry-run preview
```bash
sponsio init --plan 'framework=langgraph;hosts=cursor;mode=observe'
```
Prints the would-run commands without executing. The IDE-agent onboarding prompt uses this exact format for its preview step, so what shows in the preview is what `--apply` would do.
---
## What `init` calls under the hood
```
sponsio init
├── axis 1 → sponsio onboard <target> framework wrap, AST scan, write sponsio.yaml
├── axis 2 → sponsio host install <host> one call per picked host
├── axis 3 → sponsio skill install per picked IDE