Metadata-Version: 2.4
Name: edward-guard
Version: 0.2.0
Summary: External control plane for AI coding agents: deterministic rules + local semantic scorer, audit trail, human-resumable interventions.
Author: Edward contributors
License: MIT
Keywords: ai,agent,guardrails,safety,observability,cost-control
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Software Development :: Quality Assurance
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

<div align="center">

# Edward

**An external control plane for AI coding agents — deterministic guardrails, a local semantic scorer, and interventions you can resume.**

[![CI](https://img.shields.io/github/actions/workflow/status/VeridicalTech/Edward/ci.yml?branch=main&label=CI&logo=github)](https://github.com/VeridicalTech/Edward/actions/workflows/ci.yml)
[![PyPI](https://img.shields.io/pypi/v/edward-guard?color=blue)](https://pypi.org/project/edward-guard/)
[![python](https://img.shields.io/badge/python-3.11%2B-blue)](pyproject.toml)
[![license](https://img.shields.io/badge/license-MIT-green)](LICENSE)
[![deps](https://img.shields.io/badge/runtime%20deps-0-brightgreen)](#why-zero-dependencies)

*Agents fail quietly. Edward notices.*

</div>

<div align="center">
<img src="docs/gif/intervention.gif" alt="Edward live intervention: passive stall detected, agent cancelled at 3,589 tokens" width="780">
<br><em>Live run: a looping agent is stopped mid-flight — decision signed, audited, resumable.</em>
</div>

---

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.

```
Agent (Pi / Codex / custom)
    │ events
    ▼
Canonical Event Schema   ← normalizes tool names to capabilities
    │
    ▼
State Engine             ← materializes cross-turn agent state
    │
    ▼
Trigger Rules [FROZEN]   ← deterministic safety + convergence checks
    │
    ├─ HARD_CONSTRAINT ──→ Edward: BLOCK (scorer cannot override)
    │
    └─ SOFT_DECISION ──→ Local scorer ──→ Policy Resolver
                              │                │
                              └────────────────┘
                                       │
                                       ▼
                                 Control Kernel
                                       │
                                       ▼
                              PAUSE / CANCEL / RESUME
```

## 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](https://github.com/glo26/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 |
| HybridGuard (paper) | 75.9% | 44.4% | 0.40 | — |
| **Edward contract probe (local 4B)** | 57.4% | 20.4% | **0.79** | **~$0.00002** |
| **Edward rules only** | 7.4% | **1.9%** | — | 0 |
| 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. Adding a **local 4B scorer with evidence-grounded task-contract probes** and asymmetric temporal confirmation lands in LLMJudge-tier timing territory at zero marginal cost. Full measurement series and reproduction commands: [BENCHMARK.md](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 | sh`, … |
| 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

```bash
pipx install edward-guard            # zero dependencies, Python 3.11+

edward doctor                        # environment checks
edward demo                          # self-running proof: 6 failure scenarios, PASS/FAIL

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
```

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 (a 4B model on your GPU box is
plenty — see [deploy/](deploy/) for the team-LAN topology). Scorer down?
Edward logs a warning and runs rule-only. It stays protective.

**v0.2.0 highlights**

- **Signed evidence receipts** — every audit record is Ed25519-signed into a
  hash chain (pure stdlib, RFC 8032 vectors); `edward verify` proves
  tamper-evidence offline. Publish your public key; anyone can check.
- **Human approval loop** — `--wait-approval 300` sends Resume/Kill links to
  Slack (or stderr) and waits; PAUSE becomes a decision, not a dead end.

<div align="center">
<img src="docs/gif/resume_verify.gif" alt="resume from audit + offline receipt verification" width="780">
</div>

## 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.

## 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

- [x] v0.1.1 on PyPI, CI on three platforms
- [x] StepShield integration with paper-aligned EIR metrics
- [ ] Robustness suite as `edward eval --suite robustness`
- [ ] Scorer fine-tune (targets FPR; data flywheel from audit logs)
- [ ] Cloud fleet console (team tier)

## Contributing

Deterministic layer stays deterministic: trigger defaults are FROZEN, and
behavior-affecting changes require re-running the benchmark gate. See
[CONTRIBUTING.md](CONTRIBUTING.md).

## License

[MIT](LICENSE) — © 2026 Edward contributors
