Skip to content

Data and storage

Where a Chickpea deployment keeps its state on each target, how credentials are protected, how long each kind of record lives, and what backups can and cannot recover.

Updated View as Markdown

Deployment state is everything Chickpea keeps between turns: Agents and their channel grants, members, sessions, connections, memory, schedules, the work ledger, usage, and audit events. Where the security model says what may be read and written, this page says where those bytes land and how long they stay. It covers the stores on each target, the credential keyring, retention, what is deliberately content-free, and what a backup recovers.

What is stored

Data On Cloudflare On Node
Conversation transcripts One Flue agent Durable Object per conversation The transcript SQLite file
Agents, channel grants, thread routes, connection accounts TAG_STATE Durable Object The state SQLite file
Members, roles, encrypted Slack credential revisions TAG_STATE The state SQLite file
Memory, schedules, the work ledger, usage, management proposals, audit events TAG_STATE The state SQLite file
Browser sessions and the Slack provider account D1 AUTH_DB A separate auth SQLite file
Slack credential encryption keys Worker secrets A keyring JSON file
Shared gateway delivery inbox TAG_STATE Not available
Shared gateway connection session SLACK_GATEWAY_SESSION Durable Object Not available

There is no KV binding and no R2 bucket. The only other Cloudflare binding that holds bytes is ASSETS, which serves the public product images.

On Cloudflare

TAG_STATE is one Durable Object class, TagStateStore, addressed as a single instance named singleton. Inside it, one SQLite database carries the identity, configuration, snapshot, Slack, settings, turn job, gateway inbox, memory, schedule, usage, work, and management tables. Every store on this target is a proxy that calls that object.

Transcripts live apart from it. Flue, the open agent framework from the Astro team, now part of Cloudflare, registers three agent classes, chickpea-slack-v2 for Slack turns plus chickpea-routine-intent-v2 and chickpea-routine-execution-v2 for schedules, and gives each conversation its own instance keyed by workspace, channel, and thread. Chickpea’s retention pass never deletes a Flue transcript.

D1 AUTH_DB holds only what Better Auth owns: the opaque browser session and the Slack provider account. It is bound as chickpea-auth-db with migrations in migrations/better-auth, and a deploy stops before upload if the artifact omits the binding, changes its database ID, or points at a second database. Backing up or restoring AUTH_DB alone does not back up or restore the application. Chickpea provides no cross-store snapshot or restore tool today, so a Cloudflare recovery plan means keeping credential secrets recoverable and following the release’s own migration procedure.

On Node

Every store is a SQLite file, and the defaults derive from one path.

Data Default path Variable
Conversation transcripts ./tmp/flue.db TAG_DB_PATH
App state, identity, configuration, claims <TAG_DB_PATH>.state SLACK_STATE_DB_PATH
Better Auth sessions and accounts <SLACK_STATE_DB_PATH>.auth CHICKPEA_AUTH_DB_PATH
Slack credential encryption keys <SLACK_STATE_DB_PATH>.credential-keyring.json CHICKPEA_CREDENTIAL_KEYRING_PATH

Those are development defaults. A production host sets all four explicitly, inside a private state directory owned by a dedicated account at mode 0700, never inside a checkout that an upgrade will replace. Never use :memory: for a production database.

Each state database runs in WAL mode, so -wal and -shm files sit beside the main file and belong to the same snapshot. One process serves one state directory: several processes against these files, or a network filesystem standing in for shared state, is not supported. A consistent backup therefore means stopping the service and snapshotting the whole state directory, including the WAL files, the keyring, and the runtime environment file. Copying live SQLite files is not a backup, and a backup is not proven until its restore has been tested on an isolated host.

The credential keyring

Slack app credentials, connected Slack credentials, the gateway deployment key, and the Composio project key are sealed with AES-GCM-256 before they are written, as encrypted revisions rather than ordinary settings rows. Each envelope binds its deployment, identity, app, workspace, purpose, and revision into the ciphertext as associated data, so a revision cannot be replayed into another slot.

  • The keys live outside the databases. On Cloudflare they are Worker secrets: CHICKPEA_CREDENTIAL_KEY_CURRENT_ID names the active key ID and a CHICKPEA_CREDENTIAL_KEY_<ID> slot holds it. The deploy wrapper creates key_v1 on a fresh Worker and refuses to deploy over an existing one whose current ID or matching slot is missing.
  • The Node keyring is a file, not a database row. It is created on first use at mode 0600 in a directory created at 0700, beside but never inside the state database. Chickpea refuses to start if its permissions allow group or other access.
  • The keyring is independent of the auth secret. CHICKPEA_AUTH_SECRET signs Better Auth sessions and is never read as credential key material. Losing the keyring makes stored credentials unusable, so back it up with every database, not instead of them.
  • Connector credentials are stored by reference, outside that encrypted realm. An API or MCP connection’s credential is written to app state under its own secret reference, separate from the connection’s policy, resolved per use with no cache, and overridden by a matching environment variable when one is set. Protecting those values is the state store’s job.

Retention

Data Retention Where it is set
Run bodies in the work ledger 30 days TAG_RUN_BODY_RETENTION_DAYS, an integer from 1 to 365
Raw usage operations 90 days, rolled into daily aggregates first Fixed in code
Usage aggregates 13 months Fixed in code
Gateway delivery deduplication record 48 hours after the row is terminal Fixed in code
Gateway inbox envelope Scrubbed on completion, retry exhaustion, or recovery; at most 7 days if abandoned Fixed in code
Transcripts, memory, audit events No automatic expiry Not configurable

A run body is one ledger record, capped at 262,144 bytes, holding a trigger, a prepared input, a model output, or a rendered payload. Its expiry is stamped when it is written, so changing the retention variable moves new records only. The purge nulls the body and keeps the row with a purged_at timestamp, preserving the ledger’s shape while removing its content. That pass runs from the Cloudflare deployment’s one-minute cron and clears up to 100 records per tick; the Node target runs no scheduler, so nothing drives it there.

Memory is not on a clock. One Agent has one memory, capped at 8 KiB of body and 512 bytes of description, and it stays until somebody changes it.

What is content-free

Some records exist to prove that something happened without keeping what it said.

  • Audit events carry hashes, not values. Each row holds a before hash and an after hash of the subject, a bounded reason code, actor and subject identifiers, and a metadata object capped at 4,096 bytes whose keys are allowlisted per event type.
  • The deduplication record holds delivery identity only. Delivery, binding, and workspace IDs, event kind, status, attempt count, timestamps, and a bounded reason code. No event or message body. When its one million protected rows are full, Chickpea rejects new admission rather than deleting an unexpired record.
  • The shared gateway does not durably store Slack message or event bodies. It holds encrypted installation credentials and sanitized health metadata, and its delivery logs carry bounded reason codes such as session_missing.

Why it works this way

Splitting the stores follows the blast radius. Transcripts are the largest and most sensitive body of text, so they sit in per-conversation objects that nothing else reads. Everything that answers an authority question, grants and members and bindings, sits in one place so a single consistent read can decide a turn.

Encryption keys live outside the databases on purpose. A snapshot of state without the keyring is not a usable copy of the credentials in it, which is what makes an off-host backup safe to keep.

What is not covered

  • There is no cross-store snapshot on Cloudflare. Durable Object state and D1 are backed up and restored by separate means, and Chickpea ships no tool that captures them together.
  • Node durability is single-host SQLite. Multi-instance Node needs a shared state service.
  • A restore rolls state back in time. It may lose recent work or replay events, and the old and restored copies must never answer Slack at once.
  • Retention is not deletion on request. There is no per-person or per-channel erase operation; the knobs above are age-based.
  • Slack keeps its own copy. Messages, files, and waiting event retries follow the workspace’s Slack plan and Slack’s policies, not anything on this page.

Next steps

Navigation

Type to search…

↑↓ navigate↵ selectEsc close