Edward
The supervisor for coding agents that run when nobody's watching.
CI jobs, overnight runs, scheduled batches — unattended agents fail quietly: the same broken test retried 40 times, rm -rf on the wrong directory, budget burned at 3am with no one to hit stop. Edward watches the event stream, builds a picture of what the agent is actually doing across turns, and intervenes when the picture stops looking right — PAUSE (resumable), CANCEL, or BLOCK — with a signed receipt for every decision.
PyPI · Benchmark · Contributing · Changelog
Agents fail quietly. Edward notices.
Live run: a looping agent is stopped mid-flight — decision signed, audited, resumable.
Agents fail quietly. They retry the same broken test 40 times, burn $8 in tokens on a loop, run rm -rf on a database directory, and write to files they were never supposed to touch. The agent doesn't know it's failing — from its perspective, it's still trying.
Edward sits between the agent and its runtime. It watches the event stream, builds a picture of what the agent is actually doing across turns, and intervenes when the picture stops looking right.
What it is — an independent runtime supervisor for unattended coding agents: cross-turn runaway detection (repeats, stalls, budget burn, out-of-scope writes), resumable PAUSE / CANCEL / BLOCK, and a signed, lookable decision trail.
What it is not — not a code-correctness guarantee, not a sandbox, and not a promise to catch every runaway. It owns the process (did it go wrong? stop it, keep the evidence), not the logic (is the final diff right?). Measured detection ranges and known limits are in BENCHMARK.md — including the experiments that failed.
Why not just if/else?
A watchdog (error_count > 5 → stop) looks at individual events. The failure modes that actually kill long-running agents don't show up in any single event — they emerge from the shape of the trajectory over time. We tested this directly on held-out data (seed 137, frozen config):
| Scenario | Watchdog | Edward (State Engine) | Watchdog ctx | Edward ctx |
|---|---|---|---|---|
| budget bleed | 20% | 100% | 2,322 | 409 |
| infinite loop | 0% | 100% | 6,084 | 409 |
| convergence stall | 0% | 100% | 1,442 | 409 |
| dangerous command | 75% | 100% | 522 | 434 |
Watchdogs miss temporal failure modes entirely because they have no memory across turns. Edward maintains a sliding window of tool calls, tracks file modifications, and computes elapsed time — none of which fit in a single-event check.
Measured, not claimed
Edward validates itself against StepShield (NeurIPS 2026), the first benchmark treating intervention timing as a first-class metric (9,429 trajectories, step-level labels, 216 held-out):
| Detector | Recall | FPR (clean) | EIR₃ (timing) | Cost / decision |
|---|---|---|---|---|
| LLMJudge (GPT-4.1-mini, paper) | 95.4% | 5.6% | 0.89 | GPT-4.1-mini price |
| Edward contract probe (Jev 1.13) | 59.3% | 10.2% | 0.91 | ~$0.0001, one batched call |
| Edward contract probe (local 4B) | 58.3% | 17.6% | 0.78 | ~$0.00002, on your GPU |
| Edward rules only | 7.4% | — | — | 0 |
| HybridGuard (paper) | 75.9% | 44.4% | 0.40 | — |
| StaticGuard 847 rules (paper) | 86.1% | 77.8% | 0.23 | — |
The deterministic layer alone is quantitatively blind to content-semantic violations (7.4%) — the "silent corruption" gap — while keeping the best false-positive rate. Swapping the judgment backend changes the trade, not the architecture: the local 4B keeps events on your network at zero marginal cost; TypeSafe Jev lifts EIR₃ above the paper's GPT-4.1-mini judge (0.91 vs 0.89) with 42% fewer false positives, one batched calibrated call per probe battery. Trajectory-level recall is the shared frontier for small judges — we measure and publish it rather than claim it away. Full series, raw logs, reproduction commands: BENCHMARK.md.
What it detects
Eight trigger rules, tuned on a dev split (seed 42) and frozen for held-out evaluation (seed 137):
| Signal | Fires when |
|---|---|
| Error rate | > 40% over the last 8+ calls, no recovery signal |
| Retry count | ≥ 3 retries of the same thing |
| Token budget | > 80% consumed |
| Convergence stall | > 600s + > 5 turns, no completion |
| Passive stall | 12 consecutive reads, 0 writes |
| Dangerous command | rm -rf, sudo, git push --force, `curl |
| Scope violation | writes outside the allowed path prefixes |
| Silent corruption risk | ≥ 10 consecutive file writes with zero shell verification |
Policy packs make the knobs yours: conservative / balanced (= FROZEN defaults) / aggressive, as TOML or JSON.
Quickstart
pipx install edward-guard # zero dependencies, Python 3.11+
edward init # 3 answers -> starter policy + next commands
edward doctor # environment checks
edward demo # self-running proof: 6 failure scenarios, PASS/FAIL
edward demo --live-scorer --offline # same proof through the full scorer pipeline (heuristic stub, no GPU)
edward wrap -- pi "fix the flaky test" # full monitoring + intervention
edward wrap --no-scorer -- python my_agent.py # any command, rule-only
edward wrap --scope ./src --auto-resume 60 -- pi "task" # scoped writes, auto-resume
edward wrap --agent claude -- claude -p "task" # native claude stream supervision (resumable)
edward wrap --agent codex -- codex exec "task" # native codex stream supervision (resumable)
edward audit && edward replay # aggregates, then the session as a shareable HTML receipt
edward mcp # control plane as an MCP server (stdio, in-loop supervision)
Local semantic scorer (one GPU, three commands). Rule-only is protective; semantic judgment needs a scorer endpoint. The Ollama path — direct readout, real probabilities, ~0.2 s/decision:
ollama pull qwen3.5:9b
edward-scorer-server --upstream http://127.0.0.1:11434/v1 \
--model qwen3.5:9b --port 8001 --style ollama
export EDWARD_SCORER_URL=http://127.0.0.1:8001
edward doctor --probe # one real scored round-trip — expect OK
(or edward init --scorer ollama writes the policy with this URL and prints
the same recipe.) Measured on an RTX 3070 Ti: this stack lands within noise
of the tuned LAN service on the 216-trajectory holdout —
BENCHMARK.md has the full ladder and reproduction.
$ edward demo
policy: balanced trials/scenario: 3
scenario expect result latency
-----------------------------------------------------
normal no-fire clean — ok
transient_failure no-fire clean — ok
infinite_loop fire 100% detected 8.0 ok
budget_bleed fire 100% detected 12.0 ok
dangerous fire 100% detected 4.7 ok
stall fire 100% detected 4.0 ok
PASS in 0.0s (deterministic rules frozen defaults; scorer off)
Real output, not a mock — six failure scenarios against the frozen rule set,
plus the two clean controls. Wraps any subprocess: Pi, Codex, claude -p,
CI jobs, plain scripts. --agent auto|generic|pi picks the adapter (pi gets
native RPC; the registry is extensible via edward.adapters entry points).
Interventions are resumable, not fatal: PAUSE exits with code 75, pins the
agent session, and edward wrap --continue picks the same session back up
from the audit log. CANCEL / BLOCK exit 76. Audit lands in
~/.edward/audit.jsonl — including an estimated avoided-spend per intervention.
The scorer is optional and always advisory. Point EDWARD_SCORER_URL at
any local OpenAI-compatible scoring endpoint. The reference server ships two
styles: --style openai (generic chat completion, one-hot reference verdicts)
and --style ollama — a SemIf-style
direct readout over Ollama: one forward pass, no generated text, real
softmaxed probabilities over the option letters in ~0.2 s. On an 8 GB GPU,
qwen3.5:9b + --style ollama lands within noise of the tuned LAN service
(56.5%/19.4% vs 58.3%/17.6% recall/FPR on the 216-trajectory holdout — see
BENCHMARK.md); the express gate (≥0.98) is the recommended
operating point (85.7% precision at 7.4% FPR). See deploy/ for the
team-LAN topology. Scorer down? Edward logs a warning and runs rule-only.
It stays protective.
Pluggable scorer backends. EDWARD_SCORER_BACKEND selects where judgments
come from: endpoint (default — the LAN scorer server above), jev (TypeSafe
Jev — all probes batched into one calibrated call; set TYPESAFE_API_KEY), or
heuristic (deterministic marker stub for offline demos and tests). edward doctor shows the active backend. All backends are advisory and share the same
circuit breaker.
Edward + Jev — who decides what. With the jev backend
(edward/backends.py), one Jev call receives the full
cross-turn state (error trends, write streaks, budget) and returns calibrated
judgments — should this trajectory continue, pause, or escalate, and how
confident is that? Deterministic code owns everything irreversible: blocklists,
scope checks, budget caps, intervention execution, and the signed receipt chain.
Jev's confidence gates routing, never actions. Known limitation: the local-4B
and Jev paths trade recall differently (see BENCHMARK.md for
measured numbers and reproduction commands).
v0.3.0 highlights
- Pluggable scorer backends —
EDWARD_SCORER_BACKENDselectsendpoint(your LAN 4B),jev(TypeSafe Jev: all probes in one batched calibrated call), orheuristic.edward demo --live-scorer --offlineruns the full rules+scorer pipeline with no GPU and no API key. - Agent adapter registry —
--agent auto|generic|pi|<plugin>; third-party adapters join via theedward.adaptersentry-point group. - Process-tree interventions — pause/kill terminate the whole agent subtree (POSIX process groups, Windows CTRL_BREAK + taskkill /T) — no more orphaned npm/python children.
- v0.2.0 added signed evidence receipts (Ed25519 hash chain, offline
edward verify) and the human approval loop (--wait-approval 300).
See it work
Resumable interventions on real CLIs — the agent stalls, Edward pauses it, auto-resumes the same conversation with a supervisor note:
$ edward wrap --agent claude --auto-resume 5 -- claude -p "build the thing"
[TRIGGER] Output inactivity: no stdout for 90s
[decision] PAUSE (rule — inactivity watchdog)
[EDWARD] exit code 75. Resume: edward wrap --continue -- <command>
auto-resume 1/3 in 5s (session edward-7f6f3752)
# resumed run: claude --resume 1e316314-... -p "build the thing
# [edward supervisor] This session was paused by the external control plane...
Works for pi (native RPC), claude (--session-id/--resume with a
deterministic UUID) and codex (exec resume --last) — fake-agent E2E
included in the test suite (rm -rf inside a claude stream → intercepted,
exit 76).
edward replay — every audited session becomes a self-contained HTML
receipt page (dark, dependency-free, shareable as a single file):
$ edward replay
replay written: /root/.edward/replay-7f6f3752.html (session edward-7f6f3752)
Per session: command, timeline, every intervention with trigger / source / scorer verdict / MSS snapshot / estimated avoided spend, signed-trail note.
edward mcp — the control plane inside the agent loop. Any MCP client
(including Claude Code and opencode) gets supervision tools:
{ "mcpServers": { "edward": { "command": "edward", "args": ["mcp"] } } }
// agent reports each tool execution; Edward answers with the decision:
{"decision": {"action": "REQUEST_HUMAN_APPROVAL",
"reason": "Dangerous command detected: rm -rf /tmp/victim"},
"state": {"dangerous_commands": 1, "total_tool_calls": 42, "...": "..."}}
Deterministic layer owns irreversible calls; judge_action adds an advisory
semantic verdict when a scorer is configured. Zero dependencies — the server
is stdlib JSON-RPC over stdio.
Unattended runs & CI
Edward is built for agents nobody is watching. In a workflow:
- uses: VeridicalTech/Edward@main
with:
command: 'pi "fix the flaky test"'
policy: balanced # or a path to your policy TOML
max-seconds: 1800
# scorer-url: http://gpu-box.lan:8000 # optional local 4B judge
The command runs under supervision. If Edward intervenes — loop, stall,
budget bleed, dangerous command — the job fails with a ::warning and the
Ed25519 receipt trail is uploaded as an artifact, so the 3am failure comes
with evidence instead of a silent $12 bill. edward verify runs in-pipeline:
tampered audit logs fail the build.
Why zero dependencies?
Edward's control loop runs stdlib-only: it must boot on any Python 3.11+ box, inside any container, in front of any agent — including air-gapped ones. The heavy lifting (scoring) is delegated to a separate local service, which you own and can swap (4B quantized, bigger, whatever) without touching the control plane.
Compatibility
| dimension | supported | verified by |
|---|---|---|
| OS | Linux, macOS, Windows | CI matrix (incl. POSIX killpg + Windows CTRL_BREAK process trees) |
| Python | 3.11 – 3.13 | CI matrix |
| agents | any subprocess (edward wrap -- <cmd>); builtin claude / codex / pi adapters (native stream translation + resume parity: PAUSE -> --resume/exec resume --last with a supervisor note); MCP agents via edward mcp; more via edward.adapters entry points |
edward demo, adapter test fixtures from live CLI output |
| scorer backends | endpoint (any OpenAI-compatible URL), jev (TypeSafe Jev / Kev — same wire API), heuristic (offline), disabled |
edward doctor |
| local scorers | Ollama (--style ollama), vLLM (--style vllm, allowed_token_ids — every probability is a raw logprob), llama.cpp / LM Studio (--style openai) |
BENCHMARK.md local rows |
| GPU | none required (rule-only + heuristic); 8 GB runs qwen3.5:9b direct readout | RTX 3070 Ti measurements |
| containers | deploy/docker-compose.yml — CPU-only by default, --profile gpu adds Ollama + scorer |
compose build + containerized wrap |
Repository map
edward/ the package
cli.py wrap / demo / eval / audit / doctor
engine.py ControlPlane: events → triggers → scorer → decision → audit
state_engine.py cross-turn agent state
triggers.py 8 rules, policy-parameterized (defaults FROZEN)
scorer_client.py /v1/score client + circuit breaker
stepshield.py external benchmark adapter (EIR metrics)
scenarios.py failure scenario suite (demo/eval source of truth)
benchmark.py 300-trial held-out benchmark
robustness_eval.py 4-dimension robustness attack
BENCHMARK.md full measurement series + reproduction commands
deploy/ team-LAN deployment templates
Status & roadmap
- v0.3.0 on PyPI: scorer backends (endpoint / Jev / heuristic), agent adapter registry, process-tree hardening
- StepShield integration with paper-aligned EIR metrics — including the Jev 1.13 backend row (EIR₃ 0.906)
- Scorer fine-tune — attempted and abandoned under a pre-registered two-strike protocol (post-mortem in BENCHMARK.md)
- Robustness suite as
edward eval --suite robustness - Cloud fleet console (team tier)
Ecosystem
- JevTree — Jev-native probability-tree planner for multi-step decisions. Complementary layers: JevTree reasons about which path to take (candidate expansion, Pareto selection over utility, probability, risk and reversibility); Edward supervises whether the executed path stays safe, in-scope, and convergent (hard constraints, semantic risk, pause/resume, signed receipts). A natural integration: JevTree expands and scores candidates → Edward gates them → JevTree selects → Edward supervises execution → receipts feed the replan.
Building a Jev-ecosystem tool that touches agent execution? Edward's adapter
registry (edward.adapters entry points) and scorer backends are designed as
extension points — open an issue.
Contributing
Deterministic layer stays deterministic: trigger defaults are FROZEN, and behavior-affecting changes require re-running the benchmark gate. See CONTRIBUTING.md.
License
MIT — © 2026 Edward contributors
Metadata
Release files for edward-guard 0.5.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| edward_guard-0.5.1.tar.gz | 79.2 kB | Without provenance | Details for edward_guard-0.5.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| edward_guard-0.5.1-py3-none-any.whl | Python 3 | none | any | Details for edward_guard-0.5.1-py3-none-any.whl |
Total release size: 160.2 kB
Release files / edward_guard-0.5.1.tar.gz
| Download URL | edward_guard-0.5.1.tar.gz |
|---|---|
| Size | 79.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b9812e1f3d616371796d664571cc80008aaf20cff12fae70d0b5510536165511
|
|
BLAKE2b-256 checksum How to use checksums |
3aa0632552519b1fc775fe37bcdf1c94741b721764dc7bd5ed1bcf5f3c64166e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.6
|
Release files / edward_guard-0.5.1-py3-none-any.whl
| Download URL | edward_guard-0.5.1-py3-none-any.whl |
|---|---|
| Size | 80.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
4b31fac53dd9e95b3e67356c3806b7cbbe4cd724381069b9c01767432b7de72f
|
|
BLAKE2b-256 checksum How to use checksums |
69eade0b782d17cd2370b1b3c6ca9b5869eeb63a20d07afee316c6783a0088b3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.6
|
Security for edward-guard 0.5.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.