Curtis AI Audit — green C ring with a check
# Curtis AI Audit **Local-first audit trail for AI agent activity in your Obsidian vault.** Every agent inference gets an append-only markdown receipt — a journal you keep on your own agents, not a forensic system, and honest about the difference (see [SPEC.md](./SPEC.md)). Speaks OM-1 (Obsidian Messenger): agents, other plugins, or a shell one-liner can all file receipts — see [INTEGRATION.md](./INTEGRATION.md). The messenger plugin is *not* required — the audit inbox is just a file. ## What it does - **Receipts** — one `audit.v1` block per agent action: who, what, when, model, duration, tokens, status, plus a rendered human summary. - **Per-day ledger** — append-only shards under `Audit/ledger/YYYY-MM-DD.md`. Envelope = the receipt as canonical JSON; body = the summary. Readable in Obsidian, parseable by anything. All day boundaries are UTC — a 9pm ET action lands in tomorrow's shard, and `Open today's ledger` means UTC-today (SPEC §2). - **Ingest** — polls the OM-1 audit inbox (`Messenger/audit/inbox.md`) with proper cursor semantics; junk is rejected, redeliveries are deduped. - **Agent hooks** — `Install agent audit hooks` writes vault-scoped configs (Claude Code, OpenCode) so local agents file their own receipts through the bundled writer (`bin/curtis-audit-file.mjs`); Codex and Gemini get manual recipes. Metadata only — tool input content is never recorded. Source installs only: the writer ships with the repo, not with plugin releases (Paths A and B work everywhere). - **Direct API** — in-process plugins can skip the bus: `window.curtisAudit.record({ agent, model, tokens, ... })`. - **Query** — the read side: `window.curtisAudit.query({ agent, status, since, ... })` for status bars, dashboards, and other plugins. - **Verify** — detects malformed blocks, duplicate ids/sequences, reordered shards, and per-agent sequence gaps (deletions leave holes; no crypto, no false promises). One writer per vault: a vault open on two synced devices at once produces duplicate-sequence errors without any corruption (SPEC §5). - **Digest** — a rolling `Audit/digest.md`: counts, statuses, tokens, durations, a per-day breakdown, errors. - **Retention off by default** — an audit trail that silently ages out is a policy contradiction. Opt in if you want it; each prune is itself recorded as a receipt, so the gaps it leaves are self-explaining. ## Install From a [release](../../releases): download `main.js`, `manifest.json`, and `styles.css` into `/.obsidian/plugins/curtis-ai-audit/`, then enable the plugin in Obsidian (Settings → Community plugins). Not distributed via the Obsidian community directory; [BRAT](https://github.com/TfTHacker/obsidian42-brat) can track this repo for updates. From source: clone this repo into `/.obsidian/plugins/curtis-ai-audit/`, run `npm install && npm run build`, and enable it in Obsidian. The receipt writer alone (no Obsidian needed) — from a checkout, run `node install.mjs` to put a `curtis-audit` command on your PATH. ## File a receipt from anything Any shell, any agent, no plugin required: `echo '{"action":"inference","summary":"..."}' | node bin/curtis-audit-file.mjs --agent my-agent --inbox /Messenger/audit/inbox.md`. Copy-paste recipes for Claude Code, Codex, Gemini CLI, and raw shell are in [INTEGRATION.md](./INTEGRATION.md). ## Usage Ribbon icon or commands: | command | what it does | | ----------------------- | ---------------------------------------------- | | Check audit inbox now | poll + ingest, with a summary notice | | Verify ledger | integrity scan; details to console | | Write digest | (re)write `Audit/digest.md` and open it | | Prune ledger now | manual retention pass (needs retention > 0) | | Open today's ledger | open/create today's shard | | Install agent audit hooks | write vault-scoped hook configs (Path C) | | Uninstall agent audit hooks | remove exactly what install added | Other plugins (same window): ```ts const receipt = await window.curtisAudit.record({ agent: "curtis-ai-chat", action: "inference", // optional, default "other" model: "gpt-4o", provider: "openai", durationMs: 1234, tokens: { input: 812, output: 341 }, subject: "Notes/question.md", summary: "Answered a question about the spec", }); // also: .poll(), .verify(), .digest(days?), .query(filter) // workspace event: "curtis-audit:receipt" ``` The read side, for dashboards and status bars: ```ts const errors = await window.curtisAudit.query({ agent: "curtis-ai-chat", status: "error", since: "2026-10-01", // bounds apply to `recorded` (local write time) order: "desc", // newest first (the default) limit: 20, }); ``` Anything that can append to a file (other device, script, agent): ``` {"action":"inference","model":"gemini-2.5-pro","summary":"Summarized a paper"} ``` See [INTEGRATION.md](./INTEGRATION.md) for wiring up `curtis-ai-chat`. ## Testing ``` npm test # unit tests, in-memory vault, no Obsidian needed npm run build # typecheck + bundle to main.js ``` ## Status v0.2.4 — the local-first tier, deliberately. Tamper-evidence, retention enforcement, and reporting are the next tier up and are not pretended here. Requires Obsidian 1.13.0+ (declarative settings). ## License Source-available under the [Business Source License 1.1](./LICENSE) (BUSL-1.1) © Jordan Newell. Copying, modifying, and redistributing the code is always allowed. **Production use is permitted for personal and noncommercial purposes; any commercial use requires a separate written license from the author** — open a GitHub Discussion to inquire. Each release converts to MIT four years after it ships (Change Date: 2030-10-12). Releases up to and including v0.2.2 were published under MIT and remain MIT.