Legal holds

Bind documents to an active matter. Held documents can't be deleted or have their retention disposed until the hold releases. Bulk apply by collection or saved search.

Updated 2026-05-26

Open a hold from /legal-holds with a matter reference (e.g. "Smith v. Acme — 24-cv-1234") and a description of scope. The hold starts in status="active" immediately.

Bind documents to the hold from the hold detail page or from the document page itself ("Apply hold"). The hold detail page has three ways to apply:

- **Find subjects by description** — type what the matter is about ("correspondence about staircase rebuild safety findings") and Kodori runs a hybrid keyword + semantic search over the corpus, surfaces ranked candidates with snippets and an "already bound" badge, and lets you bulk-bind the unbound matches in one click. - **Bulk apply by source (NEW)** — pick a collection (matter, custom folder) or a saved search; Kodori applies the hold to every readable document in the source via the new `bulkAddDocumentsToLegalHold` MCP tool. Idempotent on already-held docs (counted, not re-emitted on the audit log). Permission-trimmed (docs you can't read skip silently). Result counts surface inline: newly added / already held / no permission / not found / total candidates. - **Add by document id** — paste a document UUID for the keyboard-only flow.

The agent can do the same flow via natural language ("find everything about the safety findings and bind it to the matter") — it always previews candidates and asks for your approval before binding.

**Automation-callable.** Because `bulkAddDocumentsToLegalHold` is registered as an MCP tool, the bulk apply is also addressable from /automations. Write an event-triggered rule like "when a doc is filed in the Smith Matter collection, apply the Smith litigation hold" and Claude compiles it to an mcp-tool-call action with the right args.

While the hold is active:

- the document refuses to tombstone (deletion) — the Delete button on the document page disables itself with a "Release the hold first" tooltip - retention disposal (review queue) refuses to dispose — the dispose button shows "Held · N" instead

**Custodian roster + notice emails.** A custodian is the named person responsible for preserving documents within the hold's scope — typically NOT a Kodori workspace member (often an internal employee with no DMS account, external counsel, or vendors). The custodian table on /legal-holds/[id] takes a paste-many email list (same shape as the bulk-invite form: comma / semicolon / newline / whitespace separated, capped at 100), dedupes against custodians already on the hold, and stores each row alongside the hold.

Click **Send hold notices** to email every custodian a litigation-hold notice with: matter ref, the workspace name, the verbatim hold scope from the description field, and a single-use acknowledgment URL pointing at /legal-hold-ack/[token]. Each ack URL is single-purpose: one custodian, one hold, one ack. **Re-sending rotates the ack token** (so a leaked URL from a prior send becomes inert) and clears any prior acknowledgment so the recipient is asked to ack the latest version of the scope — important when counsel expands the matter and the prior ack would no longer cover what's been preserved.

**Two resend modes.** "Re-send to all (rotates tokens)" rotates ack tokens for every custodian and clears every prior acknowledgment — use after material scope changes when everyone needs to re-confirm. "Nudge unacknowledged (N)" re-sends only to custodians who haven't acknowledged; acknowledged custodians keep their existing tokens and state untouched. The nudge button only renders when at least one custodian has been notified but hasn't ack'd; the count badge on the button shows exactly how many will be touched. Both modes emit the same legal-hold.notice-sent event per affected custodian on the audit chain.

The custodian opens the URL on the public ack page (no Kodori sign-in required — same token-as-auth pattern as share links), reads the matter ref + workspace + verbatim scope, and clicks **I acknowledge this notice**. The click stamps acknowledgedAt on the custodian row and appends `legal-hold.notice-acknowledged` to the matter audit stream with actorId=`public-hold-ack:<prefix>` so the chain captures external acks distinguishably from authenticated workspace activity. Idempotent — repeat clicks (browser back-button, double-click) don't double-stamp.

The custodian table on /legal-holds/[id] surfaces per-row: notice-sent date, ack status (Acknowledged YYYY-MM-DD / Pending / Not sent), and a Remove button. Matter owners see at a glance who's covered — the standard "are we ready to defend this?" question for a hold rollout.

**Acknowledgment progress bar.** Above the custodian table, /legal-holds/[id] now renders a stacked progress bar with three segments — emerald (acknowledged), amber (notified, awaiting acknowledgment), gray (no notice sent yet). Header shows "X of Y acknowledged" with a color-coded percentage badge (emerald 100%, amber ≥50%, neutral otherwise). Per-segment hover tooltip surfaces the bucket count. Hidden when no custodians exist — the empty-state copy below already prompts the operator. The bar lives above the existing Nudge unacknowledged / Re-send to all buttons so the natural reading order is status → action.

Four event types capture the full custodian lifecycle: `legal-hold.custodian-added` / `.custodian-removed` / `.notice-sent` / `.notice-acknowledged`. The hash-chained audit log thus records who was named, who was sent a notice, who acknowledged, and who was removed.

Release a hold by entering a free-text reason on the hold page. Subjects (the documents bound to the hold) are NOT removed — their existence on the hold is the audit evidence that they were once in scope. The release reason lands in the legal-hold.released event payload alongside the hold itself.