CoDNA Book a call
Contents

Docs Decisions

0033 · Every read and edit by an AI tool says which tool it was, and who can see it

Status: Accepted · 2026-11-05. Tornike decided who sees what (below). It extends 0021, whose agent accounts already have their reads counted, to the AI tools people connect as themselves.

Context

On 2026-10-25 the read audit moved up, from "before a bank or hospital" to the first project (wiki:decisions/codna-sits-under-the-ai-tools): CoDNA is the memory under Claude, ChatGPT and the coding agents, and the record of what they read is part of what it offers. Georgia's data law expects operations on data to be logged, and the National Bank's 2026 plans ask for controlled data sources.

What CoDNA records today stops short of that:

  • It does not know which tool is calling. The MCP endpoint is stateless and keeps nothing between requests. It never reads the client's name from initialize, nor the access token's client (azp). A person's Claude and their ChatGPT are the same person to it.
  • A person's own AI tool reading a page is not counted. Only agent accounts' reads are (agent_reads, 0021). Usage counts (data-model.md) name tools and days, never documents.
  • A version does not say what made it. revisions has an author and a summary. An edit a person's AI tool made over MCP looks like one they typed, so the strand margin (data-model.md) lights only accepted suggestions.

The design system (Ink and Strand) draws all of this: a Reads tab in the Activity panel, "Your AI tools this week" in the Inbox, versions that say "Dana's Claude edited it", and the margin's strand on every line an AI wrote.

Decision

Which tool it was

  • The tool is named by what it says it is when it connects, kept against the OAuth client it signed in through. Every MCP session starts with initialize, whose clientInfo names the client (claude-ai, openai-mcp, claude-code, codex-mcp-client and so on). CoDNA stores that name and version in mcp_clients, keyed by the access token's azp (or client_id), which the identity provider issued for that connection. Each later call on a token from that client is that tool's.
  • Known names read as people say them: Claude, ChatGPT, Claude Code, Codex, Gemini CLI. Any other name is shown as it was given. A client whose initialize CoDNA has not seen since this shipped is "an AI tool" until it connects again.
  • The name labels; it does not authorise. A client can call itself anything. What it may read is still what the person may read, and the azp is the identity provider's, not the client's.

What is recorded

  • Each version records how it was made, in revisions.via: web (the editor), api (a person's token on the REST API), mcp:<tool> (an AI tool over MCP), suggestion (an accepted suggested edit, which also keeps the tool that suggested it in edit_suggestions.via), automation, import or cli. Versions from before this have none.
  • Each page an AI tool opens is counted, per person, tool, page and day, in document_reads (count, last_at): when get_document returns it, or get_file returns one of its files. Agent accounts keep agent_reads. Searching, listing and history are not reads: they show titles and snippets, and would count every result.
  • A person reading in the browser is not counted. The decision was about AI tools; the web app stays as it was ("on the web that hour").
  • Kept for 90 days, pruned nightly, as agent_reads is.

Who sees what (Tornike, 2026-11-05)

  1. Everyone who can read a page sees what read it, by tool, with no names: "Claude read it 31 times this week, for 3 people". Agent accounts are named, since an agent is not a person.
  2. Each person sees their own reads in full: which of their tools read which page, and when.
  3. Naming colleagues is the instance's choice, off by default. With reads.names on, the Reads tab also lists who each tool read for. Management sets it (PATCH /api/manage/reads-policy, codna reads policy --names on|off), and the change is audited.
  4. Viewing as an agent counts nothing, and neither does the web app's own reading.

Before a bank or another regulated customer uses it, the names setting and the 90 days are to be checked against Georgia's Law on Personal Data Protection. That is Tornike's.

Where it shows

  • The Activity panel's Reads tab: this week and the last 30 days, by tool, then your own.
  • The Inbox, "Your AI tools this week": for each of your tools, the pages it read and edited, of those you can read now.
  • Versions say which tool made them: "Dana's Claude edited it".
  • The strand margin lights the lines an AI tool wrote directly (via mcp:…) as well as accepted suggestions: not raw material it filed, nor lines a revert put back.

Alternatives weighed

  • The User-Agent header. Rejected: it varies by library and platform, is no harder to fake than clientInfo, and is not what the protocol names the client by.
  • Asking the identity provider for each client's registered name. Rejected: the core holds no admin credentials for Keycloak, and an instance may use another provider.
  • A row per call. Rejected: it grows with every call and says nothing a daily count does not. The tool-call log (logCalls) stays a log line.
  • Counting the web app's reads too. Rejected for now: it would show who read what in the browser, which nobody asked for.

Trade-off accepted:

  • The tool's name is what it says it is.
  • Versions and reads from before this have no tool.
  • Read counts per person are personal data, kept 90 days; colleagues' names stay hidden unless the instance turns them on.

Where it stands

  • 2026-11-05: accepted and built: the record (clients, via, reads), the API and its setting, the Activity panel's Reads tab, "Your AI tools this week" in the Inbox, versions and suggestions naming the tool, and the margin marking what a tool wrote directly. Tested end to end against an MCP client naming itself claude-ai and openai-mcp (internal/api/reads_api_test.go).