Skip to content

Execution State and Scratchpad

What is execution state?

During an agentic run, the system state changes continuously:

current goal
current step
completed steps
pending work
observations
artifacts
budget usage
approvals
errors

If this is left only inside the model's conversation context, the runtime becomes fragile.

The important mental model is:

Model context is a current view. Canonical execution state belongs to the application runtime.

Canonical Run State
        ↓ projection
Model Context
        ↓
LLM decision
        ↓ validated transition
Canonical Run State

State vs context vs scratchpad vs memory

These concepts should remain separate.

Execution state

The authoritative operational state of the current run.

For example:

{
  "run_id": "run-1842",
  "status": "RUNNING",
  "goal": "Fix failing payment test",
  "current_step": "run_regression_tests",
  "completed_steps": ["inspect_failure", "patch_code"],
  "tool_calls_used": 7,
  "cost_used_usd": 0.84
}

Context

The information selected for one model call.

relevant goal
+ current state summary
+ latest observations
+ allowed tools
+ relevant code/docs

The model does not need every field of the complete state on every call.

Scratchpad

Short-lived working artifacts or explicit intermediate notes.

Examples:

suspected root causes
candidate files
current hypothesis
temporary comparison table

A scratchpad can be runtime-managed, but it is not necessarily authoritative domain state.

Memory

Information that survives beyond the current run.

Examples:

user preference
previous incident lesson
repository convention
long-term project fact

That belongs to a separate memory architecture concern.

execution state = this run
memory          = beyond this run

What should be canonical?

Usually, anything on which the runtime builds hard control:

  • run status,
  • goal and success criteria,
  • iteration counter,
  • budget usage,
  • completed/pending steps,
  • tool-call result references,
  • approval state,
  • side-effect idempotency keys,
  • checkpoint version.

Do not reconstruct these from model prose such as:

"I think I already ran the tests earlier."

Prefer explicit state:

{
  "test_execution": {
    "id": "test-781",
    "suite": "payment-regression",
    "status": "PASSED",
    "completed_at": "..."
  }
}

State-machine mental model

A run can be modeled as a state machine.

For example:

CREATED
  ↓
RUNNING
  ├──→ WAITING_FOR_HUMAN
  ├──→ WAITING_FOR_EXTERNAL_EVENT
  ├──→ FAILED
  ├──→ CANCELLED
  └──→ COMPLETED

Transitions may be triggered by:

  • a model decision,
  • a tool result,
  • a deterministic rule,
  • user input,
  • a timeout,
  • an external event.

But the runtime performs the transition.

LLM proposes: STOP_SUCCESS
        ↓
runtime checks success contract
        ↓
COMPLETED or continue

A possible RunState

Conceptually:

{
  "run_id": "run-1842",
  "status": "RUNNING",
  "goal": {
    "description": "Fix flaky retry test",
    "success_conditions": [
      "targeted test passes 20 consecutive runs",
      "related regression suite passes"
    ]
  },
  "plan": {
    "version": 3,
    "steps": []
  },
  "observations": [],
  "artifacts": [],
  "approvals": [],
  "budget": {
    "iterations": 6,
    "tool_calls": 11,
    "cost_usd": 1.18
  },
  "version": 27
}

The exact schema is system-specific; explicit state ownership is the important principle.

Project state into model context

Complete state can be large and noisy, so use a separate projection step:

Canonical state
     ↓
Context Builder
     ↓
Relevant state projection
     ↓
LLM

The model may only need:

Goal: fix flaky retry test
Current step: verify patch
Latest observation: target test passed 10/10
Remaining success criterion: related regression suite not yet run
Budget: 4 iterations remaining

It does not need every previous API response.

This is context engineering applied to runtime state.

Do not lose observations inside chat text

A tool result such as:

{
  "suite": "payment-regression",
  "passed": 418,
  "failed": 1,
  "failure": "RefundRetryTest"
}

can be stored as a normalized observation:

{
  "type": "TEST_RESULT",
  "source": "test_runner",
  "timestamp": "...",
  "data": {
    "suite": "payment-regression",
    "failed_tests": ["RefundRetryTest"]
  }
}

Conversation transcripts can remain useful audit/debug artifacts, but they should not be the only state store.

Intermediate artifacts

Agentic tasks often produce artifacts such as:

  • patches,
  • generated files,
  • query results,
  • report drafts,
  • candidate plans,
  • test logs,
  • screenshots,
  • deployment manifests.

Store large artifacts by reference:

{
  "artifact_id": "artifact-92",
  "type": "PATCH",
  "location": "object://runs/run-1842/patch.diff",
  "sha256": "..."
}

rather than copying the full payload into every subsequent context.

Checkpoints

Longer runs should not live only in process memory.

step completes
     ↓
state transition
     ↓
persist checkpoint
     ↓
next step

If execution is interrupted:

worker dies
network fails
process restarts

then:

load checkpoint
 ↓
revalidate environment
 ↓
resume

not:

read old chat transcript
 ↓
ask model what probably happened

Resume is not just continue

The checkpoint may be correct while the outside world has changed.

Example:

checkpoint:
PR #184 is open

30 minutes later:
PR was merged by a human

Before resuming, refresh relevant observations.

restore internal state
      +
refresh external preconditions
      ↓
continue / replan / stop

State versioning and concurrency

Multiple workers or events may update the same run concurrently.

Example:

worker A reads version 27
worker B reads version 27
A writes version 28
B also writes based on stale version 27

Useful techniques include:

  • optimistic locking,
  • compare-and-swap versions,
  • single-writer orchestration,
  • transactional updates,
  • event ordering.

For example:

UPDATE run
SET state=?, version=28
WHERE run_id=? AND version=27

If no row is updated, reload the current state.

Agent runtimes face the same concurrency problems as ordinary distributed applications.

Event log vs snapshot

Two common representations are useful.

Snapshot

current state object

Simple to read.

Event log

RUN_CREATED
TOOL_CALLED
TOOL_SUCCEEDED
PLAN_REVISED
APPROVAL_REQUESTED
APPROVAL_GRANTED
RUN_COMPLETED

Useful for audit and debugging.

Often the practical solution is both:

append events
    ↓
maintain current snapshot

Full event sourcing is unnecessary unless requirements justify it.

Scratchpad as explicit working state

A scratchpad may look like:

{
  "hypotheses": [
    {"text": "shared fixture race", "status": "LIKELY"},
    {"text": "retry timer bug", "status": "REJECTED"}
  ],
  "files_to_inspect": ["RetryService.java", "RefundRetryTest.java"]
}

This is more useful operationally than a long prose reasoning history.

The runtime needs relevant intermediate conclusions, evidence, and open questions; it does not need to persist a hidden reasoning transcript as canonical state.

Derived state vs source state

For example:

raw observations:
- error rate 12%
- deployment 5 minutes ago

hypothesis:
- deployment likely caused regression

Represent these separately:

FACT / OBSERVATION
vs
HYPOTHESIS / DERIVED

This reduces the chance that a previous model assumption becomes a “fact” in later context.

State cleanup and retention

Not every run artifact needs permanent retention.

Different data classes can have different policies:

run metadata: 90 days
sensitive tool payload: 7 days
large artifacts: 30 days
aggregated metrics: longer

This matters for privacy, security, and cost.

Anti-pattern: chat history as a database

Weak design:

messages[]

where important state exists only somewhere in prose.

Problems:

  • difficult deterministic querying,
  • difficult resume,
  • stale information remains visible,
  • context keeps growing,
  • concurrency is hard to manage,
  • state is implicit.

Better:

structured canonical state
+ event/trace history
+ selected context projection

Anti-pattern: copying every observation into every prompt

After 50 steps, this creates token and noise explosion.

Prefer:

persist full evidence externally
        ↓
select / summarize / reference relevant evidence
        ↓
current model call

Takeaways

  • Canonical execution state is runtime-owned, not model-owned.
  • Model context is only a relevant projection of that state.
  • State, context, scratchpad, and long-term memory are different concepts.
  • Run status, budgets, approvals, completed steps, and side-effect metadata should be explicit structured state.
  • Checkpoint/resume is a core reliability capability for long or asynchronous runs.
  • Revalidate the external environment after resuming.
  • Concurrency and state versioning remain normal software-engineering problems.
  • Represent observations and hypotheses separately.
  • Store artifacts by reference instead of putting full payloads into every context.
  • Chat history can be useful trace data, but it is a poor canonical state store.