Skip to content
decosa
LabsHostedSelf-hostMac

Log human edits and editor sign-off

A signed record per piece showing the AI draft, every human edit and the named editor's sign-off, with a public page readers can check.

Held-out test31 of 35 (0.89)Edits that changed the meaning flagged (public research test set)
On production59 smedian on production (2026-09-25); slower when the service is busy
List price~$0.20 per 100 piecesmeasured, at list price

Built on: Grounding, Signed record, Content credentials

Draft, edit, sign off

Live

The Harbour Gazette is a fictional newsroom. The model drafts a story from its press releases; you edit it as the desk editor, sign off, and publish. Every step goes into one signed, hash-chained ledger that anyone can check on the piece's public page.

Piece

The AI draft appears here with its receipt. Each saved revision shows as a diff against the draft, with the edit metrics.

After sign-off and publication, the ledger is sealed. Verify it in your browser, alter a copy, and watch the check fail.

What the ledger shows: the exact text the model wrote (with its receipt), every saved change after it, who says they reviewed which version and when, and that none of it changed after signing. What it cannot show: that the person named is who they say, or that the review was careful. The metrics measure how much changed, not whether the changes were right.

Watch a recorded run first

Watch: a newsroom piece from draft to sign-off

Replay · not live

Use it your way

Use it from your codeThe hosted API with your key, and prompts to paste into a coding agent
Hosted · by Decosa

Get an API key

  • Call the editorial-control ledger API from your own code in minutes.
  • Every model answer carries a signed receipt.
  • Nothing to install; we run the models.
Self-host · your GPUs

Run it yourself, on request

  • The same open models and app, on 1× RTX PRO 6000 (96 GB) or 1× RTX 5090 (32 GB) for the model; metrics, ledger and verification run on CPU.
  • Data never leaves your machines, and there are no Decosa charges.
  • One prompt for Claude Code or Codex assembles the whole stack.
  • Early access: the container images are not public yet and the source needs access; the prompt says how to ask.

Build with it

Paste one of these into Claude Code, Codex or another coding agent. The first wires your project to the hosted API with your DECOSA_API_KEY. The second pulls our containers and runs the same stack on your own GPU, with no Decosa charges.

Base URL
https://api.decosa.ai
Auth
Authorization: Bearer $DECOSA_API_KEY (or a demo session token)
Tool id
editorial-ledger

Use the hosted API

# Decosa editorial-control ledger: use the hosted API

You are wiring Decosa's editorial-control ledger into our publishing workflow. For each article it keeps the AI first
draft (written by an open model with a signed receipt, or by our own AI tool), every human save as a diff with edit
metrics, the claims each edit added or removed, and a named editor's sign-off, in one signed, hash-chained record. Each
published piece gets a public page that checks the record in the reader's browser. Use only what is listed below. If you
need something else, stop and ask me.

- Base URL: `https://api.decosa.ai`
- Health check: `GET https://api.decosa.ai/healthz`.
- What it proves: which text the model wrote, each saved change, who says they reviewed which exact version and when, and
  that none of it changed after signing. It does not prove the editor's identity or that the review was careful.

## Auth
1. Preferred: an API key (`dk_…`) from "Get an API key" on the tool page, in the environment variable
   `DECOSA_API_KEY`, never in code. Send `Authorization: Bearer $DECOSA_API_KEY`.
2. Without a key: `POST https://api.decosa.ai/demo/session` with `{"vertical": "editorial-ledger"}` returns `{"token", "expires_at", "budget"}`
   (a limited number of sessions per network per hour (the current limits are in `demo_sessions` of GET /healthz), 20,000 generated tokens each). A piece is visible only to the key or token that made it.

## The flow (one article)
1. `POST /editorial/pieces` `{"title", "assignment"?, "desk"?, "responsibility": "The publication that holds editorial responsibility", "sources": [{"title", "text", "url"?}], "external_id"?}`
   → `{piece_id, ...}`. Up to 6 sources, 20,000 characters each.
2. The AI draft, either:
   - `POST /editorial/pieces/{id}/draft` `{}` → the model writes it from the sources: `{text, receipt, usage, ...}`. With
     `"stream": true` or `Accept: text/event-stream`: `ready`, `receipt`, `draft`, `budget`, `done`. Or
   - `POST /editorial/pieces/{id}/draft` `{"text" | "html", "model": "our-tool"}` for a draft our own AI tool wrote. It is
     recorded as `unreceipted`: the ledger then shows what we said the model wrote.
3. Every save: `POST /editorial/pieces/{id}/revisions` `{"text" | "html", "editor": {"name", "role"?}, "base_sha256"?, "active_ms"?}`
   → `{rev, text_sha256, metrics_draft, metrics_prev}`. Send the full text each time. `base_sha256` (the previous version's
   hash) makes a concurrent save fail with 409 instead of silently branching.
4. Optional: `POST /editorial/pieces/{id}/claims` `{}` → `{items: [{removed: [...], added: [...], old, new}], removed, added, receipts}`:
   the claims the latest version dropped from or added to the AI draft (one receipted model call).
5. Sign-off: `POST /editorial/pieces/{id}/signoff` `{"editor": {"name", "role"?}, "decision": "approve" | "label", "final_sha256"}`.
   `final_sha256` must be the latest version's hash; after sign-off no more revisions are accepted.
6. Publish: `POST /editorial/pieces/{id}/publish` `{"url"?, "public_draft": false}` → `{record, check, public_id, label, label_text, reason, verify_url, credential}`.
   The piece is labelled as AI-generated when nobody signed off or the editor chose `label`. Put `label_text` on the
   article when `label` is true, and link `verify_url` from the article.

## One webhook for a CMS
`POST /editorial/hooks/cms` with `{"event": "create" | "draft" | "revision" | "signoff" | "publish", "external_id": "<our article id>", ...}`
and the same fields as the routes above. Call `revision` on every save (text or HTML).

## Example (Python, `pip install httpx`)
```python
import hashlib, httpx, os
API = "https://api.decosa.ai"
H = {"Authorization": f"Bearer {os.environ['DECOSA_API_KEY']}"}
c = httpx.Client(base_url=API, headers=H, timeout=120)
p = c.post("/editorial/pieces", json={"title": "Council approves ferry terminal upgrade", "responsibility": "Our Paper",
           "sources": [{"title": "Council release", "text": open("release.txt").read()}]}).raise_for_status().json()
d = c.post(f"/editorial/pieces/{p['piece_id']}/draft", json={}).raise_for_status().json()
edited = d["text"].replace("has approved", "approved")          # the editor's change, from our CMS
r = c.post(f"/editorial/pieces/{p['piece_id']}/revisions", json={"text": edited, "editor": {"name": "Dana Okafor", "role": "Editor"}}).raise_for_status().json()
print(r["metrics_draft"]["changed_share"], r["metrics_draft"]["depth"])
c.post(f"/editorial/pieces/{p['piece_id']}/signoff", json={"editor": {"name": "Dana Okafor"}, "decision": "approve",
       "final_sha256": hashlib.sha256(edited.encode()).hexdigest()}).raise_for_status()
out = c.post(f"/editorial/pieces/{p['piece_id']}/publish", json={"url": "https://example.org/ferry"}).raise_for_status().json()
print(out["label"], out["verify_url"])
```

## Verify
- `GET https://api.decosa.ai/editorial/p/{public_id}` (no token) → the published record and its check.
- `POST https://api.decosa.ai/editorial/verify` `{"public_id" | "record", "text"?}` (no token) checks the chain, the draft's receipt,
  the signature and the ledger rules, recomputes the metrics, and says whether `text` is the signed final version.
- `GET https://api.decosa.ai/editorial/p/{public_id}/credential.docx`: the published text as a DOCX with a C2PA credential
  (development certificate: validators show the issuer as untrusted).

## Honest limits
- The claim list is a model's reading; it can miss or over-count. The metrics measure how much changed, not how well.
- Hosted retention: open pieces 7 days, published ledgers 30 days. Keep the record JSON yourself.
- Whether a review meets the EU AI Act Art. 50(4) exception is our call, not the API's.

Run it yourself (containers)

On request. The container images and the compose file aren’t public yet. Ask for self-host access and Decosa sends the registry (DECOSA_REGISTRY) and the compose file’s URL (DECOSA_COMPOSE_URL) these steps use. They are the steps we tested end to end on a fresh machine.

# Decosa editorial-control ledger: run it yourself (containers)

You are setting up Decosa's editorial-control ledger on this machine, so unpublished drafts, embargoed copy and our
edit history stay here. The ledger (decosa-api) needs no GPU. Qwen3.8-27B is only needed if the model should write the
AI draft here (receipted) and list the claims each edit changed; if our CMS's own AI tool writes drafts, run the ledger
alone. Nothing is sent to Decosa's hosted API.

Status: the container images (${DECOSA_REGISTRY}/decosa-*) and the compose file are on request while self-host is in early access (not on a public registry yet): ask at https://decosa.ai/contact?topic=self-host, and Decosa sends the registry as DECOSA_REGISTRY, the compose file URL as DECOSA_COMPOSE_URL, and pull access. If a pull fails with
"not found", "denied" or "unauthorized", stop and tell me. Do not substitute other images.

Hardware: any Linux x86_64 machine for the ledger; 1x RTX 5090 32 GB or larger for Qwen3.8-27B (NVFP4). Ask me before
any command that needs sudo, and show me the command first.

## Step 0: set up with a coding agent, rehearse on mock data, then go private

This prompt is for a coding agent running on the machine that will host the service. We recommend Claude Code with
Claude Opus 5.5; any capable coding agent works. Work in this order:

1. Set up on mock data only. During the whole setup you (the agent) work with the synthetic sample bundle below and
   nothing else. Do not ask me for real data, and do not open, read, list or copy files that hold real data, even to
   "test with something realistic".
2. Rehearse. When the steps below are done and the service is healthy, fetch the mock-data bundle for this tool,
   https://decosa.ai/samples/editorial-ledger.zip (4 KB, 9 checks, synthetic or openly licensed: see `licence` in expected.json),
   show me what is in it, and run the rehearsal against the local API:
   `docker compose exec api python scripts/rehearse.py editorial-ledger` (the api image carries the same bundle under /app/rehearsal/editorial-ledger/;
   with no key set, the script asks the local API for a short demo token). From a decosa-api checkout instead:
   `python scripts/rehearse.py editorial-ledger --bundle editorial-ledger.zip --base-url http://127.0.0.1:<PORT>`.
   It sends the mock inputs to the local API and prints PASS or FAIL for each expected property (for example: "the model writes a first draft from the sources", "the edit is recorded as a substantial rewrite (over 20% of words changed)", "the claim check answered every item"). Show me
   the full output. Every check must pass. If one fails, fix the install and run it again; never edit `expected.json`
   to make a check pass.
3. Stop there. Once the rehearsal passes, tell me, and I will run my own data against the local API myself, on this
   machine.

For the person running this: a coding agent that runs in the cloud sees everything in its context, including files it
reads, command output and anything pasted into the chat. Keep real data out of the chat and out of anything the agent
can read. Switch to your own data only after the rehearsal has passed and the agent's work is done.

## Rules you must keep
- Bind every port to 127.0.0.1. Only the public ledger page and `GET /editorial/p/*` may be exposed, and only through a
  proxy I approve.
- The signing key is created on first start in the data volume (`attest/ed25519.pem`, 0600). Back it up; never print it.
  Publish its public key (`GET /attest/signing-key`) so readers can pin it.
- Retention: `DECOSA_EDITORIAL_TTL_S` (open pieces, default 7 days) and `DECOSA_EDITORIAL_PUBLIC_TTL_S` (published
  ledgers, default 30 days). Set them to our policy and archive sealed records ourselves; they verify offline.

## Steps
1. Docker (and the NVIDIA Container Toolkit if you run the model): if `docker compose version` fails, install it from
   the official Docker instructions.
2. Fetch the compose file: `mkdir -p ~/decosa && cd ~/decosa && curl -fsSL "${DECOSA_COMPOSE_URL}" -o compose.yaml`. Read it.
   Keep the `api` service; keep `llm` only if the model should draft and check claims. The api's data goes in a named
   volume, not a host folder.
3. In `.env`: `DECOSA_LLM_ROUTE=direct`, `DECOSA_LLM_URL=http://llm:8000/v1`, `DECOSA_LLM_MODEL=qwen3.8-27b` (with `llm`),
   `DECOSA_ADMIN_SECRET` (a long random string, file mode 0600), `DECOSA_SITE_URL` (where our public ledger page lives).
4. Start: `docker compose pull && docker compose up -d`. Wait for `curl -fsS http://localhost:<PORT>/healthz`.
5. Mint a key for the CMS: `POST /v1/keys` with the admin secret and `{"label": "cms", "verticals": ["editorial-ledger"]}`.
6. Smoke test with a token from `POST /demo/session {"vertical": "editorial-ledger"}`: take the `ferry-terminal` sample
   from `GET /editorial/samples`, create a piece, draft it (`POST .../draft {}`; without `llm`, send `{"text": ...}`
   instead), save one revision, check claims (with `llm`), sign off, publish. Expect `check.ok: true`; the draft entry's
   receipt status is `attested` (signed by this box's key: an attestation by me, not a gateway receipt).
7. `POST /editorial/verify {"public_id": ...}` must say `ok: true`. Download the record, change one word of the draft,
   and verify it again: it must fail at "AI draft".
8. Report back: health, the signing key id, the smoke-test summary and the time each step took.

## C2PA credential (optional)
`GET /editorial/p/<id>/credential.docx` needs the `provenance` extra (c2pa-python) in the image and a signing
certificate in `DECOSA_PROVENANCE_DIR`. Without them it answers 503 and everything else works. A certificate from your
own CA shows as "untrusted" in public validators.

Off by default. Leave it off unless I ask.

No NVIDIA GPU? This tool also runs entirely on an Apple Silicon Mac (MLX, 32 GB of unified memory or more): use https://decosa.ai/prompts/editorial-ledger-mac.md instead.
Run it on your own hardwareWhat it needs, and the prompt that sets it up

Run it on your own GPU

Same app, same pinned models, your hardware. Nothing goes to our servers and there are no Decosa charges.

  • CPU only, 64 GB RAMlite tierRuns with a smaller tier

    The standard tier does not fit: Qwen3.8-27B (NVFP4) needs a GPU. The lite tier fits.

  • GeForce RTX 4090standard tierRuns

    The standard tier fits with changes: Replace Qwen3.8-27B (NVFP4) with A community 4-bit build of Qwen3.8-27B (AWQ or GGUF). This build is NVIDIA NVFP4, which needs a Blackwell GPU. (Memory is an estimate.)

  • GeForce RTX 5090standard tierRuns

    The standard tier fits with changes: Qwen3.8-27B (NVFP4): run it at its smallest setting (about 28 GB instead of 57.6 GB), with a shorter context and fewer parallel sessions.

  • 2x GeForce RTX 5090standard tierRuns

    The standard tier fits with changes: Split the language model across the GPUs with tensor parallelism (vLLM --tensor-parallel-size).

  • L40Sstandard tierRuns

    The standard tier fits with changes: Replace Qwen3.8-27B (NVFP4) with Qwen3.8-27B official FP8. This build is NVIDIA NVFP4, which needs a Blackwell GPU.

  • H100 80 GB (SXM)standard tierRuns

    The standard tier fits with changes: Replace Qwen3.8-27B (NVFP4) with Qwen3.8-27B official FP8. This build is NVIDIA NVFP4, which needs a Blackwell GPU.

  • RTX PRO 6000 Blackwell 96 GBstandard tierRuns

    The standard tier fits (57.6 of 96 GB).

  • 2x RTX PRO 6000 Blackwell 96 GBstandard tierRuns

    The standard tier fits (57.6 of 192 GB).

  • Apple M3 Ultra (Mac Studio), 96 GBstandard tierRuns

    The standard tier fits (32 of 96 GB).

  • Apple M5 Max, 64 GBstandard tierRuns

    The standard tier fits (32 of 64 GB).

Memory per component comes from measured footprints, the tool's stack.json, or an estimate from its parameter count, and each is labelled that way below. Only an RTX PRO 6000 and an M3 Ultra Mac Studio have actually been run.

On request. The container images and the compose file aren’t public yet. Ask for self-host access and Decosa sends the registry (DECOSA_REGISTRY) and the compose file’s URL (DECOSA_COMPOSE_URL) these steps use. They are the steps we tested end to end on a fresh machine.

  1. 1

    Check the GPU, Docker and the NVIDIA Container Toolkit

    The driver must see the GPU, and Docker must be able to pass it into a container.

    nvidia-smi
    docker compose version
    docker run --rm --gpus all ubuntu nvidia-smi
  2. 2

    Fetch the compose file

    One file describes the API and the language model as services.

    mkdir -p ~/decosa && cd ~/decosa
    curl -fsSL "${DECOSA_COMPOSE_URL}" -o compose.yaml
  3. 3

    Pull and start

    The first start downloads pinned model weights, tens of gigabytes.

    docker compose pull
    docker compose up -d
  4. 4

    Check health

    Wait until the API reports ok with the language model loaded. Then point your app at the local base URL.

    curl -fsS http://localhost:<PORT>/healthz
    # {"ok": true, "llm": true, ...}
    curl -fsS -X POST http://localhost:<PORT>/demo/session \
      -H 'Content-Type: application/json' -d '{"vertical":"editorial-ledger"}'

Set up with a coding agent, rehearse on mock data, then go private

  1. Set up with a coding agent. Paste the self-host prompt into a coding agent on the machine that will run the service. We recommend Claude Code with Claude Opus 5.5; any capable coding agent works.
  2. Rehearse on mock data. The agent runs the tool on a bundle of synthetic inputs and checks each answer against the bundle's expected.json. Every check must print PASS.
  3. Go private. Only then do you run your own data against the local API, yourself, on that machine. Never give the agent real data during setup: a coding agent that runs in the cloud sees everything in its context, so keep real data out of the chat and out of the files it reads.
Rehearsal command
docker compose exec api python scripts/rehearse.py editorial-ledger

Download the mock-data bundle (4 KB, 9 checks)expected.json

A fictional newsroom assigns a story from two fictional source documents. The model writes the first draft (receipted), the editor files a rewritten final text, the claim check lists what the edit removed and added, a named editor signs off and the piece is sealed. The sealed record must verify, including that the published text is the signed-off text, and a copy with one entry changed must not.

What the rehearsal checks
  • the model writes a first draft from the sources
  • the edit is recorded as a substantial rewrite (over 20% of words changed)
  • the claim check answered every item
  • the editor's new lede is listed as an added or edited sentence
  • a signed-off piece is published without an AI label
  • the sealed record verifies
  • the published text is the signed-off text, byte for byte
  • a record with the sign-off editor changed no longer verifies
  • every model call has a signed receipt

Licence: Synthetic: the Harbour Gazette, Port Aldane and every name, figure and quote are invented for Decosa. Part of decosa-api, AGPL-3.0-or-later.

Prompt for your coding agent

# Decosa editorial-control ledger: run it yourself (containers)

You are setting up Decosa's editorial-control ledger on this machine, so unpublished drafts, embargoed copy and our
edit history stay here. The ledger (decosa-api) needs no GPU. Qwen3.8-27B is only needed if the model should write the
AI draft here (receipted) and list the claims each edit changed; if our CMS's own AI tool writes drafts, run the ledger
alone. Nothing is sent to Decosa's hosted API.

Status: the container images (${DECOSA_REGISTRY}/decosa-*) and the compose file are on request while self-host is in early access (not on a public registry yet): ask at https://decosa.ai/contact?topic=self-host, and Decosa sends the registry as DECOSA_REGISTRY, the compose file URL as DECOSA_COMPOSE_URL, and pull access. If a pull fails with
"not found", "denied" or "unauthorized", stop and tell me. Do not substitute other images.

Hardware: any Linux x86_64 machine for the ledger; 1x RTX 5090 32 GB or larger for Qwen3.8-27B (NVFP4). Ask me before
any command that needs sudo, and show me the command first.

## Step 0: set up with a coding agent, rehearse on mock data, then go private

This prompt is for a coding agent running on the machine that will host the service. We recommend Claude Code with
Claude Opus 5.5; any capable coding agent works. Work in this order:

1. Set up on mock data only. During the whole setup you (the agent) work with the synthetic sample bundle below and
   nothing else. Do not ask me for real data, and do not open, read, list or copy files that hold real data, even to
   "test with something realistic".
2. Rehearse. When the steps below are done and the service is healthy, fetch the mock-data bundle for this tool,
   https://decosa.ai/samples/editorial-ledger.zip (4 KB, 9 checks, synthetic or openly licensed: see `licence` in expected.json),
   show me what is in it, and run the rehearsal against the local API:
   `docker compose exec api python scripts/rehearse.py editorial-ledger` (the api image carries the same bundle under /app/rehearsal/editorial-ledger/;
   with no key set, the script asks the local API for a short demo token). From a decosa-api checkout instead:
   `python scripts/rehearse.py editorial-ledger --bundle editorial-ledger.zip --base-url http://127.0.0.1:<PORT>`.
   It sends the mock inputs to the local API and prints PASS or FAIL for each expected property (for example: "the model writes a first draft from the sources", "the edit is recorded as a substantial rewrite (over 20% of words changed)", "the claim check answered every item"). Show me
   the full output. Every check must pass. If one fails, fix the install and run it again; never edit `expected.json`
   to make a check pass.
3. Stop there. Once the rehearsal passes, tell me, and I will run my own data against the local API myself, on this
   machine.

For the person running this: a coding agent that runs in the cloud sees everything in its context, including files it
reads, command output and anything pasted into the chat. Keep real data out of the chat and out of anything the agent
can read. Switch to your own data only after the rehearsal has passed and the agent's work is done.

## Rules you must keep
- Bind every port to 127.0.0.1. Only the public ledger page and `GET /editorial/p/*` may be exposed, and only through a
  proxy I approve.
- The signing key is created on first start in the data volume (`attest/ed25519.pem`, 0600). Back it up; never print it.
  Publish its public key (`GET /attest/signing-key`) so readers can pin it.
- Retention: `DECOSA_EDITORIAL_TTL_S` (open pieces, default 7 days) and `DECOSA_EDITORIAL_PUBLIC_TTL_S` (published
  ledgers, default 30 days). Set them to our policy and archive sealed records ourselves; they verify offline.

## Steps
1. Docker (and the NVIDIA Container Toolkit if you run the model): if `docker compose version` fails, install it from
   the official Docker instructions.
2. Fetch the compose file: `mkdir -p ~/decosa && cd ~/decosa && curl -fsSL "${DECOSA_COMPOSE_URL}" -o compose.yaml`. Read it.
   Keep the `api` service; keep `llm` only if the model should draft and check claims. The api's data goes in a named
   volume, not a host folder.
3. In `.env`: `DECOSA_LLM_ROUTE=direct`, `DECOSA_LLM_URL=http://llm:8000/v1`, `DECOSA_LLM_MODEL=qwen3.8-27b` (with `llm`),
   `DECOSA_ADMIN_SECRET` (a long random string, file mode 0600), `DECOSA_SITE_URL` (where our public ledger page lives).
4. Start: `docker compose pull && docker compose up -d`. Wait for `curl -fsS http://localhost:<PORT>/healthz`.
5. Mint a key for the CMS: `POST /v1/keys` with the admin secret and `{"label": "cms", "verticals": ["editorial-ledger"]}`.
6. Smoke test with a token from `POST /demo/session {"vertical": "editorial-ledger"}`: take the `ferry-terminal` sample
   from `GET /editorial/samples`, create a piece, draft it (`POST .../draft {}`; without `llm`, send `{"text": ...}`
   instead), save one revision, check claims (with `llm`), sign off, publish. Expect `check.ok: true`; the draft entry's
   receipt status is `attested` (signed by this box's key: an attestation by me, not a gateway receipt).
7. `POST /editorial/verify {"public_id": ...}` must say `ok: true`. Download the record, change one word of the draft,
   and verify it again: it must fail at "AI draft".
8. Report back: health, the signing key id, the smoke-test summary and the time each step took.

## C2PA credential (optional)
`GET /editorial/p/<id>/credential.docx` needs the `provenance` extra (c2pa-python) in the image and a signing
certificate in `DECOSA_PROVENANCE_DIR`. Without them it answers 503 and everything else works. A certificate from your
own CA shows as "untrusted" in public validators.

Off by default. Leave it off unless I ask.

No NVIDIA GPU? This tool also runs entirely on an Apple Silicon Mac (MLX, 32 GB of unified memory or more): use https://decosa.ai/prompts/editorial-ledger-mac.md instead.

Help me customise for my hardware

Pick your GPU or Mac, or enter its memory. You get the tier that fits, the model swaps it needs, measured speed where we have it, and a setup prompt with those choices written in.

Hardware

GeForce RTX 5090: 32 GB GDDR7, 1,792 GB/s, FP8 and NVFP4. NVIDIA product page

RunsEditorial-control ledger on GeForce RTX 5090: use the Standard · Qwen3.8-27B drafts and checks claims (hosted demo) tier

The standard tier fits with changes: Qwen3.8-27B (NVFP4): run it at its smallest setting (about 28 GB instead of 57.6 GB), with a shorter context and fewer parallel sessions.

What this tool's stack says about this hardware:

  • 1× RTX 5090 32 GB (fits): Qwen3.8-27B NVFP4 needs about 20 GB of weights plus KV cache. Estimate: same model stack as the other Qwen verticals, not run here for this one.

Standard · Qwen3.8-27B drafts and checks claims (hosted demo): what changesuses estimates

  • Qwen3.8-27B (NVFP4): run it at its smallest setting (about 28 GB instead of 57.6 GB), with a shorter context and fewer parallel sessions.
Memory per component
  • Ledger: decosa-api editorial module (decosa_api/verticals/editorial). CPU. Runs on CPU (vram_gb 0 in stack.json).
  • Writes the AI first draft from the sources, a...: Qwen3.8-27B (NVFP4). ~57.6 GB (at least ~28 GB), weights 21.4 GB (from stack.json). Qwen3.8-27B NVFP4: Weights 19.9 GiB (21.4 GB), measured (field stack.json). The compose file gives the server 0.60 of a 96 GB card (57.6 GB) so the rest is FP8 KV cache for several sessions. The 28 GB minimum is an estimate: weights plus a short-context KV cache, which is why several stacks list a 32 GB RTX 5090 as 'estimate'. (stack.json lists 20 GB for this component.)
  • Optional C2PA content credential on a DOCX co...: c2pa-python 0.37 (native c2pa-rs). CPU. Runs on CPU (vram_gb 0 in stack.json).

Expected speed

Not measured.

Not measured on this hardware. The only measured setups are an RTX PRO 6000 Blackwell and a Mac Studio M3 Ultra.

Setup prompt for this hardware

The self-host prompt for Editorial-control ledger, with a hardware plan for GeForce RTX 5090 added after Step 0. Loading the full prompt; until then it points your agent at the prompt's URL.

# Set up Editorial-control ledger on my hardware

Fetch https://decosa.ai/prompts/editorial-ledger-selfhost.md and follow it (including Step 0: rehearse on mock data first), with the hardware plan below applied.

## Hardware plan for this machine (from https://decosa.ai/self-host/hardware?use=editorial-ledger)

Target machine: GeForce RTX 5090 (32 GB of GPU memory; CUDA, FP8 and NVFP4).
Quality tier: Standard · Qwen3.8-27B drafts and checks claims (hosted demo) (standard). Fit check: runs with changes, about 28 GB of 32 GB used; some memory numbers are estimates, not measurements.

First, check the machine: run `nvidia-smi` (or `rocm-smi`, or `sysctl hw.memsize` on a Mac) and confirm the GPUs and free memory match the line above. If they do not, stop and tell me before pulling anything.

Use these components (the setup below describes the standard tier; change it to match):
- Ledger: decosa-api editorial module (decosa_api/verticals/editorial), CPU
- Writes the AI first draft from the sources, a...: Qwen3.8-27B (NVFP4) (nvidia/Qwen3.8-27B-NVFP4), 57.6 GB. Change: Qwen3.8-27B (NVFP4): run it at its smallest setting (about 28 GB instead of 57.6 GB), with a shorter context and fewer parallel sessions.
- Optional C2PA content credential on a DOCX co...: c2pa-python 0.37 (native c2pa-rs), CPU

GPU placement (set each service's device and its vLLM --gpu-memory-utilization to about the share shown):
- GPU 0: Qwen3.8-27B (NVFP4) ~28 GB (88%); about 4 GB left

During the rehearsal, watch GPU memory. If a model fails to load or runs out of memory, lower its --max-model-len and --max-num-seqs first, then its memory share, and tell me what you changed.

The stack's own component list and compose layout: https://decosa.ai/prompts/editorial-ledger-assemble.md

Or on a Mac Studio

No NVIDIA GPU needed: every model this tool uses runs natively on Apple Silicon through MLX. Any M-series Mac with 32 GB of unified memory or more. Measured speeds and what runs where

From a checkout of decosa-api, one command sets up the models and the API: scripts/mac/setup.sh

Mac prompt for your coding agent

# Decosa Editorial-control ledger: run it on this Mac (Apple Silicon, no NVIDIA GPU)

You are setting up the Decosa Editorial-control ledger on this Mac, natively on Apple Silicon. The models run on the Mac's GPU
through MLX and decosa-api runs from a git checkout with `uv`. Docker is not used for the models, because Docker on
macOS cannot reach the GPU. Nothing is sent to Decosa's hosted API.

Every model this tool needs runs on the Mac. It needs 32 GB of unified memory or more.

Ask me before any command that needs sudo or installs software with Homebrew, and show me the command first. Never stop
or kill a process this setup did not start; if a port is taken, pick another one.

## Step 0: set up with a coding agent, rehearse on mock data, then go private

This prompt is for a coding agent running on the machine that will host the service. We recommend Claude Code with
Claude Opus 5.5; any capable coding agent works. Work in this order:

1. Set up on mock data only. During the whole setup you (the agent) work with the synthetic sample bundle below and
   nothing else. Do not ask me for real data, and do not open, read, list or copy files that hold real data, even to
   "test with something realistic".
2. Rehearse. When the steps below are done and the service is healthy, fetch the mock-data bundle for this tool,
   https://decosa.ai/samples/editorial-ledger.zip (4 KB, 9 checks, synthetic or openly licensed: see `licence` in expected.json),
   show me what is in it, and run the rehearsal against the local API:
   `.venv/bin/python scripts/rehearse.py editorial-ledger` in the decosa-api checkout (the key comes from ~/.decosa-mac/api.key).
   It sends the mock inputs to the local API and prints PASS or FAIL for each expected property (for example: "the model writes a first draft from the sources", "the edit is recorded as a substantial rewrite (over 20% of words changed)", "the claim check answered every item"). Show me
   the full output. Every check must pass. If one fails, fix the install and run it again; never edit `expected.json`
   to make a check pass.
3. Stop there. Once the rehearsal passes, tell me, and I will run my own data against the local API myself, on this
   machine.

For the person running this: a coding agent that runs in the cloud sees everything in its context, including files it
reads, command output and anything pasted into the chat. Keep real data out of the chat and out of anything the agent
can read. Switch to your own data only after the rehearsal has passed and the agent's work is done.

## What runs where

| Part | On an NVIDIA GPU | On this Mac | Status |
|---|---|---|---|
| Ledger: edit metrics, sentence alignment, hash chain, sign-off rules, sealing and verification (no model; CPU) | Python on CPU | The same Python module, run with uv | Runs, measured |
| Writes the AI first draft from the sources, and lists the claims an edit added or removed | NVFP4 on vLLM 0.29 (Blackwell) | MLX 4-bit (EigenLabs/Qwen3.8-27B-4bit) on mlx_lm.server 0.31.3; oMLX 0.6.1 with MTP as an option | Runs, measured |
| Optional C2PA content credential on a DOCX copy of the published text | CPU | Native macOS arm64 wheel | Runs, not measured |

## Steps
1. Check the machine: `uname -m` must print `arm64` (an M-series chip; Intel Macs cannot run MLX), and
   `sysctl -n hw.memsize` should be at least 32 GB for this tool. Check about 30 GB of free disk with
   `df -h ~`. Show me the chip (`sysctl -n machdep.cpu.brand_string`) and the memory.
2. Tools: `uv --version`. If it is missing, ask me, then `brew install uv`.
3. Code: `git clone <decosa-api source: on request at https://decosa.ai/contact?topic=self-host> ~/decosa-api` (access required) and `cd ~/decosa-api`.
   Check that `scripts/mac/setup.sh` exists; if it does not, the checkout is too old: stop and tell me.
4. Start everything with one command: `scripts/mac/setup.sh`. It creates `.venv` (decosa-api)
   and `.venv-mac` (MLX, mlx-lm, mlx-audio), downloads the weights with the Hugging Face CLI (about 16 GB for the
   language model), starts the model servers and decosa-api on 127.0.0.1, and mints a local API key
   into `~/.decosa-mac/api.key` (mode 0600). The first run takes a while because of the downloads; later runs reuse them.
   If a download fails with 401 or 403, ask me for a Hugging Face token and set `HF_TOKEN`.
5. Check health: `scripts/mac/setup.sh status` shows each server, and `curl -fsS http://127.0.0.1:8445/healthz` must
   report `"llm": true`. `curl -fsS http://127.0.0.1:8445/attest/signing-key` shows this Mac's public key:
   show it to me, because it is what others pin to check the receipts and records this Mac signs.
6. Smoke test: `.venv/bin/python scripts/mac/bench_usecases.py editorial-ledger`. It runs the tool's own sample end to end
   against the local API with the local key and prints `ok`, the wall time, the model calls and the receipts.
   `ok=True` is the pass condition. If it fails, read `~/.decosa-mac/logs/*.log` and tell me what you found.
7. Point the app at it: the API is `http://127.0.0.1:8445` with `Authorization: Bearer $(cat ~/.decosa-mac/api.key)`,
   the same routes as the hosted API. To stop everything: `scripts/mac/setup.sh stop`.
8. Report back: the chip and memory, the public key, the smoke-test result and its time, and the output of
   `scripts/mac/setup.sh status`.

## Good to know
- Receipts: every model call is signed with this Mac's own Ed25519 key and names the exact MLX weights
  (`qwen3.8-27b-mlx-4bit` with a hash of the downloaded files). There is no gateway countersignature on a
  self-hosted Mac.
- The weights are a 4-bit MLX build of the same open models, not the NVFP4 build the hosted route and the published
  evals use. Expect small differences in wording and scores.
- Faster drafting: `scripts/mac/setup.sh stop && scripts/mac/setup.sh --engine omlx` serves the
  model with oMLX and multi-token prediction (about 2x faster for a single long answer, no faster for many parallel
  calls; typed judgments then use sampling because oMLX returns no log-probabilities).
- Measured speeds for a Mac Studio M3 Ultra and the memory each tool needs: https://decosa.ai/mac. Full details:
  `docs/self-host-mac.md` in the checkout.

The proof

How we tested itEval results and end-to-end checks, hosted and self-hosted, with dates

Verified end to end

Hosted: pass (pre-release server) on 25 Sep 2026 · QA sweep · p50 59 s · ~$0.002 per run · 2 receipts

Loading the nightly status…

Self-host: verified 25 Sep 2026 · Fresh clone of the branch into a clean directory on our server, image built from docker/api/Dockerfile, api started with compose (named volume, python healthcheck), then the assemble prompt's smoke steps 1-8 and the CMS webhook with a minted dk_ key; torn down afterwards.

Measured cost to run: about $0.20 per 100 pieces (hosted, 25 Sep 2026). Self-hosting is free: the code is open and the models are open-weight. You pay only for your own hardware and power.

Verified 25 Sep 2026: image builds, the service starts healthy, and the sample passes end to end against local model servers equivalent to the documented ones (the already-running Qwen3.8-27B vLLM on 127.0.0.1:8114 instead of the compose llm service); model-server startup itself not re-verified. Receipts were attested (signed by the box's key). The C2PA credential answered 503 as documented: the default image has no c2pa-python and no certificate. Host networking and port 8437 were used because other services held the default ports.

Known limits (6)
  • Hosted check ran on the pre-release server (decosa-api the pre-release branch on our server, gateway route, signed receipts): console flow, public ledger page, text check, tamper buttons, Watch replay, 390 px layout, and the Build tab's Python example as written. The production API runs it once the branch is merged and deployed.
  • The claim check is a model's reading: on IteraTeR it caught 31 of 35 meaning-changed edits and also fired on many 'clarity' edits (most of which did change a fact).
  • The named editor's identity is what the caller sends; only an optional Ed25519 editor signature binds it to a key.
  • The C2PA credential uses a development certificate (untrusted issuer) and needs the provenance extra in the image.
  • Hosted retention is fixed at 7 days for open pieces and 30 days for published ledgers.
  • Under heavy shared load a whole piece took up to a minute.

Eval results, nightly checks and cost per runVerify a run

How it's builtThe steps, the models and what each one checks
Hosted · by Decosa

Get an API key

  • Call the editorial-control ledger API from your own code in minutes.
  • Every model answer carries a signed receipt.
  • Nothing to install; we run the models.
Self-host · your GPUs

Run it yourself, on request

  • The same open models and app, on 1× RTX PRO 6000 (96 GB) or 1× RTX 5090 (32 GB) for the model; metrics, ledger and verification run on CPU.
  • Data never leaves your machines, and there are no Decosa charges.
  • One prompt for Claude Code or Codex assembles the whole stack.
  • Early access: the container images are not public yet and the source needs access; the prompt says how to ask.
The open stack

The AI draft, every human edit and a named sign-off in one signed record, with a public check for each published piece.

An open model writes the first draft from your sources through our gateway, so the draft is bound to a signed receipt. Every save after that is a revision on a hash chain: who edited, a diff against the draft, and edit metrics anyone can recompute (share of words changed, sentence edit distance, numbers, names and quotes gone or new). A second receipted call lists the factual claims the edit added or removed. A named editor signs off on one exact version, or the piece goes out with an AI label, and the ledger records which and why. Publishing seals the record; each piece gets a public page that checks it in the reader's browser, and an optional C2PA credential on a DOCX copy of the text. A CMS drives it through one webhook. It is for EU newsrooms, agencies and comms teams that draft with AI and need to show that a person reviewed each piece.

Deployment
Hosted or self-host
Regulatory
Checked 25 Sep 2026. EU AI Act (Regulation (EU) 2024/1689) Art. 50(4), second subparagraph: deployers of an AI system that generates or manipulates text published to inform the public on matters of public interest must disclose that it was artificially generated or manipulated, unless the content has undergone a process of human review or editorial control and a natural or legal person holds editorial responsibility for the publication. It applies from 2 August 2026 (Art. 113); transparency breaches can be fined up to EUR 15 million or 3% of worldwide turnover (Art. 99(4)). The Digital Omnibus (Regulation (EU) 2026/1744, in force 27 July 2026) gave generative systems already on the market until 2 December 2026 for the provider marking duty; published summaries report no change to this deployer duty. The Commission's voluntary Code of Practice on marking and labelling AI-generated content (about 190 signatories by July 2026) and its Art. 50 guidelines restate the exception. This ledger is evidence that a review happened and who holds responsibility; whether a given review meets the exception is for the publisher, and ultimately regulators and courts, to judge, and the metrics cannot tell a careful read from a careless one. US: the Copyright Office's registration guidance (16 March 2023, 88 FR 16190) and its Part 2 report on copyrightability (29 January 2025) protect only human authorship and ask applicants to disclose AI-generated material and describe the human contribution; the ledger documents what a person changed, it does not make text protectable. Model licence: Apache-2.0 (Qwen3.8-27B). Not legal advice.
Architecture
Text description

Sources go to Qwen3.8-27B through our gateway, which writes the AI first draft and returns a signed receipt covering its hash. The draft is retained as revision 0 of a hash-chained ledger. Human edits arrive from the console or a CMS webhook; each becomes a revision with a diff and edit metrics computed in code. A second receipted model call lists the claims the edit added or removed. A named editor signs off on one exact version, or the piece is labelled as AI-generated. Publishing seals the ledger with the server's Ed25519 key; outputs are a public verify page, the signed record and a DOCX with a C2PA credential. In self-host mode the model and the ledger run on your machine.

Architecture

At a glance

What you send
Sources (up to 6, 20,000 characters each) or a draft your own tool wrote, then each saved version as text or HTML, the editor's name and role, and the sign-off.
What you get
A signed ledger per piece, a public page at /ledger/<id> that checks it in the reader's browser, a copyable article badge, and a DOCX of the published text with a C2PA credential.
Typical cost
Two model calls per piece: a fraction of a cent at the gateway list price. The ledger work is CPU.
Retention (hosted)
Open pieces 7 days, published ledgers 30 days, with the texts. Choose whether the public page shows the AI draft; its hash is always there. Self-host keeps everything on your box.
What leaves the box (self-host)
Nothing: the model, the ledger and the public page can all run on your machine. On the hosted route the sources and the texts reach our server and the gateway.
Identity
The ledger records the name your key-holder sends. For stronger evidence an editor can sign the sign-off with their own Ed25519 key; there is no SSO binding yet.
Quality tiers

Pick the tier for the quality you need

Same app at every tier. What changes is the models, the hardware they need, and whether receipts are signed. Scores are measured with the source named, or marked not measured.

  • Lite

    ledger only, on CPU (self-host)

    Your CMS's own AI tool writes the draft; the ledger keeps it (marked as not receipted), diffs every save, computes the metrics and seals the sign-off. No model here, so no claim check.

    Models
    • decosa-api editorial module (decosa_api/verticals/editorial)
    • c2pa-python 0.37 (native c2pa-rs)
    Hardware
    Any CPU
    Quality evidence
    • Edit metrics against scripted diffs of real drafts80 / 80 exactdocs/evals/editorial-ledger.md
    • Rubber-stamped vs heavily edited, words changedAUC 1.00; rubber-stamped at most 0.68%, heavy at least 51.1%docs/evals/editorial-ledger.md (32 rubber-stamped, 11 heavy)
    • Tampered ledgers caught120 / 120 (15 kinds of change, 8 ledgers); 8 / 8 genuine verifieddocs/evals/editorial-ledger.md
    Latency
    measured: a revision is saved and chained in tens of milliseconds
    Verification
    No proof yetSelf-host onlyNo model call here, so no receipt covers the draft: the ledger shows what your CMS said its AI wrote.
  • In the hosted demo

    Standard

    Qwen3.8-27B drafts and checks claims (hosted demo)

    The model writes the first draft from the sources with a signed receipt, and lists the claims each edit added or removed. This is what the hosted API runs.

    Models
    • decosa-api editorial module (decosa_api/verticals/editorial)
    • Qwen3.8-27B (NVFP4)
    • c2pa-python 0.37 (native c2pa-rs)
    Hardware
    1× RTX PRO 6000 96 GB (measured) or 1× RTX 5090 32 GB (estimate)
    Quality evidence
    • Claim check on planted edits (number changed, fact deleted or added, paragraphs moved, style-only, 24 held-out paraphrases)64 / 64; recall 24/24, no false alarm in 40docs/evals/editorial-ledger.md
    • Claim check on IteraTeR human sentence edits (test)31 / 35 meaning-changed caught; fired on 32 / 65 others (precision 0.49 against the intent label, which undercounts real fact changes)docs/evals/editorial-ledger.md
    • Drafts with a gateway-signed receipt covering the stored text8 / 8 in the eval; 2 / 2 model calls per run in the end-to-end runsdocs/evals/editorial-ledger.md; scripts/smoke/editorial-ledger.py
    • Edit metrics, separation and tamper detectionas the lite tier (same code)docs/evals/editorial-ledger.md
    Latency
    measured on a shared GPU: seconds for a draft or a claim check; under half a minute for a whole piece when quiet, about a minute under heavy shared load
    Verification
    Proof: strongThe draft and every claim check carry gateway-signed receipts embedded in the signed ledger.
Components

Every model in the stack

Models in this stack. Each row has a button that shows its licence, engine, verification and evidence.
ModelDetails
Ledger: edit metrics, sentence alignment, hash chain, sign-off rules, sealing and verification (no model; CPU)decosa-api editorial module (decosa_api/verticals/editorial)
0 GBProof: partial
Writes the AI first draft from the sources, and lists the claims an edit added or removedQwen3.8-27B (NVFP4)nvidia/Qwen3.8-27B-NVFP4 on Hugging Face (opens in a new tab)
27.8B · 20 GBProof: strongIn the hosted demo
Optional C2PA content credential on a DOCX copy of the published textc2pa-python 0.37 (native c2pa-rs)
0 GBNo proof yet

Around the models

Tools, services and hardware

Tools

  • POST /editorial/hooks/cmsApache-2.0

    One webhook for a CMS, keyed by the CMS's own article id: create, draft (a draft your CMS's AI wrote), revision on every save (text or HTML), signoff and publish.

  • Public ledger page (/ledger/<id>) and POST /editorial/verifyApache-2.0

    Checks the chain, the draft's gateway receipt, the signature and the ledger rules in the reader's browser; the server also recomputes the edit metrics and checks pasted text against the signed final version.

  • Eval: 100 labelled human sentence edits for the claim check, and 51 real human document revisions as a reference distribution for the metrics.

  • scripts/editorial_eval.py and docs/evals/editorial-ledger.mdApache-2.0

    Metric accuracy on scripted diffs, rubber-stamp vs heavy-edit separation, the claim check on planted edits, and tamper detection (decosa-api).

Services

  • decosa-api (editorial routes):8445
    ${DECOSA_REGISTRY}/decosa-api:<tag>

    POST /editorial/pieces, /draft (SSE or JSON), /revisions, /claims, /signoff, /publish; /editorial/hooks/cms; GET /editorial/p/<id>, /editorial/p/<id>/credential.docx; POST /editorial/verify, /editorial/metrics; GET /editorial/info, /editorial/samples.

  • vLLM (draft and claim check):8114
    vllm/vllm-openai@sha256:c2914767605584b6d8f45686b82de173ecc99e781897aa3d0a66dacd72c51ae1

    Qwen3.8-27B NVFP4 behind our gateway (hosted) or called directly (self-host). Not needed on the lite tier.

Hardware

  • Any CPU, no GPU Fits

    Lite tier: the ledger, metrics, sealing and verification. Drafts come from your own tool; no claim check.

  • 1× RTX 5090 32 GB Fits

    Qwen3.8-27B NVFP4 needs about 20 GB of weights plus KV cache. Estimate: same model stack as the other Qwen verticals, not run here for this one.

  • 1× RTX PRO 6000 Blackwell 96 GB Fits

    Measured on our server: the hosted demo and the eval ran on this card, shared with other services.

Latency per lane

  • AI first draft (about 560 generated tokens), hosted gateway route8.6 s

    Measuredmeasured on our server 2026-09-25: p50 8.6 s over 8 drafts while the GPU was shared with other evaluation jobs; one draft took 21 s at a busier moment

  • claim check of one edit (about 1,790 prompt and 57 generated tokens), hosted gateway route12.3 s

    Measuredmeasured on our server 2026-09-25: p50 12.3 s, p90 17.9 s over 64 calls, 4 in flight, shared GPU

  • save a revision (metrics, alignment, chain entry), local HTTP30 ms

    Estimateestimate from the recorded demo: the revision step landed 23-27 ms after the draft response

  • whole piece: draft, one revision, claim check, sign-off, publish58.7 s

    Measuredmeasured on our server 2026-09-25: 11-24 s in four runs early in the day, then 46, 59 and 61 s (p50 59 s) in three smoke runs while other evaluation jobs loaded the shared gateway

Notes

  • Edit metrics matched the expected counts on 80 of 80 scripted diffs of real drafts (word substitutions, sentence deletions and insertions, an edited sentence, a reflow). The first run found a real bug: a line break inside a paragraph counted as a sentence end. Fixed before the numbers above.
  • Rubber-stamped drafts (unchanged, reflowed, a punctuation or one-word fix) changed at most 0.68% of words; heavy edits (3 by hand, 8 simulated by the model) changed 51% to 78%. Real human document revisions from IteraTeR sit in between (median 10%). The metrics describe; they do not judge review quality.
  • The claim check got all 64 planted cases right, including 24 held-out paraphrases that must show no change. On IteraTeR's labels it caught 31 of 35 meaning-changed edits but also fired on 29 of 41 'clarity' edits; a manual audit found most of those did drop or add a fact. Treat the list as a pointer for the editor.
  • The prompt was revised once after the first run flagged style-only rewording (said/stated, will/is set to); the style-only row is therefore optimistic. The held-out paraphrases were made after that change.
  • All 120 tampered ledgers failed verification (15 kinds of change on 8 ledgers, including rewrites re-signed with another key); all 8 genuine ones verified.
  • What the ledger cannot show: that the named editor is who they say (an editor can add their own Ed25519 signature to the sign-off), or that a review with few edits was careful.
Assemble it

Run this exact stack on your machine

Paste into Claude Code / Codex to assemble this stack locally. The prompt checks your GPU, pulls the pinned models, writes the compose file and runs a smoke test.

editorial-ledger/assemble-prompt.md127 lines
# Assemble the Decosa editorial-control ledger on this machine

You are setting up an editorial-control ledger for our newsroom: for each article it keeps the AI first draft, every
human save as a diff with edit metrics, the claims each edit added or removed, and a named editor's sign-off, in one
record signed by this box's own key, with a public verify page per published piece. Work step by step, show me each
command before you run anything with `sudo`, and stop to ask if a check fails.

## Step 0: set up with a coding agent, rehearse on mock data, then go private

This prompt is for a coding agent running on the machine that will host the service. We recommend Claude Code with
Claude Opus 5.5; any capable coding agent works. Work in this order:

1. Set up on mock data only. During the whole setup you (the agent) work with the synthetic sample bundle below and
   nothing else. Do not ask me for real data, and do not open, read, list or copy files that hold real data, even to
   "test with something realistic".
2. Rehearse. When the steps below are done and the service is healthy, fetch the mock-data bundle for this tool,
   https://decosa.ai/samples/editorial-ledger.zip (4 KB, 9 checks, synthetic or openly licensed: see `licence` in expected.json),
   show me what is in it, and run the rehearsal against the local API:
   `docker compose exec api python scripts/rehearse.py editorial-ledger` (the api image carries the same bundle under /app/rehearsal/editorial-ledger/;
   with no key set, the script asks the local API for a short demo token). From a decosa-api checkout instead:
   `python scripts/rehearse.py editorial-ledger --bundle editorial-ledger.zip --base-url http://127.0.0.1:<PORT>`.
   It sends the mock inputs to the local API and prints PASS or FAIL for each expected property (for example: "the model writes a first draft from the sources", "the edit is recorded as a substantial rewrite (over 20% of words changed)", "the claim check answered every item"). Show me
   the full output. Every check must pass. If one fails, fix the install and run it again; never edit `expected.json`
   to make a check pass.
3. Stop there. Once the rehearsal passes, tell me, and I will run my own data against the local API myself, on this
   machine.

For the person running this: a coding agent that runs in the cloud sees everything in its context, including files it
reads, command output and anything pasted into the chat. Keep real data out of the chat and out of anything the agent
can read. Switch to your own data only after the rehearsal has passed and the agent's work is done.

## 0. Ground rules and licences
- Model: Qwen3.8-27B (Apache-2.0), used for the AI draft and the claim check. It is optional: without it the ledger
  still records drafts our CMS's own AI tool wrote (marked "unreceipted"), diffs and metrics, and sign-offs.
- The ledger is decosa-api (AGPL-3.0-or-later) and needs no GPU. C2PA credentials use c2pa-python (MIT OR Apache-2.0).
- Unpublished drafts and embargoed copy stay on this machine. Bind every port to 127.0.0.1. Logs carry ids and counts
  only; do not add request logging.
- Be honest about what it shows: a named person reviewed a version and what changed, not that the review was good.
  Whether a review meets the EU AI Act Art. 50(4) exception is our judgement.

## 1. Check the machine
1. For the model: `nvidia-smi` shows one GPU with at least 32 GB (Qwen3.8-27B NVFP4 needs about 20 GB of weights plus KV
   cache; an RTX 5090 32 GB or an RTX PRO 6000 96 GB). Driver 570 or newer. Blackwell cards run NVFP4; on older cards
   use `Qwen/Qwen3.8-27B-FP8`. For the ledger alone, any x86_64 Linux box.
2. `docker --version` and `docker compose version`. If Docker or the NVIDIA container toolkit is missing, install them
   from the official Docker and NVIDIA repositories after asking me, then run
   `docker run --rm --gpus all nvidia/cuda:12.8.0-base-ubuntu24.04 nvidia-smi`.
3. Disk: about 30 GB free for the model weights; the ledger itself needs well under 1 GB.

## 2. Images and weights
- `${DECOSA_REGISTRY}/decosa-api:<tag>` (**publishing soon**). If the pull fails, build from source:
  `git clone <decosa-api source: on request at https://decosa.ai/contact?topic=self-host>` (access required), check out a release that contains
  `decosa_api/verticals/editorial/`, and run `docker build -f docker/api/Dockerfile -t decosa-api:local .`
- `vllm/vllm-openai:v0.29.0` for the model; weights `nvidia/Qwen3.8-27B-NVFP4`. Keep the pinned image and weights: the
  eval numbers were measured with them.

## 3. docker-compose.yml
Write this in `~/decosa/editorial/` (drop the `llm` service and the `depends_on` for the ledger-only setup):

```yaml
services:
  llm:
    image: vllm/vllm-openai:v0.29.0
    command: ["--model", "nvidia/Qwen3.8-27B-NVFP4", "--served-model-name", "qwen3.8-27b", "--max-model-len", "32768",
              "--enable-prefix-caching", "--seed", "0"]
    ports: ["127.0.0.1:8114:8000"]
    volumes: ["~/.cache/huggingface:/root/.cache/huggingface"]
    deploy: { resources: { reservations: { devices: [{ driver: nvidia, count: 1, capabilities: [gpu] }] } } }
    healthcheck: { test: ["CMD", "curl", "-fs", "http://localhost:8000/v1/models"], interval: 30s, retries: 20 }
  api:
    image: ${DECOSA_REGISTRY}/decosa-api:<tag>
    ports: ["127.0.0.1:8445:8445"]
    environment:
      DECOSA_HOST: 0.0.0.0
      DECOSA_PORT: "8445"
      DECOSA_DATA_DIR: /data
      DECOSA_LLM_ROUTE: direct
      DECOSA_LLM_URL: http://llm:8000/v1
      DECOSA_LLM_MODEL: qwen3.8-27b
      DECOSA_ADMIN_SECRET: ${DECOSA_ADMIN_SECRET}   # for minting keys on this box; put it in ~/decosa/editorial/.env (0600)
      DECOSA_EDITORIAL_TTL_S: "604800"              # open pieces: 7 days
      DECOSA_EDITORIAL_PUBLIC_TTL_S: "31536000"     # published ledgers: set to our retention policy
      DECOSA_SITE_URL: https://news.example.org      # where our public ledger page lives
    volumes: ["decosa-data:/data"]
    depends_on: { llm: { condition: service_healthy } }
    healthcheck: { test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8445/editorial/info', timeout=4)"], interval: 30s, retries: 10 }
volumes:
  decosa-data:
```

On the first start the api service creates this box's Ed25519 key in the `decosa-data` volume (`attest/`, mode 0600).
Back it up; never print it. Every model call on the direct route gets a receipt signed with that key (status
`attested`): an attestation by me, the operator, not a proof of computation. The ledger is signed with the same key.

For real use, mint a key for the CMS: `curl -s -XPOST localhost:8445/v1/keys -H "authorization: Bearer $DECOSA_ADMIN_SECRET" -H 'content-type: application/json' -d '{"label": "cms", "verticals": ["editorial-ledger"], "daily_llm_tokens": 500000}'`
and keep the returned `dk_…` key in the CMS's secret store. A piece costs about 3,600 model tokens (draft and claim check).

## 4. Smoke test
1. `curl -s localhost:8445/editorial/info | jq '{format, model, signed, credential: .credential.c2pa}'`.
2. Token: `T=$(curl -s -XPOST localhost:8445/demo/session -H 'content-type: application/json' -d '{"vertical":"editorial-ledger"}' | jq -r .token)`.
3. `curl -s localhost:8445/editorial/samples | jq '.[0] | {title, assignment, desk, responsibility, sources}' > piece.json`, then
   `P=$(curl -s -XPOST localhost:8445/editorial/pieces -H "authorization: Bearer $T" -H 'content-type: application/json' -d @piece.json | jq -r .piece_id)`.
4. Draft: `curl -s -XPOST localhost:8445/editorial/pieces/$P/draft -H "authorization: Bearer $T" -H 'content-type: application/json' -d '{}' > draft.json`.
   Expect `text` and a `receipt` with status `attested`. Without the model, send our own text instead:
   `-d '{"text": "…", "model": "our-cms-ai"}'` (status `unreceipted`).
5. Edit: change the first sentence of `draft.json`'s text, save it as `edited.txt`, and post
   `jq -Rs '{text: ., editor: {name: "Test Editor", role: "Editor"}}' edited.txt` to `/editorial/pieces/$P/revisions`.
   Expect `metrics_draft.changed_share` above 0, `sentences.edited` or `removed` and `added`, and a `depth`.
6. `POST /editorial/pieces/$P/claims` `{}` (with the model): expect one item per changed sentence and a receipt.
7. Sign off with `{"editor": {"name": "Test Editor"}, "decision": "approve", "final_sha256": "<sha256 of edited.txt>"}` at
   `/editorial/pieces/$P/signoff`, then `POST /editorial/pieces/$P/publish` `{"public_draft": true}`: expect
   `check.ok: true`, `label: false`, a `public_id`.
8. `curl -s -XPOST localhost:8445/editorial/verify -H 'content-type: application/json' -d "{\"public_id\": \"<public_id>\"}" | jq '{ok, summary}'`
   must be ok. Download `GET /editorial/p/<public_id>`, change one word of the `draft` entry's `text`, send it as
   `{"record": …}` and verify again: it must fail at "AI draft".
9. Tell me the verify summary, the edit metrics and the claim list.

## 5. Point our CMS and site at it
- CMS: call `POST /editorial/hooks/cms` with the `dk_` key on create, on every save (`event: revision`, text or HTML,
  the editor's name), on sign-off and on publish; put the returned `label_text` on the article when `label` is true and
  link `verify_url`. Contract: `API_CONTRACT.md`, section "Editorial-control ledger".
- The Decosa site's console and `/ledger/<id>` page work against this box with `NEXT_PUBLIC_DECOSA_API=http://127.0.0.1:8445`.
- C2PA credential (optional): install the image with the `provenance` extra (c2pa-python) and mount a signing
  certificate at `DECOSA_PROVENANCE_DIR`; otherwise `/editorial/p/<id>/credential.docx` answers 503.

Off by default. Joining serves other people's requests on this GPU; never do it on a box that holds unpublished copy.
If I ask for it, follow the provider guide at `/provide` on the site, and do not enable it without my explicit yes.
Rules and regulations it checks againstDated, linked to the primary source; not legal advice

Regulation watch

Loading the watch status…

5 laws, rules and guidance pages cited; 5 watched nightly at the primary source. A change marks this page for a human re-check; nothing is edited automatically. What we cite and how it is watched

Technical detailsModels, where it runs, labels

In short

Last reviewed

What it is
A record for the EU AI Act Article 50 human-review exception: the AI draft, every human edit and a named editor's sign-off on one exact version, sealed in one signed ledger, with a public check for each published piece. It is evidence that a person reviewed the text, not a safe harbour.
Who it's for
Teams in creative and media and compliance and trust.
Where it runs
Hosted for published pieces; self-host for embargoed copy
Key numbers

On the IteraTeR test split the claim check flagged 31 of 35 human edits that changed meaning (precision 0.49 against a proxy label), and 120 of 120 tampered ledgers failed verification (synthetic). No real newsroom editors on real copy were measured.

  • 24 / 24 Wording-only paraphrases with no claim change flagged (held out) (held out, n = 24)
  • 31 of 35 (0.89) IteraTeR test, meaning-changed edits flagged (recall) (test split, n = 35)
  • 80 / 80 Edit metrics exact against scripted diffs (synthetic, n = 80)
All results, datasets and caveats
Models
Qwen3.8-27B (draft and claim check)
Where
Hosted for published pieces; self-host for embargoed copy
Checks
Receipted draft; signed, hash-chained ledger; optional C2PA credential
Output
Signed record or verdict · Notes, reports and drafts
Data
Confidential business data
Hardware
1× 96 GB GPU
Licence
Permissive (Apache-2.0, MIT)

Questions people ask

What is Article 50 of the EU AI Act, and what does it say about AI-drafted text?

Article 50 holds the AI Act's transparency duties, including marking and labelling AI-generated content. For text, Art. 50(4) says deployers who publish AI-generated or manipulated text to inform the public on matters of public interest must disclose it, unless the text had human review or editorial control and a natural or legal person holds editorial responsibility. It applies from 2 August 2026; transparency breaches can be fined up to EUR 15 million or 3% of worldwide turnover. This is not legal advice.

What does the ledger record?

The AI first draft (bound to a signed model receipt when drafted here), every later save as a diff with edit metrics anyone can recompute, a model's list of factual claims the edit added or removed, and a named editor's sign-off on one exact version, or the AI-label decision and why. Publishing seals it and each piece gets a public page that checks it in the reader's browser.

How do I prove a human reviewed an AI-drafted article?

Keep the evidence as the review happens: the AI draft, each saved version with a diff and edit metrics anyone can recompute, the claims each edit added or removed, and a named editor's sign-off on one exact version, sealed so a later change shows. That is what the ledger records, and the public page lets a reader check it in the browser. It shows that a review happened and who is responsible, not how careful the review was.

What do the Article 50 code of practice and guidelines say about human review?

The Commission's voluntary Code of Practice on marking and labelling AI-generated content (about 190 signatories by July 2026) and its Article 50 guidelines restate the same exception: human review or editorial control, plus a person who holds editorial responsibility. The ledger records what happened; whether a given review meets the exception is the publisher's judgement, and ultimately regulators' and courts'. Not legal advice.

Does the ledger make us meet the exception?

No. It is evidence that a review happened and who holds responsibility. Whether a given review meets Art. 50(4) is the publisher's judgement, and ultimately regulators' and courts'. The metrics cannot tell a careful read from a careless one: an editor who changes nothing looks the same as a rubber stamp.

How was it tested?

Edit metrics matched 80 of 80 scripted diffs; 120 of 120 tampered ledgers failed verification and all 8 genuine ones verified; the claim check flagged 31 of 35 meaning-changed edits in the IteraTeR test set (precision against its intent labels 0.49). There were no real newsroom editors on real copy.

What does it cost and where does the text go?

Two model calls per piece, about 3,600 tokens, around $0.002 at the gateway list price. Self-hosted, nothing leaves your box; hosted, open pieces are kept 7 days and published ledgers 30 days.

Ask a question or leave feedbackWe read every message and publish useful answers
Questions & feedback

Ask about Editorial-control ledger

We read every message. Questions, comments and our answers show here once we have reviewed and approved them.

Loading questions…

This is a

Plain text. Please leave out personal, patient or client data.

Shown with your message if we publish it. Leave blank to post as “A visitor”.

Nothing appears here until we have read and approved it.