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.