Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
1e8f967
feat(agents): move state into an opt-in Lifecycle capability (agents/…
Aug 28, 2026
847054a
fix(agents): retain async state change notifications
Sep 2, 2026
65cf89e
fix(agents): remove unnecessary state capability plumbing
Sep 2, 2026
cc28c2c
Merge remote-tracking branch 'origin/main' into feat/state-capability
mattzcarey Sep 9, 2026
9903f9e
refactor(agents): rename StateManager to State and seed initialState …
mattzcarey Sep 9, 2026
1c0a0f5
refactor(agents): give the State capability sole ownership of cf_agen…
mattzcarey Sep 9, 2026
8aa12e3
Merge remote-tracking branch 'origin/main' into feat/state-capability
mattzcarey Sep 11, 2026
87474b6
refactor(agents): drop LifecycleServices.sql and trim the State surface
mattzcarey Sep 11, 2026
3505c5c
refactor(agents): rename Agent's State type parameter to TState
mattzcarey Sep 11, 2026
e762d79
refactor(agents): use # private fields in the State capability
mattzcarey Sep 11, 2026
4799e62
fix(state): persist before caching so a failed write cannot poison get()
mattzcarey Sep 11, 2026
398e6dc
test(state): cover a failed write leaving the cache untouched
mattzcarey Sep 11, 2026
dee5614
fix(state): normalize onChanged results with Promise.resolve
mattzcarey Sep 11, 2026
e12383a
Merge origin/main into feat/state-capability
mattzcarey Sep 11, 2026
04c5483
fix(state): register the async onChanged continuation with the runtime
mattzcarey Sep 11, 2026
e5331fe
Revert "fix(state): register the async onChanged continuation with th…
mattzcarey Sep 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions .changeset/state-capability.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
"agents": minor
---

Move agent state into the opt-in `State` Lifecycle capability.

State was one method doing four jobs inside `Agent` — validate,
persist, broadcast, notify. It moves wholesale into a `State`
capability that owns storage and change ordering, so any Lifecycle
host gets durable, validated state without inheriting `Agent`:

```ts
new State({
initialState: { count: 0 },
validateStateChange: (next, source) => validate(next, source),
onChanged: (state, source) => notify(state, source)
});
```

The capability owns the `cf_agents_state` state row, lazy load with an
in-memory cache, initial-state seeding, and validated persistence. It
runs only the `onStart` hook (versioned schema init under its own
`cf_agents:state_schema_version` key) and reaches Lifecycle only for
`storage` — no alarm, no request path. It never touches connections.

Host-owned behavior is injected, not moved: `validateStateChange`
stays an overridable `Agent` method and the post-change `onChanged`
hook is passed into `State` as a plain option. Synchronous and
asynchronous notification hooks are both supported. `Agent` keeps its
`initialState` field and seeds it from the `state` getter (standalone
hosts pass `initialState` to `State` directly). Broadcast and the
notification hook stay on `Agent`, and the `onMessage` state branch
stays too; only its inner write delegates to the capability.

`State` is the only owner of `cf_agents_state`: it creates the table,
holds the state row, and runs the legacy `cf_state_was_changed`
cleanup in its own versioned migration. `Agent` used to keep its
global schema version as a row in that table; it now lives under the
`cf_agents:schema_version` KV key like every other capability's
version. A DO created under the old layout has the row read once,
moved to the key, and deleted on its next construction.

`Agent`'s public API and wire protocol are unchanged: `state`,
`setState()`, `onStateChanged`, and the `CF_AGENT_STATE` frames behave
identically.
5 changes: 5 additions & 0 deletions packages/agents/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,11 @@
"import": "./dist/websockets/index.js",
"require": "./dist/websockets/index.js"
},
"./state": {
"types": "./dist/state/index.d.ts",
"import": "./dist/state/index.js",
"require": "./dist/state/index.js"
},
"./schedule": {
"types": "./dist/schedule.d.ts",
"import": "./dist/schedule.js",
Expand Down
1 change: 1 addition & 0 deletions packages/agents/scripts/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const entries = [
"src/streams/index.ts",
"src/context/index.ts",
"src/sessions/index.ts",
"src/state/index.ts",
"src/websockets/index.ts",
"src/codemode/ai.ts",
"src/browser/index.ts",
Expand Down
Loading
Loading