wardn 0.1.0.dev0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- wardn-0.1.0.dev0/.gitattributes +2 -0
- wardn-0.1.0.dev0/.gitignore +12 -0
- wardn-0.1.0.dev0/LICENSE +21 -0
- wardn-0.1.0.dev0/PKG-INFO +213 -0
- wardn-0.1.0.dev0/README.md +187 -0
- wardn-0.1.0.dev0/TODO.md +403 -0
- wardn-0.1.0.dev0/docs/ci.md +92 -0
- wardn-0.1.0.dev0/docs/concepts.md +179 -0
- wardn-0.1.0.dev0/docs/embedding.md +97 -0
- wardn-0.1.0.dev0/docs/ledger-format.md +220 -0
- wardn-0.1.0.dev0/docs/policy-format.md +102 -0
- wardn-0.1.0.dev0/examples/flagship/README.md +77 -0
- wardn-0.1.0.dev0/examples/flagship/act1_enforcement.py +82 -0
- wardn-0.1.0.dev0/examples/flagship/act2_ci_gate.py +70 -0
- wardn-0.1.0.dev0/examples/flagship/agent.py +254 -0
- wardn-0.1.0.dev0/pyproject.toml +59 -0
- wardn-0.1.0.dev0/spike/policy.json +37 -0
- wardn-0.1.0.dev0/spike/spike.py +442 -0
- wardn-0.1.0.dev0/src/wardn/__init__.py +32 -0
- wardn-0.1.0.dev0/src/wardn/__main__.py +6 -0
- wardn-0.1.0.dev0/src/wardn/_version.py +1 -0
- wardn-0.1.0.dev0/src/wardn/approvals.py +103 -0
- wardn-0.1.0.dev0/src/wardn/cli.py +149 -0
- wardn-0.1.0.dev0/src/wardn/diff.py +154 -0
- wardn-0.1.0.dev0/src/wardn/engine.py +105 -0
- wardn-0.1.0.dev0/src/wardn/ledger.py +237 -0
- wardn-0.1.0.dev0/src/wardn/matchers.py +62 -0
- wardn-0.1.0.dev0/src/wardn/orchestrator.py +122 -0
- wardn-0.1.0.dev0/src/wardn/policy.py +313 -0
- wardn-0.1.0.dev0/src/wardn/proxy.py +478 -0
- wardn-0.1.0.dev0/src/wardn/py.typed +1 -0
- wardn-0.1.0.dev0/src/wardn/revert.py +101 -0
- wardn-0.1.0.dev0/src/wardn/types.py +49 -0
- wardn-0.1.0.dev0/tests/conftest.py +147 -0
- wardn-0.1.0.dev0/tests/fake_server.py +115 -0
- wardn-0.1.0.dev0/tests/test_cli_replay_revert.py +76 -0
- wardn-0.1.0.dev0/tests/test_diff.py +103 -0
- wardn-0.1.0.dev0/tests/test_dogfood_filesystem.py +166 -0
- wardn-0.1.0.dev0/tests/test_engine.py +107 -0
- wardn-0.1.0.dev0/tests/test_import_purity.py +32 -0
- wardn-0.1.0.dev0/tests/test_ledger_replay.py +94 -0
- wardn-0.1.0.dev0/tests/test_multi_server.py +202 -0
- wardn-0.1.0.dev0/tests/test_policy.py +90 -0
- wardn-0.1.0.dev0/tests/test_proxy_e2e.py +184 -0
- wardn-0.1.0.dev0/tests/test_revert.py +59 -0
wardn-0.1.0.dev0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Pi-Wi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: wardn
|
|
3
|
+
Version: 0.1.0.dev0
|
|
4
|
+
Summary: Bounded, auditable action governor for LLM agents — a policy gate between an MCP client and its servers.
|
|
5
|
+
Project-URL: Repository, https://github.com/Pi-Wi/warden
|
|
6
|
+
Project-URL: Documentation, https://github.com/Pi-Wi/warden/tree/main/docs
|
|
7
|
+
Author: Pi-Wi
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: agents,audit,governance,ledger,llm,mcp,model-context-protocol,policy
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
20
|
+
Classifier: Topic :: System :: Logging
|
|
21
|
+
Classifier: Typing :: Typed
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# wardn
|
|
28
|
+
|
|
29
|
+
Bounded, auditable action governor for LLM agents — a policy gate between an
|
|
30
|
+
MCP client and its servers. Every tool call is allowed, denied, or
|
|
31
|
+
quarantined by a rule you declared; run-wide budgets cap total side-effects
|
|
32
|
+
with loud refusals; and every attempt lands in an append-only,
|
|
33
|
+
timestamp-free, byte-deterministic ledger that replays offline and diffs in
|
|
34
|
+
CI.
|
|
35
|
+
|
|
36
|
+
wardn bounds and audits. It does not sandbox, and it is not a content-safety
|
|
37
|
+
product.
|
|
38
|
+
|
|
39
|
+
## Status
|
|
40
|
+
|
|
41
|
+
**Pre-release — M2 (adoptable, CI-integrable) is built.** The proxy governs
|
|
42
|
+
one or more real MCP servers over stdio; `wardn review` decides quarantined
|
|
43
|
+
calls from any terminal; `wardn replay` reproduces every decision offline;
|
|
44
|
+
`wardn revert` undoes declared-reversible effects from the ledger file
|
|
45
|
+
alone; and `wardn diff` turns two ledgers into a CI verdict — exit 4 the
|
|
46
|
+
moment governed activity drifts. Two identical runs emit byte-identical
|
|
47
|
+
ledgers, including against the real
|
|
48
|
+
`@modelcontextprotocol/server-filesystem`.
|
|
49
|
+
|
|
50
|
+
Not yet on PyPI — install from a clone (`pip install .`). The roadmap,
|
|
51
|
+
locked decisions, and spike record live in [TODO.md](TODO.md).
|
|
52
|
+
|
|
53
|
+
## The gap
|
|
54
|
+
|
|
55
|
+
Permission systems exist (per-client allow/ask/deny rules), approval
|
|
56
|
+
services exist, security gateways exist, eval-time diffs exist. But none of
|
|
57
|
+
them owns the middle: side-effect governance that is *declared* (a policy
|
|
58
|
+
file, not scattered client settings), *bounded* ("at most 2 writes", not
|
|
59
|
+
just "ask"), and *auditable* — emitted as a single deterministic artifact a
|
|
60
|
+
CI job can diff with an exit code.
|
|
61
|
+
|
|
62
|
+
That artifact is the whole point of wardn. A permission prompt leaves no
|
|
63
|
+
record. An observability stream isn't deterministic. wardn's ledger is
|
|
64
|
+
both: the decision log and the drift detector, one file.
|
|
65
|
+
|
|
66
|
+
## The core idea: one ledger, three uses
|
|
67
|
+
|
|
68
|
+
Every attempt — allowed, denied, quarantined, refused by a budget — is one
|
|
69
|
+
recorded event stream: what was attempted, which rule matched, how the call
|
|
70
|
+
was classified (and by what authority), what was decided, why, and what came
|
|
71
|
+
back.
|
|
72
|
+
|
|
73
|
+
- **Read it forward** → the audit report. Every side-effect, explained.
|
|
74
|
+
- **Replay it** (`wardn replay`) → the decisions, reproduced offline from
|
|
75
|
+
policy + file alone. A tampered ledger or a swapped policy refuses loudly.
|
|
76
|
+
- **Replay it inverted** (`wardn revert`) → the undo. Declared-reversible
|
|
77
|
+
effects are reverted from the file alone — the undo call was resolved and
|
|
78
|
+
recorded at capture time, so revert needs no policy, only the ledger and a
|
|
79
|
+
live server. If the world drifted since the run, revert refuses rather
|
|
80
|
+
than guesses.
|
|
81
|
+
|
|
82
|
+
And because the file is byte-deterministic — no timestamps, no PIDs, no
|
|
83
|
+
nonces — `wardn diff yesterday.jsonl today.jsonl` is a CI gate: same
|
|
84
|
+
policy, same attempts, same decisions, or exit 4.
|
|
85
|
+
|
|
86
|
+
## Quick look
|
|
87
|
+
|
|
88
|
+
A policy is an ordered list of rules, first match wins, with a mandatory
|
|
89
|
+
explicit default — a policy that doesn't say what happens to unmatched
|
|
90
|
+
calls is invalid:
|
|
91
|
+
|
|
92
|
+
```json
|
|
93
|
+
{
|
|
94
|
+
"format": "wardn-policy/1",
|
|
95
|
+
"default": "deny",
|
|
96
|
+
"budget": {"max_calls": 20, "max_writes": 5, "max_spend": 1.0},
|
|
97
|
+
"approval_timeout_s": 300,
|
|
98
|
+
"rules": [
|
|
99
|
+
{
|
|
100
|
+
"id": "allow-reads",
|
|
101
|
+
"match": {"server": "filesystem", "tool": "read_text_file"},
|
|
102
|
+
"effect": "read",
|
|
103
|
+
"disposition": "allow",
|
|
104
|
+
"reason": "reads are harmless here"
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"id": "bound-writes",
|
|
108
|
+
"match": {"server": "filesystem", "tool": "write_file",
|
|
109
|
+
"args": {"path": {"path_prefix": "/work/out/"}}},
|
|
110
|
+
"effect": "write",
|
|
111
|
+
"disposition": "allow",
|
|
112
|
+
"bound": {"max_calls": 2},
|
|
113
|
+
"cost_per_call": 0.1,
|
|
114
|
+
"capture": {"tool": "read_text_file", "args_from": {"path": "path"}},
|
|
115
|
+
"inverse": {"tool": "write_file",
|
|
116
|
+
"args": {"path": "$args.path", "content": "$capture.value"},
|
|
117
|
+
"expect": "$args.content"},
|
|
118
|
+
"reason": "writes capped at 2, under out/ only, pre-state captured"
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
"id": "quarantine-moves",
|
|
122
|
+
"match": {"server": "filesystem", "tool": "move_file"},
|
|
123
|
+
"effect": "destructive",
|
|
124
|
+
"disposition": "quarantine",
|
|
125
|
+
"reason": "moves need a human"
|
|
126
|
+
}
|
|
127
|
+
]
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Put wardn between your client and the server — the client config points at
|
|
132
|
+
wardn, wardn spawns the real server:
|
|
133
|
+
|
|
134
|
+
```json
|
|
135
|
+
{
|
|
136
|
+
"mcpServers": {
|
|
137
|
+
"filesystem": {
|
|
138
|
+
"command": "wardn",
|
|
139
|
+
"args": ["proxy", "--policy", "policy.json", "--ledger", "run.jsonl",
|
|
140
|
+
"--", "npx", "-y", "@modelcontextprotocol/server-filesystem",
|
|
141
|
+
"/work"]
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
The agent now reads freely, gets its third write refused loudly (an error
|
|
148
|
+
result naming the rule and the bound — the agent can read why and adapt),
|
|
149
|
+
and blocks on `move_file` until someone, in any terminal, runs:
|
|
150
|
+
|
|
151
|
+
```console
|
|
152
|
+
$ wardn review list --queue run.jsonl.queue.json
|
|
153
|
+
q6 move_file {"source":"/work/report.txt","destination":"/work/old.txt"} rule=quarantine-moves — moves need a human
|
|
154
|
+
|
|
155
|
+
$ wardn review approve q6 --queue run.jsonl.queue.json --by phiwir
|
|
156
|
+
q6: approve by phiwir
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Afterwards, the run is a file:
|
|
160
|
+
|
|
161
|
+
```console
|
|
162
|
+
$ wardn replay --policy policy.json --ledger run.jsonl
|
|
163
|
+
replayed 8 attempt(s) — every decision reproduced by this policy
|
|
164
|
+
|
|
165
|
+
$ wardn revert --ledger run.jsonl -- npx -y @modelcontextprotocol/server-filesystem /work
|
|
166
|
+
applied 2 undo(s) — revert ledger: run.jsonl.revert.jsonl
|
|
167
|
+
|
|
168
|
+
$ wardn diff approved/run.jsonl run.jsonl && echo "no drift"
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Several servers can sit behind one gate — `wardn proxy --servers
|
|
172
|
+
servers.json` takes a file in the same shape as an MCP client config, and
|
|
173
|
+
rules match on the server's name. See [docs/concepts.md](docs/concepts.md)
|
|
174
|
+
for what multi-server mode honestly does and doesn't proxy.
|
|
175
|
+
|
|
176
|
+
## What wardn is not
|
|
177
|
+
|
|
178
|
+
- **Not a sandbox.** wardn is a policy gate in front of cooperative
|
|
179
|
+
transports. A hostile server, or a client that bypasses the proxy, is out
|
|
180
|
+
of scope — and argument constraints are string constraints:
|
|
181
|
+
`path_prefix: "out/"` happily matches `out/../secret.txt`. wardn does not
|
|
182
|
+
resolve paths; that is a sandbox's job. Never treat a wardn policy as an
|
|
183
|
+
isolation boundary.
|
|
184
|
+
- **Not content safety.** wardn governs side-effects, not text. Prompt
|
|
185
|
+
injection defense is a different layer.
|
|
186
|
+
- **No LLM anywhere in the enforcement path.** A decision is a pure
|
|
187
|
+
function of policy + request + recorded approvals. Ever.
|
|
188
|
+
- **No measured spend.** `max_spend` refuses on arithmetic over *declared*
|
|
189
|
+
costs (`cost_per_call`), never on billing APIs — wardn cannot observe
|
|
190
|
+
real spend and doesn't pretend to.
|
|
191
|
+
|
|
192
|
+
## Docs
|
|
193
|
+
|
|
194
|
+
- [docs/concepts.md](docs/concepts.md) — rules, dispositions, bounds,
|
|
195
|
+
classification, quarantine, capture/revert, and the determinism claim
|
|
196
|
+
stated precisely (including its honest asterisks).
|
|
197
|
+
- [docs/policy-format.md](docs/policy-format.md) — `wardn-policy/1`,
|
|
198
|
+
normative.
|
|
199
|
+
- [docs/ledger-format.md](docs/ledger-format.md) — `wardn-ledger/1`, the
|
|
200
|
+
compatibility contract. The ledger format and the proxy CLI are the two
|
|
201
|
+
things wardn promises stability for.
|
|
202
|
+
- [docs/ci.md](docs/ci.md) — the CI story: budgets as drift tripwires,
|
|
203
|
+
`wardn diff` as the gate.
|
|
204
|
+
- [docs/embedding.md](docs/embedding.md) — the engine API for non-MCP
|
|
205
|
+
embeddings: `govern_call` in front of any tool-calling boundary.
|
|
206
|
+
|
|
207
|
+
## Family
|
|
208
|
+
|
|
209
|
+
Sibling to [agentrec](https://github.com/Pi-Wi/agent-rec) (records what
|
|
210
|
+
models *say*) and [mendr](https://github.com/Pi-Wi/mendr) (repairs what data
|
|
211
|
+
*got wrong*) — wardn governs what agents *do*. Same ethos: declared,
|
|
212
|
+
bounded, recorded; the record is the product; no LLM anywhere in the
|
|
213
|
+
enforcement path.
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# wardn
|
|
2
|
+
|
|
3
|
+
Bounded, auditable action governor for LLM agents — a policy gate between an
|
|
4
|
+
MCP client and its servers. Every tool call is allowed, denied, or
|
|
5
|
+
quarantined by a rule you declared; run-wide budgets cap total side-effects
|
|
6
|
+
with loud refusals; and every attempt lands in an append-only,
|
|
7
|
+
timestamp-free, byte-deterministic ledger that replays offline and diffs in
|
|
8
|
+
CI.
|
|
9
|
+
|
|
10
|
+
wardn bounds and audits. It does not sandbox, and it is not a content-safety
|
|
11
|
+
product.
|
|
12
|
+
|
|
13
|
+
## Status
|
|
14
|
+
|
|
15
|
+
**Pre-release — M2 (adoptable, CI-integrable) is built.** The proxy governs
|
|
16
|
+
one or more real MCP servers over stdio; `wardn review` decides quarantined
|
|
17
|
+
calls from any terminal; `wardn replay` reproduces every decision offline;
|
|
18
|
+
`wardn revert` undoes declared-reversible effects from the ledger file
|
|
19
|
+
alone; and `wardn diff` turns two ledgers into a CI verdict — exit 4 the
|
|
20
|
+
moment governed activity drifts. Two identical runs emit byte-identical
|
|
21
|
+
ledgers, including against the real
|
|
22
|
+
`@modelcontextprotocol/server-filesystem`.
|
|
23
|
+
|
|
24
|
+
Not yet on PyPI — install from a clone (`pip install .`). The roadmap,
|
|
25
|
+
locked decisions, and spike record live in [TODO.md](TODO.md).
|
|
26
|
+
|
|
27
|
+
## The gap
|
|
28
|
+
|
|
29
|
+
Permission systems exist (per-client allow/ask/deny rules), approval
|
|
30
|
+
services exist, security gateways exist, eval-time diffs exist. But none of
|
|
31
|
+
them owns the middle: side-effect governance that is *declared* (a policy
|
|
32
|
+
file, not scattered client settings), *bounded* ("at most 2 writes", not
|
|
33
|
+
just "ask"), and *auditable* — emitted as a single deterministic artifact a
|
|
34
|
+
CI job can diff with an exit code.
|
|
35
|
+
|
|
36
|
+
That artifact is the whole point of wardn. A permission prompt leaves no
|
|
37
|
+
record. An observability stream isn't deterministic. wardn's ledger is
|
|
38
|
+
both: the decision log and the drift detector, one file.
|
|
39
|
+
|
|
40
|
+
## The core idea: one ledger, three uses
|
|
41
|
+
|
|
42
|
+
Every attempt — allowed, denied, quarantined, refused by a budget — is one
|
|
43
|
+
recorded event stream: what was attempted, which rule matched, how the call
|
|
44
|
+
was classified (and by what authority), what was decided, why, and what came
|
|
45
|
+
back.
|
|
46
|
+
|
|
47
|
+
- **Read it forward** → the audit report. Every side-effect, explained.
|
|
48
|
+
- **Replay it** (`wardn replay`) → the decisions, reproduced offline from
|
|
49
|
+
policy + file alone. A tampered ledger or a swapped policy refuses loudly.
|
|
50
|
+
- **Replay it inverted** (`wardn revert`) → the undo. Declared-reversible
|
|
51
|
+
effects are reverted from the file alone — the undo call was resolved and
|
|
52
|
+
recorded at capture time, so revert needs no policy, only the ledger and a
|
|
53
|
+
live server. If the world drifted since the run, revert refuses rather
|
|
54
|
+
than guesses.
|
|
55
|
+
|
|
56
|
+
And because the file is byte-deterministic — no timestamps, no PIDs, no
|
|
57
|
+
nonces — `wardn diff yesterday.jsonl today.jsonl` is a CI gate: same
|
|
58
|
+
policy, same attempts, same decisions, or exit 4.
|
|
59
|
+
|
|
60
|
+
## Quick look
|
|
61
|
+
|
|
62
|
+
A policy is an ordered list of rules, first match wins, with a mandatory
|
|
63
|
+
explicit default — a policy that doesn't say what happens to unmatched
|
|
64
|
+
calls is invalid:
|
|
65
|
+
|
|
66
|
+
```json
|
|
67
|
+
{
|
|
68
|
+
"format": "wardn-policy/1",
|
|
69
|
+
"default": "deny",
|
|
70
|
+
"budget": {"max_calls": 20, "max_writes": 5, "max_spend": 1.0},
|
|
71
|
+
"approval_timeout_s": 300,
|
|
72
|
+
"rules": [
|
|
73
|
+
{
|
|
74
|
+
"id": "allow-reads",
|
|
75
|
+
"match": {"server": "filesystem", "tool": "read_text_file"},
|
|
76
|
+
"effect": "read",
|
|
77
|
+
"disposition": "allow",
|
|
78
|
+
"reason": "reads are harmless here"
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"id": "bound-writes",
|
|
82
|
+
"match": {"server": "filesystem", "tool": "write_file",
|
|
83
|
+
"args": {"path": {"path_prefix": "/work/out/"}}},
|
|
84
|
+
"effect": "write",
|
|
85
|
+
"disposition": "allow",
|
|
86
|
+
"bound": {"max_calls": 2},
|
|
87
|
+
"cost_per_call": 0.1,
|
|
88
|
+
"capture": {"tool": "read_text_file", "args_from": {"path": "path"}},
|
|
89
|
+
"inverse": {"tool": "write_file",
|
|
90
|
+
"args": {"path": "$args.path", "content": "$capture.value"},
|
|
91
|
+
"expect": "$args.content"},
|
|
92
|
+
"reason": "writes capped at 2, under out/ only, pre-state captured"
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"id": "quarantine-moves",
|
|
96
|
+
"match": {"server": "filesystem", "tool": "move_file"},
|
|
97
|
+
"effect": "destructive",
|
|
98
|
+
"disposition": "quarantine",
|
|
99
|
+
"reason": "moves need a human"
|
|
100
|
+
}
|
|
101
|
+
]
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Put wardn between your client and the server — the client config points at
|
|
106
|
+
wardn, wardn spawns the real server:
|
|
107
|
+
|
|
108
|
+
```json
|
|
109
|
+
{
|
|
110
|
+
"mcpServers": {
|
|
111
|
+
"filesystem": {
|
|
112
|
+
"command": "wardn",
|
|
113
|
+
"args": ["proxy", "--policy", "policy.json", "--ledger", "run.jsonl",
|
|
114
|
+
"--", "npx", "-y", "@modelcontextprotocol/server-filesystem",
|
|
115
|
+
"/work"]
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
The agent now reads freely, gets its third write refused loudly (an error
|
|
122
|
+
result naming the rule and the bound — the agent can read why and adapt),
|
|
123
|
+
and blocks on `move_file` until someone, in any terminal, runs:
|
|
124
|
+
|
|
125
|
+
```console
|
|
126
|
+
$ wardn review list --queue run.jsonl.queue.json
|
|
127
|
+
q6 move_file {"source":"/work/report.txt","destination":"/work/old.txt"} rule=quarantine-moves — moves need a human
|
|
128
|
+
|
|
129
|
+
$ wardn review approve q6 --queue run.jsonl.queue.json --by phiwir
|
|
130
|
+
q6: approve by phiwir
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Afterwards, the run is a file:
|
|
134
|
+
|
|
135
|
+
```console
|
|
136
|
+
$ wardn replay --policy policy.json --ledger run.jsonl
|
|
137
|
+
replayed 8 attempt(s) — every decision reproduced by this policy
|
|
138
|
+
|
|
139
|
+
$ wardn revert --ledger run.jsonl -- npx -y @modelcontextprotocol/server-filesystem /work
|
|
140
|
+
applied 2 undo(s) — revert ledger: run.jsonl.revert.jsonl
|
|
141
|
+
|
|
142
|
+
$ wardn diff approved/run.jsonl run.jsonl && echo "no drift"
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Several servers can sit behind one gate — `wardn proxy --servers
|
|
146
|
+
servers.json` takes a file in the same shape as an MCP client config, and
|
|
147
|
+
rules match on the server's name. See [docs/concepts.md](docs/concepts.md)
|
|
148
|
+
for what multi-server mode honestly does and doesn't proxy.
|
|
149
|
+
|
|
150
|
+
## What wardn is not
|
|
151
|
+
|
|
152
|
+
- **Not a sandbox.** wardn is a policy gate in front of cooperative
|
|
153
|
+
transports. A hostile server, or a client that bypasses the proxy, is out
|
|
154
|
+
of scope — and argument constraints are string constraints:
|
|
155
|
+
`path_prefix: "out/"` happily matches `out/../secret.txt`. wardn does not
|
|
156
|
+
resolve paths; that is a sandbox's job. Never treat a wardn policy as an
|
|
157
|
+
isolation boundary.
|
|
158
|
+
- **Not content safety.** wardn governs side-effects, not text. Prompt
|
|
159
|
+
injection defense is a different layer.
|
|
160
|
+
- **No LLM anywhere in the enforcement path.** A decision is a pure
|
|
161
|
+
function of policy + request + recorded approvals. Ever.
|
|
162
|
+
- **No measured spend.** `max_spend` refuses on arithmetic over *declared*
|
|
163
|
+
costs (`cost_per_call`), never on billing APIs — wardn cannot observe
|
|
164
|
+
real spend and doesn't pretend to.
|
|
165
|
+
|
|
166
|
+
## Docs
|
|
167
|
+
|
|
168
|
+
- [docs/concepts.md](docs/concepts.md) — rules, dispositions, bounds,
|
|
169
|
+
classification, quarantine, capture/revert, and the determinism claim
|
|
170
|
+
stated precisely (including its honest asterisks).
|
|
171
|
+
- [docs/policy-format.md](docs/policy-format.md) — `wardn-policy/1`,
|
|
172
|
+
normative.
|
|
173
|
+
- [docs/ledger-format.md](docs/ledger-format.md) — `wardn-ledger/1`, the
|
|
174
|
+
compatibility contract. The ledger format and the proxy CLI are the two
|
|
175
|
+
things wardn promises stability for.
|
|
176
|
+
- [docs/ci.md](docs/ci.md) — the CI story: budgets as drift tripwires,
|
|
177
|
+
`wardn diff` as the gate.
|
|
178
|
+
- [docs/embedding.md](docs/embedding.md) — the engine API for non-MCP
|
|
179
|
+
embeddings: `govern_call` in front of any tool-calling boundary.
|
|
180
|
+
|
|
181
|
+
## Family
|
|
182
|
+
|
|
183
|
+
Sibling to [agentrec](https://github.com/Pi-Wi/agent-rec) (records what
|
|
184
|
+
models *say*) and [mendr](https://github.com/Pi-Wi/mendr) (repairs what data
|
|
185
|
+
*got wrong*) — wardn governs what agents *do*. Same ethos: declared,
|
|
186
|
+
bounded, recorded; the record is the product; no LLM anywhere in the
|
|
187
|
+
enforcement path.
|