Sections in this lesson
Context, Memory & Run State
Keep model context, private conversation memory and durable execution checkpoints distinct.
Reviewed: · Content owner: Interview Preparator
What you will learn
- Identify what must survive a worker restart.
- Scope checkpoints by tenant, user, conversation and run.
- Reject stale writes using optimistic concurrency.
Before you start
The flow
- 1.Resolve owned scope
- 2.Load checkpoint
- 3.Verify version
- 4.Perform bounded step
- 5.Save next version
Do not confuse the three stores
Model context is the information selected for the next request. Conversation memory is a policy-managed subset of previous turns. Execution state records pending work, counters, original expiry, receipts and terminal status. An LLM-generated summary is not a reliable replacement for a durable checkpoint.
The example retains only six recent observations for the model, while receipt and budget state are bounded by the run limits. Production context still needs actual token counting, retention and sensitive-data controls.
Scope before loading
JdbcRunStore uses tenant, owner, conversation and run ID in its primary key and every lookup/update. The host must first authenticate and authorize that scope; passing a string into a repository does not establish caller identity.
Tests recreate the repository, resume a waiting action and prove that another tenant, user or conversation cannot inherit the private state under the same run ID. The initial expiry is loaded rather than refreshed on restart.
Protect transitions, not only rows
Each save includes the expected version. A stale worker receives a conflict instead of overwriting newer state. This does not itself lease a run or atomically cover remote tool effects; the target idempotency receipt handles the write/checkpoint gap in the local reference.
A production worker system should add claim/lease fencing, queue redelivery rules, schema evolution and retention. Do not call a single-row CAS store a complete durable workflow engine.
Try it
package com.interviewpreparator.agents;import java.util.List;import org.springframework.jdbc.core.JdbcTemplate;import tools.jackson.databind.json.JsonMapper;import static com.interviewpreparator.agents.AgentContracts.*;public final class JdbcRunStore implements RunStore { private final JdbcTemplate jdbc; private final JsonMapper mapper = JsonMapper.builder().build(); public JdbcRunStore(JdbcTemplate jdbc) { this.jdbc = jdbc; } public void initializeLocalSchema() { // Teaching/local setup only. Production schema changes belong in migrations. jdbc.execute(""" create table if not exists agent_runs ( tenant text not null, owner_id text not null, conversation text not null, run_id text not null, version bigint not null, state_json text not null, primary key (tenant, owner_id, conversation, run_id)) """); } @Override public Snapshot createOrLoad(Scope scope, State initial) { jdbc.update(""" insert into agent_runs (tenant, owner_id, conversation, run_id, version, state_json) values (?, ?, ?, ?, 0, ?) on conflict do nothing """, scope.tenant(), scope.user(), scope.conversation(), scope.runId(), mapper.writeValueAsString(initial)); List<Snapshot> rows = jdbc.query(""" select version, state_json from agent_runs where tenant=? and owner_id=? and conversation=? and run_id=? """, (result, index) -> new Snapshot(result.getLong(1), mapper.readValue(result.getString(2), State.class)), scope.tenant(), scope.user(), scope.conversation(), scope.runId()); if (rows.size() != 1) throw new Conflict(); return rows.getFirst(); } @Override public Snapshot save(Scope scope, long expectedVersion, State state) { long next = Math.addExact(expectedVersion, 1); int changed = jdbc.update(""" update agent_runs set version=?, state_json=? where tenant=? and owner_id=? and conversation=? and run_id=? and version=? """, next, mapper.writeValueAsString(state), scope.tenant(), scope.user(), scope.conversation(), scope.runId(), expectedVersion); if (changed != 1) throw new Conflict(); return new Snapshot(next, state); }}examples/spring-ai-lab/src/main/java/com/interviewpreparator/agents/JdbcRunStore.java · mvn -f examples/spring-ai-lab/pom.xml -Pintegration verify. Java examples target 21. Models and usage are fixtures; no live inference or billing guarantee is implied.
Interview questions
Can a conversation summary replace a checkpoint?
No. It can omit identifiers, pending effects or exact counters; execution state needs validated durable fields.
What does a version check prevent?
A stale update from silently replacing a more recent checkpoint; it does not automatically prevent every external side effect.
Why persist the original expiry?
Otherwise retries or worker restarts can extend the run indefinitely.
Practice
Add a run-lease owner and fencing token, then reject a worker that resumes after losing its lease.
Check your solution
- Every query is scoped.
- Stale checkpoint versions fail.
- Restart preserves counters, pending action and expiry.
Official references
Continue learning
Category sequence
Agentic AI
Lesson 4 / 6
Was this lesson useful?
Change notes: Phase 3: bilingual agent engineering lesson and executable reference.