4 min read

Define the Agent Contract Before You Pick a Framework

Mehdi Rezaei
Mehdi
Author
Engineering
Software
Technology

Every quarter someone on the team asks which agent framework we should standardize on. The question is usually premature. We already have three half-finished runners in the monorepo, two abandoned prompt packs, and a Notion page titled "Agent Platform v2" that nobody opens after standup.

I do not think the missing piece is another SDK. The missing piece is a thin, versioned contract for what an agent run *is* in our systems. Without that, every new framework looks like salvation for six weeks, then we rewrite again because the old glue never named the same things twice.

What keeps getting reinvented

Look at the last three agent experiments in a typical full-stack shop. Different model providers. Different tool registries. Different ideas of "memory." Same underlying pain:

  • How do I identify this session across a webhook retry?
  • Which tools exist, and which ones need approval?
  • What is the agent allowed to touch in the sandbox?
  • What artifacts must survive for a human to review the PR?
  • What happens when the budget is gone or the laptop sleeps?
  • How do I resume without replaying a side effect twice?

Frameworks answer those questions with their own nouns. Your queue, deploy pipeline, and incident tooling still speak yours. The rewrite tax is the translation layer, not the model call.

A contract thin enough to version

I would write the contract before picking LangGraph, the Agents SDK, eve, WorkflowAgent, or a home-grown loop. Something close to this — not clever, just shared vocabulary:

ts
1type AgentContract = {
2 version: '1'
3 sessionId: string
4 tools: Array<{
5 name: string
6 // read | write | network | deploy | billing
7 risk: 'read' | 'mutate' | 'side_effect'
8 needsApproval: boolean
9 }>
10 permissions: {
11 repos: string[]
12 env: 'sandbox' | 'staging' | 'prod-read'
13 network: 'none' | 'allowlist' | 'open'
14 }
15 approvalPoints: Array<'install' | 'migrate' | 'pr' | 'deploy'>
16 artifacts: {
17 plan?: string
18 diff?: string
19 logs?: string
20 evalReport?: string
21 }
22 budget: {
23 maxUsd: number
24 maxSteps: number
25 maxWallClockMinutes: number
26 }
27 resumeToken?: string
28}

Session id is identity. Tools are the capability surface. Permissions bound the sandbox and network. Approval points are the human checkpoints, not a vague "HITL" slogan. Artifacts are what a reviewer can open without replaying the chat. Budget is the kill switch. Resume token is how a webhook or UI reconnects without pretending the WebSocket was the source of truth.

Ship `version: '1'` in the schema. When you add a field that changes resume semantics, bump it. That one habit beats another framework migration.

Why frameworks get blamed for contract failures

When a run cannot resume after a deploy, people say "the framework is immature." Sometimes true. Often the app never persisted a resume token, never stored which tool call already mutated the schema, and never recorded whether the approval covered the final args or the draft args. That is a contract gap.

When two teams cannot share an agent, people say "we need a platform." Sometimes true. Often one team treats tools as free functions and another treats them as MCP servers with different permission names. Same idea, unversioned edges.

When evals disagree with production, people blame the model. Sometimes fair. Often the eval harness never saw the same permissions, budget, or approval policy the prod webhook enforces. You evaluated a different agent.

Field rules that have survived contact

Keep the contract smaller than the framework. If the type needs a plugin system, you overbuilt it.

Map framework concepts onto your nouns in one adapter module. Do not let LangChain memory objects leak into your queue payload. Do not let a vendor session id become your only id if your webhook already has a correlation id.

Put approval points on irreversible work: dependency installs, migrations, opening a PR, deploy aliases, anything that spends money or rotates secrets. Reading the repo and running unit tests should not page a human.

Persist artifacts even when the transcript is huge. A 40-message chat is weak evidence in an incident. A stored diff, test log, and eval report are usable.

Budget is not optional. Agents will burn tokens looping on a flaky test if you let them. Cap steps and wall clock the same way you cap a CI job.

Pick the framework after the interface exists

Once the contract is boring, framework choice gets easier. Prefer the runtime that can honor resume, approvals, and artifact export without heroic glue. Prefer the one your deploy and queue already resemble. Reject the one that forces you to abandon `sessionId` because their dashboard has a prettier name.

I would rather maintain a 40-line contract and swap runners than marry a framework whose mental model never matched our webhook, sandbox, or review flow. The industry will keep shipping agent frameworks. That is fine. Version the contract first, and the rewrites get smaller — or stop being necessary.

Share this article