M2 removes the matter monopoly from the agent framework. Threads become scope-anchored
(scopeType/scopeId), non-matter scopes get real authorization, the
framework VFS gains write verbs (create/edit) and its first
firm-scoped namespace (matters://, fronting matter creation), and framework
traces reach Langfuse parity with the Python agent.
The acceptance vehicle is a firm-scoped matter-intake agent: admin-principal, opens a matter and seeds it, fully traced. It is a proving vehicle, not a product.
M2 is the gate for the milestones behind it: M3's tool port needs the write-verb surface, and M4's background execution needs scoped, persisted threads.
01Why this milestone
The 2027 vision has agents operating a law firm with humans supervising and signing off. Extrapolated past lawyers to whole-firm operations — this project's direction — that means agents for matter intake and creation before any lawyer is involved, document-centre filing, and financial workflows. Every one of those agents starts outside a matter.
Today the platform cannot host a single one of them. The matter assumption is structural, not conventional:
- the VFS path grammar hardcodes
matterIdas the host segment; - all 18 content adapters take
matterIdas their first parameter; AIThreadrequiresmatterIdanduserIdwith a matter-keyed uniqueness constraint;- the transports skip authorization entirely for any non-matter scope —
authorize-agent-request.tsreturns a bare principal.
And the framework VFS is read-only (read/list) — no agent can
change anything through it yet.
02Two ticket premises, corrected against the code
Both re-anchored against what actually shipped in the framework core. This is the part most likely to be news if you last read the tickets.
The ticket describes reshaping a per-namespace×verb tool explosion. But the shipped framework
already emits one tool per verb with a namespace argument (vfs_read /
vfs_list). The reshape is done.
apis/lawrence-api/src/framework/vfs/verbs.ts:95-97// One tool per verb with the namespace as an // argument — not a tool per (namespace, verb).
So the real M2 work is adding create and
edit write verbs and converging the vocabulary at read / list / create /
edit. stat and POSIX-style verbs stay out.
The ticket frames thread generalisation as riding on the shipped transports. But the shipped
/agents/:slug transports persist no threads at all.
apis/lawrence-api/src/framework/runtime.ts// Client-supplied conversation; // not persisted server-side.
So LEX-641 is really two things: generalise the
AIThread schema and give framework chats opt-in persistence. Session-attribution
parity (LEX-642) is gated on threads existing at all.
03The decisions
Condensed from the confirmed Key Decisions. These are the calls I'd most want reaction on.
Verb vocabulary: read / list / create / edit
LEX-640 adds create and
edit. stat is deferred; mkdir/mv/rm stay
out of M2.
Server-fulfilled writes only
Client-fulfilled surfaces (email body/subject, note content) are unreachable through framework write verbs by construction — which kills the LEX-533 silent-failure class for new agents.
AIThread becomes scope-anchored
(scopeType, scopeId) ∈
matter | firm | user | org, plus kind and agentSlug.
matterId kept denormalised; userId nullable as M4 runway; existing threads backfill
as scopeType = matter.
Thread scope ≠ data-access scope
Where a conversation lives and what an agent may read/write are separate problems. Access is bounded at VFS dispatch — never inferred from the thread.
Run-local grant for firm-scoped writers
Resources an agent creates during a run become addressable for the rest of that run. A firm-scoped agent never gets blanket write access across the firm's matters.
Four scopes wired; org typed-only
M2 wires matter, firm and user;
org stays typed-only. Jurisdiction is dropped from the agent-scope set — it becomes a
namespace-level concern later.
matters:// wraps the ADMIN creation path
Matter creation via VFS wraps
the existing createMatter (source ADMIN) with can_create_matter
enforced. Marketplace orchestration is not reachable through agents.
Identity and scope ids are never model-asserted
firmId and scope ids
resolve server-side from the authenticated request and are verified against membership. The model only
chooses namespaces, resource ids, and payloads.
Instrumentation parity is concrete
Defined by the Python agent's actual surface — per-agent trace name, session = thread id, user attribution, tags, purpose-named spans, prompt-version linking, root-span final metadata — not aspiration.
The intake agent is a proving vehicle
Dev-flagged and params-fed; it exercises the whole substrate end-to-end. A product-facing intake experience gets its own brief with design.
04The containment model
Two gates, and access is bounded at both. Authorization happens once per run before any model call; VFS containment is re-checked on every tool call. Neither is inferred from where the thread lives.
VfsConfig allow-list(scopeType, scopeId)the thread is a home; only the VFS gate continues to a write ↓
05Sequencing: durable streams land first
The feat/durable-stream stack merges before LEX-641's AIThread
migration. Thread generalisation then builds on its (threadId, userId) keying and keeps the
sidebar's sidebar:{matterId}:{threadId} ids working via the denormalised
matterId — rather than landing in parallel.
Consequence: LEX-641 is no longer "startable now". What can start immediately is
LEX-640's write verbs and the firm-scope authorization work — neither touches AIThread.
06Firm-ops runway
Prioritisation context, not M2 scope — how the vision's firm-operations agents map onto today's domain surface once M2 lands. This is my working prioritisation, not a ratified roadmap.
| Firm-ops archetype | Domain surface today | M2 unlocks | Still missing after M2 |
|---|---|---|---|
| Matter intake & creation | createMatter (ADMIN path), contacts, conflict-check service, firm custom fields |
Firm-scoped agent + matters:// create — the proving agent |
Lead-api integration (leads are pre-firm, no firmId); client-principal intake |
| Document-centre filing | Matter documents/files/folders services; folder ops server-fulfilled | create/edit verbs on matter namespaces for background-shaped work |
Background execution (M4); bulk / file-move ergonomics |
| Finance (disbursements, billing) | Quotes / fee agreements exposed read-only; ledger has firm-level client/office accounts | Firm-scoped read patterns | Any finance write namespace (deliberately none yet); product decision on agent-touchable money |
| Compliance (conflicts) | Full conflict-check service in identity-service (firm-scoped) | Firm namespace pattern to expose it | A conflictchecks:// adapter; no KYC/AML domain surface exists at all |
| Firm task & date management | Task already supports matterId: null end-to-end; KeyDate modeled but service requires a matter |
Firm-scoped threads + write verbs | Key-date service gap (firm-level dates); firm task UI |
07What M2 is not
The substrate, not the products on top of it. Named explicitly so scope doesn't creep in planning.
- Background execution, agent principal, triggers M4
- The Python tool port + skills migration M3
- Org-scope wiring — stays typed-only
- Jurisdiction-rooted namespaces (e.g.
forms://) - Email / note server-fulfillment LEX-533
- Adapter-contract unification M3
- The firm-ops agents themselves (filing, finance, compliance)
- Global multi-matter "Lawrence everywhere" reads
- Marketplace matter creation (quotes, fee-agreement signing) via agents
- A policy engine — deliberately none; VFS exposure is the control surface