The project's README from github.com/CharlesHoskinson/anthropies. As one markdown file: README.md.
Restore clean title in Outputs the user already owns.
Apache-2.0. Usable from any LLM coding agent. Not affiliated with Anthropic PBC.
Current release: 1.0.0 DarioCyclovir.
This tool certifies Layer A (Unicode, trailers, banners) and hard-bound C2PA/metadata on files you own. It does not remove Anthropic's keyed text mark.
| Channel | What you get |
|---|---|
| official | unavailable unless ANTHROPIC_DETECT_URL is set. No default URL. No score. |
| c2pa | present / absent / removed / not-applicable. Soft-binding and pixel marks are out of scope. |
| deterministic | Per-kind counts only. Certificate for the bytes we strip. |
| statistical | Best-effort 5-gram overlap. Not an official-detector certificate. |
clean does not remove the keyed text mark. humanize is best-effort. capture fetches a Claude Output you own; it does not watermark. demo prints four channels and never claims official text-kill.
This is the first production release. Package, CLI, and plugin version is 1.0.0.
HTTP GET /health and GET /capabilities keep JSON version "0.3.0". That field is the locked HTTP API version, not the package version. Kernel API is 1.0.0. Sidecar protocol is 1.0.0. The default Compose image tag remains anthropies:0.3.0 to match that HTTP contract.
Pack implementationVersion values stay 0.4.0 unless a later pack ships its own bump.
| Surface | Value |
|---|---|
| Package / CLI / plugin | 1.0.0 |
GET /health version |
0.3.0 |
GET /capabilities version |
0.3.0 |
kernelApiVersion |
1.0.0 |
| Sidecar protocol | 1.0.0 |
| Default Compose image | anthropies:0.3.0 |
What this release includes:
inspect, clean, humanize, init-rewrite, capture, demo, serve/health, /capabilities, /inspect, /clean, /detect, /openapi.jsondocs/COMPATIBILITY.mdWhat this release does not do:
version field off 0.3.0/humanizegit clone https://github.com/CharlesHoskinson/anthropies.git
cd anthropies
pnpm install
pnpm build
pnpm install -g .
Or one-shot: npx anthropies --help. From this repo after pnpm build: node dist/cli.js --help. Version is 1.0.0.
npx anthropies inspect COMMIT_EDITMSG
npx anthropies clean notes.md --in-place
npx anthropies humanize essay.md
npx anthropies init-rewrite
npx anthropies capture --model <allowlisted-id> --prompt "..."
npx anthropies demo
npx anthropies serve
serve binds loopback 127.0.0.1:8765 by default. Remote bind requires an explicit --host. The skill prefers ANTHROPIES_SERVICE_URL (same default) and health-checks first. Official stays unavailable unless ANTHROPIC_DETECT_URL is set.
| Method | Path | Role |
|---|---|---|
GET |
/health |
{ "ok": true, "version": "0.3.0" } |
GET |
/capabilities |
Pack inventory, tools, scorers. JSON version is 0.3.0. |
POST |
/inspect |
Inspect an owned file |
POST |
/clean |
Strip Layer A and hard-bound metadata |
GET, POST |
/detect |
Run detector packs. Not a clean certificate |
GET |
/openapi.json |
OpenAPI 3.0.3 |
There is no HTTP /humanize. Detection is not a clean certificate. Official stays unavailable unless ANTHROPIC_DETECT_URL is set.
Docker (Dockerfile + compose.yaml) is the same service. docker compose up --build publishes 127.0.0.1:8765:8765. The process inside binds 0.0.0.0:8765. The host mapping stays loopback. The image does not install qpdf, exiftool, or c2patool. GitHub Actions runs pnpm test and pnpm build on Ubuntu and Windows.
GET /capabilities lists these ids. Optional packs fail soft when their sidecar or weights are absent.
| Pack id | Channel | Role |
|---|---|---|
anthropies.layer-a |
deterministic | Invisible Unicode, agent trailers, generated banners |
anthropies.c2pa |
c2pa | Hard-bound C2PA / metadata on raster and SVG |
anthropies.pdf |
c2pa | PDF metadata |
anthropies.pdf-tools |
c2pa | PDF structure inspect |
anthropies.html |
c2pa | HTML metadata |
anthropies.md |
c2pa | Markdown metadata |
anthropies.svg-strip |
c2pa | SVG metadata strip |
anthropies.docx |
c2pa | Word OOXML metadata |
anthropies.odt |
c2pa | OpenDocument metadata |
anthropies.xlsx |
c2pa | Excel OOXML metadata |
anthropies.pptx |
c2pa | PowerPoint OOXML metadata |
anthropies.epub |
c2pa | EPUB OPF metadata |
anthropies.raster-strip |
c2pa | Raster provenance metadata. Does not drop AVIF/HEIC pixels |
anthropies.audit-directory |
deterministic | Bounded directory audit |
anthropies.audit-website |
deterministic | Website audit with SSRF defenses |
anthropies.rewrite-stylometry |
statistical | Rewrite observation. Not an official-kill |
anthropies.gemini-synthid |
statistical | Gemini detector adapter. Fail-soft |
anthropies.official |
official | Official detector when ANTHROPIC_DETECT_URL is set |
anthropies.markllm |
statistical | Optional Apache-2.0 wrap. Operator checkout |
anthropies.markdiffusion |
statistical | Optional Apache-2.0 wrap. Operator checkout |
anthropies.ctrlregen |
statistical | Original method code. Operator-supplied weights |
anthropies.image-scoring |
statistical | Optional noncommercial sidecar. Not in the core image |
The default Compose profile is local-only TypeScript core. Licensed and heavy optional packs stay off until you enable one optional pack profile independently, for example docker compose --profile markllm up (or markdiffusion, ctrlregen, image-scoring).
License disposition and redistribution limits apply per profile. image-scoring is optional-noncommercial and is not redistributed inside the core image. markllm / markdiffusion wrap Apache-2.0 upstreams. ctrlregen keeps operator-supplied weights out of the default image.
Kernel ranges, sidecar protocol 1.0.0, pins, and troubleshooting for unavailable or incompatible optional packs are in docs/COMPATIBILITY.md.
humanize is title restoration: a best-effort wording rewrite on a non-origin model so the keyed text mark is no longer the shipped prose. Residual statistical risk remains. It is not an official-kill and does not prove the official Claude text detector will fail.
Default humanize is print-prompt: it prints a rewrite prompt and does not destamp. Run that prompt on a local unmarked model (Llama, Qwen, Mistral, DeepSeek with watermarking off). Do not run it with Claude or Gemini.
# Grok
mkdir -p ~/.grok/skills
ln -sfn "$(pwd)/skills/purge-anthropies" ~/.grok/skills/purge-anthropies
# Claude Code: clean + print-prompt only. Do not rewrite with Claude.
mkdir -p ~/.claude/skills
ln -sfn "$(pwd)/skills/purge-anthropies" ~/.claude/skills/purge-anthropies
# or: claude --plugin-dir "$(pwd)"
In an agent session: /purge-anthropies. Skill: skills/purge-anthropies/SKILL.md. Slash: commands/purge-anthropies.md. Plugin: .claude-plugin/plugin.json.
capture and demo need ANTHROPIC_API_KEY only when the committed allowlist has a model ID. An empty allowlist is valid; then those commands skip live fetch. Live captures land in fixtures/live/ and are gitignored. They are pipeline smoke, not a "this is watermarked" proof.
humanize defaults to print-prompt (no HTTP call). To point it at a local or remote rewrite model, run the interactive setup:
anthropies init-rewrite
This walks you through backend, model, base URL, API key, and remote-allow, then writes ~/.anthropies/config.json. POSIX mode is 0600. Windows uses the profile directory ACLs.
| Setting | Config file key | Env var override | Default |
|---|---|---|---|
| Backend | rewrite.backend |
ANTHROPIES_REWRITE_BACKEND |
print-prompt |
| Model | rewrite.model |
ANTHROPIES_REWRITE_MODEL |
(unset) |
| Base URL | rewrite.baseUrl |
ANTHROPIES_REWRITE_BASE_URL |
http://127.0.0.1:11434 |
| API key | rewrite.apiKey |
ANTHROPIES_REWRITE_API_KEY |
(unset) |
| Allow remote | rewrite.allowRemote |
ANTHROPIES_REWRITE_ALLOW_REMOTE |
false |
Precedence: env var > config file > built-in default. Env vars win for CI/CD and Docker; the config file is for day-to-day interactive use. Non-loopback base URLs require allowRemote: true (or ANTHROPIES_REWRITE_ALLOW_REMOTE=1) — this is a fail-closed guard because the rewrite request sends your full text to the remote server.
| Command | deterministic | c2pa | official | statistical |
|---|---|---|---|---|
inspect |
report present/absent | report present/absent | unavailable unless URL set | unavailable / not-run |
clean |
strip Layer A | strip hard-bound metadata | unavailable unless URL set | not-run |
humanize |
Layer A first | n/a on text | unavailable unless URL set | best-effort rewrite_metric |
init-rewrite |
n/a | n/a | n/a | writes rewrite config only |
capture |
does not detect | n/a | does not detect | n/a |
demo |
run inspect/clean | skipped or certified | unavailable unless URL set | skipped if print-prompt |
inspect exit 1 is driven only by deterministic or c2pa present. Official and statistical never flip inspect to 1 by themselves.
Claude output can carry marks at three different layers: generated wording, file metadata, and commit text. They fail in different ways, and a tool that removes one can leave the other two intact. The diagram follows the text through those layers.
┌─────────────────────────────────────────────┐
YOUR PROMPT ───▶ │ C L A U D E │
│ │
│ next-token distribution │
│ │ │
│ ▼ │
│ tournament sampling ◀── secret key k │
│ │ │
└────────┼────────────────────────────────────┘
▼
┌─────────┐
│ TEXT │ ① keyed watermark (IN the wording)
└────┬────┘
│
┌──────────────┼──────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ .png/.jpg│ │ commit │ │ prose │
│ .svg │ │ message │ │ as-is │
└────┬─────┘ └────┬─────┘ └────┬─────┘
▼ ▼ ▼
② C2PA ③ trailer ① only
signed Co-Authored-By
manifest : Claude
(metadata) (plain text)
① is the hard one. ② and ③ are metadata and text, so a deterministic edit can remove them. ① is the wording itself.
Anthropic's own analogy is the clearest one available. Imagine a board game where each turn you roll a die. Now replace the die with the digits of π, starting from some agreed position. The moves are still random as far as any player can tell. But someone who knows you used π and knows where you started can look at the finished game and work out that the rolls were not dice.
The watermark changes word choice without adding characters or altering meaning. It changes the source of the randomness the model uses to pick among words that were equally good.
Every position gets its own seed, computed from the preceding text and a secret key:
… the court will probably ▓▓▓▓▓
└─────────┬─────────┘ ▲
last H tokens │
(H = 4 by default) │
│ │
▼ │
┌────────────────┐ │
│ hash(context, │ │
│ key k) │ │
└────────┬───────┘ │
▼ │
seed r_t ───────────┘
│
▼
m pseudorandom "g-functions", each of which
scores EVERY candidate token 0 or 1
The preceding H tokens and secret key produce the per-position seed r_t. That seed selects the g-functions that score each candidate token. Change the key and every score changes. Without the key the scores are unrecoverable, which is why only the holder of the key can detect the mark.
Candidates come from Claude's own probability distribution, then pass through a knockout bracket. At each layer, the survivor is whichever candidate scores higher under that layer's g-function.
layer 1 [hold] [find] [rule] [conclude] ← drawn from the model's
g=1 g=0 g=0 g=1 own distribution
\ / \ /
\ / \ /
layer 2 [hold] [conclude]
g=0 g=1
\ /
\ /
layer 3 [conclude] ◀── emitted
(30 layers by default)
Every survivor is a word the model already rated as good. The tournament breaks ties the same way every time for a given key and context. That consistency creates the signal.
Two consequences follow:
This follows from the sampling rule. The tournament can only bias a choice if there is a choice.
HIGH ENTROPY: prose LOW ENTROPY: code / facts
many equally-good next words one correct next token
"the argument is …" "for (int i = 0; i <"
├── compelling █ ├── i ████████████
├── persuasive █ ├── j ▏
├── forceful █ └── k ▏
├── strong █
└── weak █ tournament has nothing to pick
tournament picks among them between: NO MARK
MARK LANDS
Anthropic states the limit in its own words. Where there is no choice to make, the watermark is not applied: where "something would be factually wrong or a piece of code would break if a different term was chosen." And code "has generally less watermarking than some other forms of text," because it "in very many cases has to be exact."
Syntax and exact facts leave little room for the tournament, but comments and identifier names can still offer several acceptable choices. They remain more exposed.
Detection needs the key, not the model. The mean detector reports a raw score, the frequentist detector reports a p-value against an unkeyed-text null, and the Bayesian detector reports a conditional probability. They answer different questions:
scored g-values ──┬─▶ MEAN raw score, ~0.5 under the null
│ (needs a length-specific threshold)
│
├─▶ FREQUENTIST an exact p-value against
│ "this text is not keyed"
│
└─▶ BAYESIAN P(watermarked | g-values)
⚠ conditioned on an assumed base rate,
default 0.5; must be trained per key
The reported operating point is a true-positive rate of about 70% at 200 tokens against a 1% false-positive rate, rising to roughly 87% at 400 tokens. Short passages sit near the detector's floor: maximum true-positive rate around 0.3 at 50 tokens.
A detection is a statement about contact with the system, not about authorship. It cannot distinguish "Claude wrote this" from "Claude edited this."
A C2PA credential is a cryptographically signed manifest attached to .png, .jpg, and .svg. Its hash binds the manifest to a particular file under a certificate chain, so the credential can show that the file it accompanies is the file that was signed.
┌──────────────────────────────┐
│ image bytes │ ← unchanged
├──────────────────────────────┤
│ C2PA manifest │ ← signed, tamper-evident
│ "processed with Claude" │
└──────────────────────────────┘
│
re-encode, screenshot,
or strip metadata
▼
┌──────────────────────────────┐
│ image bytes │ ← still valid, credential gone
└──────────────────────────────┘
Verification is deterministic: no threshold, no error rate, no length dependence. Re-encoding, s