architecture / tasks & key dates m2

Tasks & Key Dates M2 — making the task list agentic

Assign a task to Lawrence from the list, let it work and keep the task's state true, review the work and its process in the thread, and keep the list fed automatically from incoming documents.

Status: draft for review  ·  Date: 2026-07-02  ·  Author: Peter Zachares
Primary reviewers: PM Backend Frontend Legal AI  ·  Scope out: a full task-list redesign (statuses, priority, calendar/Linear views, issue/sub-issue families); assignment to anyone beyond Lawrence + the matter's existing participants; the firm-wide agent proposal queue (deferred — review is the sidebar + thread for now, see §3).
Source: PRD — Tasks & Key Dates M2  ·  Builds on: Skills feature, Proactive Lawrence  ·  Detailed by: Lawrence as a first-class identity (the assignee / identity work), Agent proposal queue (the deferred review surface).

This page is a review surfaceselect any text to leave a comment; reactions and a sign-off / approval are supported (at the bottom of the page). The proposed option for each decision is my recommendation, not a settled call — push back freely.

Overview

Lawrence can already complete individual tasks end to end — but only when a user asks it to, in chat. This milestone closes that gap in two directions, without redesigning the task list:

  • Pull — a lawyer looking at their task list can hand a task straight to Lawrence, watch it work in a live thread, and later review both the finished work and the steps it took to get there.
  • Push — when a document lands on a matter, Lawrence reads it for dates and actionable items and offers them back as key dates and tasks, attributed to the document they came from.

The point where those two loops meet is the task list itself: one loop keeps it done, the other keeps it true.

flowchart TB
  subgraph pull["Work a task with Lawrence"]
    direction LR
    T[Task in the list] -->|Assign to Lawrence| TH[A thread opens]
    TH --> W[Lawrence works it,
live and step by step] W --> S[The task's state
stays current] S --> R[Review the output
+ the process, together] end subgraph push["Keep the list true from documents"] direction LR D[Document added
to a matter] --> X[Scan for dates
+ actions] --> P[Proposed key dates / tasks,
attributed to the source] end P -.->|lawyer confirms| T classDef d fill:#eef1f6,stroke:#9aa3b2,color:#1f2430; classDef hot fill:#fff,stroke:#B5491F,color:#1f2430,stroke-width:2px; class T,TH,W,S,R,D,X,P d; class T hot;

The four PRD requirements map onto the two loops: Pull = assign (§1) → manage state (§2) → review (§3); Push = auto-extract (§4), gated on the agent proposal queue.

Solution shape

Four building blocks, each a numbered section below. The theme throughout: reuse a merged pattern, add the one missing edge.

BlockWhat it deliversReuses (merged today)The new edge
§1 Assign FEAn "Assign to Lawrence" action on every task that opens a live working thread linked back to the task.The skill-library trigger: a provisional thread → prompt → server streams the real thread back.Carry a taskId into the run; store a task ↔ thread link so the two can navigate to each other.
§2 Manage state BE AIAs it works, Lawrence (re)assigns the task (to itself), edits it, marks it for review, and can delete an irrelevant one — deletion gated by a confirmation.The VFS write mechanism (create + read already exist) and the editable-surface pattern from documents/notes/emails; the client_fulfilled HITL pattern. The edit/delete logic already exists in matter-service.Expand the write surface — add edit (rename, reschedule, reassign, complete, relink) + soft-delete, exposed as internal.* procedures + an editable surface + a delete tool. Plus a "for review" state and an assignee that can be Lawrence.
§3 Review FEReview completed work via the agent sidebar (it signals a task is done) + the linked thread (its process and output). Matter-scoped, per-thread.The per-thread Activity blocks and the task ↔ thread link from §1.Signal "done / for review" in the sidebar and deep-link to the thread. The firm-wide aggregated queue is deferred (§3).
§4 Auto-extract BE AINew documents yield proposed key dates + tasks, attributed to the source, that the lawyer confirms.The ingestion→ingestionSucceeded event, the structured-LLM extraction pattern, and the existing date (UpcomingCaseEvent) + task (CaseTask) schemas.A write-back from the AI platform (it only reads today) + dedup, feeding the agent proposal queue as its confirm surface — so §4 depends on the queue and sequences after it.

Two PRD comments already suspect this "necessitates a rethink of the design of tasks" and ask "what happens to assignment". They're right the pressure is real — it concentrates in KD1 (assignee) and KD2 (for-review state). The "what happens to assignment" question grew into its own workstream — Lawrence as a first-class identity, which this plan now depends on; for the for-review state we add the smallest true edge and defer the wider redesign.

1 · Assign a task to Lawrence Frontend

Requirement. Every task exposes an "Assign task to Lawrence" action. Triggering it opens a new thread — mirroring the skill-library trigger — where Lawrence works the task and the lawyer watches in real time. The task stays linked to that thread so the lawyer can move between the list and the working thread.

The thread-creation pattern to mirror already exists and is merged. When a lawyer runs a skill, the app doesn't call a "create thread" endpoint. It mints a client-only provisional_<uuid> thread, opens the panel, and posts a first message with body: { matterId }. The server creates the persisted thread and streams its real id (aithrd_…) back over SSE as a data-thread event; the client then "promotes" the provisional tab to the real id. Assigning a task reuses exactly this thread lifecycle, plus carrying a taskId through the run. What we deliberately don't copy is how the skill fills the prompt — see the note below the diagram.

sequenceDiagram
  actor U as Lawyer
  participant FE as Task list (legal-os)
  participant TR as Lawrence panel
(chat-manager) participant API as lawrence-api
/lawrence/chat participant AG as Lawrence agent U->>FE: Click "Assign to Lawrence" on a task FE->>TR: startNewThread() → provisional_… + expand panel TR->>API: POST first message
"Execute this task" + { matterId, taskId } API->>AG: /generate (request incl. taskId) AG->>AG: Read the task first (tasks://)
name, description, context AG-->>TR: SSE — thinking, tool calls, edits (live) API-->>TR: data-thread → persisted aithrd_… TR->>FE: promoteTab + persist task ↔ thread link Note over FE,TR: Task row now shows "Working with Lawrence"
and deep-links to the thread

Deliberate divergence from the skill trigger. The skill trigger's compileSkillToPrompt inlines the skill's entire markdown body into the first message. We do not copy that here. Lawrence already reads tasks (the tasks:// namespace, see §2), so the trigger injects only an instruction to execute the task plus its taskId — and the agent's first step is to read the live task itself. This keeps a single source of truth (the current task, not a snapshot frozen into the prompt), avoids prompt bloat, and means any edit to the task between click and execution is picked up automatically.

What we build on top

Trigger hook
A task-scoped sibling of use-trigger-skill.ts. Unlike the skill trigger — which inlines the whole skill body — it submits a thin instruction ("Execute this task") with body: { matterId, taskId }, then relies on the agent reading the task. From the global /tasks list — where there's no matter-scoped chat panel — reuse the skills' ?skill= convention: navigate to the matter with ?task=<id> and auto-trigger on arrival (mirror of use-auto-trigger-skill.ts).
The button
Lowest-friction home is the existing TaskActions overflow menu (today it holds only "Delete") in the task detail panel, plus a primary action on the row. Keep the label literally "Assign task to Lawrence".
Task ↔ thread link
The new persisted edge — see KD3 for where it lives. Association today is matter-only (baked into the chat id sidebar:${matterId}:${threadId}); a task is a new associable entity. This link is what powers both the "navigate to the thread" requirement here and the thread-based review in §3.

Boundary (PRD open question). Which task types can Lawrence meaningfully complete this milestone? We can ship the action on every task and let Lawrence decline gracefully in-thread, or gate the button to task types we've validated. Recommendation: ship broadly, measure, then gate — captured as Open Q1.

2 · Lawrence manages task state as it works Backend Legal AI

Requirement. On assignment the task's assignee becomes Lawrence (ownership is unambiguous); when the work is done Lawrence marks the task for review; when a task is no longer relevant Lawrence can delete it, behind a human confirmation.

The data model today, and the three edges we add

Tasks live in matter-service. A Task has no status column — its state is derived from nullable timestamps: completedAt means done, deletedAt means soft-deleted (a soft-delete Prisma client intercepts .delete()). "Matter-level vs global" is just whether matterId is set. Assignment is a one-to-one TaskAssignment row carrying assignedIdentityId. A KeyDate is its own model (one key date → many tasks).

erDiagram
  KeyDate ||--o{ Task : "has many"
  Task ||--o| TaskAssignment : "one"
  KeyDate {
    string id "kd_…"
    string matterId "nullable → global"
    datetime dueAt "required"
  }
  Task {
    string id "tsk_…"
    string matterId "nullable → global"
    string name
    datetime dueAt "nullable"
    datetime completedAt "nullable = done"
    datetime deletedAt "nullable = soft-deleted"
    datetime markedForReviewAt "NEW (see KD2)"
    string threadId "NEW link (see KD3)"
  }
  TaskAssignment {
    string id "tskasm_…"
    string assignedIdentityId "human — or Lawrence (identity plan)"
  }
  

Today Lawrence can create and read tasks and key dates, but cannot edit or delete them (mechanism below). M2 opens up the edit + delete surface; three parts are worth calling out:

Assignee = Lawrence
Not possible today. assignTask runs assertFirmMembership on the assignee, and identity-service only knows IdentityType.USER — there is no agent identity. This is the PRD's "what happens to assignment" comment, and it grew into its own workstream: Lawrence as a first-class identity. The resolution there is a per-firm agent identity; once Lawrence has a firm-membership row the existing assign gate passes unchanged. The agent must still take the assignee from its run context, never from the model — the same prompt-injection defence the document-edit surface applies to personId.
"For review" state
There is no review state today. Proposed: a nullable markedForReviewAt, consistent with completedAt/deletedAt. The alternative — a real status enum — is where the "task redesign" pressure sits (KD2).
Confirm every write
Confirmation isn't delete-specific: every agent-initiated write — create, edit, delete — goes through the client_fulfilled HITL (ask_user_question). The agent proposes the change, the turn pauses, the lawyer confirms, and it applies on the resume turn. Soft-delete already exists (deletedAt + restoreTask), so delete is just one confirmed write among several. See the flow below — and the boundary question (OQ7): assignee→Lawrence and mark-for-review may be implicit since they follow the user's own action.

What the agent can touch — today vs the M2 target

Task field / actionCreate todayEdit todayM2 target
name, descriptionedit (rename)
dueAtedit (reschedule)
assignee✓ (self)(re)assign, incl. to Lawrence
completedAt / for reviewmark complete / for review
key-date link (keyDateId)edit (relink)
delete / restoresoft-delete + restore (HITL)
matterId, orderIndexout of scope for the agent
Key date field / actionCreate todayEdit todayM2 target
nameedit (rename)
dueAtedit (reschedule)
deletesoft-delete

Two key-date wrinkles to decide: deleting a key date can either detach its child tasks or soft-delete them too (the existing shouldDeleteTasks flag on deleteKeyDate); and there is no restoreKeyDate today (only restoreTask) — decide whether the agent needs key-date restore parity.

How the agent writes — and why every write is confirmed

Two things this section pins down: the write path is exposure of mutations that already exist (not new task logic — that's the de-risking point), and every agent-initiated write is gated by a human confirmation before it lands.

Lawrence can already read and create tasks and key dates — tasks:// / keydates:// have create + list adapters. What it cannot do is edit or delete: both adapters' write() throws "read-only via VFS", there is no task/key-date editable surface in the registry, and there is no delete VFS tool at all. Crucially the edit/delete logic already exists in matter-servicerename, schedule, assign, updateCompletedAt, updateKeyDate, delete, restore — but only as user-scoped protectedPerson mutations, with no internal.* variant the agent's service token can reach (only listMatterTasks / get / create are internal today). So the work is exposure, not new logic: (1) add internalPreAuth variants of those mutations; (2) implement the adapters' edit() / delete() (they currently throw); (3) register task/key-date editable surfaces and add a delete tool in the agent's VFS layer (only read / edit / create / stat exist today) — all via the same KMS-signed VFSClient the agent's writes already use.

flowchart LR
  AG["Lawrence agent
proposes a write"] --> HITL{"Confirm?
(ask_user_question)"} HITL -->|cancel| STOP["not applied"] HITL -->|lawyer confirms| SUR["Task / key-date
editable surface (new)"] SUR -->|KMS-signed VFSClient| LAPI["lawrence-api"] LAPI --> INT["matter-service
internal.* mutation
(create / edit / delete)"] INT --> DB[("matter-service DB")] classDef d fill:#eef1f6,stroke:#9aa3b2,color:#1f2430; classDef n fill:#fff,stroke:#5a8a64,color:#1f2430,stroke-width:2px; classDef bad fill:#fff,stroke:#a83838,color:#1f2430; class AG,SUR,LAPI,INT,DB d; class HITL n; class STOP bad;

Write-with-confirmation, step by step (create · edit · delete)

Agent proposes
Lawrence decides on a change — create a task, reschedule it, relink a key date, delete it — and calls the confirmation tool with a plain-language summary of what it's about to do.
Turn pauses
client_fulfilled tools are never run server-side; the loop validates the args, emits the confirm panel, and ends the turn — it does not block.
Lawyer answers
The FE renders the confirm panel; the answer comes back as a new request whose last message is a tool result. The loop detects the resume and continues.
Change applies
On "confirm", the agent calls the matching internal.* mutation (create / edit / soft-delete). On "cancel", it acknowledges and moves on. For a soft-delete, restore stays available.

3 · Review the work & its process Frontend

Requirement. The lawyer should be able to review what Lawrence completed — not just the final output but the process (the thread and the steps it followed).

What M2 builds — the fastest path to value. No dedicated or aggregated review view. When Lawrence finishes a task it marks it for review (§2), and the agent sidebar signals that it's done; review then happens in the thread — the task ↔ thread link from §1 deep-links to the conversation, where the existing Activity blocks ("Explored · 5 reads · 2 searches") show the process and the produced output is visible inline. This reuses what already exists (the sidebar, the thread, Activity blocks, the task ↔ thread link) with no new surface to build.

flowchart LR
  D["Agent sidebar:
task marked for review"] --> C[Open the task] C -->|task ↔ thread link| TH["The linked thread"] TH --> A["Activity blocks
the process: reads, searches, edits"] TH --> O["The produced output
document / email / note"] classDef d fill:#eef1f6,stroke:#9aa3b2,color:#1f2430; classDef n fill:#fff,stroke:#5a8a64,color:#1f2430,stroke-width:2px; class C,TH,A,O d; class D n;

Supported workflow (the M2 scope line). A person triggers a task on a matter → Lawrence works it in a thread → that person, or another participant on the same matter, reviews by reading the thread. Review is matter-scoped and per-thread; there is no firm-wide aggregation and no review of background or cross-matter work yet.

Deferred — the agent proposal queue. A firm-wide, RBAC-scoped surface aggregating everything Lawrence has done (across matters, including background / autonomous work) is a separate workstream: the agent proposal queue. Shipping sidebar-plus-thread review now is the fastest path to value; the queue becomes necessary once background execution exists — when there's no live thread to watch, an aggregated review surface is the only way to catch what the agent did.

4 · Auto-extract tasks & key dates from documents Backend Legal AI

Requirement. When a document is added to a matter, Lawrence scans it for key dates and actionable items and creates the corresponding tasks / key dates automatically, clearly attributable to the source document.

Blocked by the agent proposal queue. Auto-extraction is a background process — it produces proposed tasks/key dates with no live thread for a human to watch. Those proposals need a review/confirm surface, and for background work that surface is the agent proposal queue (which we've planned). So §4 depends on the queue being built first — the same "no live thread → you need the queue" argument as §3. It sequences after the queue, not in M2's first cut.

Most of this pipeline already exists. Ingestion parses an uploaded document and, on completion, publishes a notification that the platform re-emits as the Inngest event app/case/file.ingestionSucceeded. That event already triggers structured extraction in the AI platform, which already has schemas for dates/deadlines (UpcomingCaseEvent — court dates, deadlines, expirations) and for predicted next actions (CaseTask), all produced by the same structured-LLM-with-versioned-prompt pattern. The genuinely missing pieces are a per-document extraction step and, above all, a write-back: the AI platform only ever reads from the platform today.

flowchart TB
  D[Document added] --> ING["ingestion
parse + index"] ING -->|completion notification| BR["platform bridges to
app/case/file.ingestionSucceeded"] BR --> EX["AI platform:
extract dates + actions
from the new doc's text"] EX --> WB["Write-back to lawrence-api
(NEW outbound path)"] WB --> PR["Proposed key dates / tasks
attributed to the source doc"] PR -->|lawyer confirms| L[On the matter's list] classDef d fill:#eef1f6,stroke:#9aa3b2,color:#1f2430; classDef n fill:#fff,stroke:#5a8a64,color:#1f2430,stroke-width:2px; class D,ING,BR,EX,PR,L d; class WB,PR n;
Reuse
Trigger
app/case/file.ingestionSucceeded (already fires per file; extraction functions already subscribe, with a debounce per case).
Extraction
A dedicated per-document pass (its own prompt + eval loop), reusing the structured_complete + Langfuse-prompt + Pydantic response_model machinery and the UpcomingCaseEvent / CaseTask schemas — chosen over piggybacking the statement pipeline or deriving from the whole CCO (KD7). Dedupe against the tasks and key dates the case context already holds (CaseTask + UpcomingCaseEvent).
New
Write-back
A new outbound endpoint on lawrence-api to create key dates / tasks on a matter, plus a client for it in the AI platform. This is the main new backend surface — see KD5.
Confirm
Proposed model: propose → confirm, through the agent proposal queue — the review surface for background-produced proposals (which is why §4 is gated on it). Extracted items land as source-attributed proposals the lawyer approves. Alternative (commit directly) in KD4.

5 · Key design decisions

Only genuine forks — a reasonable reviewer could argue the other way. The Proposed column is my recommendation, not a settled call. Comment on any row.

#DecisionProposedAlternativesWhy
KD1How Lawrence becomes an assignee
assignees must be human firm members today
A dedicated agent identity (IdentityType.AGENT + a per-firm Lawrence identity)(b) an assigneeKind/isLawrence flag on TaskAssignment; (c) a sentinel identity id that bypasses the membership checkAn agent identity keeps the assignee a real, referenceable identity and is reusable well beyond tasks, avoiding a parallel assignee dimension the FE must special-case. Resolved — this became its own workstream, Lawrence as a first-class identity, which works out the full identity + access model (borrow / mint / grant, per-firm scope, isolation); M2 consumes it.
KD2How "for review" is modeled
no status enum exists today
A nullable markedForReviewAt timestampA real status enum (OPEN / FOR_REVIEW / COMPLETE)The timestamp matches the existing completedAt/deletedAt shape, is a tiny migration, and honours the PRD's "no redesign" scope. The enum is cleaner long-term but touches every state-deriving site and starts the redesign the PRD defers.
KD3Where the task ↔ thread link livesthreadId on Task (matter-service owns the link), set when the run is promotedA link owned by the chat/thread store; or a dedicated join table for many-threads-per-taskThe list is the primary navigation surface and matter-service owns the task, so the link is cheapest to read there. A join table is only worth it if one task legitimately spawns several threads — probably later.
KD4Auto-extraction commit modelPropose → confirm (source-attributed suggestions)Commit directly with attribution + easy undoPropose→confirm keeps the human in control, avoids list noise from wrong/duplicate items, and matches the "agent proposes, lawyer confirms" pattern already argued for in the Proactive Lawrence proposal. Direct-commit shows value faster but risks eroding trust in the list.
KD5Extraction write-back pathA new outbound lawrence-api endpoint to create key dates / tasks, called from a new AI-platform Inngest stepLet the platform pull extracted items from the AI platform (as CCO/suggestions are pulled today)A push write-back gives a clean, auditable creation point and keeps attribution close to creation. Pull reuses more existing wiring but couples list freshness to a poll and to CCO regeneration timing.
KD6Agent task-write surfaceA new editable surface under the existing VFS surface registry (tasks:// already reads)Dedicated standalone tools per action (like fill_form)An editable surface is the consistent home given tasks already read through the VFS namespace, and keeps auth/streaming uniform with document/note/email edits. Standalone tools are warranted only if an action needs bespoke async workflow behaviour.
KD7Where document-extraction hooks inA dedicated per-document pass — a new Inngest step on ingestionSucceeded, own prompt + eval, reusing the structured-LLM machinery and UpcomingCaseEvent/CaseTask schemas, feeding the agent proposal queue for propose→confirm (which §4 is therefore gated on)(A) piggyback the CCO statement-extraction pass; (C) derive from the whole CCO post-cco.generated; (D) the proactive module alone; (E) an on-demand agent run per documentPer-document extraction gives the source-document attribution the requirement demands plus an independently tunable eval loop; the whole-case options (C/D) blur attribution, piggybacking (A) dilutes quality and can't be versioned separately, and the agent run (E) is the future once background execution + the autonomous identity land. Reuse the tasks + key dates the case context already holds (CaseTask + UpcomingCaseEvent) to dedupe rather than re-extract.

6 · Open questions

  • OQ1 — Task-type boundary. Which task types can Lawrence meaningfully complete this milestone? Ship the action on all tasks and let it decline in-thread, or gate to validated types? (PRD open question.)
  • OQ2 — Review surface (resolved for M2; rest deferred). M2 reviews per-thread via the agent sidebar + the linked thread — no aggregated view. The firm-wide "everything Lawrence did" surface (its filters, cross-matter and background scope) is the agent proposal queue's concern, deferred until background execution exists.
  • OQ3 — Agent identity (resolved). Settled: a first-class per-firm agent identity, not the flag/sentinel. The full identity + access design (borrow / mint / grant, isolation, autonomous modes) is its own plan — Lawrence as a first-class identity.
  • OQ4 — Does M2 start the task redesign? KD2 defers the status-enum migration. If the team believes the redesign is inevitable, is it cheaper to start the enum now rather than add a third timestamp we later unwind?
  • OQ5 — Extraction precision bar & de-dup. What precision must extraction hit before we surface it? How do we avoid proposing a key date the matter already has — deduping against both the matter's existing tasks/key dates and the tasks + key dates the case context already holds (CaseTask + UpcomingCaseEvent)?
  • OQ6 — Concurrency & ownership. What happens if a lawyer edits or reassigns a task while Lawrence is mid-work in its thread? Who wins, and how do we make that legible?
  • OQ7 — Confirmation boundary. Every agent-initiated write is confirmed via ask_user_question. Should the writes that follow the user's own action be implicitassignee → Lawrence (they clicked "Assign to Lawrence") and mark for review (the done signal) — while only agent-decided changes (rename, reschedule, relink, new task/key-date, delete) prompt a confirm? Proposed: yes, those two are implicit.

7 · Rollout

  • Flags. The matter task tab and the Lawrence 2 sidebar are already flag-gated; gate the new "Assign to Lawrence" action and auto-extraction behind their own flags so each can ship and be dogfooded independently.
  • Sequence — fastest path to user value (which is also the Linear milestone order). Order by what puts capability in a lawyer's hands soonest: (1) expand the agent's allowable VFS actions — let Lawrence manage task state (mark for review, delete-with-HITL, assignee), the quickest visible capability on top of what exists (VFS milestone); (2) backend identity, which unblocks assignee → Lawrence itself (Backend identity milestone / the identity plan); (3) the execution button — assign from the list + the task ↔ thread link (§1, Execution button milestone). That first cut ships value with review via the sidebar + thread (§3). After it: (4) the agent proposal queue (planned separately) — the deferred prerequisite for both background review and extraction; (5) document extraction (§4), which is gated on the queue because its background-produced proposals need that confirm surface.
  • Quality gate for extraction. Version prompts in Langfuse and evaluate precision/recall on a labelled document set before enabling the write-back for a firm; start in propose→confirm so early misses are caught by the lawyer, not committed silently.
  • Attribution & audit. Every Lawrence-driven state change (assignee, review, delete, auto-created item) must be attributable — to the thread for interactive work, to the source document for extraction.
  • Failure behaviour. Confirm before build: agent write failures should surface an error in-thread (not a silent no-op), and a delete that races a concurrent edit should abort and tell the lawyer rather than clobber.

8 · Execution & review plan

PRs atomic by responsibility, grouped into per-repo stacks (≤7 each). Sizes below are estimates for review, not measured against a prototype — calibrate before committing.

Required dependency — Lawrence must exist as an identity first. The assignee work (Stack A1 / §2) cannot land until the Lawrence as a first-class identity project ships: Lawrence has to be an assignable identity before it can be set as a task's assignee. That project is a prerequisite for M2's execution, not an optional detail.

Stack A — matter-service + identity (backend)

PRContents
A1Assignee = Lawrence: consume the per-firm agent identity from the identity plan (the Backend identity milestone). Once Lawrence has a firm-membership row the existing assertFirmMembership gate passes unchanged — no gate change needed here.
A2markedForReviewAt column + migration + derive-state updates (KD2).
A3threadId on Task + set-on-promote (KD3).
A4New internalPreAuth variants wrapping the existing services — tasks: rename, schedule, assign, updateCompletedAt, updateKeyDate, delete, restore; key dates: rename, schedule, delete (+ a new restoreKeyDate for parity, if wanted). Plus markedForReviewAt exposure to the FE. The logic exists; only the internal-procedure wrappers are new.

Stack B — Lawrence agent

PRContents
B1Task + key-date editable surfaces (KD6): implement the adapters' edit() (they throw today) for the full field set — rename, reschedule dueAt, (re)assign, mark complete / for-review, relink key date — via the KMS-signed client; assignee forced from run context.
B2Write-confirmation: a client_fulfilled confirm tool (ask_user_question) gating every agent-initiated create / edit / delete; plus the delete VFS tool (none today). On confirm, the matching internal.* mutation applies on the resume turn.
B3Carry taskId into the run (request schema + context) so a task-triggered thread knows its task.

Stack C — legal-os (frontend)

PRContents
C1"Assign task to Lawrence" action (overflow menu + row) and the task trigger hook; from the global list, the ?task= navigate-and-auto-trigger.
C2Task ↔ thread linkage in the UI: "working with Lawrence" state on the row, deep-link into the thread, and back.
C3Assignee UI showing Lawrence as an assignee; "for review" surfaced on the row/detail panel.
C4Review UX (§3): surface task "done / for review" in the agent sidebar and deep-link from the task to its linked thread (reusing Activity blocks). No aggregated queue — that's a separate future plan.

Stack D — auto-extraction (AI platform + lawrence-api) · blocked by the queue

Gated on the agent proposal queue. Extraction produces background proposals with no live thread, so this stack can't ship until the queue exists to provide their confirm surface — it sequences after the queue, not in M2's first cut.

PRContents
D1New outbound lawrence-api endpoint to create key dates / tasks on a matter, with source-document attribution (KD5).
D2Per-document extraction Inngest step off ingestionSucceeded, reusing the structured-LLM pattern + UpcomingCaseEvent/CaseTask schemas; client to call D1.
D3Route proposals through the agent proposal queue (their confirm surface) — this is the blocker: needs the queue built first. Source attribution + dedupe against existing tasks/key dates and the case context (CaseTask / UpcomingCaseEvent).

Glossary

Domain + plan shorthand, so no tribal knowledge is needed.

TermMeaning
LawrenceThe agentic paralegal — the AI chat agent that performs legal work on a matter.
ThreadA Lawrence chat conversation, scoped to a matter. A matter can have several, shown as tabs.
Skill / skill-library triggerA predefined Lawrence capability the lawyer can run from a library; triggering it opens a new thread pre-seeded with a compiled prompt. §1 mirrors this flow.
Task / Key dateBoth live in matter-service. A task is a to-do (name, optional due date, optional assignee); a key date is a first-class dated event that can group tasks.
Global vs matter taskWhether the task's matterId is set (matter) or null (firm-wide/personal).
Soft deleteA Prisma client intercepts .delete() and sets deletedAt; reads filter deleted rows. restoreTask undoes it.
HITL / client_fulfilledHuman-in-the-loop. A tool that isn't run server-side: the agent emits it, the turn pauses, the lawyer answers, and the run resumes with that answer. Used for delete confirmation.
Editable surface / VFSThe agent's write mechanism: a registry of surfaces (document, note, email, form, folder) it edits via a KMS-signed call to lawrence-api. We add a task surface.
CCOCase Context Object — the AI platform's cached, structured understanding of a matter, including extracted UpcomingCaseEvents (dates/deadlines).
ingestionSucceededThe event fired once a document has been parsed/indexed; already triggers structured extraction. §4 hangs off it.
Activity blocksThe grouped rendering of Lawrence's tool activity in a thread ("Explored · 5 reads") — the process record §3 reuses.