previews · Architecture & design · Agent Platform M2

Agent Platform · M2 requirements · July 2026

Agent Platform M2 — generalise beyond matters

M2 takes the matter assumption out of the TypeScript agent framework — scope-anchored threads, real authorization for firm- and user-scoped runs, VFS write verbs, and the first firm-scoped namespace — so an agent can operate a firm, not just sit inside a matter. This is a reader-facing recut of the confirmed requirements, published so you can react to the scope decisions before units and PRs are cut.

Scope confirmed · pre-planning Requirements explainer Gates M3 + M4 LEX-640 · 641 · 642 · 643

TL;DR

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:

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.

LEX-640 The verb-tool reshape already shipped re-scoped: reshape → extend

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.

LEX-641 "Only needs the shipped transports" is two jobs, not one two jobs

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.

Agent definition
scope + VfsConfig allow-list
deny-by-default — an agent touches a namespace/verb only by explicit grant
Scope authorization · once per run
matter: membership via matter  ·  firm: firm membership  ·  user: identity match
× denied → 401 / 403 before any model call
granted
Run context
principal + server-verified scope
Scoped thread
(scopeType, scopeId)
where the conversation lives — not a permission
VFS dispatch · per call
namespace granted?  ·  namespace scope ⊆ agent scope?  ·  scope ref server-verified?
× denied → tool error returned to the agent

the thread is a home; only the VFS gate continues to a write ↓

granted
Adapter → domain service
payload Zod-validated · identity injected server-side

05Sequencing: durable streams land first

A dependency changed since the work plan

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 archetypeDomain surface todayM2 unlocksStill 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.