Skip to main content
Some things an agent must remember outside any one conversation: a buyer’s question it passed to a seller, waiting for the answer, or a note it keeps about a person for next week. Records hold that state as small JSON documents in collections you declare on createAgent. They live in the same store as the agent’s conversations, so they survive restarts and deploys, and they behave the same in the playground, self-hosted on SQLite or Postgres, and on Flow’s hosting. Records need @flow-engineer/flow 0.6.0 or later.
If anything other than the agent reads the data, it belongs in your own database. Records are the agent’s own small working state. Orders, bookings, leads and anything your team, your app or a report reads go in your database, reached through a tool; keep only an id here when the agent needs one.

Declare a collection

Add records to the createAgent options in your buildAgent (src/agent.ts in the starter). Each collection names the top-level fields it can be queried by:
src/agent.ts — add to the createAgent options
Only declared fields can be queried. Everything else in a record is stored and returned, but not matched on. A collection may declare no fields at all: it is then read by id, or listed whole. agent.records.collections lists the declared names, in the order declared.

Change a declaration

Add a field to people or fields and restart the agent. Before anything runs, the start indexes the records already kept for the new field, so query and forgetting a person find them. A value the new declaration refuses, such as a number in a people field, stays in the record but is left out of the index. The store keeps every field ever declared for a collection and keeps indexing it, so an older declaration on the same store takes nothing away: a deploy’s previous revision still running, or an old checkout running flow records export. Records it puts are indexed for the newer fields too.

Use records in a tool

A tool reaches the records as context.records. This pair of tools lets a buyer ask about a listing and lets the seller see the questions waiting for them. Add them inside the existing tools array in src/tools.ts, and the SELLERS map and Question type above it:
src/tools.ts
The id uses context.idempotencyKey, which is the same when a cut-off turn runs again, so a retry replaces the record instead of adding a second one. buyer is context.person, the verified identity, never a name the model supplied.

Try it

1

Ask as the buyer

Run npm start, open the playground, choose Maria and send “Does the bike-42 come with lights? Please ask the seller.”
2

See the record

Open the Records tab, shown when the agent declares collections. It lists the agent’s records by collection, every person’s, newest first and read only, and refreshes after each turn.Check it worked: questions shows one record whose value includes "buyer": "phone:+15555550101", "seller": "phone:+15555550102" and "status": "waiting".
3

Read it as the seller

Choose Sam and send “Any questions for me?” The agent calls questions_for_me, which returns:
Tool result
The question’s wording is what the model passed to ask_seller. As Maria, the same tool returns []: she is not the seller of anything.
The starter keeps its store in memory, so records go when npm start stops. To keep them locally, give createAgent a durable database such as database: sqlite("data/agent.db") (self-hosting). Clear history in the playground forgets the person, which deletes the records that name them in a people field.

Use records outside a turn

Everywhere else (your routes, beforeSend, intercept, a script, a job in your backend) use agent.records. It is the same object a tool gets. This route takes the seller’s answer from your own app, keeps it on the record and has the agent tell the buyer:
src/agent.ts — buildAgent
Export Question from src/tools.ts and import it here. The agent answers routes through agent.fetch(request). The nudge follows the usual delivery rules: its status says whether it was sent, held for quiet hours, or refused, for example outside WhatsApp’s window.

What the records API does

A query matches by equality only and returns the oldest first (by when each record was first put, then by id). Without where it returns the whole collection. It returns 50 entries unless limit says otherwise, at most 100. For the next page, pass the last entry you got as after:
Read every waiting question, page by page
There are no ranges, sorting, joins, text search or transactions across records. Two puts to the same id are not merged: the later value replaces the earlier one.
A value is a plain JSON object. A Date is refused: keep a time as milliseconds (Date.now()) or ISO text. NaN, Infinity, a Map, a class instance or undefined inside a list are refused too, with an error naming where it is. Keys whose value is undefined are left out.A declared field holds a string, a number, a boolean or null. A people field holds a string. Queries compare JSON values, so 1 and "1" are different. A query cannot match a missing field or null.
Every refusal is an error whose message starts with flow: records: and says what to change, for example no collection named orders: declare it in createAgent(...) or questions cannot be queried by askedAt: declare it in .... A declaration that breaks a limit throws when createAgent runs.

Limits

RECORD_LIMITS, exported from @flow-engineer/flow: A collection name starts with a letter and has up to 64 letters, digits, - or _. A declared field is a top-level name of letters, digits and _. A put of a new record past 10,000 is refused, and nothing is written; replacing an existing record always works. The count takes no lock, so new records put at the same moment may pass 10,000 by up to as many as are put at once. Delete records the agent no longer needs, or keep that data in your own database.

Forgetting a person

agent.forgetPerson(conversationId) deletes everything kept about a person, and with it every record that names them in a people field. A record matches when that field holds any of their names:
  • their person id (phone:+15550100100, key:<your id> or <channel>:<address>),
  • their conversation id,
  • any channel identity they wrote from, as <channel>:<address>,
  • the number of each identity they have on one of the agent’s phone channels, as phone:+15550100100 or +15550100100. This holds also when your resolve or a sign-in keys them as key:....
A record that mentions the person only in a field not listed in people is kept. So is a record holding just a bare chat address, such as a Telegram id, since another channel may use the same value. A tool of theirs that is still running when they are forgotten cannot keep a record afterwards. Forgetting waits for puts already under way and deletes what they wrote. A later put from that tool is refused with flow: records: not kept: the person was forgotten while the tool ran. Someone named in records who never wrote to the agent has no conversation to forget, such as a seller who only answers through your app. agent.records.forgetPerson(person) deletes the records holding exactly that value in a people field and resolves to how many it deleted:
Forget a seller who never wrote

Export records

From the agent’s folder, print every record as JSON Lines, one record per line:
Terminal
records.jsonl
It ends with Exported 1 record. on stderr and exit status 0 (1 for a usage error or an agent that could not be built). The command builds the agent from src/agent.ts (loading .env first) with a model that is never called, so it needs no model key, starts no turn and sends nothing. It reads the store your buildAgent opens on this machine, never a hosted agent’s: with the starter’s in-memory store it exports nothing. In code, agent.records.export() gives the same records, oldest first. To move them to another store, put each one, in that order, into an agent built on the new store:
Copy records to an agent on another store
Each copy gets new createdAt and updatedAt times, the time of its put. Putting them oldest first, as export gives them, keeps their order for query.

Records in MCP tools

A tool served over MCP gets context.records only when the server is given them. Without them, a tool that uses records fails with not available here: give mcpServer({ records: agent.records }).
In the process that runs the messaging agent, give the MCP server that agent’s records:
src/server.ts — a fragment
Serve mcp as in the MCP guide. Its tools then read and write the same records as the messaging agent, in the agent’s store. Building the agent needs its model key, as npm start does.
A standalone MCP call has no channel address, so context.address is absent there.

Where records are kept

Records are rows in the agent’s store, beside its conversations, keyed by the agent’s id: The first start on 0.6.0 adds three tables to the agent’s database: records, record_index and record_collections (the fields declared for each collection). It changes no existing table. Back records up with the rest of the database.
  • ToolContext has a new field, records. Flow fills it for every tool call. Code that builds a ToolContext itself, such as a unit test calling a tool’s run, adds records: agent.records.
  • A custom store that forwards each call to a Store, as Flow’s hosting does, forwards the new record methods too: putRecord, getRecord, queryRecords, deleteRecord, forgetRecords, exportRecords and reindexRecords.
  • On Flow’s hosting, an agent on 0.6 deploys once the host runs 0.6; until then flow deploy refuses it, as for any newer release line.

Records or a nudge

A follow-up at a given time is not a record. agent.nudge({ to, prompt, at }) gives the agent a turn then, and that turn’s tools can read the records. See reminders and nudges.

Write a tool

Reach your own database and APIs for data other systems read.

Keep the store durable

Configure SQLite or Postgres and handle restarts.