Skip to content

feat(agents): move state into an opt-in Lifecycle capability (agents/state) - #2179

Merged
mattzcarey merged 16 commits into
cloudflare:mainfrom
AntoniTok:feat/state-capability
Sep 12, 2026
Merged

mattzcarey merged 16 commits into
cloudflare:mainfrom
AntoniTok:feat/state-capability

Conversation

@AntoniTok

@AntoniTok AntoniTok commented Aug 28, 2026 •

Copy link
Copy Markdown
Contributor

State was one method (_setStateInternal) doing four jobs inside the Agent god-class — validate, persist, broadcast, notify — with the state row, in-memory cache, and schema all threaded through the class. This moves storage and change ordering into a State capability (agents/state), so any Lifecycle host gets durable, validated state without inheriting Agent. Same pattern as the WebSockets (#2169) and MCP client (#1895) capabilities.

Agent's public API and wire protocol are unchanged.

API

import { State } from "agents/state";

class Counter extends DurableObject {
  readonly state = new State<{ count: number }>({
    initialState: { count: 0 },
    validateStateChange: (next, source) => { /* throw to reject */ },
    onChanged: (state, source) => { /* sync or async */ }
  });
  readonly lifecycle = Lifecycle.install(this).use(this.state);
}

counter.state.get();            // { count: number } | undefined
counter.state.set({ count: 1 }); // "server" source
counter.state.set(next, connection); // client-originated

StateOptions is plain data: initialState, validateStateChange, onChanged. StateChangeSource is Connection | "server". Async onChanged is tracked and its rejection logged; set() stays synchronous. The capability reaches Lifecycle only for storage and uses # private fields like its siblings.

Architecture

+--------------------------------------+
| Agent                                |
|  get state -> _state.get()           |
|    (seeds initialState if undefined) |
|  setState  -> _state.set()           |
|  _handleStateChanged():              |
|    3. broadcast --------> clients    |
|    4. notify hook                    |
|         ^ onChanged                  |
+---------|----------------------------+
+---------+----------------------------+
| State (capability)                   |
|  onStart: own schema version key,    |
|           create table, legacy clean |
|  get(): load / cache                 |
|  set(): 1.validate 2.persist         |
|         3. onChanged(state, source)  |
+---------+----------------------------+
          | this.lifecycle.sql / storage
          v
   cf_agents_state (SQLite)

The capability never references connections, the Agent, env, or ctx. Agent passes two closures (validateStateChange, onChanged) as static options and does the WebSockets-specific work itself. Agent's generic parameter is renamed State → TState (positional for consumers, so not a breaking change) so the class can be imported as plain State.

Data flows

Server sets state (source = "server", broadcast to all):

setState(next) -> _state.set(next, "server")
  1. validate  (Agent.validateStateChange)
  2. persist   -> cf_agents_state
  3. onChanged -> Agent._handleStateChanged(next, "server")
       broadcast CF_AGENT_STATE to all protocol clients
       waitUntil -> onStateChanged / onStateUpdate dev hook

Client sends state over WS (source = connection, echo excludes sender):

onMessage (stays in Agent): parse, readonly check, CF_AGENT_STATE_ERROR
  -> _state.set(next, connection)
       1. validate  2. persist
       3. onChanged -> _handleStateChanged(next, connection)
            broadcast to all EXCEPT connection.id
            dev hook

What moved vs. what stays

Concern Owner
cf_agents_state table, state row, load/cache, validate + persist State
legacy cf_state_was_changed cleanup State (its v1 migration)
own schema version key (cf_agents:state_schema_version) State
initialState field and first-access seeding Agent (state getter)
broadcast to connections Agent (_handleStateChanged)
dev hooks (validateStateChange, onStateChanged/onStateUpdate) Agent (override surface)
onMessage parse / readonly / error responses Agent / WebSockets

initialState stays an Agent field because subclass fields initialize after the base constructor. Rather than the capability reaching back into the host lazily, Agent's state getter seeds it on first read when the capability returns undefined (not JSON-representable, so it uniquely means "no row"). The seed goes through set(), so it persists, broadcasts, and runs the hook as before.

Table ownership

State is the only owner of cf_agents_state. Agent used to keep its global schema version as a row in that table, which was the only reason it still created the table. That version now lives under the cf_agents:schema_version KV key, read synchronously via storage.kv so the constructor can still gate DDL — the same convention every capability uses. A DO created under the old layout has the row read once, moved to the key, and deleted on its next construction.

Compatibility

state, setState(), onStateChanged, and the CF_AGENT_STATE frames behave identically. The full Agent state suite and the schema suite pass; the fixture reach-ins to the removed _state cache are replaced by eviction (evictDurableObject) or by driving the capability's own migration.

Tests

Capability suite installs State on a bare Durable Object through withCapabilityHarness: persist/read, access before lifecycle start, initial-state seeding, falsy-value row existence, rehydration across eviction, validation rejection, onChanged source on both origins, sync/async hook failures. Schema suite adds a legacy schema-version-row migration case.

@changeset-bot

changeset-bot Bot commented Aug 28, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: e5331fe

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
agents Minor
@cloudflare/agent-think Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new

pkg-pr-new Bot commented Aug 29, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

agents

npm i https://pkg.pr.new/agents@2179

@cloudflare/ai-chat

npm i https://pkg.pr.new/@cloudflare/ai-chat@2179

@cloudflare/codemode

npm i https://pkg.pr.new/@cloudflare/codemode@2179

hono-agents

npm i https://pkg.pr.new/hono-agents@2179

@cloudflare/shell

npm i https://pkg.pr.new/@cloudflare/shell@2179

@cloudflare/think

npm i https://pkg.pr.new/@cloudflare/think@2179

@cloudflare/voice

npm i https://pkg.pr.new/@cloudflare/voice@2179

@cloudflare/worker-bundler

npm i https://pkg.pr.new/@cloudflare/worker-bundler@2179

commit: e5331fe

@AntoniTok
AntoniTok force-pushed the feat/state-capability branch from f5a1e1f to 4ed2efc Compare September 1, 2026 09:48
@AntoniTok
AntoniTok marked this pull request as ready for review September 1, 2026 10:00
devin-ai-integration[bot]

This comment was marked as resolved.

Comment thread packages/agents/src/index.ts Outdated
Comment thread packages/agents/src/index.ts Outdated
Comment thread packages/agents/src/index.ts Outdated
Comment thread packages/agents/src/index.ts Outdated
Comment thread packages/agents/src/index.ts Outdated
@AntoniTok
AntoniTok force-pushed the feat/state-capability branch from 4ed2efc to 9d170ac Compare September 2, 2026 09:42
devin-ai-integration[bot]

This comment was marked as resolved.

@AntoniTok
AntoniTok force-pushed the feat/state-capability branch from 9d170ac to e1a785a Compare September 2, 2026 10:12
…state)

State was one method doing four jobs inside the Agent god-class —
validate, persist, broadcast, notify — with the state row, cache, and
schema all threaded through the class. It moves wholesale into a
StateManager capability that owns storage and change ordering, so any
Lifecycle host gets durable, validated state without inheriting Agent:

  new StateManager({
    resolveInitialState: () => ({ value: this.initialState }),
    validateStateChange: (next, source) => this.validateStateChange(next, 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:
after validate + persist it fires a typed onStateChanged emitter,
mirroring the MCP client's onServerStateChanged seam. The getter,
write path, and corrupt-row fallback move verbatim; the only changes
are ctx->lifecycle storage and the broadcast becoming the emitter.

Host-owned behavior is injected, not moved: validateStateChange stays
an overridable Agent method, and initialState is resolved lazily so a
subclass field — initialized after the base constructor — is read at
its final value.

Broadcast and the notification hook stay on the Agent as an
onStateChanged subscriber (_handleStateChanged): it broadcasts
CF_AGENT_STATE to protocol-enabled connections excluding the source
id, then runs onStateChanged/onStateUpdate off the invocation tail.
The onMessage state branch stays too — parse, readonly check, and
CF_AGENT_STATE_ERROR responses are WebSockets concerns; only its inner
write becomes #state.set(state, connection). Agent installs the
capability in the .use() chain and delegates state/setState to it.

The cf_agents_state table is shared: StateManager ensures it in
onStart and owns the state row, while Agent keeps its global
schema-version row in _ensureSchema and ensures the table there too —
each side idempotent, each tracking its own version, the same pattern
as Scheduler's ensureScheduleTable.

Agent's public API and wire protocol are unchanged — state,
setState(), onStateChanged, and the CF_AGENT_STATE frames behave
identically, and the full Agent state suite (22 cases) plus the schema
suite pass as-is. New capability suites install StateManager on a bare
Durable Object through withCapabilityHarness and cover persist/read,
initial-state seeding, falsy-value row existence, rehydration across a
simulated eviction, injected-validation rejection, and the
onStateChanged source-exclusion payload on both server and client
origins.
@AntoniTok
AntoniTok force-pushed the feat/state-capability branch from e1a785a to 1e8f967 Compare September 2, 2026 10:29
devin-ai-integration[bot]

This comment was marked as resolved.

@agent-think

agent-think Bot commented Sep 2, 2026 •

Copy link
Copy Markdown
Contributor

🟡 agents import sizes

Measured 337 runtime imports as minified bundles. The primary size is gzip; raw minified size is included for diagnosis. An existing import growing by more than 10% is marked red. This report is informational.

Red Yellow Green Unchanged New Removed
0 52 0 284 1 0

Compared c96418d5 with e5331fe6. Open workflow run.

Changed imports (53)
Status Import Base gzip Head gzip Delta
🟡 agents#SUB_PREFIX 258.7 KiB 259.2 KiB +449 B (+0.17%)
🟡 agents#createHeaderBasedEmailResolver 258.9 KiB 259.4 KiB +447 B (+0.17%)
🟡 agents#MessageType 258.9 KiB 259.3 KiB +444 B (+0.17%)
🟡 agents#AGENT_TOOL_PROGRESS_PART 258.8 KiB 259.2 KiB +443 B (+0.17%)
🟡 agents#routeAgentRequest 259.4 KiB 259.8 KiB +438 B (+0.16%)
🟡 agents#isDurableObjectStorageReset 258.7 KiB 259.2 KiB +434 B (+0.16%)
🟡 agents#buildAgentPath 259.3 KiB 259.7 KiB +425 B (+0.16%)
🟡 agents#buildAgentUrl 259.4 KiB 259.8 KiB +425 B (+0.16%)
🟡 agents#MCP_SERVER_ID_MAX_LENGTH 258.8 KiB 259.2 KiB +422 B (+0.16%)
🟡 agents#routeSubAgentRequest 259.0 KiB 259.4 KiB +419 B (+0.16%)
🟡 agents#getSubAgentByName 259.0 KiB 259.4 KiB +417 B (+0.16%)
🟡 agents#StreamingResponse 258.8 KiB 259.2 KiB +414 B (+0.16%)
🟡 agents#callable 258.8 KiB 259.2 KiB +413 B (+0.16%)
🟡 agents#Agent 258.8 KiB 259.2 KiB +411 B (+0.16%)
🟡 agents#routeAgentEmail 259.1 KiB 259.5 KiB +410 B (+0.15%)
🟡 agents/chat-sdk#ChatSdkStateAgent 260.5 KiB 260.9 KiB +412 B (+0.15%)
🟡 agents/chat-sdk#ChatSdkStateAdapter 261.2 KiB 261.6 KiB +411 B (+0.15%)
🟡 agents#unstable_callable 258.9 KiB 259.3 KiB +407 B (+0.15%)
🟡 agents/chat-sdk#createChatSdkState 261.2 KiB 261.6 KiB +410 B (+0.15%)
🟡 agents#isDurableObjectMemoryLimitReset 258.8 KiB 259.2 KiB +403 B (+0.15%)
🟡 agents#parseSubAgentPath 258.8 KiB 259.2 KiB +402 B (+0.15%)
🟡 agents#getAgentByName 258.8 KiB 259.2 KiB +400 B (+0.15%)
🟡 agents#isDurableObjectCodeUpdateReset 258.8 KiB 259.2 KiB +400 B (+0.15%)
🟡 agents/workflows#AgentWorkflow 260.1 KiB 260.5 KiB +402 B (+0.15%)
🟡 agents#__DO_NOT_USE_WILL_BREAK__agentContext 258.8 KiB 259.2 KiB +398 B (+0.15%)
🟡 agents#DurableObjectOAuthClientProvider 258.8 KiB 259.2 KiB +398 B (+0.15%)
🟡 agents/workflows#WorkflowRejectedError 258.8 KiB 259.2 KiB +398 B (+0.15%)
🟡 agents#camelCaseToKebabCase 258.8 KiB 259.2 KiB +395 B (+0.15%)
🟡 agents#DEFAULT_AGENT_STATIC_OPTIONS 258.8 KiB 259.2 KiB +392 B (+0.15%)
🟡 agents#__DO_NOT_USE_WILL_BREAK__withInvocationScope 258.8 KiB 259.2 KiB +392 B (+0.15%)
🟡 agents/chat-sdk#defaultThreadShard 258.8 KiB 259.2 KiB +391 B (+0.15%)
🟡 agents/chat-sdk#defaultKeyShard 258.9 KiB 259.3 KiB +391 B (+0.15%)
🟡 agents#SqlError 258.8 KiB 259.2 KiB +390 B (+0.15%)
🟡 agents#normalizeServerId 258.8 KiB 259.2 KiB +389 B (+0.15%)
🟡 agents#getCurrentAgent 258.8 KiB 259.1 KiB +386 B (+0.15%)
🟡 agents#AGENT_TOOL_MILESTONE_PART 258.8 KiB 259.2 KiB +385 B (+0.15%)
🟡 agents#isPlatformTransientError 258.8 KiB 259.1 KiB +382 B (+0.14%)
🟡 agents/mcp#RPC_DO_PREFIX 342.6 KiB 343.1 KiB +478 B (+0.14%)
🟡 agents/mcp#ElicitRequestSchema 342.6 KiB 343.1 KiB +467 B (+0.13%)
🟡 agents/mcp#MCP_SERVER_ID_MAX_LENGTH 342.6 KiB 343.1 KiB +464 B (+0.13%)
🟡 agents/mcp#DurableObjectEventStore 342.6 KiB 343.1 KiB +461 B (+0.13%)
🟡 agents/mcp#StreamableHTTPEdgeClientTransport 342.7 KiB 343.2 KiB +459 B (+0.13%)
🟡 agents/mcp#McpAgent 342.6 KiB 343.1 KiB +457 B (+0.13%)
🟡 agents/mcp#RPCClientTransport 342.6 KiB 343.1 KiB +453 B (+0.13%)
🟡 agents/mcp#normalizeServerId 342.6 KiB 343.1 KiB +453 B (+0.13%)
🟡 agents/mcp#RPCServerTransport 342.6 KiB 343.1 KiB +453 B (+0.13%)
🟡 agents/mcp#WorkerTransport 346.0 KiB 346.4 KiB +445 B (+0.13%)
🟡 agents/mcp#createMcpHandler 388.3 KiB 388.8 KiB +498 B (+0.13%)
🟡 agents/mcp#SSEEdgeClientTransport 342.7 KiB 343.1 KiB +436 B (+0.12%)
🟡 agents/mcp#getMcpAuthContext 342.6 KiB 343.1 KiB +432 B (+0.12%)
🟡 agents/mcp#experimental_createMcpHandler 376.2 KiB 376.6 KiB +394 B (+0.1%)
🟡 agents/mcp#createLegacyMcpHandler 376.1 KiB 376.4 KiB +358 B (+0.09%)
⚪ agents/state#State — 1.1 KiB —
All 337 current runtime imports
Status Import Gzip Raw minified
🟡 agents#__DO_NOT_USE_WILL_BREAK__agentContext 259.2 KiB 1131.6 KiB
🟡 agents#__DO_NOT_USE_WILL_BREAK__withInvocationScope 259.2 KiB 1131.6 KiB
🟡 agents#Agent 259.2 KiB 1131.6 KiB
🟡 agents#AGENT_TOOL_MILESTONE_PART 259.2 KiB 1131.6 KiB
🟡 agents#AGENT_TOOL_PROGRESS_PART 259.2 KiB 1131.7 KiB
🟡 agents#buildAgentPath 259.7 KiB 1134.0 KiB
🟡 agents#buildAgentUrl 259.8 KiB 1134.4 KiB
🟡 agents#callable 259.2 KiB 1131.7 KiB
🟡 agents#camelCaseToKebabCase 259.2 KiB 1131.6 KiB
🟡 agents#createHeaderBasedEmailResolver 259.4 KiB 1132.1 KiB
🟡 agents#DEFAULT_AGENT_STATIC_OPTIONS 259.2 KiB 1131.6 KiB
🟡 agents#DurableObjectOAuthClientProvider 259.2 KiB 1131.6 KiB
🟡 agents#getAgentByName 259.2 KiB 1131.6 KiB
🟡 agents#getCurrentAgent 259.1 KiB 1131.6 KiB
🟡 agents#getSubAgentByName 259.4 KiB 1132.3 KiB
🟡 agents#isDurableObjectCodeUpdateReset 259.2 KiB 1131.6 KiB
🟡 agents#isDurableObjectMemoryLimitReset 259.2 KiB 1131.6 KiB
🟡 agents#isDurableObjectStorageReset 259.2 KiB 1131.7 KiB
🟡 agents#isPlatformTransientError 259.1 KiB 1131.6 KiB
🟡 agents#MCP_SERVER_ID_MAX_LENGTH 259.2 KiB 1131.7 KiB
🟡 agents#MessageType 259.3 KiB 1132.0 KiB
🟡 agents#normalizeServerId 259.2 KiB 1131.6 KiB
🟡 agents#parseSubAgentPath 259.2 KiB 1131.6 KiB
🟡 agents#routeAgentEmail 259.5 KiB 1132.4 KiB
🟡 agents#routeAgentRequest 259.8 KiB 1133.6 KiB
🟡 agents#routeSubAgentRequest 259.4 KiB 1132.2 KiB
🟡 agents#SqlError 259.2 KiB 1131.6 KiB
🟡 agents#StreamingResponse 259.2 KiB 1131.6 KiB
🟡 agents#SUB_PREFIX 259.2 KiB 1131.7 KiB
🟡 agents#unstable_callable 259.3 KiB 1131.9 KiB
⚪ agents/agent-tools#agentTool 112.5 KiB 538.2 KiB
⚪ agents/browser#BrowserConnector 50.5 KiB 176.6 KiB
⚪ agents/browser#browserContent 36.3 KiB 127.4 KiB
⚪ agents/browser#browserExtract 36.3 KiB 127.4 KiB
⚪ agents/browser#browserLinks 36.3 KiB 127.4 KiB
⚪ agents/browser#browserMarkdown 36.3 KiB 127.4 KiB
⚪ agents/browser#browserPdf 36.3 KiB 127.3 KiB
⚪ agents/browser#BrowserRenderingError 36.0 KiB 126.7 KiB
⚪ agents/browser#browserScrape 36.3 KiB 127.4 KiB
⚪ agents/browser#browserScreenshot 36.3 KiB 127.3 KiB
⚪ agents/browser#browserSnapshot 36.3 KiB 127.4 KiB
⚪ agents/browser#CdpSession 37.2 KiB 129.8 KiB
⚪ agents/browser#CodemodeRuntime 39.6 KiB 139.0 KiB
⚪ agents/browser#connectBrowser 37.8 KiB 131.4 KiB
⚪ agents/browser#connectBrowserSession 37.5 KiB 130.4 KiB
⚪ agents/browser#connectUrl 37.6 KiB 130.5 KiB
⚪ agents/browser#createBrowserSession 36.3 KiB 127.5 KiB
⚪ agents/browser#DEFAULT_EXEC_SWEEP_IDLE_MS 36.0 KiB 126.6 KiB
⚪ agents/browser#DEFAULT_SWEEP_IDLE_MS 36.0 KiB 126.6 KiB
⚪ agents/browser#deleteBrowserSession 36.1 KiB 126.9 KiB
⚪ agents/browser#DurableBrowserSessionStore 36.4 KiB 127.6 KiB
⚪ agents/browser#getBrowserRecording 36.2 KiB 127.1 KiB
⚪ agents/browser#listBrowserTargets 36.1 KiB 126.9 KiB
⚪ agents/browser#loadCdpSpec 36.6 KiB 128.3 KiB
⚪ agents/browser#runQuickAction 36.0 KiB 126.6 KiB
⚪ agents/browser/ai#createBrowserRuntime 147.0 KiB 632.8 KiB
⚪ agents/browser/ai#createBrowserTools 147.0 KiB 632.9 KiB
⚪ agents/browser/ai#createQuickActionTools 122.5 KiB 554.3 KiB
⚪ agents/browser/tanstack-ai#createBrowserTools 162.7 KiB 701.7 KiB
⚪ agents/channels#ChannelHost 3.3 KiB 9.1 KiB
⚪ agents/channels#consumeChunks 237 B 321 B
⚪ agents/channels#createUserIdentityStore 1.3 KiB 3.2 KiB
⚪ agents/channels#fallback 155 B 160 B
⚪ agents/channels#fallbackChannel 893 B 1.9 KiB
⚪ agents/channels#fanout 151 B 156 B
⚪ agents/channels#fanoutChannel 875 B 1.9 KiB
⚪ agents/channels#identityKey 178 B 223 B
⚪ agents/channels#isChannelMessageSurface 271 B 452 B
⚪ agents/channels#linkChannelIdentities 113 B 99 B
⚪ agents/channels#matchesPath 110 B 96 B
⚪ agents/channels#routes 307 B 507 B
⚪ agents/channels#UserIdentityConflictError 248 B 335 B
⚪ agents/channels/ai-sdk#createSendMessageTool 112.2 KiB 537.4 KiB
⚪ agents/channels/ai-sdk#toChannelChunks 112.5 KiB 538.4 KiB
⚪ agents/channels/email#email 23.5 KiB 75.4 KiB
⚪ agents/channels/email#inboundEmail 22.3 KiB 72.3 KiB
⚪ agents/channels/slack#slack 5.5 KiB 14.6 KiB
⚪ agents/channels/slack#slackWebhook 2.2 KiB 5.1 KiB
⚪ agents/channels/tanstack-ai#createSendMessageTool 16.2 KiB 68.4 KiB
⚪ agents/channels/telegram#telegram 3.8 KiB 10.3 KiB
⚪ agents/channels/telegram#telegramWebhook 1.6 KiB 3.6 KiB
⚪ agents/channels/voice#browserVoice 1010 B 2.2 KiB
⚪ agents/chat#AbortRegistry 2.5 KiB 8.9 KiB
⚪ agents/chat#AGENT_TOOL_STREAM_PROGRESS_BUMP_THROTTLE_MS 2.3 KiB 8.2 KiB
⚪ agents/chat#AgentToolProgressEmitter 2.7 KiB 9.5 KiB
⚪ agents/chat#AgentToolStreamProgressThrottle 2.4 KiB 8.3 KiB
⚪ agents/chat#aiSdkRecoveryCodec 2.3 KiB 8.2 KiB
⚪ agents/chat#applyAgentToolEvent 3.2 KiB 11.0 KiB
⚪ agents/chat#applyChunkToParts 2.3 KiB 8.2 KiB
⚪ agents/chat#applyToolUpdate 2.4 KiB 8.4 KiB
⚪ agents/chat#AutoContinuationController 2.3 KiB 8.2 KiB
⚪ agents/chat#awaitWithDeadline 2.4 KiB 8.4 KiB
⚪ agents/chat#broadcastTransition 3.2 KiB 11.4 KiB
⚪ agents/chat#buildChatRecoveringFrame 2.4 KiB 8.4 KiB
⚪ agents/chat#buildInClauseStrings 2.4 KiB 8.4 KiB
⚪ agents/chat#bumpChatRecoveryProgress 2.4 KiB 8.3 KiB
⚪ agents/chat#byteLength 2.5 KiB 8.5 KiB
⚪ agents/chat#CHAT_LAST_TERMINAL_KEY 2.3 KiB 8.2 KiB
⚪ agents/chat#CHAT_MESSAGE_TYPES 2.3 KiB 8.2 KiB
⚪ agents/chat#CHAT_RECOVERING_FLAG_TTL_MS 2.3 KiB 8.2 KiB
⚪ agents/chat#CHAT_RECOVERING_KEY 2.3 KiB 8.2 KiB
⚪ agents/chat#CHAT_RECOVERY_ALARM_DEBOUNCE_MS 2.3 KiB 8.2 KiB
⚪ agents/chat#CHAT_RECOVERY_INCIDENT_KEY_PREFIX 2.3 KiB 8.2 KiB
⚪ agents/chat#CHAT_RECOVERY_INCIDENT_TTL_MS 2.3 KiB 8.2 KiB
⚪ agents/chat#CHAT_RECOVERY_PROGRESS_KEY 2.3 KiB 8.2 KiB
⚪ agents/chat#CHAT_RECOVERY_STABLE_RETRY_DELAY_SECONDS 2.3 KiB 8.2 KiB
⚪ agents/chat#CHAT_RECOVERY_TASK_NAME 2.3 KiB 8.2 KiB
⚪ agents/chat#CHAT_STREAM_PROGRESS_CREDIT_THROTTLE_MS 2.3 KiB 8.2 KiB
⚪ agents/chat#ChatRecoveryEngine 4.4 KiB 15.3 KiB
⚪ agents/chat#chatRecoveryTaskRunOptions 2.4 KiB 8.6 KiB
⚪ agents/chat#ChatStreamStalledError 2.4 KiB 8.3 KiB
⚪ agents/chat#classifyAgentToolChildRecovery 2.4 KiB 8.5 KiB
⚪ agents/chat#clearChatTerminal 2.3 KiB 8.3 KiB
⚪ agents/chat#clientResolvableToolNames 2.3 KiB 8.3 KiB
⚪ agents/chat#ContinuationState 2.7 KiB 9.8 KiB
⚪ agents/chat#createAgentToolEventState 2.3 KiB 8.3 KiB
⚪ agents/chat#createChatFiberSnapshot 2.5 KiB 8.6 KiB
⚪ agents/chat#createChatRecoveryTaskDefinition 2.6 KiB 9.0 KiB
⚪ agents/chat#createChatStreams 6.4 KiB 22.1 KiB
⚪ agents/chat#createChatTurnTaskDefinition 2.7 KiB 8.9 KiB
⚪ agents/chat#createToolsFromClientSchemas 114.3 KiB 545.4 KiB
⚪ agents/chat#crossMessageToolResultUpdate 2.4 KiB 8.6 KiB
⚪ agents/chat#DEFAULT_CHAT_RECOVERY_MAX_ATTEMPTS 2.3 KiB 8.2 KiB
⚪ agents/chat#DEFAULT_CHAT_RECOVERY_MAX_OOM_RETRIES 2.3 KiB 8.2 KiB
⚪ agents/chat#DEFAULT_CHAT_RECOVERY_MAX_WORK 2.3 KiB 8.2 KiB
⚪ agents/chat#DEFAULT_CHAT_RECOVERY_NO_PROGRESS_TIMEOUT_MS 2.3 KiB 8.2 KiB
⚪ agents/chat#DEFAULT_CHAT_RECOVERY_STABLE_TIMEOUT_MS 2.3 KiB 8.2 KiB
⚪ agents/chat#DEFAULT_CHAT_RECOVERY_TERMINAL_MESSAGE 2.4 KiB 8.3 KiB
⚪ agents/chat#dispatchChatRecoveryToHandoff 3.0 KiB 9.9 KiB
⚪ agents/chat#drainInteractionApplies 2.3 KiB 8.3 KiB
⚪ agents/chat#enforceRowSizeLimit 3.5 KiB 11.2 KiB
⚪ agents/chat#hasIncompleteToolBatch 2.4 KiB 8.6 KiB
⚪ agents/chat#interceptAgentToolBroadcast 2.5 KiB 8.7 KiB
⚪ agents/chat#isPlatformFailure 2.6 KiB 8.9 KiB
⚪ agents/chat#isReplayChunk 2.4 KiB 8.7 KiB
⚪ agents/chat#iterateWithStallWatchdog 2.6 KiB 8.8 KiB
⚪ agents/chat#KV_DELETE_MAX_KEYS 2.3 KiB 8.2 KiB
⚪ agents/chat#listActiveChatRecoveryIncidents 2.4 KiB 8.4 KiB
⚪ agents/chat#MAX_BOUND_PARAMS 2.3 KiB 8.2 KiB
⚪ agents/chat#MessageType 2.4 KiB 9.0 KiB
⚪ agents/chat#normalizeToolInput 2.3 KiB 8.2 KiB
⚪ agents/chat#parseProtocolMessage 2.5 KiB 9.0 KiB
⚪ agents/chat#partAwaitsClientInteraction 2.4 KiB 8.6 KiB
⚪ agents/chat#pausedExecutionUpdate 2.4 KiB 8.4 KiB
⚪ agents/chat#pendingChatTerminal 2.3 KiB 8.3 KiB
⚪ agents/chat#persistReconstructedOrphan 3.1 KiB 11.0 KiB
⚪ agents/chat#PreStreamTurns 2.6 KiB 9.3 KiB
⚪ agents/chat#readChatRecoveryProgress 2.3 KiB 8.3 KiB
⚪ agents/chat#reconcileMessages 2.8 KiB 9.6 KiB
⚪ agents/chat#reconcileOrphanPartial 2.4 KiB 8.5 KiB
⚪ agents/chat#recordChatTerminal 2.4 KiB 8.3 KiB
⚪ agents/chat#repairInterruptedToolParts 2.6 KiB 9.1 KiB
⚪ agents/chat#resolveChatRecoveryConfig 2.6 KiB 8.9 KiB
⚪ agents/chat#resolveToolMergeId 2.4 KiB 8.5 KiB
⚪ agents/chat#ResumableStream 5.1 KiB 16.8 KiB
⚪ agents/chat#ResumeHandshake 3.0 KiB 10.4 KiB
⚪ agents/chat#ROW_MAX_BYTES 2.3 KiB 8.2 KiB
⚪ agents/chat#runChatRecoveryExhaustion 2.6 KiB 8.9 KiB
⚪ agents/chat#sanitizeMessage 2.5 KiB 8.8 KiB
⚪ agents/chat#sendIfOpen 2.4 KiB 8.4 KiB
⚪ agents/chat#setChatRecovering 2.5 KiB 8.5 KiB
⚪ agents/chat#shouldCreditStreamProgress 2.4 KiB 8.3 KiB
⚪ agents/chat#STREAM_RESUME_NONE_REASONS 2.3 KiB 8.2 KiB
⚪ agents/chat#StreamAccumulator 2.9 KiB 10.7 KiB
⚪ agents/chat#StreamProgressCreditThrottle 2.4 KiB 8.3 KiB
⚪ agents/chat#SubmitConcurrencyController 2.9 KiB 10.3 KiB
⚪ agents/chat#sweepStaleChatRecoveryIncidents 2.4 KiB 8.5 KiB
⚪ agents/chat#TextSegmentJoiner 2.7 KiB 9.2 KiB
⚪ agents/chat#TIMED_OUT 2.3 KiB 8.2 KiB
⚪ agents/chat#toolApprovalUpdate 2.4 KiB 8.5 KiB
⚪ agents/chat#toolPartHasSettledResult 2.3 KiB 8.4 KiB
⚪ agents/chat#toolResultUpdate 2.4 KiB 8.5 KiB
⚪ agents/chat#truncateOlderMessages 3.2 KiB 10.4 KiB
⚪ agents/chat#TurnQueue 2.6 KiB 9.2 KiB
⚪ agents/chat#unwrapChatFiberSnapshot 2.4 KiB 8.5 KiB
⚪ agents/chat#wrapChatFiberSnapshot 2.3 KiB 8.2 KiB
🟡 agents/chat-sdk#ChatSdkStateAdapter 261.6 KiB 1143.2 KiB
🟡 agents/chat-sdk#ChatSdkStateAgent 260.9 KiB 1140.7 KiB
🟡 agents/chat-sdk#createChatSdkState 261.6 KiB 1143.3 KiB
🟡 agents/chat-sdk#defaultKeyShard 259.3 KiB 1131.9 KiB
🟡 agents/chat-sdk#defaultThreadShard 259.2 KiB 1131.7 KiB
⚪ agents/chat/react#detectToolsRequiringConfirmation 3.3 KiB 8.3 KiB
⚪ agents/chat/react#extractClientToolSchemas 3.2 KiB 8.3 KiB
⚪ agents/chat/react#getAgentMessages 3.4 KiB 8.6 KiB
⚪ agents/chat/react#getToolApproval 3.1 KiB 8.0 KiB
⚪ agents/chat/react#getToolCallId 3.1 KiB 8.0 KiB
⚪ agents/chat/react#getToolInput 3.1 KiB 8.0 KiB
⚪ agents/chat/react#getToolOutput 3.1 KiB 8.0 KiB
⚪ agents/chat/react#getToolPartState 3.2 KiB 8.2 KiB
⚪ agents/chat/react#useAgentChat 132.9 KiB 609.7 KiB
⚪ agents/chat/react#WebSocketChatTransport 5.7 KiB 17.1 KiB
⚪ agents/chat/transport#WebSocketChatTransport 2.8 KiB 9.2 KiB
⚪ agents/client#AgentClient 5.7 KiB 16.6 KiB
⚪ agents/client#AgentConnectionError 582 B 993 B
⚪ agents/client#agentFetch 4.2 KiB 12.3 KiB
⚪ agents/client#createStubProxy 638 B 1.0 KiB
⚪ agents/client#DEFAULT_CALL_TIMEOUT_MS 473 B 770 B
⚪ agents/client#isTerminalCloseEvent 509 B 822 B
⚪ agents/context#AgentContextProvider 412 B 792 B
⚪ agents/context#AgentSearchProvider 640 B 1.3 KiB
⚪ agents/context#ContextBlocks 87.4 KiB 429.7 KiB
⚪ agents/email#createAddressBasedEmailResolver 193 B 227 B
⚪ agents/email#createCatchAllEmailResolver 110 B 97 B
⚪ agents/email#createHeaderBasedEmailResolver 334 B 492 B
⚪ agents/email#createSecureReplyEmailResolver 718 B 1.3 KiB
⚪ agents/email#DEFAULT_MAX_AGE_SECONDS 56 B 39 B
⚪ agents/email#isAutoReplyEmail 201 B 249 B
⚪ agents/email#signAgentHeaders 424 B 812 B
⚪ agents/experimental/webmcp#registerWebMcp 85.2 KiB 295.8 KiB
⚪ agents/lifecycle#getCurrentAgent 376 B 798 B
⚪ agents/lifecycle#Lifecycle 8.3 KiB 25.9 KiB
⚪ agents/lifecycle#LifecycleCapability 497 B 999 B
🟡 agents/mcp#createLegacyMcpHandler 376.4 KiB 1573.9 KiB
🟡 agents/mcp#createMcpHandler 388.8 KiB 1619.1 KiB
🟡 agents/mcp#DurableObjectEventStore 343.1 KiB 1432.4 KiB
🟡 agents/mcp#ElicitRequestSchema 343.1 KiB 1432.4 KiB
🟡 agents/mcp#experimental_createMcpHandler 376.6 KiB 1574.2 KiB
🟡 agents/mcp#getMcpAuthContext 343.1 KiB 1432.4 KiB
🟡 agents/mcp#MCP_SERVER_ID_MAX_LENGTH 343.1 KiB 1432.4 KiB
🟡 agents/mcp#McpAgent 343.1 KiB 1432.4 KiB
🟡 agents/mcp#normalizeServerId 343.1 KiB 1432.4 KiB
🟡 agents/mcp#RPC_DO_PREFIX 343.1 KiB 1432.4 KiB
🟡 agents/mcp#RPCClientTransport 343.1 KiB 1432.4 KiB
🟡 agents/mcp#RPCServerTransport 343.1 KiB 1432.4 KiB
🟡 agents/mcp#SSEEdgeClientTransport 343.1 KiB 1432.7 KiB
🟡 agents/mcp#StreamableHTTPEdgeClientTransport 343.2 KiB 1432.7 KiB
🟡 agents/mcp#WorkerTransport 346.4 KiB 1449.3 KiB
⚪ agents/mcp/client#getNamespacedData 62.9 KiB 240.0 KiB
⚪ agents/mcp/client#MCP_SERVER_ID_MAX_LENGTH 62.9 KiB 239.9 KiB
⚪ agents/mcp/client#MCPClientManager 158.6 KiB 702.6 KiB
⚪ agents/mcp/client#normalizeServerId 63.0 KiB 240.2 KiB
⚪ agents/mcp/do-oauth-client-provider#DurableObjectOAuthClientProvider 2.1 KiB 6.6 KiB
⚪ agents/mcp/server#createMcpHandler 80.5 KiB 307.2 KiB
⚪ agents/mcp/server#getMcpAuthContext 64.0 KiB 245.5 KiB
⚪ agents/observability#channels 259 B 549 B
⚪ agents/observability#genericObservability 470 B 1.2 KiB
⚪ agents/observability#subscribe 324 B 668 B
⚪ agents/observability/ai#wrapAISDK 8.8 KiB 30.5 KiB
⚪ agents/react#_testUtils 3.8 KiB 9.5 KiB
⚪ agents/react#useAgent 10.8 KiB 31.1 KiB
⚪ agents/react#useAgentToolEvents 5.6 KiB 16.8 KiB
⚪ agents/routing#getAgentByName 795 B 1.7 KiB
⚪ agents/routing#routeAgentRequest 1.6 KiB 3.6 KiB
⚪ agents/routing#RoutedAgents 2.4 KiB 6.2 KiB
⚪ agents/schedule#getSchedulePrompt 85.8 KiB 424.7 KiB
⚪ agents/schedule#scheduleSchema 85.3 KiB 423.6 KiB
⚪ agents/schedule#unstable_getSchedulePrompt 85.9 KiB 424.9 KiB
⚪ agents/schedule#unstable_scheduleSchema 85.3 KiB 423.6 KiB
⚪ agents/schedules#Scheduler 6.8 KiB 22.0 KiB
⚪ agents/schedules/parser#getSchedulePrompt 85.8 KiB 424.7 KiB
⚪ agents/schedules/parser#scheduleSchema 85.3 KiB 423.6 KiB
⚪ agents/sessions#COMPACTION_PREFIX 147 B 160 B
⚪ agents/sessions#createCompactFunction 1.7 KiB 4.0 KiB
⚪ agents/sessions#isCompactionMessage 177 B 200 B
⚪ agents/sessions#Session 2.0 KiB 6.3 KiB
⚪ agents/sessions#Sessions 9.6 KiB 34.3 KiB
⚪ agents/skills#fromManifest 309.8 KiB 1084.0 KiB
⚪ agents/skills#parseSkillFrontmatter 328.4 KiB 1146.2 KiB
⚪ agents/skills#parseSkillMarkdown 328.6 KiB 1146.5 KiB
⚪ agents/skills#r2 330.2 KiB 1150.4 KiB
⚪ agents/skills#runner 369.0 KiB 1297.8 KiB
⚪ agents/skills#SkillRegistry 416.4 KiB 1581.7 KiB
⚪ agents/skills/compile#compileSkillScript 15.4 KiB 43.4 KiB
⚪ agents/skills/compile#isCompilableSkillScript 15.4 KiB 43.3 KiB
⚪ agents/state#State 1.1 KiB 2.4 KiB
⚪ agents/streams#DEFAULT_MAX_CHUNK_BYTES 94 B 96 B
⚪ agents/streams#sseResponse 857 B 1.6 KiB
⚪ agents/streams#StreamClosedError 175 B 212 B
⚪ agents/streams#StreamNotFoundError 215 B 276 B
⚪ agents/streams#Streams 4.3 KiB 13.9 KiB
⚪ agents/streams#StreamSerializationError 171 B 201 B
⚪ agents/tasks#DuplicateTaskStepError 328 B 463 B
⚪ agents/tasks#MAX_SERIALIZED_BYTES 190 B 232 B
⚪ agents/tasks#MissingTaskDefinitionError 358 B 536 B
⚪ agents/tasks#NonRetryableError 238 B 308 B
⚪ agents/tasks#TaskReplayDivergedError 341 B 483 B
⚪ agents/tasks#Tasks 8.9 KiB 31.8 KiB
⚪ agents/tasks#TaskSerializationError 258 B 339 B
⚪ agents/types#MessageType 211 B 365 B
⚪ agents/vite#default 353.8 KiB 1356.1 KiB
⚪ agents/voice#addSFUTracks 324 B 424 B
⚪ agents/voice#createSFUSession 255 B 306 B
⚪ agents/voice#createSFUWebSocketAdapter 331 B 429 B
⚪ agents/voice#decodeVarint 157 B 160 B
⚪ agents/voice#downsample48kStereoTo16kMono 238 B 324 B
⚪ agents/voice#encodePayloadToProtobuf 189 B 279 B
⚪ agents/voice#encodeVarint 129 B 122 B
⚪ agents/voice#extractPayloadFromProtobuf 271 B 425 B
⚪ agents/voice#iterateText 1.6 KiB 3.8 KiB
⚪ agents/voice#renegotiateSFUSession 329 B 428 B
⚪ agents/voice#SentenceChunker 550 B 1.1 KiB
⚪ agents/voice#sfuFetch 294 B 358 B
⚪ agents/voice#upsample16kMonoTo48kStereo 211 B 272 B
⚪ agents/voice#VOICE_PROTOCOL_VERSION 51 B 31 B
⚪ agents/voice#withVoice 9.5 KiB 32.7 KiB
⚪ agents/voice#withVoiceInput 4.8 KiB 16.0 KiB
⚪ agents/voice#WorkersAIFluxSTT 1.5 KiB 4.3 KiB
⚪ agents/voice#WorkersAINova3STT 1.6 KiB 4.3 KiB
⚪ agents/voice#WorkersAITTS 682 B 1.4 KiB
⚪ agents/voice/client#VOICE_PROTOCOL_VERSION 473 B 768 B
⚪ agents/voice/client#VoiceClient 10.1 KiB 31.6 KiB
⚪ agents/voice/client#WebSocketVoiceTransport 4.6 KiB 13.3 KiB
⚪ agents/voice/errors#logVoiceError 132 B 173 B
⚪ agents/voice/errors#toVoiceError 94 B 80 B
⚪ agents/voice/errors#voiceErrorMessage 125 B 111 B
⚪ agents/voice/errors#VoiceProviderError 190 B 345 B
⚪ agents/voice/react#useVoiceAgent 13.6 KiB 42.5 KiB
⚪ agents/voice/react#useVoiceInput 13.3 KiB 41.4 KiB
⚪ agents/voice/react#WebSocketVoiceTransport 7.4 KiB 21.3 KiB
⚪ agents/voice/sfu#addSFUTracks 323 B 424 B
⚪ agents/voice/sfu#createSFUSession 254 B 306 B
⚪ agents/voice/sfu#createSFUWebSocketAdapter 329 B 429 B
⚪ agents/voice/sfu#decodeVarint 155 B 160 B
⚪ agents/voice/sfu#downsample48kStereoTo16kMono 236 B 324 B
⚪ agents/voice/sfu#encodePayloadToProtobuf 188 B 279 B
⚪ agents/voice/sfu#encodeVarint 129 B 122 B
⚪ agents/voice/sfu#extractPayloadFromProtobuf 270 B 425 B
⚪ agents/voice/sfu#renegotiateSFUSession 329 B 428 B
⚪ agents/voice/sfu#sfuFetch 292 B 358 B
⚪ agents/voice/sfu#upsample16kMonoTo48kStereo 209 B 272 B
⚪ agents/voice/text#iterateText 1.6 KiB 3.8 KiB
⚪ agents/voice/text#SentenceChunker 549 B 1.1 KiB
⚪ agents/voice/types#VOICE_PROTOCOL_VERSION 51 B 31 B
⚪ agents/voice/workers-ai#WorkersAIFluxSTT 1.5 KiB 4.3 KiB
⚪ agents/voice/workers-ai#WorkersAINova3STT 1.6 KiB 4.3 KiB
⚪ agents/voice/workers-ai#WorkersAITTS 682 B 1.4 KiB
⚪ agents/websockets#CALLABLES_RPC_QUERY 12.4 KiB 43.3 KiB
⚪ agents/websockets#CALLABLES_RPC_VALUE 12.4 KiB 43.3 KiB
⚪ agents/websockets#callablesFromDecorated 12.7 KiB 44.3 KiB
⚪ agents/websockets#callablesRpcUrl 12.5 KiB 43.5 KiB
⚪ agents/websockets#isCallablesRpcUpgrade 12.4 KiB 43.4 KiB
⚪ agents/websockets#WebSockets 17.8 KiB 62.4 KiB
🟡 agents/workflows#AgentWorkflow 260.5 KiB 1136.4 KiB
🟡 agents/workflows#WorkflowRejectedError 259.2 KiB 1131.9 KiB
⚪ agents/x402#normalizeNetwork 14.7 KiB 61.1 KiB
⚪ agents/x402#withX402 23.0 KiB 89.2 KiB
⚪ agents/x402#withX402Client 104.1 KiB 346.5 KiB

Reported by agent-think[bot].

@AntoniTok

Copy link
Copy Markdown
Contributor Author
  • Rebased onto current main; PR is mergeable.
  • Moved state storage, caching, validation, and notifications into StateManager.
  • _state is public.
  • Added direct initialState and onChanged options.
  • Preserved the real state-change source.
  • Removed _setStateInternal.
  • Removed all shipped test-only cache reset methods.
  • Exposed the Lifecycle SQL helper.
  • Added public agents/state export and type tests.
  • State table is ensured before Lifecycle startup.
  • onChanged supports sync and async hooks; errors are handled while setState() stays synchronous.
  • Tests now use real eviction or fresh instances.
  • Added regression tests; all 2,001 Workers tests pass locally.

@AntoniTok

Copy link
Copy Markdown
Contributor Author

I assume you didn’t want me to change StateManager to State because State already names the agent’s state type and would just be confusing. Anyway #state is now _state.

Anyways now initialState passes the starting value, and onChanged passes the hook that runs after state changes; no initial-state setter is needed.

The wierd stuff marked by your last 2 comments was from splitting the state-change logic into two functions connected by an event causing us to lose the original connection value, so the code had to fake it back. Fixed now.

Devin flagged that async onChanged promises were not handled. Agent state changes now use the existing ctx.waitUntil, while standalone StateManager instances catch rejected promises. No Lifecycle API or behavior changed.

@AntoniTok
AntoniTok requested a review from mattzcarey September 2, 2026 13:58
…from Agent

- Rename the capability class to State (StateOptions), matching the other
  capability names; Agent imports it as StateCapability since State is its
  type parameter.
- Drop the lazy initialState getter: State takes plain static options.
  Agent keeps initialState as a field and seeds it from the state getter,
  so the capability no longer reaches back into the host.
- Remove the stray divider comment in onMessage.
…ts_state

Agent kept its global schema version as a row in cf_agents_state, which
was the only reason it still created the table alongside the State
capability. The version now lives under the cf_agents:schema_version KV
key (read synchronously via storage.kv so the constructor can still gate
DDL), matching how every capability stores its own version. A DO created
under the old layout has the row read once, moved to the key, and deleted.

The legacy cf_state_was_changed cleanup moves into State's own v1
migration. Agent no longer touches the table outside that one-time read.
Capabilities already hold storage, and sql hangs off it, so every sibling
runs storage.sql.exec directly. State now does the same and the services
contract stays as it was. Fold the one-interface options module into
state/index.ts, drop the unused generic on StateChangeSource, and shorten
the Agent field comment.
devin-ai-integration[bot]

This comment was marked as resolved.

Frees the State name for the capability class so Agent imports it
directly instead of aliasing. Type parameters are positional for
consumers, so this is not a breaking change.
Matches Streams, Tasks, Sessions, Scheduler, and Lifecycle, which use
ECMAScript private fields throughout.
Devin review on cloudflare#2179: set() cached nextState before serializing and
writing it, so a JSON.stringify or SQLite failure left later get()
calls returning a value storage and onChanged never received.
Also guard the async-hook branch with instanceof Promise so a hook that
returns a non-promise value at runtime cannot crash set().
devin-ai-integration[bot]

This comment was marked as resolved.

Handles thenables that are not instances of this realm's Promise while
still tolerating non-promise return values.
Devin on cloudflare#2179: an onChanged hook that keeps working after it returns
could be abandoned once the invocation that set the state ended.
LifecycleServices gains waitUntil (ctx.waitUntil behind the narrow
services surface) and State.set registers the hook's continuation with
it, still without awaiting it.
@mattzcarey mattzcarey reopened this Sep 11, 2026

@mattzcarey mattzcarey left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed and iterated on this together: State capability owns cf_agents_state outright, Agent's schema version moved to a KV key, static options, TState rename, # privates. CI green, no open threads.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants