可執行的託管邊界
讓一個執行服務隨宿主長駐
當 request 與訂閱的生命週期不同、客戶端可能重新連線,或一個回合需要可獨立定址的取消與核准生命週期時,請加入 `ConversationRunService`。
客製化深度
進階生命週期
託管深度
長駐 Node 行程
文件狀態
可執行參考範例
前置假設
- 單一宿主行程可以在 active run 的整個生命週期內擁有它。
- 應用程式會先完成身分驗證,再建立執行位址。
- 你的產品能把後續訂閱、取消與核准操作路由到同一個執行擁有者。
- 每個宿主定義位址只允許一個 active run,並提供穩定 run ID、依序活動、單一終結事件、取消、核准與有界重播。
- 即使較晚完成的 executor 忽略 abort signal,取消仍具有最終決定權。
- 在發布終結事件前,等待結果投影完成,並套用安全的對外錯誤投影。
- 經過驗證的帳號或租戶範圍,以及穩定的對話 ID。
- 引擎建立、憑證、工具、儲存轉接器、核准決策、遙測與行程生命週期。
- 每次 retained run 查詢的授權,以及多行程部署中的路由。
安裝與匯入
Hosted surface 明確表示伺服器行程的託管條件,但不會加入任何 HTTP 框架。
npm install @roackb2/heddleimport { ConversationRunCancelledError, createConversationEngine } from '@roackb2/heddle'
import { ConversationRunService } from '@roackb2/heddle/hosted'只建立一次
在與伺服器或 worker 相同的生命週期邊界建立一個 service。位址可以包含產品範圍,但一定要包含穩定的 sessionId。
type ProductRunAddress = {
accountId: string
sessionId: string
}
const runs = new ConversationRunService<ProductRunAddress>({
addressKey: ({ accountId, sessionId }) =>
JSON.stringify([accountId, sessionId]),
replay: { maxEventsPerRun: 512, retentionMs: 300_000 },
})不要在每個 request 中各建立一個 service。這會丟失 active-run discovery 與重新連線重播,也會讓不同 request 對目前 active run 有不同答案。
啟動持久回合
先透過引擎取得或建立 Heddle session,再啟動 run。請使用穩定的產品對話 ID,讓第二個 prompt 延續同一段對話。
const engine = createConversationEngine({
workspaceRoot,
stateRoot,
model: 'gpt-5.4',
})
const session =
(await engine.sessions.readExisting(productConversationId)) ??
(await engine.sessions.create({ id: productConversationId }))
const run = runs.startTurn({
address: { accountId, sessionId: session.id },
engine,
turn: { sessionId: session.id, prompt, host },
projectResult: async (result, context) => {
context.controller.signal.throwIfAborted()
await applyAuthorizedProductResult(result)
return { outcome: result.outcome, summary: result.summary }
},
projectError: () => ({
code: 'agent_failed',
message: 'The agent could not complete this request.',
}),
})
return { accepted: true, runId: run.runId, acceptedAt: run.acceptedAt }projectResult 是一個 transaction boundary。Heddle 會等它完成才發布成功終結事件,因此產品持久化或 reconciliation 不會晚於使用者看見的成功結果。若投影失敗,run 也會失敗。projectError 是對稱的安全邊界:宿主保留原始 failure 供診斷,訂閱端只收到核准過的 code 與 message。
訂閱、重播與取消
for await (const event of run.events({ afterSequence, signal })) {
await sendToSubscriber(event)
}
const result = await run.resultCancellation 是另一條終結路徑。cancel() 具有最終決定權,result promise 會以 ConversationRunCancelledError reject:
run.cancel()
try {
await run.result
} catch (error) {
if (!(error instanceof ConversationRunCancelledError)) throw error
}每個事件都有 runId、單調遞增的 sequence 與時間戳記。串流先輸出 activity,最後只會有一個 result、cancelled 或 error 終結事件。中止 run.events(...) 只會中斷該訂閱端,不會取消 run。
Handle 上的 cancel() 與 resolveApproval(...) 永遠綁定那一個確切的 run。過期 handle 無法影響同一位址上的後續 run。
授權 retained handle
後續 request 可以查詢 retained run,但只有 run ID 絕不代表已授權:
const retained = runs.getRetainedRun(runId)
if (!retained || retained.accountId !== authenticatedAccountId) {
throw new Error('Run not found')
}對錯誤範圍回傳 not found,可以避免洩漏其他租戶的 run 是否存在。不要只為了授權再維護第二套宿主 run registry;retained handle 已包含原始的宿主定義位址。
必須納入設計的限制
- 重播與 retained handle 位於記憶體且有上限。預設每個 run 保留 512 個事件,保留時間為五分鐘。
- 持久的最終對話狀態位於對話 repository,而不是 replay buffer。
- 此 service 不提供重啟後復原或跨 instance 傳遞。
- 多行程宿主必須把操作路由到擁有者行程,或加入共享傳遞基礎設施。
- 範例中的
HostedAgentService是示範組合方式的應用程式碼,不由 Heddle 匯出,也不應成為產品必須使用的 wrapper。