Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
218 changes: 80 additions & 138 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,95 +1,98 @@
# Cogitator

Cogitator is a Rust harness for deterministic, tamper-evident records of agent/tool execution.
**A Rust harness that turns an agent run into a tamper-evident, independently verifiable record.**

[![CI](https://img.shields.io/github/actions/workflow/status/noctem-o/cogitator/ci.yml?branch=main&label=CI&style=flat-square)](https://github.com/noctem-o/cogitator/actions/workflows/ci.yml)
[![License: Apache-2.0](https://img.shields.io/badge/License-Apache--2.0-blue.svg?style=flat-square)](LICENSE)
[![Rust](https://img.shields.io/badge/Rust-stable-orange.svg?style=flat-square)](https://www.rust-lang.org/)
[![Release](https://img.shields.io/github/v/release/noctem-o/cogitator?style=flat-square)](https://github.com/noctem-o/cogitator/releases)
[![Protocol](https://img.shields.io/badge/protocol-draft-informational?style=flat-square)](spec/COGITATOR_WITNESS_PROTOCOL.md)
[![Threat model](https://img.shields.io/badge/threat%20model-design%20notes-informational?style=flat-square)](#design-notes--threat-model)

**Navigation:** [Quick start](#quick-start) · [Verification model](#verification-model) · [Policy interception](#policy-interception) · [Protocol](spec/COGITATOR_WITNESS_PROTOCOL.md) · [Threat model](#design-notes--threat-model) · [Development](#development)
Cogitator records what an agent did, what it asked to do, and what a policy stopped it from doing — then seals the run under a single cryptographic fingerprint, the **witness root**. Anyone holding the run bundle can recompute that fingerprint and detect whether the record was altered after the fact.

Each run emits a witness root, a replayable run bundle, and a policy-aware tool transcript. The witness root is computed from canonical witnessed semantics (not from report files), and blocked/phantom operations are recorded explicitly.
## The idea in 60 seconds

## Why it exists
Most agent "audit trails" are ordinary log files: mutable, easy to trim, and written by the same system you are trying to audit. Cogitator treats the run itself as evidence:

Most agent audit trails are mutable logs plus summaries written after execution. That is useful for debugging, but weak as evidence.
1. Every agent step and tool call is serialized as canonical JSON (sorted keys, integer-only numbers, byte-stable output).
2. Before any tool call is dispatched, a policy gate returns a verdict: **allow**, **block**, or **phantom** (observe the request without executing it). Blocked and phantom calls are recorded as first-class entries — refusals are part of the evidence, not silently dropped.
3. Each event is folded into a domain-separated BLAKE3 hash chain. The final digest is the witness root.
4. A verifier recomputes the chain from the recorded semantics. One changed byte in any committed field produces a different root.

Cogitator commits the execution boundary before tool effects become the only source of truth. It records what the agent requested, what was executed, and what was intercepted.
Timestamps, hostnames, toolchain versions, thread counts, and other environment details are recorded as *provenance*, deliberately **outside** the commitment — so the same logical run hashes identically across machines.

The project is a verifier/audit substrate. It is not a full agent framework and it does not claim to solve runtime trust on its own.
```text
Agent step
-> tool request
-> policy gate
-> allow: executed ToolCall
-> block/phantom: PhantomEntry (recorded, not executed)
-> canonical JSON witness events
-> domain-separated BLAKE3 chain
-> witness root
```

## Scope: what this is, and is not

**It is** the reference implementation of the [Cogitator Witness Protocol](spec/COGITATOR_WITNESS_PROTOCOL.md): a recorder, a pre-call policy gate, and a verifier, plus deterministic fixtures that exercise and pin the protocol.

## What you get
**It is not** an agent framework, and it does not yet ship a drop-in integration for external agents. The agents and tools it currently drives are deterministic stand-ins:

| Capability | What it means in practice |
- `clawdbot` — a small scripted demo agent.
- `ordeal` — a fixed 50-task conformance suite with stubbed tool responses. It guards the protocol against accidental change (its witness root is pinned as a golden in CI); it is **not** a benchmark and passing it says nothing about real agent quality.
- Tool calls are answered by deterministic stubs, and the `llm.generate` backend is a stub. No external model or service is contacted.

Wiring a real agent in today means driving the Rust APIs (`ToolTranscript`, the witness encoding) from your own code.

**What a matching root proves — and what it doesn't:**

| | |
|---|---|
| Witness root | Domain-separated BLAKE3 commitment over witnessed metadata, agent trace entries, and tool/phantom operations. |
| Strict canonical witnessed bytes | Deterministic canonical JSON subset for committed bytes (JCS-style, integer-only I-JSON-oriented subset). |
| Tool transcript | Executed calls with outcomes and faults, plus policy digest when present. |
| Phantom entries | Blocked/phantom tool requests are recorded and committed instead of silently dropped. |
| Bundle verifier | Path confinement + artifact self-consistency checks on bundle contents. |
| Replay/drift reports | Deterministic diagnostics for fixture/replay mismatch analysis (report-only artifacts). |
| Provenance split | Provenance is recorded separately and excluded from witness bytes. |
| Nix provenance (optional) | `nix_provenance.json` is diagnostic/provenance-only and does not change witness root. |
| Proves | The bundle's committed semantics (metadata, agent trace, tool/phantom operations) are exactly what the root commits to — nothing was edited, reordered, truncated, or injected after sealing. |
| Does not prove | That the run actually happened at a given time. A bundle and its root can be regenerated wholesale; to prove occurrence, anchor the root externally (publish it, timestamp it, or compare against a root you received out-of-band via `--expect`). |
| Does not prove | Hardware or runtime integrity of the machine that produced the bundle, or determinism of nondeterministic model backends. |

## Quick start

`--clean` now refuses dangerous output paths (such as filesystem root, home, repo root, `.git`, and symlinked output dirs).
`--nix-provenance=auto|on` may execute bounded local Nix commands for diagnostic provenance (closed stdin, timeout-bounded, output-capped); it does not affect witness root computation.

```bash
cargo build --release
```

Run the drift demo (four scenarios: baseline, regressed, with and without injected faults) and verify the resulting bundle:

```bash
cargo run --release -- demo drift --seed 42 --threads 1 --fault-profile stress --out-dir demo_out --clean
```

```bash
cargo run --release -- verify --witness demo_out/drift/baseline_faults
cargo run --release -- verify --witness demo_out/drift/baseline_faults --recompute-witness-root
```

Expected result:

- verify reports no bundle issues
- witness recompute reports `matched=true`
Expected: verification reports no bundle issues, and the recompute reports `matched=true`.

## Record and verify a fixture-grade agent run
Record and verify a fixture run:

```bash
cargo run --release -- run --agent ordeal --runs 1 --out-dir out --clean
# NOTE: `ordeal` is a deterministic conformance fixture, not a scientific benchmark.
```

```bash
cargo run --release -- verify --witness out/run_0000
```

```bash
cargo run --release -- verify --witness out/run_0000 --recompute-witness-root
```

## Policy interception
Notes:

Cogitator evaluates policy before dispatch. First matching rule wins.
- `--clean` refuses dangerous output paths (filesystem root, home, repo root, `.git`, symlinked dirs).
- `--nix-provenance=auto|on` may run bounded local Nix commands (closed stdin, 3s timeout, capped output) to capture diagnostic provenance. This never affects the witness root.

- `allow`: call is executed and recorded as a real tool entry.
- `block`: call is denied; a blocked phantom entry is recorded.
- `phantom`: call is intercepted; a phantom entry is recorded.
## Policy interception

If no policy file is provided, current behavior is allow-all.
Policy is evaluated before dispatch; the first matching rule wins. When a policy file is loaded, its SHA-256 digest is committed into the witness root, so the exact policy in force is part of the evidence. If no policy file is present, the current behavior is **allow-all** and no digest is committed.

```toml
schema_version = 1

[[rules]]
id = "trade-budget"
tool_pattern = "trade.*"
tool_pattern = "trade.*" # '*' stays within a dot-segment; '**' crosses segments
history_tool_pattern = "trade.*"
history_max_calls = 2
history_max_calls = 2 # allow calls 1-3, block call 4 onward
verdict = "block"
reason = "trade call budget exceeded"

Expand All @@ -100,97 +103,45 @@ verdict = "phantom"
reason = "observe only"
```

```text
Agent step
-> tool request
-> policy gate
-> allow: executed ToolCall
-> block/phantom: PhantomEntry
-> canonical witness event stream
-> BLAKE3 witness root
```
Verdicts:

## Verification model
- `allow` — the call executes and is recorded as a real `ToolCall`.
- `block` — the call is denied; a `PhantomEntry` with disposition `blocked` is recorded and committed.
- `phantom` — the call is intercepted; a `PhantomEntry` with disposition `phantom` is recorded and committed.

Cogitator verification has three recomputation-backed layers:
## Verifying a bundle

1. **Bundle self-consistency**: manifest paths + diagnostic artifact hashes.
2. **Semantic witness recompute**: recompute root from witnessed semantics.
3. **Anchored verification**: compare against an externally supplied expected root (`--expect`).
Three layers, in increasing strength:

`witness_root.txt` stored in the same bundle is useful for internal consistency checks, but by itself it does not prove original occurrence.
| Layer | Command | What it checks |
|---|---|---|
| Bundle self-consistency | `verify --witness <dir>` | Manifest paths resolve under strict confinement (no absolute paths, `..` escapes, or symlink escapes) and the SHA-256 artifact/bundle hashes match the recorded values. |
| Semantic recompute | `verify --witness <dir> --recompute-witness-root` | The witness root recomputes from the bundle's recorded semantics. Recompute fails closed on incomplete or inconsistent transcripts (missing steps, orphan or duplicate tool calls, trace/transcript mismatches). |
| Anchored verification | `verify --witness <dir> --expect <root>` | The bundle matches a root you obtained from somewhere you trust. This is the only layer that defends against wholesale bundle replacement. |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Document the anchored bundle command with recompute

As documented here, verify --witness <dir> --expect <root> does not actually compare the bundle semantics to the supplied root: verify_cmd returns after drift::verify_witness_bundle whenever --witness is a directory and --recompute-witness-root is absent, so args.expect is never used in that path (src/main.rs:1128-1145). Users following this “only layer” for anchored verification can pass a stale or wrong root and still succeed if the bundle's manifest hashes are self-consistent; the bundle command needs --recompute-witness-root --expect <root>.

Useful? React with 👍 / 👎.


| Check | Command | What it proves | What it does not prove |
|---|---|---|---|
| Bundle self-consistency | `verify --witness <bundle_dir>` | Manifest paths resolve under path confinement and diagnostic hashes match recorded values. | That the run originally occurred. |
| Semantic recompute | `verify --witness <bundle_dir> --recompute-witness-root` | Witnessed semantics recompute to the bundle's expected/co-located root. | External timestamping or publication. |
| Anchored verification | `verify --witness <bundle_dir> --expect <root>` | Bundle semantics match an externally supplied expected root. | Runtime/hardware integrity of the original execution. |
The `witness_root.txt` stored inside a bundle is a convenience for self-checks. It is not external anchoring — treat the first two layers as integrity checks and the third as the actual evidentiary comparison.

## Artifact layout
## Inside a bundle

```text
out/run_0000/
├── meta.json
├── agent_trace.json
├── tool_transcript.json
├── chaos_profile.json
├── drift_report.json
├── hash_chain.txt
├── witness_manifest.json
├── witness_root.txt
├── verify_report.json # after verify
└── nix_provenance.json # optional
├── meta.json # witnessed metadata + uncommitted provenance
├── agent_trace.json # per-step agent entries (committed semantics)
├── tool_transcript.json # executed calls, phantom entries, policy digest
├── chaos_profile.json # deterministic fault-injection schedule
├── drift_report.json # replay/fixture mismatch diagnostics (report-only)
├── hash_chain.txt # human-readable debugging chain (not the root)
├── witness_manifest.json # artifact hashes + bundle hash (diagnostics)
├── witness_root.txt # the BLAKE3 witness root
├── verify_report.json # written by verify
└── nix_provenance.json # optional, provenance-only
```

Manifest artifact paths are bundle-relative and verified under strict path confinement.

Committed vs diagnostic (high-level):

- committed semantics: witnessed metadata, agent trace entries, tool/phantom operations
- diagnostic/self-consistency: manifest artifact hashes, bundle hash, verify/report artifacts

## Commitment boundary

Committed into witness root:

- witnessed metadata (`meta.json` witnessed section)
- canonicalized `agent_trace` entry semantics
- canonicalized executed tool-call witness view
- canonicalized phantom/intercepted witness view
- policy digest when a policy is loaded
- chaos/fault information when represented in committed tool outcomes

Not committed into witness root:

- provenance section of metadata
- external environment attestations
- report-only analytics and verifier output files

## Design notes / threat model
Committed into the witness root: witnessed metadata, agent trace entries, executed tool-call witness views, phantom entries, and the policy digest. Everything else — artifact hashes, bundle hash, drift and verify reports, all provenance — is diagnostic and lives outside the commitment.

Cogitator is good at catching:
## Replay and drift

- post-hoc edits to committed run semantics (when checked against an expected root)
- malformed, incomplete, or inconsistent transcripts during recompute
- path escape attempts in manifest artifact references

Cogitator does not by itself provide:

- proof that a run happened unless root is externally anchored
- hardware/runtime attestation
- deterministic LLM outputs for nondeterministic backends

## Ordeal fixture guardrail

`ordeal` currently executes a fixed 50-task suite with deterministic stubbed tool responses and schema-fingerprint checks. It is valuable as a conformance/tamper fixture and golden-root guard, but it is **not** evidence of general agent robustness or benchmark-grade performance.

## Proves vs does not prove

| Cogitator output | Proves | Does not prove |
|---|---|---|
| Matching witness root under semantic recompute | Bundle semantics match the witnessed commitment boundary. | That the execution was externally anchored in time/public logs. |
| Anchored verification (`--expect`) | Bundle semantics match an externally provided root. | Hardware/runtime integrity of original execution. |
| Passing ordeal fixture run | Deterministic fixture wiring stayed stable (trace/transcript/schema-fingerprint path). | Scientific benchmark validity or real tool-semantic quality. |
A recorded bundle can be replayed (`run --replay <bundle_dir>`): tool responses are substituted from the transcript instead of re-executed, and any divergence between the recorded and replayed runs is localized in `drift_report.json` down to the first mismatching call and field. Deterministic fault injection (`--faults on --fault-profile ci|stress`) is keyed by seed/run/step/call, so fault schedules are identical across runs and never a source of spurious drift.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include --runs 1 in the replay example

The shorthand run --replay <bundle_dir> fails with the default CLI settings because --runs defaults to 5000, and run_agent rejects replay unless the selected run list has exactly one entry (src/main.rs:771-777). Users copying this example will get --replay requires a single --case or --runs 1, so the documented replay command should include --runs 1 or a --case value.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Limit the drift guarantee to transcript changes

This promises that drift_report.json localizes “any divergence” between recorded and replayed runs, but the replay path only calls drift::detect_transcript_drift(expected_transcript, &transcript_record) and extends it with tool mismatches (src/main.rs:980-984); the expected agent_trace.json is loaded only to select the agent and is never compared. If the replayed agent changes its thought/action/final trace while issuing the same tool requests, the semantic root changes but drift_report.json will not report that divergence, so the README should narrow this claim or the code should compare traces too.

Useful? React with 👍 / 👎.


## Development

Expand All @@ -201,25 +152,16 @@ cargo test --locked
cargo run --locked -- ordeal check --golden goldens/ordeal_witness_root.txt
```

## Release / supply chain

Current repository workflows include:

- `cargo-dist` release automation
- GitHub artifact attestations in release workflow
- pinned GitHub Actions enforcement
- no-git build gate checks
- RustSec `cargo audit` checks
CI also enforces SHA-pinned GitHub Actions, a no-git build gate, RustSec `cargo audit`, and the pinned ordeal golden root. Releases use `cargo-dist` with GitHub artifact attestations.

See workflow files for exact enforcement points.
A Nix dev shell is available via `nix develop`.

## Nix

```bash
nix develop
```
## Protocol and further reading

Nix provenance capture is provenance-only and does not alter witness root computation.
- [Witness protocol specification](spec/COGITATOR_WITNESS_PROTOCOL.md) — schema versions, commitment boundary, verification rules. Status: draft, intentionally implementation-coupled.
- `schemas/` — JSON Schemas for the bundle artifacts.
- `replay/run_0000/` — a checked-in example bundle.
- `main.pdf` — design write-up of the commitment model and determinism experiments.

## License

Expand Down
Loading