feat: add Agent.keepAlive() to prevent idle eviction#1029
Merged
threepointone merged 2 commits intomainfrom Mar 1, 2026
Merged
feat: add Agent.keepAlive() to prevent idle eviction#1029threepointone merged 2 commits intomainfrom
Agent.keepAlive() to prevent idle eviction#1029threepointone merged 2 commits intomainfrom
Conversation
Introduce an experimental Agent.keepAlive API that schedules a 30s alarm heartbeat to keep Durable Objects alive and returns a disposer to cancel it. Add an internal no-op callback _cf_keepAliveHeartbeat and switch fiber-related cleanup and tests to use the new callback name. Remove the standalone keepAlive implementation from the experimental forever mixin and centralize the API on Agent. Update AIChatAgent to call keepAlive() during streaming to prevent idle eviction. Add TestKeepAliveAgent, tests (keep-alive.test.ts), and test worker/wrangler entries to validate schedule creation, disposal, idempotence, and concurrent calls. Includes a changeset describing the minor/patch bump.
🦋 Changeset detectedLatest commit: 1639dab The changes in this PR will be included in the next version bump. This PR includes changesets to release 2 packages
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 |
commit: |
Add keepAlive to the Agent/AIChatAgent type picks and remove the local keepAlive noop/override in ai-chat. The ai-chat DurableChatAgent no longer imports or overrides keepAlive/_cf_streamKeepAlive and instead relies on the base Agent implementation, removing duplicate code and keeping types consistent.
Merged
This was referenced Mar 1, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Add a public
keepAlive()method to the baseAgentclass that prevents Durable Object eviction during long-running work. The method creates a 30-second heartbeat via the existing scheduling system and returns a disposer function to stop it.Problem
Durable Objects are evicted after ~70–140 seconds of inactivity. During long-running operations — streaming LLM responses, waiting on tool calls, running multi-step workflows — the DO can be evicted mid-flight, losing in-progress work. Users currently have no built-in way to prevent this.
Solution
keepAlive()usesscheduleEvery()to create an interval schedule with an internal_cf_keepAliveHeartbeatcallback. The alarm fires every 30 seconds, which is well within the eviction window. When the work is done, calling the disposer cancels the schedule.Why use the scheduling system?
setAlarm()approach would conflict with user-created schedulesgetSchedules()and emit standardschedule:executeeventskeepAlive()call gets its own schedule ID, so concurrent long-running operations don't interfere with each otherAIChatAgent integration
AIChatAgent._reply()now automatically wraps every LLM stream withkeepAlive(). The heartbeat starts before streaming begins and is disposed in a.finally()block, so it cleans up on both success and error. This means every AI chat response is now protected from idle eviction by default — no user code changes needed.Changes
packages/agents/src/index.tsKEEP_ALIVE_INTERVAL_MS(30s),keepAlive()method,_cf_keepAliveHeartbeat()no-op callbackpackages/ai-chat/src/index.ts_reply()acquireskeepAlive()before streaming, disposes in.finally()packages/agents/src/experimental/forever.tskeepAlive()— now inherits from Agent. Override_cf_keepAliveHeartbeatfor internal recovery logic. Removed standalonekeepAliveexport (experimental, unstable API)packages/agents/src/tests/keep-alive.test.tspackages/agents/src/tests/agents/keep-alive.tspackages/agents/src/tests/agents/fiber.tsworker.ts,wrangler.jsonc,index.ts)TestKeepAliveAgentNotes for reviewers
@experimentaltag —keepAlive()is marked experimental. The API surface is small (returns a disposer), so it's unlikely to change much, but the tag gives us room.disposedflag). Thevoidprefix oncancelScheduleis intentional — fire-and-forget cleanup._cf_keepAliveHeartbeatis a no-op on the base Agent — subclasses can override it to add work on each heartbeat tick (the experimental module does this for recovery logic)._reply(). The.finally()ensures cleanup even if streaming errors out.keepAlive()is a method on Agent, not a separate import.Testing