> ## Documentation Index
> Fetch the complete documentation index at: https://docs.formation.texturehq.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Persistence

> How Formation records conversation and runtime state in PostgreSQL.

PostgreSQL stores Formation's canonical conversation graph, memory revisions, and
execution history. Each graph belongs to one agent's [perspective](https://docs.formation.texturehq.com/perspective.md).
Other agents observing the same external message create their own observations.

## Writes and deduplication

A perspective-bound graph store resolves the connector, conversation, external
identity, and actor; inserts the message event; and records participation. The
transactional store commits those related writes together. Foreign-perspective
identifiers cannot be used to insert a message into another agent's graph.

Message bodies are immutable observations. Identity and conversation rows can be
upserted as their metadata changes. A duplicate
`(conversation_id, external_message_id)` returns the existing message without
replacing its body or moving its participation timestamp forward.

This is a logical append-only conversation contract, not a claim that every
database row is immutable. Configuration, leases, queue state, retention, and
current-state projections have their own update and cleanup rules.

## Intake, execution, and delivery

Slack, Telegram, and phone connectors persist accepted intake before acknowledging it. Graph
capture then precedes model execution. [Durable intake](https://docs.formation.texturehq.com/runtime/durable-intake.md)
describes the queue and the connectors that still use an in-process handoff.

Runs record model calls, tool outcomes, budget usage, and ownership. Their
[checkpoints](https://docs.formation.texturehq.com/runtime/turn-checkpoints.md) support restart recovery without repeating
completed rounds. Unknown external effects are not automatically retried.

The actual outbound message ID is learned from the connector send, so the
canonical outbound event is recorded afterward. A durable reply boundary precedes
the send. A crash in this interval can leave delivery uncertain; persistence does
not make a remote send exactly once.

## Context reads

Every graph and memory read is scoped to the owning agent. Audience and lineage
checks further restrict what can enter a turn. Search indexes are derived data;
they cannot grant access to a row or replace canonical text. See the
[data model](https://docs.formation.texturehq.com/data-model.md), [context resolution](https://docs.formation.texturehq.com/runtime/context-resolution.md), and
[memory privacy](https://docs.formation.texturehq.com/runtime/memory-privacy.md).

## Authoring a migration

Create a forward migration with a unique UTC timestamp prefix:

```sh theme={null}
./scripts/new-migration.sh add_example_field
```

The migration runner applies every version not yet recorded in its ledger,
in numeric order within that pending batch. A pending version below the largest
applied version is still applied. Applied versions are checked against their
recorded checksums; do not edit or renumber a migration already deployed.

Timestamp prefixes reduce conflicts between independent branches. They are not a
dependency solver. Put dependent changes in the same file/PR and ensure their
required order explicitly. Legacy numbered migrations remain valid ledger keys;
new migrations must use timestamps.

Run the embedded migration runner, which is also used at service boot:

```sh theme={null}
DATABASE_URL='postgres:///mesh_test' go run ./cmd/mesh migrate
```

Do not replace it with a raw loop over SQL files: that bypasses the migration
ledger, checksums, and validation.

## Query generation and validation

`internal/store` is generated from `db/queries` and migrations. Edit the SQL, then
regenerate rather than modifying generated Go:

```sh theme={null}
sqlc generate
```

For a disposable local PostgreSQL database:

```sh theme={null}
createdb mesh_test
DATABASE_URL='postgres:///mesh_test' go run ./cmd/mesh migrate
MESH_TEST_DATABASE_URL='postgres:///mesh_test' go test -p 1 ./internal/graph/... ./internal/migrate/...
```

Database integration tests are skipped when their database variable is absent.
CI supplies PostgreSQL and runs the migration runner before integration tests.

Formation uses pgx's simple-query protocol for compatibility with transaction-pooling
proxies. For JSONB parameters, use an explicit SQL cast and a string value;
passing Go `[]byte` can encode a bytea literal instead of JSON.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.