Coding agents · AI agents · Software architecture

Migrate Coding-Agent Frameworks Without Losing Behavioral Guarantees

Move a coding agent to a new framework through contract inventory, compatibility tests, dual runs, staged rollout, and a defined rollback.

Published
Updated
Code examples
Illustrative
Reading time
7 minutes

Migrate a coding agent by preserving observable contracts, not by translating framework classes one for one. Inventory inputs, tools, state, approvals, stop conditions, telemetry, and artifacts; freeze a representative evaluation set; build a thin adapter for the new runtime; then dual-run, canary, and roll back through a versioned boundary. Keep mechanical rewrites separate from behavioral changes.

This approach applies whether the destination is a vendor SDK, an open-source agent framework, or an internal orchestrator. It avoids claims about any framework's current API: those details change, while the migration controls should remain stable.

The OpenHands platform paper describes a coding-agent architecture with explicit agents, tools, sandboxing, and benchmark integration. Those are useful migration surfaces to inventory, but this article does not assume that OpenHands or any other framework is the source or destination.

Decide whether migration is justified

Write the decision before touching code. A migration should address a measurable constraint such as:

  • unsupported model or tool protocol;
  • missing cancellation, streaming, or durable state;
  • unacceptable latency or operating cost;
  • security controls that cannot be enforced externally;
  • poor observability or evaluation support;
  • maintenance risk or an incompatible dependency roadmap.

Record the current baseline and a minimum acceptance threshold. If the problem is one adapter, tool, or queue, replacing that component may be safer than moving the whole workflow.

Do not use “newer architecture” as the success metric. A migration succeeds when required behavior and safety guarantees remain intact while the motivating constraint improves.

Inventory the real contract

The public entry point is only part of an agent's behavior. Build a migration ledger:

ContractQuestions to capture
Task inputschema, defaults, size limits, trust level
Model adaptermessage roles, tool-call encoding, stop reasons, usage
Toolsnames, argument schemas, timeouts, retries, idempotency
Statecheckpoints, event ordering, resumption, retention
Policypath rules, network rules, budgets, approval triggers
Outputpatch, summary, citations, status, artifacts
Failureexception classes, partial work, retry eligibility
Telemetryrun IDs, spans, usage, redaction
Operationscancellation, deployment, rollback, data migration

Trace behavior from production configuration and tests, not only documentation. Hidden contracts often live in middleware, callbacks, environment variables, queue retry settings, or dashboards.

For each contract, classify it as:

  • must preserve because a consumer, security control, or audit process relies on it;
  • intentionally change with an owner and migration plan;
  • remove because it is unused, with evidence and rollback.

Freeze a framework-neutral boundary

Move application policy into types the old and new runtimes can both implement:

interface CodingAgentRuntime {
  run(input: {
    task: CodingTask;
    policyVersion: string;
    idempotencyKey: string;
  }): Promise<{
    status: "completed" | "failed" | "cancelled" | "needs-approval";
    patchArtifact?: string;
    eventArtifact: string;
    usage: { inputTokens?: number; outputTokens?: number };
  }>;

  cancel(runId: string): Promise<void>;
}

Do not leak destination-framework objects through this boundary. Normalize model stop reasons, tool failures, usage units, and cancellation semantics in the adapter. When information cannot be mapped without loss, make that an explicit compatibility gap rather than filling it with a plausible default.

Centralize command, credential, workspace, and approval enforcement in the harness described in Build a Coding-Agent Harness. Framework callbacks can notify policy; they should not be the only barrier to a dangerous action.

Pin inputs before rewriting

Create a reproducible migration corpus with:

  • exact repository and base commit;
  • task and repository instructions;
  • model identifier and inference settings;
  • tool schemas and fixtures;
  • environment image and dependencies;
  • expected invariants and allowed outcome range;
  • redacted traces from representative failures.

Include easy, difficult, invalid, cancelled, approval-required, timeout, tool-error, prompt-injection, and resume cases. Preserve tasks where the current system fails; otherwise the new framework can look better merely because the evaluation excludes known weaknesses.

Do not require byte-identical text or an identical sequence of model turns. Compare externally meaningful outcomes: valid patch, tests, policy compliance, state transitions, cost units, latency distribution, and human review result.

Split the migration into two change classes

First perform mechanical changes:

  • introduce the neutral interface;
  • wrap the current runtime;
  • move shared schemas and policies behind it;
  • add contract tests;
  • remove direct imports from business logic.

Then implement the new adapter in separate commits. Do not simultaneously change prompts, models, tool descriptions, retry rules, and framework. That creates too many explanations for every regression.

If a codemod is useful, make it deterministic and review its diff. An agent can help classify or repair exceptions, but it should not run an unbounded repository rewrite and declare success from compilation alone.

Keep generated files and lockfiles deliberate. Regenerate from pinned sources, review dependency graph changes, and avoid mixing formatter churn with semantic code.

Build a compatibility matrix

For every old behavior, record the new behavior and verification:

CapabilityOld runtimeNew runtimeVerificationDecision
cancellationprocess-tree terminationunknown initiallylong-running child fixtureblock until proven
tool timeout30 s externaladapter defaulthanging tool fixturepreserve externally
approval resumecheckpoint tokenevent cursorreplay fixturemigrate token
usageprovider tokensnormalized tokenscaptured response fixturedocument difference

Mark unknown honestly. A framework feature name is not evidence of semantic equivalence. For example, “retry” may retry one HTTP call, one tool, one turn, or the entire task, with very different duplicate-side-effect risk.

Test contracts below the model layer with deterministic fake adapters. Then evaluate end-to-end behavior with real model calls. The first catches integration mistakes cheaply; the second captures emergent differences.

Treat state migration as a data migration

In-flight runs are harder than new runs. Choose one strategy:

  1. Drain: stop accepting old-runtime work and wait for current runs to finish.
  2. Pin: new tasks use the new runtime; existing tasks resume on the old one.
  3. Translate: convert checkpoints into a versioned neutral event model.
  4. Restart: deliberately abandon and recreate safe, idempotent tasks.

Translation is the riskiest because framework state may include opaque serialization, hidden callbacks, or provider-specific message history. Define invariants, validate both schemas, retain the original checkpoint, and make conversion idempotent.

Never resume a side-effecting step merely because the conversation appears to stop before its success message. Consult the tool's idempotency record or external system of record.

Dual-run without duplicating side effects

Shadow the new runtime on recorded inputs or read-only tasks. For live traffic, give the shadow tools simulated or read-only implementations. It must not open a second pull request, send a notification, modify a database, or consume a one-time approval.

Compare:

  • task completion under independent verification;
  • policy denials and approval requests;
  • changed-path and patch-size distributions;
  • tool calls, retries, and stop reasons;
  • latency and provider-reported usage;
  • reviewer-confirmed defects and regressions.

Use paired tasks and report the number and type of cases. Do not publish a single aggregate score without failure categories or uncertainty. The protocol in Benchmark Repository Instruction Files provides a reusable measured-comparison structure.

Canary with a rollback that actually works

Route a small, explicitly eligible slice to the new runtime. Exclude destructive tasks, production credentials, and non-idempotent tools first. Define automatic stop conditions before the canary:

  • policy violation;
  • unexplained credential request;
  • cancellation or checkpoint corruption;
  • statistically or operationally material failure-rate increase;
  • unexpected cost or latency ceiling;
  • artifact incompatibility.

Keep old code, dependencies, configuration, and state readers deployable until the observation window closes. “Revert the commit” is not a rollback if checkpoints or database rows were already written in a new format.

Version stored events and artifacts. Prefer additive fields, dual readers, and backward-compatible writers during the transition. Remove old readers only after retained data expires or a verified conversion completes.

Diagnose regressions systematically

When a case changes, classify the first divergent layer:

  1. input normalization;
  2. model request or response mapping;
  3. tool schema or result mapping;
  4. orchestration order;
  5. retry or timeout behavior;
  6. checkpoint or resume;
  7. external verifier.

Preserve the smallest redacted trace that reproduces the divergence. Use git bisect with a deterministic test when a regression lies within the migration history. The Git documentation describes binary search across commits; model-dependent tests should be replaced with captured fixtures where possible before serving as the bisect predicate.

Do not paper over a regression by adding prompt text until the contract mismatch is understood. Prompts are useful for model behavior, not for compensating for missing cancellation, incorrect retries, or lost tool results.

Completion checklist

  • The migration has a measurable reason and acceptance threshold.
  • Observable, security, state, and operational contracts are inventoried.
  • Business logic depends on a framework-neutral interface.
  • Inputs, environment, model settings, and evaluators are pinned.
  • Mechanical and behavioral changes land separately.
  • Every capability has a compatibility decision and verification method.
  • In-flight state follows an explicit drain, pin, translate, or restart plan.
  • Shadow runs cannot repeat side effects.
  • Canary eligibility and stop conditions are predeclared.
  • Old readers and runtime remain usable through the rollback window.
  • Removal happens only after stored-state and operational dependencies expire.