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

# State

> getState, setState, replaceState — merge-patch persistent state on a single key, plus the initialState and onStateChanged hooks.

Agent state is one persistent value of your `State` type. `setState` **merges**, which is
what you want for the common case — update two fields without re-writing the rest.

```ts theme={null}
interface ConvState extends Record<string, unknown> {
  status: "idle" | "processing";
  lastReply: string;
  at: number;
}

export class Conversation extends Agent<Env, ConvState> {
  protected override initialState(): ConvState {
    return { status: "idle", lastReply: "", at: 0 };
  }

  async process(): Promise<void> {
    await this.setState({ status: "processing" });          // merge: other keys untouched
    // ...
    await this.setState({ status: "idle", at: Date.now() });
  }
}
```

State lives in the actor's persistent storage — the same layer the scheduler and the
message log write to — as a single entry holding the whole `State` value. Reads return
that value in full, and `setState` re-persists it inside a storage transaction, so keep
it small and fixed-shape: status flags, cursors, the instance's working memory. It
follows the storage layer's codec rules and per-value size cap (see the
[storage reference](/docs/edge-compute/stateful-actors/api-reference/storage)). For
data that accumulates or needs querying — rows you'd filter, aggregate, or join —
use the agent's embedded [SQL database](/docs/agent-sdk/sql) instead; it lives in the
same instance and scales to 1 GB per agent.

## initialState()

> `protected` **initialState**(): `State`

Default state for a fresh agent instance.

Called whenever state is read and nothing has been stored yet. Reading
alone does not persist the default — the first write does. Subclasses
override this to declare their initial `State`.

**Returns**

`State`

**Default Value**

`{}`

## getState()

> `protected` **getState**(): `Promise`\<`State`>

Read the agent's persistent state.

**Returns**

`Promise`\<`State`>

The stored state, or [\`initialState()\`](/docs/agent-sdk/api-reference/agent/state#initialstate)
if nothing has been written yet.

## setState()

> `protected` **setState**(`patch`): `Promise`\<`State`>

Merge `patch` into the persistent state and return the merged result.

The merge is a recursive JSON merge patch (RFC 7396): top-level keys in
`patch` are merged in, nested objects are deep-merged, and a `null` value
deletes that key. The merge runs in a storage transaction, and
[\`onStateChanged\`](/docs/agent-sdk/api-reference/agent/state#onstatechanged) fires after it commits.

Values must be storage-codec-safe (JSON-native values plus `Date`, `Map`,
`Set`, `ArrayBuffer`/TypedArray, `BigInt`, `RegExp`); functions, class
instances, or circular references throw a `CodecError`.

**Parameters**

| Parameter | Type                   | Description                                           |
| --------- | ---------------------- | ----------------------------------------------------- |
| `patch`   | `MergePatch`\<`State`> | The fields to merge; `null` deletes a key (RFC 7396). |

**Returns**

`Promise`\<`State`>

The full state after the merge.

The merge is recursive, and `null` deletes:

```ts theme={null}
// stored: { status: "processing", job: { id: "j1", step: 2, note: "retrying" } }
await this.setState({ job: { step: 3, note: null } });
// stored: { status: "processing", job: { id: "j1", step: 3 } }
```

Untouched keys survive at every level — `status` and `job.id` were never rewritten.

## replaceState()

> `protected` **replaceState**(`next`): `Promise`\<`State`>

Replace the persistent state wholesale (no merging) and return it.

Unlike [\`setState\`](/docs/agent-sdk/api-reference/agent/state#setstate), nothing of the previous state
survives. [\`onStateChanged\`](/docs/agent-sdk/api-reference/agent/state#onstatechanged) **does** fire —
with the new state and the state before the replacement.

**Parameters**

| Parameter | Type    | Description             |
| --------- | ------- | ----------------------- |
| `next`    | `State` | The complete new state. |

**Returns**

`Promise`\<`State`>

`next`, as written.

## onStateChanged()

> `protected` **onStateChanged**(`_next`, `_prev`): `Promise`\<`void`>

Hook: fires after every persistent state change — after each
[\`setState\`](/docs/agent-sdk/api-reference/agent/state#setstate) **and**
[\`replaceState\`](/docs/agent-sdk/api-reference/agent/state#replacestate) resolves.

Override to react to changes: mirror state to an external system, log
transitions, invalidate caches, or fan state out to connected clients.
The default implementation does nothing.

**Parameters**

| Parameter | Type    | Description                  |
| --------- | ------- | ---------------------------- |
| `_next`   | `State` | The state after the change.  |
| `_prev`   | `State` | The state before the change. |

**Returns**

`Promise`\<`void`>

One override fans state out to connected clients — `broadcastSnapshot` pushes to every
socket subscribed to `state`, and the hook covers `setState` and `replaceState` alike:

```ts theme={null}
private desk = new AgentSocketServer<ConvState>(this, {
  getState: () => this.getState(),
});

protected override async onStateChanged(next: ConvState, prev: ConvState): Promise<void> {
  await this.desk.broadcastSnapshot(next);
}
```

For patch-level pushes — sending only what changed — broadcast from a `setState`
override instead, where the patch object is in hand; the example on
[AgentSocketServer](/docs/agent-sdk/api-reference/agent-socket-server) shows that
variant.
