On this page
On this page
Plugin internals
Migrate synchronous SQLite APIs
Migrate synchronous SQLite SDK calls to the awaited or prepared replacements
below. The final compatibility day is December 31, 2026 (2026-12-31).
Removal ships in the first January 2027 stable release, without a Plugin SDK
major version. This deprecation release retains the listed contracts; it does
not remove them. See the removal timeline
for the release and shipped-surface gates.
Start with the families your plugin uses. For direct Async replacements, keep
the arguments and await the result. Factories that return adapters or prepared
readers may remain synchronous: await the operations on the returned object as
described below. A Promise-returning wrapper around a native database callback
still runs that callback on the caller's thread; migrate the callback contract,
not just its call site.
These changes preserve stored data, schemas, retention, and update behavior. Await a write before reading its result or publishing an external effect. Never retry a failed awaited operation through a deprecated synchronous path. Bundled plugins target the current host and do not need older-host fallbacks. External plugins should declare the host version that supplies the capabilities they use.
Session reads and inbound envelopes
Use session-store-runtime for session readers and channel-inbound for
prepared envelope construction. The same method mapping
applies to api.runtime.agent.session and channel runtime aliases.
| Before | After |
|---|---|
getSessionEntry |
await getSessionEntryAsync(...) |
getConversationSession |
await getConversationSessionAsync(...) |
listSessionEntries |
await listSessionEntriesAsync(...) |
loadCombinedSessionStoreForGateway |
await loadCombinedSessionStoreForGatewayAsync(...) from session-transcript-hit |
readAmbientTranscriptWatermark |
await readAmbientTranscriptWatermarkAsync(...) |
readSessionUpdatedAt |
await readSessionUpdatedAtAsync(...) |
resolveTranscriptSessionKeyBySessionId |
await resolveTranscriptSessionKeyBySessionIdAsync(...) |
loadTranscriptEventsSync |
await loadTranscriptEvents(...) |
readTranscriptStatsSync |
await readTranscriptStatsAsync(...) |
readTranscriptStatsBatchReadOnlySync |
await readTranscriptStatsBatchReadOnlyAsync(...) |
statSessionEntrySync |
await statSessionEntry(...) |
readAcpSessionEntry |
await readAcpSessionEntryAsync(...) |
AcpSessionManager.resolveSession |
await manager.resolveSessionAsync(...) |
createChannelInboundEnvelopeBuilder |
await createChannelInboundEnvelopeBuilderAsync(...) |
resolveChannelInboundRouteEnvelope |
resolveAgentRoute(...), then await createChannelInboundEnvelopeBuilderAsync({ cfg, route }) |
resolveInboundSessionEnvelopeContext |
await resolveInboundSessionEnvelopeContextAsync(...) |
createInboundEnvelopeBuilder |
await createChannelInboundEnvelopeBuilderAsync({ cfg, route }) |
resolveInboundRouteEnvelopeBuilder |
resolveAgentRoute(...), then await createChannelInboundEnvelopeBuilderAsync({ cfg, route }) |
resolveInboundRouteEnvelopeBuilderWithRuntime |
Runtime routing.resolveAgentRoute(...), then await createChannelInboundEnvelopeBuilderAsync({ cfg, route }) |
dispatchInboundDirectDmWithRuntime |
await dispatchInboundDirectDm(...) |
The historical transcript-key resolver includes retained session_windows
after a session resets. getSessionEntryByIdAsync(...).sessionKey is not a
replacement for historical lookup. See the exact historical lookup
contract.
Prepare the envelope builder once per inbound message. It returns a synchronous
formatter yielding the body string; the older generic builder returned
{ storePath, body }. If the caller still needs a store path, resolve it from
the same configured store and route through runtime.channel.session.resolveStorePath.
For custom formatting, await readSessionUpdatedAtAsync once and pass that
timestamp into the formatter instead of retaining a database-reading callback.
listSessionEntriesAsync returns a read-only metadata projection, not detached
snapshots such as saved skill prompts. It does not take the synchronous listing's
readOnly, snapshot-hydration, or source-capture options. Use
getSessionEntryAsync for a full public entry. Keep authorization for transcript
or metadata disclosure at the existing effect boundary. Memory statistics and
archive readers remain on their existing documented or local subpaths; this
migration does not make private host facades public.
For entry writes, replace opaque callbacks with owner preparation or an already-prepared patch:
| Before | After |
|---|---|
patchSessionEntry({ update, ...target }) |
await prepareSessionEntryPatch({ prepare, ...target }), or await applySessionEntryPatch({ patch, ...target }) |
updateSessionStoreEntry |
prepareSessionEntryPatch / applySessionEntryPatch |
updateLastRoute with assertCommitAllowed |
await updateLastRouteWithAuthority(...) |
upsertSessionUpstreamLink |
await upsertSessionUpstreamLinkAsync(...) |
deleteSessionUpstreamLink |
await deleteSessionUpstreamLinkAsync(...) |
Initialization owner's prepare().link(...) |
Await prepare(), then await prepared.linkAsync(...) |
See entry patch conflicts and authority when translating transaction-local callback behavior.
Auth profiles and provider readiness
Use the public provider-auth surface for profile stores and writes. Bundled
plugins also use the private provider-auth-runtime surface for profile metadata
and agent-sessions for AuthStorage and ModelRegistry.
Existing agent-runtime and runtime modelAuth aliases follow the same mapping;
prefer the focused public subpath when one exists.
| Before | After |
|---|---|
ensureAuthProfileStore |
await ensureAuthProfileStoreAsync(...) |
ensureAuthProfileStoreForLocalUpdate |
await ensureAuthProfileStoreForLocalUpdateAsync(...) |
upsertAuthProfile |
await upsertAuthProfileAsync(...) |
upsertApiKeyProfile |
await upsertApiKeyProfileAsync(...) |
isProviderApiKeyConfigured |
await isProviderApiKeyConfiguredAsync(...) |
isProviderAuthProfileConfigured |
await isProviderAuthProfileConfiguredAsync(...) |
listUsableProviderAuthProfileIds |
await listUsableProviderAuthProfileIdsAsync(...) |
resolveProviderAuthProfileMetadata |
await resolveProviderAuthProfileMetadataAsync(...) |
loadAuthProfileStoreWithoutExternalProfiles |
await loadAuthProfileStoreWithoutExternalProfilesAsync(...) |
loadAuthProfileStoreForRuntime |
await loadAuthProfileStoreForRuntimeAsync(...) |
loadAuthProfileStoreForSecretsRuntime |
await loadAuthProfileStoreForRuntimeAsync(agentDir, { ...options, readOnly: true, allowKeychainPrompt: false }) |
saveAuthProfileStore |
await updateAuthProfileStoreWithLock(...) from provider-auth; mutate the current store under its owner |
findPersistedAuthProfileCredential |
await findPersistedAuthProfileCredentialAsync(...) |
resolvePersistedAuthProfileOwnerAgentDir |
await resolvePersistedAuthProfileOwnerAgentDirAsync(...) |
resolveAuthStorePathForDisplay |
Retire physical database-path displays; display provider/profile identifiers instead. No public async path replacement. |
replaceRuntimeAuthProfileStoreSnapshots |
await replaceRuntimeAuthProfileStoreSnapshotsAsync(...) |
AuthStorage.create(authPath) |
await AuthStorage.forAgentAsync(agentDir) using the path's owning agent directory |
AuthStorage.forAgent |
await AuthStorage.forAgentAsync(...) |
AuthStorage.reload |
await auth.reloadAsync(...) |
AuthStorage.set |
await auth.setAsync(...) |
AuthStorage.remove |
await auth.removeAsync(...) |
AuthStorage.logout |
await auth.logoutAsync(...) |
ModelRegistry.create |
await ModelRegistry.createAsync(...), or SQL-free ModelRegistry.fromCapturedSources(...) when sources are already prepared |
ModelRegistry.refresh |
await registry.refreshAsync(...) |
ModelRegistry.unregisterProvider |
await registry.unregisterProviderAsync(...) |
resolveModelAuthMode |
await resolveModelAuthModeAsync(...) |
formatModelsAvailableHeader |
await formatModelsAvailableHeaderAsync(...) |
resolveCapabilityModelCandidates |
await resolveCapabilityModelCandidatesAsync(...) |
resolveConfiguredCapabilityProvider |
await resolveConfiguredCapabilityProviderAsync(...) |
resolveConfiguredRealtimeVoiceProvider |
await resolveConfiguredRealtimeVoiceProviderAsync(...) |
getTtsProvider |
await getTtsProviderAsync(...) |
isTtsProviderConfigured |
await isTtsProviderConfiguredAsync(...) |
resolveExplicitTtsOverrides |
await resolveExplicitTtsOverridesAsync(...) |
Runtime webSearch.listProviders |
await webSearch.listProvidersAsync(...) |
api.runtime.agent.resolveCliBackendDispatchEligibility |
await api.runtime.agent.resolveCliBackendDispatchEligibilityAsync(...) |
Harnesses should consume the host-prepared params.authStorage. AuthStorage
get, list, and has remain synchronous reads of its prepared view; login
and getApiKey already return promises. Do not replace a newer whole auth store
with a previously loaded snapshot: use updateAuthProfileStoreWithLock for
owner-backed mutation.
Physical database-path displays are retired after 2026-12-31. External plugins
should display provider/profile identifiers; there is no public async API for
the selected database path. The private bundled-plugin helper
AuthStorage.resolvePathForDisplayAsync preserves agent-local-versus-shared
database selection, including locators for profiles that do not exist yet.
Metadata helpers return profile/account identities and are not path substitutes.
Provider readiness hooks add an awaited callback:
| Before | After |
|---|---|
ImageGenerationProvider.isConfigured |
isConfiguredAsync |
MusicGenerationProvider.isConfigured |
isConfiguredAsync |
VideoGenerationProvider.isConfigured |
isConfiguredAsync |
SpeechProviderPlugin.isConfigured |
isConfiguredAsync |
RealtimeTranscriptionProviderPlugin.isConfigured |
isConfiguredAsync |
RealtimeVoiceProviderPlugin.isConfigured |
isConfiguredAsync |
RealtimeVoiceProviderPlugin.resolveConfig |
resolveConfigAsync |
InternalRealtimeVoiceProviderApi.isBrowserSessionConfigured |
isBrowserSessionConfiguredAsync |
InternalRealtimeVoiceProviderApi.isGatewayRelayConfigured |
isGatewayRelayConfiguredAsync |
InternalRealtimeVoiceProviderApi.resolveBrowserSessionCapabilities |
resolveBrowserSessionCapabilitiesAsync |
Use the hook's existing context and provider-specific inputs. The host awaits readiness and prefers the async hook; a failed async hook is not retried through the legacy one. A readiness result does not authorize later credential use. Keep the synchronous hooks required by the provider type through 2026-12-31. Bundled providers also retain synchronous hooks to support callers of the public synchronous SDK APIs until that date; ordinary runtime callers use async hooks. See auth and availability details.
Approval routing and command authorization
Direct resolvers return promises. Factories whose names end in Async below
still return adapters synchronously; their routing and eligibility callbacks
return promises and the host awaits them.
| Before | After |
|---|---|
readChannelAllowFromStoreSync |
await readChannelAllowFromStore(...) |
loadExecApprovals |
await loadExecApprovalsReadOnlyAsync(...) |
readExecApprovalsSnapshot |
await readExecApprovalsSnapshotAsync(...) |
resolveCommandAuthorization |
await resolveCommandAuthorizationAsync(...) |
resolveStoredModelOverride |
await resolveStoredModelOverrideAsync(...) |
resolveCommandArgMenu |
await resolveCommandArgMenuAsync(...) |
createApproverRestrictedNativeApprovalCapability |
createApproverRestrictedNativeApprovalCapabilityAsync |
createChannelApprovalNativeRuntimeAdapter |
createChannelApprovalNativeRuntimeAdapterAsync |
createLazyChannelApprovalNativeRuntimeAdapter |
createLazyChannelApprovalNativeRuntimeAdapterAsync |
createNativeApprovalChannelRouteGates |
createNativeApprovalChannelRouteGatesAsync |
createChannelNativeOriginTargetResolver |
createChannelNativeOriginTargetResolverAsync |
createChannelExecApprovalProfile |
createChannelExecApprovalProfileAsync |
createApproverRestrictedNativeApprovalCapabilityFromForwardingRoutes |
createApproverRestrictedNativeApprovalCapabilityFromForwardingRoutesAsync |
createChannelApproverDmTargetResolver |
createChannelApproverDmTargetResolverAsync |
createNativeApprovalForwardingFallbackSuppressor |
createNativeApprovalForwardingFallbackSuppressorAsync |
resolveExecApprovalSessionTarget |
await resolveExecApprovalSessionTargetAsync(...) |
resolveApprovalRequestSessionTarget |
await resolveApprovalRequestSessionTargetAsync(...) |
resolveApprovalRequestOriginTarget |
await resolveApprovalRequestOriginTargetAsync(...) |
resolveApprovalRequestAccountId |
await resolveApprovalRequestAccountIdAsync(...) |
resolveApprovalRequestChannelAccountId |
await resolveApprovalRequestChannelAccountIdAsync(...) |
doesApprovalRequestMatchChannelAccount |
await doesApprovalRequestMatchChannelAccountAsync(...) |
doesApprovalRequestSelectChannelAccount |
await doesApprovalRequestSelectChannelAccountAsync(...) |
If one operation needs several routing decisions, prepare
resolveApprovalRequestSessionDeliveryAsync once and pass its sessionDelivery
to the helpers. Keep current approval, command execution, and delivery authority
at their effect boundaries. See approval routing and import paths.
Conversation and native session bindings
Create current-host managers with createAccountScopedConversationBindingManagerV2
and register custom adapters with registerSessionBindingAdapterV2. The V2
manager factory remains synchronous and returns an async-only receiver.
| Before | After |
|---|---|
createAccountScopedConversationBindingManager |
createAccountScopedConversationBindingManagerV2 |
iMessage createIMessageConversationBindingManager |
createIMessageConversationBindingManagerV2; await the same receiver methods below |
Manager getByConversationId, listBySessionKey |
Await getByConversationIdAsync, listBySessionKeyAsync |
Manager bindConversation, touchConversation |
Await bindConversationAsync, touchConversationAsync |
Manager unbindConversation, unbindBySessionKey |
Await unbindConversationAsync, unbindBySessionKeyAsync |
registerSessionBindingAdapter / SessionBindingAdapter |
registerSessionBindingAdapterV2 / SessionBindingAdapterV2 |
Service or adapter touch |
touchAsync |
Service or adapter resolveByConversation |
resolveByConversationAsync |
Service or adapter listBySession |
listBySessionAsync |
Adapter inspectByConversation |
inspectByConversationAsync |
inspectSessionBindingByConversation |
await inspectSessionBindingByConversationAsync(...) |
inspectConversationBinding |
await inspectConversationBindingAsync(...) |
resolveRuntimeConversationBindingRoute |
await resolveRuntimeConversationBindingRouteAsync(...) |
ChannelConversationBindingSupport.setIdleTimeoutBySessionKey and runtime threadBindings.setIdleTimeoutBySessionKey |
setIdleTimeoutBySessionKeyAsync |
ChannelConversationBindingSupport.setMaxAgeBySessionKey and runtime threadBindings.setMaxAgeBySessionKey |
setMaxAgeBySessionKeyAsync |
setThreadBindingIdleTimeoutBySessionKey |
await setThreadBindingIdleTimeoutBySessionKeyAsync(...) |
setThreadBindingMaxAgeBySessionKey |
await setThreadBindingMaxAgeBySessionKeyAsync(...) |
setTelegramThreadBindingIdleTimeoutBySessionKey |
await setTelegramThreadBindingIdleTimeoutBySessionKeyAsync(...) |
setTelegramThreadBindingMaxAgeBySessionKey |
await setTelegramThreadBindingMaxAgeBySessionKeyAsync(...) |
setMatrixThreadBindingIdleTimeoutBySessionKey |
await setMatrixThreadBindingIdleTimeoutBySessionKeyAsync(...) |
setMatrixThreadBindingMaxAgeBySessionKey |
await setMatrixThreadBindingMaxAgeBySessionKeyAsync(...) |
Channel-specific setter names remain on their existing local channel surfaces. They do not create new cross-plugin imports. Custom adapters own their storage and must implement the V2 asynchronous and current-owner contracts.
Native harnesses must migrate ownership, not just rename a getter:
| Before | After |
|---|---|
captureNativeSessionGenerationAuthority |
Await prepareNativeSessionGenerationAuthority, or use the current retained preparation API below |
resolveNativeSessionBinding |
Await resolvePreparedNativeSessionBinding and release its capture |
reclaimNativeSessionGeneration |
Prepare authority, then await reclaimNativeSessionGenerationWithAuthority(...) |
NativeSessionGenerationOperations |
NativeSessionGenerationOperationsV2 |
createNativeSessionBindingLifecycle |
createNativeSessionBindingLifecycleV2 |
NativeSessionBindingStateStore |
NativeSessionBindingStateStoreV2 |
resolveNativeSessionBindingWithAuthority |
Await resolvePreparedNativeSessionBinding and release its capture |
AgentHarness.resolveSessionRuntimeOwnership |
resolveSessionRuntimeOwnershipAsync |
For new harness code, follow native session lineage:
await resolvePreparedNativeSessionBinding or
retainNativeSessionGenerationAuthority, consume the owned capture, and call
release() in finally. Keep the owner alive through effects and terminal
cleanup. A cold or invalidated cached binding cannot substitute for current
owner authority.
Keyed state
Use api.runtime.state.openKeyedStoreV2, or the lazy
createPluginStateKeyedStoreV2 factory from plugin-state-store-runtime when
providing an explicit plugin owner. These factories return immediately; their
methods perform the worker operations.
| Before | After |
|---|---|
openSyncKeyedStore, createPluginStateSyncKeyedStore |
openKeyedStoreV2, createPluginStateKeyedStoreV2 |
Sync register, registerIfAbsent |
Await V2 register, registerIfAbsent |
Sync lookup, lookupMany |
Await V2 lookup, lookupMany |
Sync consume, delete |
Await V2 consume, delete |
Sync entries, count, clear |
Await V2 entries, count, clear |
Sync update, deleteIf |
V2 observe plus compareAndApply, or an owner-defined worker createOperation |
V1 openKeyedStore / createPluginStateKeyedStore receiver update, deleteIf |
Open V2 and replace the callback with observe / compareAndApply, or createOperation |
V1 callback methods already return promises, but their opaque JavaScript callbacks still execute in a native host transaction. Do not replace an atomic update with an unconditional lookup and write:
For deleteIf, evaluate the predicate from an observation, then conditionally
apply { operation: "delete", action: "delete" } with that observation's
comparison. A conflict means no requested mutation committed. Do not replay an
unknown write or a callback with external side effects. See keyed-state
migration
for multi-key conditions and worker operations. Ordinary V1 data-only methods
are not the callback removal target.
SessionManager, extension persistence, and replay
Use agent-sessions for manager APIs. Every persistence method below must settle
before reading the resulting view or publishing a dependent effect.
| Before | After |
|---|---|
SessionManager.open |
await SessionManager.openAsync(...) |
SessionManager.openBounded |
await SessionManager.openBoundedAsync(...) |
SessionManager.openDetachedBounded |
await SessionManager.openDetachedBoundedAsync(...) |
SessionManager.openModelContext |
await SessionManager.openModelContextAsync(...) |
SessionManager.readSessionContext |
await SessionManager.readSessionContextAsync(...) |
setSessionTarget |
await manager.setSessionTargetAsync(...) |
reloadPersistedTranscript |
await manager.reloadPersistedTranscriptAsync(...) |
SessionManager.appendMessageToTranscript |
await SessionManager.appendMessageToTranscriptAsync(...) |
appendMessage |
await manager.appendMessageAsync(...) |
appendMessageWithTranscriptAnchor |
await manager.appendMessageWithTranscriptAnchorAsync(...) |
appendCompaction |
await manager.appendCompactionAsync(...) |
appendResetBoundary |
await manager.appendResetBoundaryAsync(...) |
appendCustomEntry |
await manager.appendCustomEntryAsync(...) |
appendSessionInfo |
await manager.appendSessionInfoAsync(...) |
appendCustomMessageEntry |
await manager.appendCustomMessageEntryAsync(...) |
appendLeafControl |
await manager.appendLeafControlAsync(...) |
appendLabelChange |
await manager.appendLabelChangeAsync(...) |
branch |
await manager.branchAsync(...) |
branchWithSummary |
await manager.branchWithSummaryAsync(...) |
persist |
await manager.persistAsync(...) |
removeTrailingEntries |
await manager.removeTrailingEntriesAsync(...) |
prepareTranscriptRewrite |
await manager.prepareTranscriptRewriteAsync(...); await its commit(...) too |
resolveCurrentTurnEntryId({ includeOmittedCustomMessages: true }) |
Await full SessionManager.openAsync(target) hydration, then call the loaded-view resolveCurrentTurnEntryId() |
ExtensionAPI.appendEntry |
await api.appendEntryAsync(...) |
ExtensionAPI.setSessionName |
await api.setSessionNameAsync(...) |
ExtensionAPI.setLabel |
await api.setLabelAsync(...) |
AgentSession.setSessionName |
await session.setSessionNameAsync(...) |
ProviderPlugin.sanitizeReplayHistory |
sanitizeReplayHistoryAsync |
ProviderReplaySessionState.appendCustomEntry |
V2 appendCustomEntryAsync |
ProviderReplaySessionState, ProviderSanitizeReplayHistoryContext |
ProviderReplaySessionStateV2, ProviderSanitizeReplayHistoryContextV2 |
sanitizeGoogleGeminiReplayHistory |
await sanitizeGoogleGeminiReplayHistoryAsync(...), or the provider-local awaited replay hook |
SessionPendingInputReceipt.complete |
await receipt.completeAsync(...) |
UserTurnTranscriptRecorder.completeProcessing |
await recorder.completeProcessingAsync(...) |
Use ExtensionActionsV2, ExtensionRuntimeV2, and bindCoreAsync to supply
awaited extension actions. The original action/runtime types and bindCore
are not removed merely because they can host legacy actions. SQL-free prepared
view getters, inMemory(), and fromEntries() remain synchronous. See
session persistence
and provider replay
for completion semantics and types.
Transcript write callbacks, readers, and catalogs
| Before | After |
|---|---|
withSessionTranscriptWriteLock |
await withSessionTranscriptWrite(...) |
withCodexSessionTranscriptMirrorWriteLock |
await withCodexSessionTranscriptMirrorWrite(...) |
Lock append or appendSessionTranscriptMessageByIdentity / appendSessionTranscriptMessageByIdentityStrict option prepareMessageAfterIdempotencyCheck |
preparation.prepareMessage, which may return a promise |
Those appends' beforeFreshMessageCommit option |
preparation.source using the host-owned source assertion |
loadArchivedSessions |
await loadArchivedSessionsAsync(...) |
resolveMemorySessionTargets |
await resolveMemorySessionTargetsAsync(...) |
loadMemorySessionMetadata |
await loadMemorySessionMetadataAsync(...), or loadMemorySessionMetadataBatchAsync for a batch |
readSessionTranscriptCatalogTitle |
await readSessionTranscriptCatalogTitleAsync(...) |
createSessionCatalogSourceActorProjector |
await prepareSessionCatalogSourceActorProjector(...) |
createSessionCatalogGitHubLinker |
await prepareSessionCatalogGitHubLinker(...) |
validateCodexSessionTranscriptReadAdmission |
await validateCodexSessionTranscriptReadAdmissionAsync(...) |
validateCodexSessionTranscriptContextVersion |
await validateCodexSessionTranscriptContextVersionAsync(...) |
Preparation runs after duplicate detection and outside the database transaction.
Return undefined to suppress a fresh append; a duplicate keeps its stored bytes
and skips preparation. Keep externally visible side effects out of preparation.
The new write scope does not promise the old callback's native transaction view
or roll back already committed appends. See locked transcript
preparation.
Reply, observer, and harness preparation
| Before | After |
|---|---|
sessionObserver.handleEvent |
await sessionObserver.handleEventAsync(...) |
sessionObserver.getCompanionSnapshot |
await sessionObserver.getCompanionSnapshotAsync(...) |
sessionObserver.dispose |
await sessionObserver.disposeAsync() |
Reply-dispatch shouldSendToolSummaries, shouldSendFullToolDetails |
Await shouldSendToolSummariesAsync(), shouldSendFullToolDetailsAsync() |
onVerboseProgressVisibility |
onVerboseProgressVisibilityAsync; await its visibility getter |
tryDispatchAcpReplyHook input booleans shouldSendToolSummaries, shouldSendFullToolDetails |
Pass shouldSendToolSummariesAsync and shouldSendFullToolDetailsAsync predicates |
GetReplyOptions.onAgentRunStart |
onPreparedAgentRunStart with required transcriptStart facts (or null) |
resolveEntryResetFreshness |
await resolveEntryResetFreshnessAsync(...) |
runAgentEndSideEffects |
await runAgentEndSideEffectsAsync(...) |
Attempt replyOperation.bindToolAuthoritySnapshot |
await replyOperation.bindToolAuthoritySnapshotAsync(...) |
Attempt replyOperation.projectToolAuthorityFingerprint |
await replyOperation.projectToolAuthorityFingerprintAsync(...) |
Attempt replyOperation.bindToolAuthorityRoute |
await replyOperation.bindToolAuthorityRouteAsync(...) |
ReplyToolAuthoritySnapshot.fingerprint, project |
Await fingerprintAsync, projectAsync |
ReplyBackendMessageInjectionV2.queueMessage |
queueMessageAsync with the supplied preparation object |
ReplyBackendMessageInjectionV2.claimPendingUserInputAnswer |
claimPendingUserInputAnswerAsync with the supplied preparation object |
ReplyBackendMessageInjectionV2.cancelPendingUserInput |
cancelPendingUserInputAsync with the supplied preparation object |
Question-dispatcher authority.assertCurrent as the database-reading preparation step |
Await authority.assertCurrentAsync, retaining the SQL-free assertCurrent at the effect |
buildWatchedSessionsHarnessContext |
await prepareWatchedSessionsHarnessContext(...) |
listSkillCommandsForAgents, listSkillCommandsForWorkspace |
Await listSkillCommandsForAgentsAsync, listSkillCommandsForWorkspaceAsync |
createToolSurface, createOpenClawCodingTools |
Await createToolSurfaceAsync, createOpenClawCodingToolsAsync |
createAgentHarnessToolSurfaceRuntime |
await createAgentHarnessToolSurfaceRuntimeAsync(...); returned compactTools and cleanup remain synchronous |
getOpenRouterModelCapabilities |
await loadOpenRouterModelCapabilities(modelId), then SQL-free getLoadedOpenRouterModelCapabilities(modelId) |
onPreparedAgentRunStart must still return completion ownership synchronously;
prepare transcript facts before invoking it. Awaited policy preparation is not
permission to execute or disclose later. Preserve the host's current authority
immediately before the effect. See reply authority
and observer visibility.
Account services, runtime config, workspace, and TTS
| Before | After |
|---|---|
modelAccountConnectService.listLinks, link, unlink, list, select, status, cancel |
Await listLinksAsync, linkAsync, unlinkAsync, listAsync, selectAsync, statusAsync, cancelAsync |
GitHub OAuth cancelAuthorization, retireProfile |
Await cancelAuthorizationAsync, retireProfileAsync |
Personal GitHub cancelAuthorization, disconnect |
Await cancelAuthorizationAsync, disconnectAsync |
getRuntimeConfig on runtime-config-snapshot / memory-core-host-runtime-core |
Prepared readRuntimeConfig(); for cold preparation, await captureRuntimeConfigAsyncReader()() |
resolveCronStorePath |
await resolveCronStorePathAsync(...) |
OpenClawPluginNodeHostCommandContext.acquireManagedWorkspace |
await context.acquireManagedWorkspaceAsync(...); release the lease in finally |
ensureAgentWorkspace.beforePersistentApply |
Await owner preparation before the call and use the SQL-free guard.assertHost at the effect |
Unprepared resolveTtsPrefsPath |
await resolveTtsPrefsPathAsync(...), or pass preparedTtsPreferences |
Unprepared buildTtsSystemPromptHint |
await api.runtime.tts.buildSystemPromptHintAsync(...) |
Bundled plugins can call private prepareTtsPreferences once per turn and pass
that result to the supported prepared overloads. Those SQL-free synchronous
overloads are not deprecated. External plugins use the public runtime method
above. See the public TTS preparation
recipe.
Debug proxy capture and raw SQLite
| Before | After |
|---|---|
DebugProxyCaptureStore, getDebugProxyCaptureStore, acquireDebugProxyCaptureStore |
await acquireDebugProxyCaptureStoreAsync(...); await methods on lease.store |
closeDebugProxyCaptureStore |
await lease.release() in finally |
DebugProxyCaptureReader, createDebugProxyCaptureReader |
AsyncDebugProxyCaptureReader, createDebugProxyCaptureReaderAsync; factory stays synchronous, reads are awaited |
initializeDebugProxyCapture |
await initializeDebugProxyCaptureAsync(...) |
captureHttpExchange |
await captureHttpExchangeAsync(...) |
captureWsEvent, capture context captureWsEvent |
await captureWsEventAsync(...) |
finalizeDebugProxyCapture |
await finalizeDebugProxyCaptureAsync(...) |
The following sqlite-runtime operations have no mechanical async SQL wrapper.
Use openOpenClawAgentSqliteWorkerStoreV2 and serializable owner commands for
ordinary runtime storage. Move the native implementation into the paired worker
backend using sqlite-worker-runtime:
| Before | After |
|---|---|
Writable openNodeSqliteDatabase, openOpenClawAgentDatabase, borrowOpenClawAgentDatabase |
Worker store acquisition, preparation, and data-only commands |
Legacy worker-store publicationSource.DatabaseSync |
V2 worker-store publication source |
withOpenClawAgentDatabaseAsync, withOpenClawAgentDatabaseWrite, runOpenClawAgentWriteAdmission |
Await V2 worker-store commands; do not pass native host callbacks |
runSqliteImmediateTransaction, runSqliteImmediateTransactionSync |
Transaction inside the worker backend |
executeSqliteQuerySync, executeSqliteQueryTakeFirstSync, iterateSqliteQuerySync, prepareSqliteQuerySync, getNodeSqliteKysely |
Worker-local primitives; expose a data-only operation to the host |
Preserve explicit read-only inspection, offline Doctor/CLI maintenance custody, and supported worker-local primitives. Do not treat them as permission for writable native handles in Gateway handlers. See worker store construction, cleanup, and missing-store semantics.
Verify the migration
Exercise one read after an awaited in-process write for each migrated owner.
For conditional state updates, prove that a competing write produces a conflict
instead of being lost. Check callback completion and release resources in
finally. Confirm that the migrated flow emits no synchronous SQLite
deprecation warning and that errors propagate without a legacy retry.
This guide covers the synchronous SQLite removal window. Unrelated aliases, active compatibility records, prepared SQL-free overloads, and private/internal implementation kernels retain their own contracts. Removing the public adapters in January does not authorize removing live security checks or changing stored data.