Persisting States with Databases and Callbacks
Introduction: Why Database Persistence Matters
In the previous lesson, you built an Express server that exposes AI agents through REST endpoints and stores agent states in memory using a plain JavaScript Map. While this worked well for learning the basics, it had a critical flaw: when the server restarts, all agent states disappear. For Factor 6 — Launch / Pause / Resume with simple APIs to work reliably, an agent paused yesterday must still be resumable today. SQLite database persistence solves this by storing states on disk. In this lesson, you will replace in-memory storage with SQLite using the StateStore class and implement progress callbacks that save the state after each agent step, enabling real-time progress tracking for clients. This persistent foundation is what makes the pause and resume endpoints that you will build in the next lesson truly practical. Before we write any code, let's see where the new pieces fit in the project.
Project Structure
The clean separation between the core agent logic and the server layer that you saw in the last lesson still holds. The key change in this lesson is a brand-new database.ts module inside the server directory, which encapsulates all persistence logic behind the StateStore class. A couple of existing files also gain small but important upgrades:
Here's how each file changes relative to the previous lesson:
src/server/database.ts(new): Home of theStateStoreclass. It opens theSQLiteconnection, creates thestatestable, and providesget,save, andupdatemethods. AllSQLand all serialization logic lives here — nothing else in the app touches the database directly.src/core/models/state.ts(updated): Adds theAGENT_STATUSESruntime array so that astatusstring read back from the database can be validated at runtime, not just at compile time.src/core/agent.ts(updated): Therunmethod now accepts an optionalProgressCallbackthat fires after every step, letting the server persist progress incrementally.src/server/app.ts(updated): Swaps the in-memoryMapfor aStateStoreinstance, and passes a save callback intoagent.run(...).src/server/main.ts(unchanged): Still just importsappand callsapp.listen(...).test.ts(updated): Printsstatus,steps, andpending_tool_callson every poll so you can watch progress accumulate in real time.
Keeping all persistence behind database.ts means the rest of your server never has to think about SQL, JSON serialization, or file paths — it just calls store.save(...) and store.get(...). This is the same decoupling principle from Factor 11 applied to storage: the core agent stays a pure reducer, and the database is just another thin adapter around it.
