Lawrence engineering · Decision doc · 11 June 2026
Court bundles, without the round-trip
Lawyers build roughly 3,700 court bundles a year in the legacy platform's
wizard, and exactly zero in v3, because v3 has no bundler at all. The documents live in the case;
the bundling happens outside it. This doc lays out four shapes for letting Lawrence curate,
index and assemble bundles in chat, so we can pick one.
By Adolfo Tamayo Briceno · Lawyer Experience (LEX)
Two consent orders landed since January. Shall I re-issue the bundle with them in section A?
Yes, and put orders first.
What the lawyer sees
The target experience, common to all four paths: the lawyer asks for a bundle in
chat, Lawrence proposes contents from the case files, the lawyer edits the proposal in plain
English, and a preview assembles with the index resolved against real page numbers. Nothing
touches the case file until they confirm.
legal-os · matter / Patel v Patel
Hearing bundle
Lawrence
The problem
A court bundle is the paginated, indexed PDF of case documents a lawyer files before a
hearing or to close a case: an index up front, every page numbered, contents in the order the
court expects. The documents are already in Lawhive. The bundling isn't. Today a lawyer
downloads everything, assembles the bundle in a local PDF tool, uploads the result back, and
emails it on to the court.
This isn't a niche workflow, and we don't have to guess. The legacy platform has a manual
bundling wizard, and its analytics say lawyers lean on it constantly:
~3,700
bundles saved in the last 12 months (legacy wizard)
~40
unique lawyers bundling per month
1.5×
previews generated per saved bundle: lawyers already iterate
0
bundling capability in v3 today
The wizard (built by George Gebbett, May 2024, still live at
/solicitor/cases/[id]/bundles/create) merges hand-picked PDFs and images into one
file with optional page numbers. What it doesn't do is the part that makes a bundle a
court bundle: no index, no sections, no automatic ordering, no DOCX support, and
encrypted PDFs are silently dropped. It's also whole files only: the tool's entire input is a
list of file ids plus one page-numbering boolean, so there is no way to include just pages
3–9 of an exhibit. A lawyer who needs a partial document (the order without its cover
letters, the signed page of an agreement) extracts it locally first. The lawyer does all the
curation by drag-and-drop, then fixes the gaps locally anyway. And none of it was ever ported
to v3, so every firm that migrates loses even that.
The judgment work, what goes in, in what order, under what index entry, is
exactly what a static wizard can't do and an agent can.
That's the bet of this doc: don't port the wizard, and don't build a fancier one. The
failure mode of bundling tools is prescriptiveness; every court, practice area and judge has
its own expectations, and a form-based UI has to hardcode a shape. A conversation doesn't.
Lawrence curates, orders and writes the index in dialogue with the lawyer; deterministic
machinery converts, merges, stamps and resolves. The question this doc settles is only
what shape the machinery's tool surface takes.
What's already on the shelf
A scan of platform-v3, agents and the legacy platform (June 2026). The encouraging part:
most of the hard pieces exist somewhere already. The honest part: the two pieces in the middle
of the feature don't exist in v3 at all.
Exists, reusable
✓Document storage + client visibility.MatterFile + S3, with a visibility flag the client portal already respects. A saved bundle is client-visible by flipping one field.platform-v3 · matter-service
✓DOCX → PDF conversion. LibreOffice-based converter, plus image and email conversion. Mixed-format inputs are mostly solved.platform-v3 · packages/pdf-converter
✓PDF merge + page stamping. Production-proven pdf-lib code in the legacy bundler, including the preview-bucket-then-copy save pattern. Portable.legacy platform · cases/files/bundles
✓Lawrence's tool surface. VFS read/create/edit, document creation, and a proven pattern for long-running agent work with progress in chat.agents · platform-vfs
✓Page-aware file reads. Lawrence reads PDFs natively as binary content blocks (DOCX via its PDF preview rendition), page structure intact, so it can reason about what sits on which page: the prerequisite for proposing "include only pages 3–9".platform-v3 · lawrence-api file adapter
✓Email with attachments. Nylas draft system. Once the bundle is a case file, emailing it to court is existing functionality.platform-v3 · email-service
Missing in v3
✗Any bundle concept. No model, no API, no UI. Only seeded test data referencing cases/…/bundles/*.pdf paths.
✗A PDF merge dependency. No pdf-lib or equivalent anywhere in the v3 stack.
✗Index / table-of-contents generation. Nowhere, including legacy. This is the piece lawyers fix by hand today.
✗Bookmarks and section structure. Bundles are flat concatenations or nothing.
✗Graceful handling of awkward inputs. The legacy wizard silently excludes encrypted PDFs; nothing surfaces the gap to the lawyer.
The shape we've already agreed
Four product decisions are settled from the brainstorm and constrain every path below.
They're the fixed points; the paths only differ in how they implement them.
Primitives, not formatsNo hardcoded bundle format, no per-court templates. Lawrence composes the bundle, writes the index entries and adapts to the lawyer's or the court's directions conversationally. The machinery offers capabilities, not opinions.
The index is never guessedIndex page references are computed against the final merged pagination by the assembly engine. The agent supplies the words; the engine supplies the numbers. A bundle with a wrong index is worse than no bundle.
Preview, then confirmThis is a court-bound artifact. Lawrence assembles to a preview the lawyer can open and iterate on; nothing lands on the case until they confirm. Built on existing models (the legacy preview-bucket pattern), not a new draft-state entity.
v1 ends at a case fileThe confirmed bundle is an ordinary case file with the requested visibility. Client access, email to court, and anything else that consumes files ride existing rails. No bespoke distribution.
Four ways to hand Lawrence the scissors
All four paths share the same foundation: port the legacy merge and stamping code, reuse
the converter, reuse preview-then-copy. In every path a bundle item can be a whole document
or a page range of one (the partial-document need the legacy tool never met). They split into
two families. A and B keep assembly in platform code the agent calls. C and D hand
composition to the agent itself: as a chain of tool calls, or as a program it writes.
A
One declarative assembly call
Lawrence passes an ordered list of items, case documents plus any pages it authored itself (index, cover, dividers), in a single call. The engine converts, merges, stamps and resolves; the chat holds the working state.
agent curates in conversation, drafts index entries
↓
assemble (items + options) → engine merges, paginates, resolves index
↓
preview ↔ tweak = re-issue the call with an edited list
↓
confirm → ordinary case file
✓Leanest build; no new entities anywhere
✓Index accuracy is the engine's job by construction
✓Evolves into B without a rewrite: the call's payload is the manifest
✗Bundle definition isn't durable; resuming next week means re-deriving from chat
✗Large bundles re-process fully on every tweak
Best for shipping v1 fast and validating that agentic bundling solves the real problem.
B
Persistent manifest, compiled
The same engine, but the bundle definition is a persisted artifact on the matter, riding existing artifact/document models. The agent edits the manifest; the engine compiles manifest to PDF, as many times as needed.
agent creates a manifest artifact on the matter
↓
compile manifest → preview
↓
edit + recompile ↔ durable across sessions and weeks
↓
confirm → case file; manifest stays for re-issue
✓Durable, resumable, auditable definition
✓Re-issuing an updated bundle before a hearing is "add two items, recompile"
✓Same engine and index guarantees as A
✗The manifest becomes a first-class thing: schema, lifecycle, maybe UI
✗Schema churn while we're still learning what lawyers ask for
Best when bundles prove to be living, re-issued deliverables rather than one-shots.
C
Fine-grained PDF primitives
The inversion: merge, stamp pages, insert page, bookmark and page-count as independent tools the agent chains step by step in a working area. The agent performs the assembly, not just the curation.
agent converts each doc, tracks intermediates
↓
merge → page-count → compute index refs itself
↓
render index → insert → re-stamp → preview
↓
confirm → case file
✓Maximum theoretical flexibility
✓Primitives reusable for other document workflows
✗The agent does page math: the one place accuracy must be deterministic
✗Intermediates need a workspace, colliding with "no new state holder"
✗Slow multi-call assembly; every step is a failure surface
Best almost never: if the agent should compose the assembly itself, path D delivers the same flexibility as one program instead of a call chain.
D
One generic tool: a code sandbox
Lawrence gets a sandboxed interpreter with a filesystem. Working copies of the case files are staged in; the agent writes a short Python program (against a vetted bundle library, with pypdf underneath) that assembles the whole bundle in one execution; the output comes back through the same preview-then-confirm publish step.
agent writes a driver script: the manifest, as code
↓
sandbox runs it: convert, extract, merge, index, stamp in one pass
↓
preview ↔ tweak = edit the script, re-run
↓
confirm → ordinary case file
✓One generic tool instead of eight bespoke ones; flexibility without a new tool per need
✓Composition moves into a program: loops, asserts and one-pass page math, where models are strong
✓A platform capability with other customers waiting (form calculated fields is already named)
✗A sandbox platform to stand up: image, egress lockdown, scoped credentials, security review for client documents
✗Assembly code is generated per run unless a vetted library ships in the image
Best when code execution is wanted as a platform capability anyway; bundles become its second customer rather than the reason to build it.
The same request, four tool surfaces
What "build me a hearing bundle" turns into underneath, per path. Contracts are
illustrative, not specs; the point is the shape, not the field names.
The manifest became code. The index maths stays in a vetted library baked into the image; the agent writes only the driver. Drop to raw pypdf for requests the library never anticipated.
Side by side
A · declarative call
B · persistent manifest
C · primitives
D · code sandbox
Net-new machinery
Assembly engine + 2 tools
Engine + manifest schema, lifecycle, edit tools
7–8 PDF tools + a workspace protocol
A sandbox platform + image + publish path
Working state lives
In the conversation
On the matter, as an artifact
In agent-managed intermediates
Sandbox filesystem, ephemeral; the script in chat
Iterating on the preview
Re-issue the call
Edit manifest, recompile
Re-run the chain
Edit the script, re-run
Resume next week
Re-derive from chat
Open the manifest
Gone
Re-derive from chat
Re-issue before a hearing
Re-derive, re-assemble
Add items, recompile
Full re-run
Re-run the script with additions
Partial documents (page ranges)
A pages field per item
A pages field per item
An extract step per range
A slice in code
Index page references
Engine-resolved
Engine-resolved
Agent arithmetic
Library-resolved if a vetted helper ships; agent arithmetic on raw pypdf
"No new state holder" fit
Clean
Depends on host model choice
Needs a workspace
Clean: nothing outlives the run
Failure surface
One call to harden
One call + manifest drift
Every step in the chain
One generated program per run
Path C, concretely
What the primitives approach would actually mean, in tools and state. Each tool is small
and honest; the cost only becomes visible when you watch the agent thread them together.
Tool
What it does
Returns
pdf_info
Page count, encryption status, dimensions of a case file or workspace ref
metadata
pdf_convert
DOCX / image / email to PDF via the existing converter; PDFs pass through
new workspace:// ref + page count
pdf_extract
Pull a page range out of a document ("3-9", "12", "20-end")
new ref + page count
pdf_merge
Concatenate refs in order
new ref + per-part start pages
pdf_insert
Insert one ref's pages into another at a position (the index, a divider)
new ref
pdf_stamp
Stamp page numbers (or re-stamp after an insert)
new ref
pdf_bookmarks
Write the PDF outline from {title, page} pairs
new ref
publish_to_case_files
Copy a workspace ref into case files with filename + visibility
case file id
The workspace is where every intermediate lives: concretely, a
per-conversation S3 prefix behind a workspace:// VFS protocol, every tool taking
refs in and returning a new immutable ref, stat/list so the agent can re-orient mid-task, and
an S3 lifecycle rule that expires the lot after a few days. None of that is exotic. But it is
a new state surface: the agent owns a directory of intermediates and must keep a dozen refs
coherent across the chain, where in paths A and B the only state is one items list.
The index is where the chain bites. Inserting the index changes the page
numbers the index refers to: a two-page index shifts every reference by two. The engine in A
and B solves that once, in tested code (lay out the index, measure it, offset the page map,
render). In C the same procedure lives in a skill prompt, executed by the model with tool
calls: merge, read the start pages, write the index, convert it, insert it, notice the shift,
recompute, re-render, re-insert, then re-stamp. Every step is individually trivial and the
composition is exactly the kind of bookkeeping models fumble under load. For the
six-document demo bundle the count is twelve-plus calls against A's two.
Where C genuinely shines: page surgery as a standalone need ("pull the
signed page out of this agreement", "split this scan into three files"), and reuse, since
each primitive is useful to other document workflows. But everything C is good at, D does
better with one tool instead of eight. That's the next section.
The sandbox, concretely (path D)
The wariness about eight bespoke tools has a named answer in the current agent-tooling
playbook: code mode. Instead of exposing each operation as a tool, expose one
generic capability, sandboxed code execution with a filesystem, and let the agent write the
program. Cloudflare published the pattern in September 2025; Anthropic's
code-execution-with-MCP
post validated it that November. The insight transfers exactly: models are far better at
writing a sixty-line script (loops, asserts, one-pass page arithmetic) than at threading
twelve stateful tool calls through a chat loop. C's whole failure mode, the index two-pass
bookkeeping, becomes ordinary programming.
Determinism comes back through the image, not the prompt: bake a vetted
lawhive_bundle helper library into the sandbox alongside pypdf. The agent writes
the driver (the manifest, as code); the library computes index layout and page references in
tested code. That is path A's engine delivered as a library instead of a service, with raw
pypdf available underneath for requests the library never anticipated. The publish step stays
on the platform side either way: the sandbox returns the PDF, and the existing
preview-then-confirm flow registers it, so no sandbox ever holds broad S3 credentials.
Getting the files in and out
The data flow is the same whichever runtime wins, and it is the same contract as paths A
and B: the tool call carries refs, never bytes and never credentials. lawrence-api
resolves them, permission-checks them against the matter, copies only the curated
subset (the six-to-fifteen items the agent chose, not the whole matter's files) to a
throwaway staging prefix, and pushes those into the sandbox filesystem. Lawrence iterates in
place against the staged copies; the single output PDF comes back through the preview, and
only a confirm turns it into a case file.
The staging contract, identical across every runtime (and the DIY Lambda): refs in,
a permission-checked copy of the curated subset to a throwaway prefix, bytes pushed
into a walled sandbox, one PDF back through the preview and confirm gate. The sandbox's whole
reachable world is /case and /out — no egress, no matter-bucket
credentials.
On the latency you flagged: the staging copy (step 3) is an S3 CopyObject,
a server-side metadata operation, so it neither pulls bytes through lawrence-api nor scales with
file size in any way you'd feel. The byte movement that actually costs wall-clock is getting
files into the sandbox filesystem (step 4) and, more than that, the DOCX→PDF conversion
inside — CPU-bound per document, and the same step that dominated the worked demo. Two
things bound it: you stage only the curated subset, not the whole matter, and most bundles are
a handful of already-PDF files where the whole flow is sub-second. The case to actually
benchmark before committing is the large scanned matter (hundreds of MB), where inline push
may lose to mount-and-pull — and that's a measurement, the same one, whichever runtime
wins, not a guess to make now.
The real question is the runtime, and the options are not equal (state of play June
2026):
Runtime
Runs pypdf?
Data boundary
Verdict for legal documents
Pydantic Monty
No, by design
In-process (Rust VM)
Third-party libraries are an explicit non-goal (no classes, no zlib/io yet; v0.0.18). Right tool for safe in-process glue logic over host-injected functions; injecting PDF host functions would reinvent path C's toolbox in-process. Not a fit for assembly.
Vercel Sandbox
Yes (pip install)
us-east-1 only
GA Jan 2026, Firecracker microVMs, pleasant SDK. But a single US region means client documents leave the UK/EU boundary. Hard sell without an Enterprise region story.
Cloudflare Sandbox SDK
Yes (own Dockerfile)
Region pinning unverified
GA Apr 2026, containers behind Durable Objects, and a genuinely good egress-proxy design (credentials injected per-host by a Worker, never visible to agent code). UK data-localisation for Containers needs confirming.
Bedrock AgentCore Code Interpreter
Yes, pre-installed
Inside our AWS account (VPC mode)
Production, pypdf and friends pre-installed, S3 file flows to 5 GB, sandbox mode blocks all egress, VPC/PrivateLink mode keeps documents inside the existing AWS boundary. The natural fit for this stack: Code Interpreter is confirmed available in eu-west-2 (London) as of mid-2026, so the AWS-boundary story holds in the UK. Note the 2026 credential-exfiltration research (patched) as proof this class still needs a real security review.
What D actually costs is not the bundle feature, it's the platform: the
image with its vetted library, egress lockdown, scoped credentials, observability for
agent-written code, and a data-protection review before client documents enter any sandbox.
That bill is bigger than everything in "what it costs to build" combined. The reason it might
still be worth paying is that it isn't a bundle bill: the
form-filling plan already names calculated
fields via code execution as a fast-follow, and the long tail of document surgery ("split
this scan", "pull the signed page") has no better home. Code execution is a platform bet
with a queue of customers; bundles shouldn't be the reason to make it, and shouldn't wait
for it either.
How you'd de-risk it cheaply. There is no local emulator for the
Code Interpreter — it's cloud-only — so a "fully offline" test isn't possible, but
a faithful one is: run the orchestration locally and call the real sandbox in a
dev AWS account, on seeded matters only. Put the runner behind a swappable interface
so a plain Docker container (network off, scoped mounts) covers the fast inner loop while the
dev-account AgentCore call validates the things Docker can only fake — cold start, the
file APIs and their limits, egress modes, real cost. Run that real-runtime test
early, not last: it's what catches parity drift before the laptop conveniences are
baked in. The only prerequisite the cloud path forces sooner is a light data-handling check
before anything resembling client material enters a sandbox; the full review still gates real
data.
Where this leans
A, with B as the named evolution and D as the named platform bet. A's call payload is
already the manifest; persisting it later is additive, so B's schema decisions can wait until
real usage shows what lawyers actually put in bundles and how often they re-issue them. The
legacy data gives B's case some early weight (1.5 previews per save says iteration is real;
whether cross-week re-issue is real, we can't see yet), but not enough to pay B's
design cost up front. C is dominated by D: if the agent should compose the assembly itself,
one program beats twelve chat-loop tool calls on every axis, so the eight-tool toolbox
shouldn't be built in any future. The partial-document requirement moves nothing: it's a
pages field on an item in A and B, a slice in code in D.
That leaves A versus D as the live decision, and it's a sequencing call, not a religious
one. A ships bundles on existing infrastructure with court-bound determinism and nothing new
to secure. D is the strategically interesting bet, but standing up a code-execution platform
(runtime, image, egress, data-protection review) just to ship ~300 bundles a month is the
most expensive possible way to get a bundle. And A forecloses nothing: its engine internals
are exactly the vetted lawhive_bundle library a future sandbox would bake into
its image. Build A now; if and when code execution lands as a platform capability, the
engine moves into the image and the assemble call becomes one of several programs Lawrence
can write.
One framing worth keeping even if it never becomes scope: the assembly engine is really a
general "compile documents into a deliverable" primitive. Court bundles are the first user;
exhibit packs, disclosure packs and closing packs are the same machinery with different
curation. That's a reason to keep the engine's contract bundle-agnostic, not a reason to
build any of those now.
The worked trace (path A)
The walkthrough at the top of the page, as the operations Lawrence would run.
Contract shapes are illustrative. Note the password-protected file: the agent surfaces it
conversationally instead of silently dropping it like the legacy wizard does. Under B, the
items array would live in a manifest artifact instead of the call; under C, the
assemble step explodes into the chain shown above; under D, it becomes a script written
against the same engine, packaged as a library in the sandbox image.
Lawrence · tool calls matter mat_ptl · hearing bundle
What it costs to build
Shared foundation, whichever path wins:
Port the merge. Bring the legacy bundler's pdf-lib merge
and page-stamping into a v3 package, plus mixed-format input via the existing converter.
Index resolution. The genuinely new piece: lay out agent-authored index
entries with page references computed from the merge's page map.
Preview plumbing. Preview bucket + signed URL + copy-to-case-files on
confirm, lifted from the legacy pattern.
Chat surfaces. Tool cards for assemble/confirm and a preview link the
lawyer can open, following the existing tool-message components.
Then the per-path delta: A adds two tools and a skill. B adds everything A does plus the
manifest as a designed object (schema, host model, edit operations, lifecycle). C adds five
or six tools, workspace management, and a skill complex enough to do bookkeeping the other
paths get for free. D adds a code-execution platform: runtime selection, the image with its
vetted library, egress lockdown, scoped credentials, observability, and a data-protection
review, none of which is bundle-specific.
What would change the decision
Re-issue frequency. If lawyers routinely rebuild the same bundle as
hearings approach, B's durability stops being a nice-to-have. The legacy data can partially
answer this: repeated copyToCaseDocs saves on the same case within a window.
Worth the query before committing.
Bundle size and runtime. The legacy wizard runs in-request; an agent
bundle with 100+ mixed-format documents may not. If assembly needs to go async, the
form-filling progress pattern is the precedent, but it changes the conversational texture
of the preview loop.
Court directions in the wild. The thesis says primitives + agent cover
court-specific formats. Validating against a handful of real bundle directions (family,
civil, employment) before building would test the riskiest assumption cheaply.
Why legacy usage is sliding. Monthly saves fell from ~430 (autumn 2025)
to ~200–300 (spring 2026). If that's firms migrating to v3, where bundling is
impossible, demand on v3 is pent-up and this rises in priority; if it's seasonal, it
doesn't. One cohort query settles it.
Awkward inputs at volume. Encrypted PDFs, exotic formats, scanned
images. The agent can narrate exclusions, but if they're common, conversion coverage
becomes part of v1 rather than a fast-follow.
Sources: legacy bundler at packages/api/src/modules/cases/files/bundles
(platform repo) and apps/platform/src/features/bundles; usage from PostHog
project [prd] [v2] Platform, events document-bundle-created,
bundle.createFromCaseDocs, bundle.copyToCaseDocs, 12 months to
June 2026; v3/agents capability scan June 2026; sandbox-runtime research June 2026
(Monty,
Vercel Sandbox,
Cloudflare Sandbox SDK,
Bedrock AgentCore Code Interpreter).
Companion reading:
agentic form filling and
Lawrence file organisation, the two
nearest shipped patterns.