TL;DR
A long-running agent needs a verifiable handoff: task versions, acceptance evidence, and a record of external effects. Compaction reduces conversation length; it does not create durable execution state. Recovery must check the real environment, reconcile uncertain writes, and derive completion from independent acceptance rather than the agent's summary.
Why the second session loses the task
The difficult part of continuing a long task is preserving facts that remain valid, not preserving every message. A first session may report that login works. A second session may inherit changed code, expired test credentials, and an ambiguous database write. Treating the summary as current evidence creates a false starting point.
An agent harness manages execution around a model: tools, state, budgets, and acceptance. For long-running work, it also needs a handoff protocol. Ending a model session, crashing a process, and restarting a machine are different events. A memory file does not automatically handle all three.
Anthropic's 2025 long-running agent account describes an initializer, a feature list, progress files, and subsequent coding sessions. Its useful engineering move is to convert an open-ended goal into inspectable work and require later sessions to examine the environment. Browser-based acceptance exposes problems that reading code alone can miss. This is evidence from a particular development setup, not a guarantee of unlimited autonomous execution.
Consider building an order-search application across several sessions. The contract requires login, order filtering, and error handling. Demonstrating login does not complete the application. Recovering after a tool timeout does not authorize creating the same test order again.
Compaction, resets, and external state have different jobs
Compaction controls context length, a reset constructs a new working context, and external state preserves facts that can be checked across sessions. They complement each other but solve different failure modes.
| Mechanism | What it changes or preserves | Main risk | Required recovery check |
|---|---|---|---|
| Context compaction | Replaces history with a smaller representation | Constraints or evidence disappear | Summary claims remain valid |
| Context reset | Starts a session from selected handoff material | Repeated exploration and missing implicit rules | Contract and current environment |
| Durable checkpoint | Saves execution state and progress | State and external writes diverge | Whether effects committed |
| External task ledger | Stores goals, candidates, checks, and operation status | Concurrent updates or stale reads | Revisions and downstream outcomes |
Context compression cannot recover an external transaction identifier that was never recorded. A framework checkpoint also does not automatically make downstream writes exactly once. The LangGraph persistence documentation distinguishes thread-level checkpoints from a Store used across threads. An in-memory checkpointer cannot survive process restart; choosing a durable backend addresses only the persistence layer.
Model changes can change the appropriate context policy. Anthropic's 2026 application-development experiment describes needing resets with Sonnet 4.5, then using compaction without proactive resets with Opus 4.5 in that setup. It does not establish that resets are universally inferior. Compare policies on your tasks, including recovery cost and acceptance quality.
Externalization should not become indiscriminate transcript storage. Preserve facts and their provenance in the ledger, then retrieve the subset relevant to the current stage. The audit history can grow independently of the model's working context. Temporary hypotheses should be labeled as hypotheses so that a later session does not promote them into requirements.
Make the handoff mechanically inspectable
A useful handoff identifies the task contract, current candidate, acceptance evidence, and uncertain operations. Prose explains intent; structured fields constrain what the next executor may do.
{
"schemaVersion": 1,
"taskId": "order-app-42",
"revision": 7,
"contractHash": "sha256:...",
"artifactHash": "sha256:...",
"checks": [
{"id": "login-browser", "status": "passed", "evidenceId": "ev-91"}
],
"effects": [
{"operationId": "seed-order-8", "status": "unknown"}
],
"nextAction": "reconcile seed-order-8 before retry"
}
The ledger revision controls concurrent updates. The artifact hash identifies the candidate. The contract hash identifies what must be achieved. These are separate identities. Acceptance evidence should additionally record the grader version, environment snapshot, and immutable output reference, so that a test from yesterday cannot silently certify today's candidate.
Use compare-and-swap when committing a new ledger revision. An executor that read revision 7 must not overwrite revision 8 produced by another worker. It should reload, reconcile observations, and attempt a new update. Store large artifacts separately and keep immutable references in the ledger to reduce write contention.
The generator may submit a candidate and request checks. The trusted acceptance path writes the passed status. Failure evidence belongs in the ledger too: a permissions error is different from a malformed query, and preserving that distinction prevents the next session from repeatedly fixing the wrong component.
Recovery is a protocol
Recovery validates the handoff, reconciles uncertain effects, and only then selects executable work. The next model call should not receive write authority before these checks succeed.
Suppose a downstream service creates an order but the client crashes before recording the response. Replaying the checkpoint can create another order. A production design needs stable operation identifiers, downstream idempotency keys, or a queryable business key. If the operation cannot be queried or safely retried, recovery must pause rather than infer success from a summary.
The recovery path also checks workspace and service identity. A changed repository commit or a different tenant invalidates previous observations. You can recheck only affected dependencies when the dependency model is trustworthy. File modification times alone do not establish that a previously passed requirement remains satisfied.
For externally mutable state, an artifact hash is insufficient. Bind evidence to a domain revision or environment snapshot. If those identities cannot be established, the conservative result is revalidation rather than inherited completion.
Runnable Go: reject stale evidence and incomplete acceptance
This Go 1.23 program checks a handoff at the recovery entrance using only the standard library. It demonstrates a changed candidate, an uncertain operation, and a missing acceptance check. It does not implement persistence, authenticated evidence, or downstream idempotency.
package main
import (
"crypto/sha256"
"encoding/hex"
"fmt"
)
type Check struct {
ID string
Passed bool
ArtifactHash string
}
type Handoff struct {
SchemaVersion int
TaskID string
Revision int
ArtifactHash string
Checks []Check
UnknownOps []string
}
func digest(data []byte) string {
sum := sha256.Sum256(data)
return hex.EncodeToString(sum[:])
}
func resume(h Handoff, taskID string, artifact []byte, required []string) (string, error) {
if h.SchemaVersion != 1 || h.TaskID != taskID || h.Revision < 1 {
return "", fmt.Errorf("invalid handoff identity or version")
}
currentHash := digest(artifact)
if currentHash != h.ArtifactHash {
return "revalidate", nil
}
if len(h.UnknownOps) > 0 {
return "reconcile:" + h.UnknownOps[0], nil
}
verified := make(map[string]bool)
for _, check := range h.Checks {
if check.Passed && check.ArtifactHash == currentHash {
verified[check.ID] = true
}
}
for _, id := range required {
if !verified[id] {
return "verify:" + id, nil
}
}
return "completed", nil
}
func main() {
artifact := []byte("order-app revision 7")
hash := digest(artifact)
h := Handoff{
SchemaVersion: 1, TaskID: "order-app-42", Revision: 7,
ArtifactHash: hash,
Checks: []Check{{ID: "login", Passed: true, ArtifactHash: hash}},
UnknownOps: []string{"seed-order-8"},
}
required := []string{"login", "orders"}
for _, input := range [][]byte{[]byte("changed artifact"), artifact} {
action, err := resume(h, "order-app-42", input, required)
if err != nil {
panic(err)
}
fmt.Println(action)
}
h.UnknownOps = nil
action, err := resume(h, "order-app-42", artifact, required)
if err != nil {
panic(err)
}
fmt.Println(action)
}
Save it as main.go and run go run main.go:
revalidate
reconcile:seed-order-8
verify:orders
The final line deliberately does not say completed. Passing login cannot certify the order-search requirement. In production, required must come from the trusted task contract; the tested agent must not be able to remove difficult requirements.
A hash detects changed content, not a trustworthy grader. Authenticate evidence writers, include contract and grader identities, and bind dynamic tasks to the relevant environment version. Returning revalidate here grants no write permission: reconciliation remains necessary before the executor can resume external mutations.
Evaluate recovery through injected failures
Recovery evaluation compares the same tasks with and without interruptions, grades the final outcomes, and counts duplicate effects separately. Continuing to produce text is not an acceptance criterion.
| Interruption point | Injected failure | Independent acceptance |
|---|---|---|
| Candidate written, ledger not saved | Process exit | Candidate recovered without false completion |
| Downstream commit, response not recorded | Lost response | No duplicate order |
| Check finished, completion not saved | Ledger write failure | Evidence reread and status reconciled |
| Two executors resume together | Revision conflict | Only valid updates commit |
| Context compacted | Critical failure context omitted | External blocker still recovered |
Repeat each task from clean snapshots with multiple runs. Report recovery success, duplicate-effect rate, false-completion rate, latency, and additional cost. Slice by task length, number of external writes, and handoff count. The repeated-trial reliability article explains why average success alone hides unstable tasks.
The evaluator should choose interruption points, including dangerous commit windows. Allowing the agent to stop only when convenient creates selection bias. Distinguish infrastructure restoration failures from model decision failures; both matter, but they need different remedies.
FAQ
Why is a good summary insufficient?
A summary has no transaction semantics. “Test orders prepared” does not establish whether an order reached the database. Keep prose for intent and an operation ledger for queryable business facts.
Must recovery rerun every test?
No, provided affected checks can be identified from trustworthy dependencies and version identities. Without that information, full revalidation is more credible than retaining stale green checks.
What happens after acceptance fails?
Submit evidence-backed revision requests containing expected behavior, observed behavior, and reproduction conditions. An evaluator-driven feedback loop adds candidate retention and bounded revision so that recovery does not become endless rework.
Should all task history become long-term memory?
No. Temporary task facts, speculative diagnoses, and durable user preferences have different lifetimes. A handoff ledger supports recovery of the current task. Long-term memory needs its own update, revocation, and access policies.
References
- Anthropic: Effective harnesses for long-running agents, November 26, 2025.
- Anthropic: Harness design for long-running application development, March 24, 2026.
- LangGraph: Persistence, checkpoint and Store boundaries.
- Agent evaluation harness design, task, trajectory, and outcome foundations.