The Lawrence agent runtime runs in TypeScript on the Vercel AI SDK. v3 layers
apps → apis → services, and an agent loop is api-tier work, so the runtime is an api:
the loop lives in the chat api and its tools call the matter, content and search services through the
shared @lawhive/content-framework, the same path lawrence-api already uses. Every tool is
TypeScript and lawrence-api stays the data authority. Capability 02 already rewrites this loop, so
the move is largely a choice of language — and this doc also settles where durable work and
code execution run under UK/EU residency.
By Adolfo Tamayo Briceno · Lawyer Experience (LEX)
agents repo · not Evestraight port · api-tierstatus: draft for review
Post-review iteration (29 Jun 2026). This drew detailed feedback — fluent builder, declarative registry, deny-by-default VFS, model-from-config, layout. It's folded into a proposal: defineAgent, reshaped (current vs proposed, side by side).
Update · AI SDK v7 is the base (GA 2026-06-25). This doc was written against v6;
v7 is now the target, and a breaking major. Two things change the plan, not the direction. (1)
Agents are first-class — ToolLoopAgent, WorkflowAgent, and a
HarnessAgent (which can even run Pi / Claude Code / Codex) — and several pass-2 items we'd
deferred now ship natively: durable/resumable runs, tool approvals (HITL, HMAC-signed), and typed tool
context (which maps onto our initialContext hook). (2) Durable runs are now
self-hostable in eu-west-2 with zero US residency: WorkflowAgent backed by
@workflow/world-postgres (our own Postgres + an ECS worker), which re-opens the "keep Inngest"
call below — left as an open question pending a small spike. The live v3 chat loop is Python
(lawrence-api proxies to the agent-gateway), so bumping TS ai can't break the agents' reasoning;
the real build-time gates (ESM-only, the ai version pin) are in Open questions.
First, the primitives
Vercel frames its agent tooling as a three-layer "agent stack" (their post). Here's the whole board, with what each piece is and whether it's in scope for this plan. The rest of the doc only goes deep on the four that touch our decision.
1 · Model connectivity
AI SDKOne TypeScript interface to any model — streaming, tool calls, structured output, an agentic loop with stop conditions.the runtime target
AI Gateway"A CDN for tokens": one endpoint to hundreds of models with failover, cost tracking, and bring-your-own-key.evaluate as model edge
2 · Workflow execution
Workflow (SDK)Durable execution — checkpoint every step, resume after a crash or deploy, sleep for months, wait on human input.managed is US-only
SandboxIsolated Firecracker microVMs — full Linux, filesystem, Docker; credentials injected per task, not baked in.US-only → AgentCore instead
Queues + Fluid ComputeThe substrate beneath the above: a region-pinnable message bus and long-running streaming functions.
3 · Data & tool integration
ConnectAgents act on Slack / GitHub / Salesforce / Notion via short-lived, scoped, per-task tokens — auditable user→agent→service.later, for outbound
Chat SDKShip one agent across 15+ channels from a single codebase, keeping context across surfaces.
eve— the opinionated framework on top: agents as files (a model spec + markdown instructions + TypeScript tools + skills + sub-agents + channels + schedules). It bundles all of the above as fully-managed infra. We're explicitly not adopting eve — we take the SDK layer (the AI SDK) and self-host on AWS, which is what the rest of this doc works out.
The loop is already being rewritten
Capability 02 — the framework refactor in One
loop, many Lawrences — is a from-scratch rewrite of the loop.ChatAgent.generate_stream() is ~780 lines welded to an HTTP request, and the plan tears it
down to BaseAgent + hooks. So the rewrite cost is committed; the only open question is which
language to rewrite it in.
Rewriting the loop in TypeScript rather than Python is a marginal delta on work that is
already committed — and it unlocks a structural win.
Two things make TypeScript the high-leverage version of that same work:
lawrence-api is already TypeScript. It owns the thread model and
consumes the ai-stream wire format the loop emits. Today every wire-format change is a
two-language, two-repo edit across a Python emitter and a TS consumer. A TS loop collapses that seam
into one language with shared types — a structural win only this path unlocks.
The Vercel AI SDK gives the loop's four hard requirements as first-class primitives.
A multi-step agentic loop with stopWhen; zod tools; tool execute as an
async generator that yields progress (the direct analogue of our BaseToolExecutionDelta);
and gateway/provider routing. We are not building a loop framework in TS — we are adopting one.
This doc takes the decision already made — not Eve, no fully-managed infra, only the
agent runtime moves, lawrence-engine and ingestion stay Python — and
works out exactly what that costs, tool by tool, plus where the Vercel primitives (Sandbox, Workflow,
AI Gateway) actually fit once you check where the data lands.
The shape: a straight port, in the api tier
v3 layers apps → apis → services, and an agent loop calling tools is api-tier
work — so the runtime is an api, not a new layer. It reuses the shared
@lawhive/content-framework + service clients (already TS) to call services directly. content-retrieval and kms-utils are transport to lawrence-api, not a
data/crypto layer, so every tool ports the same way. Two placements fit (see the topology diagrams): the loop in the chat api
(B-fit-1, recommended), or a sibling agent-runtime api.
The runtime is an api: the loop lives in the chat api and its tools call the domain services
through the shared @lawhive/content-framework — the same way lawrence-api already does
— a normal in-tier call, in a single language, with every edge pointing down. Inngest orchestrates
durable/background work over events; AI Gateway is an optional model edge. This is B-fit-1, the loop in
the chat api; B-fit-2 runs it as a sibling api instead — see the
topology diagrams.
Eighteen live tools (four dead packages — edit, read,
faq, legal-search — already absorbed into platform-vfs or
retired). The tool code is mostly thin glue; the libraries a tool seems to “drag in” turn out
to be transport to lawrence-api and the services, not substance.
18
live tools to migrate
18
port to TypeScript
2
stay Python (engine + ingestion)
~8.4k
tool LOC — thin glue over services
SML port effortstream yields progress deltascite carries citation configPII reads/writes matter client data
Pure-logic, LLM & public-API 13 tools
Pure-logic, LLM, or public-API — no shared-data coupling. They port directly and
keep native streaming via async-generator execute.
Sintent-classification
Splanning-tools
Sload-skill
Sdecompose-query
Sreformulate-query
Sretrieve-documents
Slegal-questionstream
Svector-searchcitePII
Sweb-searchcite
Scheck-case-status
Sfill-formPII
Mcase-lawcite
Mlegislationcite
Matter-data tools 5 tools
These read and write matter data. In TS they call the matter / content / search
services directly through @lawhive/content-framework — the same path lawrence-api
uses — and the tools decrypt nothing, since the services return resolved content.
Mdocument-questionstreamcitePII
Mmatter-infostreamcitePII
Mmatter-messagesstreamcitePII
LsearchPII
Lplatform-vfscitePII
The two L
tools (search 1,559 LOC + platform-vfs 3,228 LOC) are 57% of all tool code.
The dependency gravity is the real story
What each shared library a tool depends on becomes in TypeScript. The two biggest are transport
— client stubs to lawrence-api and the services — so they're deleted, not reimplemented:
Shared library
Verdict
What it costs
Tools affected
lawhive-content-retrieval
delete
Not a data layer — client stubs to lawrence-api (v3) plus the matter / content / search services and S3. No crypto. In-tier the tools call those services directly via the shared framework, so the package goes away.
Service-to-service request signing, not data crypto — and it already no-ops on local URLs. In-zone it's unneeded; the only residual is the hop to the ingestion search-service. Not a keystone.
4 public-API clients (easy) + lxml AKN/Atom namespaced XML parsing (no clean TS analogue — the watch-item).
case-law, check-case-status, legislation
citation-models
port
zod equivalent of the reference/passage models + the ❦…❧ citation parser in the loop.
8 tools + the loop
ai-stream / tool-definition
port (first)
The wire protocol + base tool contract. Largely subsumed by AI SDK tool() + streamText. Must land before any tool ships.
all
The matter-data tools reuse @lawhive/content-framework + the service
clients, which already exist in TS — so they're a straight port, not a rewrite of the data layer.
The migration's real cost is the loop itself plus the genuinely-new TS pieces: the
ai-stream wire contract, the citation parser, and legal-sources' AKN/XML. The
runtime calls services the way lawrence-api already does, so content-retrieval and
kms-utils don't carry over — there is no Python data layer.
Where MCP fits
The runtime reaches our own matter data in-tier, through @lawhive/content-framework
— a normal service call, not a cross-language hop. MCP is for a different job:
external / third-party tools (Gmail, court portals, Slack via Vercel Connect), where a
scoped, per-task token and an auditable user→agent→service trail are the point. Use MCP for
outbound integrations; talk to our own services directly.
Code execution: keep the compute in-region
Capability 06 lets the agent run code in a sandbox over matter files. That code
processes client PII, so where it runs is a compliance gate, not a latency choice.
The pattern: stage working copies of matter files into a sandbox, let the agent run code that
transforms or assembles them in one pass, return the result. (Court bundling is the first driver; the
capability is general.) Vercel Sandbox is the obvious primitive — except for where it runs.
Confirmed: Vercel Sandbox is US-only
Vercel Sandbox provisions only in iad1 (US-East / N. Virginia).
Sandbox.create() has no region parameter (verified full param list);
sandbox.region is a read-only output. The docs state verbatim: "Currently, Vercel
Sandbox is only available in the iad1 region." It is persistent-by-default. So staging
matter files into it is a cross-border transfer of personal data to the US, not a
latency choice. Vercel offers lawful-transfer mechanisms (signed DPA, EU SCCs + UK Addendum,
DPF, SOC 2/ISO) but no product-level EU/UK residency guarantee for Sandbox —
the concepts page itself punts residency to "your compliance team." An EU region was requested
(Feb 2026) but is unscheduled.
The design is runtime-independent: the agent passes file refs, never bytes or credentials;
lawrence-api stages a decrypted, curated subset and holds the keys; a vetted library does the
deterministic work; output returns through a preview-then-confirm step; egress is locked and the sandbox
is non-persistent. Only the microVM swaps.
So the runtime is AWS Bedrock AgentCore in eu-west-2, inside Lawhive's
own AWS account — the same boundary where lawrence-engine and ingestion
already live. (Confirm current eu-west-2 availability before committing.)
Since v3 serves both US and UK traffic, route the compute by the matter's residency — UK matters to
eu-west-2, US matters to us-east-1 — under one multi-region AgentCore API;
the runtime-independent design makes that a routing config, not two builds.
Code execution is the most expensive way to get a result — it earns its place only
when the work is genuinely open-ended.
It is demand-sequenced and last on the capability ladder. Where a fixed in-region engine call serves
the need, prefer that; stand up the sandbox when a capability genuinely needs agent-written code —
in eu-west-2, not iad1.
Durable execution: keep Inngest
Two independent disqualifiers rule Vercel Workflow out for the agent loop, either one
sufficient on its own. The headline event price is irrelevant.
Dimension
Inngest Cloud + our execution (current)
Vercel Workflow (managed)
Execution model
Split — the cloud orchestrates (queue, schedule, retry, durable state); your function/step code runs on your servers via Connect websocket / HTTP serve. You own the compute.
Not split — each step compiles to a Vercel Function route and runs on Vercel. The managed product has no "your compute" mode; the only self-run path is the self-hosted Workflow SDK.
Where the data lands
Mixed — execution in-region (eu-west-2), but durable step state + event payloads persist in Inngest Cloud (US). Already your exposure today.
US — managed persistence (all state + event logs) pinned to iad1, and compute moves to the US too. Strictly more US-resident than Inngest.
Latency for a tool loop
Built for it — keep the loop in one coarse step; sleeps/waits aren't billed or counted
Structural tax — each LLM call / tool exec is a step; ~4–5s queue round-trip/step, grows with step count; a 10–20 step turn becomes minutes (sourced to vercel/workflow issues — treat as hypothesis)
Billing unit
N+1 executions/run · Pro $75/mo incl 1M, $50/1M over
~3N events/step · $0.02/1K ($20/1M) plus unbundled Functions compute plus Queues ops
Cost shape
bundled; cheap if the loop stays coarse-grained
headline events cheap (~$27/mo illustrative) but compute + queue ops + latency tax are the real bite
So is Workflow the same orchestration-only model as Inngest? No. With Inngest you run
the execution and the cloud orchestrates; with managed Workflow, Vercel runs your step code on its own
Functions. The only way to keep compute on our servers under the Workflow umbrella is the self-hosted
Workflow SDK (Postgres World) — which also means owning the Postgres durable backend, i.e. fully
self-hosting, with no edge over self-hosting Inngest. Either way the TS runtime enqueues and awaits
Inngest functions via the Inngest TS SDK, so keeping Inngest is fully compatible with the TS loop.
The honest residency picture, now that we've confirmed we're on Inngest Cloud: durable step
state already lands in the US today, and in the managed tier of both products that's
unavoidable. So "keep Inngest" wins not because it's fully EU, but because it keeps execution on our
servers, adds zero migration, and doesn't also move compute to the US the way managed Workflow
would. Two levers narrow the exposure without leaving Inngest: keep the agent's LLM/tool loop in
one coarse step — or outside durable steps entirely, using Inngest only to
trigger/background — so PII-bearing loop internals are never checkpointed to Inngest Cloud (the
same lever that controls cost); and if a hard EU-residency line is required, self-host
Inngest (single binary, Postgres + Redis, eu-west-2), which removes the US state
entirely. Workflow would only win if Vercel both ships EU persistence with a contractual
guarantee and eliminates the per-step replay overhead. Neither is true today.
What v7 changes here (June 2026). When this was written, the self-hosted Workflow SDK
(Postgres World) had "no edge over self-hosting Inngest." v7 gives it one: WorkflowAgent
(@ai-sdk/workflow) is built to run on that runtime, so backing it with
@workflow/world-postgres (own Postgres + an ECS Fargate worker; graphile-worker;
Postgres NOTIFY/LISTEN; zero Vercel callout) yields durable runs, restart-surviving approvals
and stream persistence in eu-west-2 — the pass-2 durable-streams capability, native and
in-region. There is no viable Inngest "World" adapter (the interface mismatch makes building one a
bad bet), so the call is cleanly either/or: WorkflowAgent + Postgres World vs
keep Inngest and compose ToolLoopAgent inside a step.run (reuses
today's pattern, but we re-implement approvals / replay / stream-persistence ourselves). Left open pending a
spike; either way Inngest stays for non-agent background jobs.
Where the data lands — the residency map
The whole Vercel-primitives question reduces to one table. EU/UK regions exist for Functions
and Queues; they do not exist for Sandbox or managed Workflow persistence.
Primitive
EU/UK region?
For our use
Vercel Sandbox
No — iad1 only
Code execution over matter files would put PII in the US. Use AgentCore eu-west-2 instead.
Managed Vercel Workflow
No — iad1 only
Persistence holds the loop's PII step I/O in the US. Ruled out. Keep Inngest.
Workflow SDK (Postgres World)
Yes — self-host
EU-resident but full self-managed ops; no edge over self-hosted Inngest.
Vercel Queues
Region-selectable
lhr1/fra1 available, but "strict residency not yet supported" (failover may relocate).
Vercel Functions
Yes
Pin to lhr1 (Pro = 5 regions). Not where we host anyway (AWS).
AI Gateway
Unverified
Adopt as the model edge via createOpenAICompatible; if the gateway data-path residency is unconfirmed, point it at a self-hosted in-region proxy.
Where we'd start: a matter-opening agent
Don't migrate Lawrence's chat first. Prove the platform on one small agent that lives off
Lawrence's critical path — and that's a draft nobody minds reviewing.
The first agent on the TS runtime isn't Lawrence — it's a matter-intake agent.
An admin finishes an intake call, drops in the documents and a couple of notes, and the agent opens a
matter pre-populated with a title and summary, custom fields, key dates and tasks, in OPENING
status for the admin to confirm. It's the deliberate opposite of Lawrence: firm-scoped (it creates
the matter, so no matterId goes in), admin-principal, a handful of write tools, and a
structured output instead of free-form chat.
That's exactly why it's the right trial — it exercises the whole generalised-agent envelope at
once (scope, principal, a reduced tools() set, structured output), and the only new
infrastructure it forces is a single matter:// create tool; key dates, tasks, notes and
custom fields the platform already exposes. Bounded blast radius, real value, off the chat path.
Intake is the proof; the platform is the point. We learn the slug / principal / scope
envelope on a job nobody enjoys doing by hand, then reuse it for the rest of "many Lawrences."
The full trial — the agent spec, the v3 matter-model grounding, and the one capability to build
— is in the matter-intake trial companion.
The sequence
With the platform proven by the intake agent, the broader loop migration follows. Rough
order, not a calendar; each stage is independently shippable. Foundations (the wire contract + citation
parser) come before any tool ships.
stage0
Foundations — nothing ships to users
Port ai-stream/tool-definition onto AI SDK tool() + streamText with a golden-fixture conformance test against the real Python emitter. Port citation-models to zod + the ❦…❧ parser into the loop. (Signing only matters for a cross-trust-zone hop like the ingestion search-service; in-tier calls need none.) Loop runs with zero tools, streams text.
stage1
Runtime shell + cheapest tools
Stand up the streamText loop (stopWhen, prepareStep, gateway routing); wire it to lawrence-api. Port the pure-logic / LLM tools first — intent, planning, load-skill, decompose, reformulate, retrieve-documents. Prove the async-generator streaming path end-to-end.
stage2
KMS + public-API tools
legal-question, vector-search; web-search (exa-js directly); case-law, check-case-status, legislation (legal-sources port — AKN XML parity is the watch-item, fall back to a Python parser microservice only if fixtures slip); fill-form (Inngest TS SDK). 13 of 18 tools now native in TS.
stage3
Port the data-coupled tools
search, platform-vfs, document-question, matter-info, matter-messages — calling matter / content / search through @lawhive/content-framework, the way lawrence-api does. Preserve the server-side person-id guard (identity from request context, never LLM args) and platform-vfs's client-fulfilled PatchPayload contract. No bridge.
stage4
Delete the Python agent-gateway
With every tool in TS, the Python agent-gateway service has no job left — the loop is the chat api. Decommission it; the only Python that remains is lawrence-engine and ingestion.
Top risks
Risk
Severity
Mitigation
Residency — state / code-exec inputs / prompts in US infra
Critical
Self-hosted Inngest (eu-west-2); no Vercel Workflow; AgentCore eu-west-2 for code execution; in-region model proxy if gateway residency unconfirmed.
ai-stream wire-format parity
High
Golden-fixture conformance test: capture real Python emitter output, assert TS output is shape-identical. This seam is the consolidation payoff and the likeliest silent regression.
Citation subsystem (no native AI SDK analogue)
High
Own CitationConfig in the TS loop (stage 0); emit citations as source/data parts. Getting it wrong silently drops citations in legal answers.
Cross-zone signing (ingestion search-service)
Medium
The one residual KMS hop. Port that signer and fixture-validate against real signed requests; in-tier service calls need no signing.
person-id injection guard
High (security)
lawrence-api rejects LLM-supplied person/identity IDs today. The TS tools must keep that: identity comes from the authenticated request context, never tool input params. Assert in contract tests.
legal-sources AKN/Atom XML parity
Medium
Full TS port with a fixture corpus; isolated Python parser microservice as a bounded fallback (no crypto/PII in this path).
Frameworks we benchmarked
Before committing to a thin in-house framework on the AI SDK, we wrote up the two
strongest off-the-shelf harnesses against this exact proposal. Neither changes the recommendation —
one is worth adopting later, the other worth borrowing from.
Mastra's Agent Harness (full
evaluation) — built on the same Vercel AI SDK we're porting to, so its harness (sessions,
observational memory, approvals, sub-agents, modes) drops into lawrence-api as a library: our
own Postgres via pg.Pool, an Inngest-backed runner for durable runs, no separate runtime and no
leaving our base. It's the one framework worth adopting for the deferred pass-2 layer if we choose not to
build those primitives ourselves. Verdict: viable pass-2 adopt — revisit when we reach
sessions and memory.
Flue (full evaluation) —
the Astro team's headless, durable-first harness (Apache-2.0) is the mirror image: a clean design, but built
on Pi / pi-ai rather than the AI SDK. Adopting it means re-platforming off
our base, embedding into Hono instead of Express, and betting legal PII on a one-week-old beta — and its
remote() sandboxes reintroduce the US-pinning we rejected for Vercel Sandbox. State, at least, is
self-hostable on our own Postgres in eu-west-2. Verdict: don't adopt — but
borrow its Durable Streams design (append-only event log, resume/reconnect, pluggable
Postgres store) as the blueprint for our deferred durable-sessions work.
What v7 does to this (June 2026). AI SDK v7 now ships the headline pass-2 primitives
natively — durable/resumable runs (WorkflowAgent, self-hostable via
@workflow/world-postgres) and tool approvals (HITL) — on our own base. That partly
supersedes both verdicts: the case for adopting Mastra in pass-2 shrinks (the SDK now gives us
sessions / approvals / durable runs directly), and Flue's "borrow Durable Streams" is largely redundant (the
SDK has it). Both stay useful as references, not dependencies.
What stays Python — forever
lawrence-engine (CCO, material-case-change, proactive) and ingestion are out
of scope and stay Python — they aren't agent loops, and they're genuinely separate services. That's
it. There is no Python data layer to keep on the agent side: matter data lives in lawrence-api
(TS) and the domain services, and content-retrieval — the Python client lib that
reached them — is deleted, because the TS tools call those services directly via the shared
framework. The tools decrypt nothing, so nothing crypto-sensitive crosses a language boundary —
because nothing crosses at all. Inngest stays polyglot; the TS loop enqueues its Python functions via the TS SDK.
Open questions
Inngest Cloud step-state residency. Confirmed: we're on Inngest Cloud
(orchestration, US) with execution on our own servers — so durable step state already lands in
the US. Decide the line: coarse-step the loop to keep PII out of checkpoints, or self-host Inngest for
hard EU residency.
Per-matter region routing. Since v3 serves both US and UK traffic, code execution
over case files should route by matter residency — confirm the jurisdiction signal exists on the
matter and where that routing decision lives.
AgentCore eu-west-2 specifics — current availability, runtime image,
and S3 file-flow limits; verify before committing cap-06 (code execution).
AI Gateway data-path residency — do UK/EU prompts transit the US before
routing? If unverifiable, default to a self-hosted in-region proxy via createOpenAICompatible.
Loop step granularity in Inngest — pull real loop-iteration counts from
Langfuse before fixing the step model (one coarse step vs step-per-iteration drives both cost and resilience).
Citation config lifecycle — is it read-only per call, or mutated across
steps? Decides the shape of the TS loop wrapper.
Durable-runs backend (v7) — WorkflowAgent + self-hosted
@workflow/world-postgres (eu-west-2) vs keeping Inngest and composing
ToolLoopAgent in a step.run. Spike the Postgres World on ECS before committing;
managed Vercel Workflows stays iad1-only and is out.
v7 build-time gates — v7 is ESM-only but lawrence-api ships a
CJS bundle (bun build --format=cjs, no "type":"module"); confirm the bundle still
boots with ai@7. The root pnpm.overrides["ai"] pin forces lawrence-api + legal-os
in lockstep — scope it so the api can migrate first. Node is already 24.
One line: cap-02 already rewrites the loop and lawrence-api is already
TS, so port the whole loop to TypeScript and place it in the api tier — the loop in the chat api
(B-fit-1, recommended) or a sibling agent-runtime api. content-retrieval and
kms-utils are transport, not a data layer, so every tool ports (calling services via
@lawhive/content-framework) and the Python agent-gateway is deleted; keep
self-hosted Inngest; and hold Vercel Sandbox/Workflow out because both default to US-pinned
iad1 and the data is UK/EU legal PII. And prove the platform first on a small
matter-opening agent, off Lawrence's path, before porting the loop.