Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions .changeset/controller-review-recovery.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@agentskit/core": patch
---

Keep older pending or superseded tool calls from blocking later controller turns, recover decisions after a pending-registration save failure, and resume interrupted streaming messages loaded from memory. Use a fresh run ID for durable decision executions outside a live stream.
7 changes: 7 additions & 0 deletions .changeset/fair-tools-resume.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@agentskit/core": patch
---

Register streamed confirmations on arrival, treat identical in-flight decisions as no-ops, and preserve active streams when a decision is rejected. Resume only the latest completed assistant turn after every confirmation is decided, with results immediately following their originating assistant in call order.

Restarting streamed decisions requires current ChatMemory because immutable arrival snapshots may omit subsequent calls. Without current conversation memory, an incomplete streamed snapshot raises AK_CONFIG_INVALID before claiming or executing.
2 changes: 1 addition & 1 deletion .doc-bridge/capabilities.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"name": "AgentsKit",
"root": "."
},
"contentHash": "85ceaef8fdb67f5b5644c4b297c3b9bade5667bb1458af891e07718f69633d7f",
"contentHash": "8efbc15b9798e169144aed1ad9efab22b1418a9053a014ee8806baea29b7f4e0",
"artifacts": {
"index": ".doc-bridge/index.json",
"llmsTxt": ".doc-bridge/llms.txt"
Expand Down
110 changes: 77 additions & 33 deletions .doc-bridge/index.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion apps/docs-next/content/docs/for-agents/core.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ npm install @agentskit/core
- `defineTool(config)` — typed tool factory with JSON-Schema input
inference.
- `controller.send(string | ContentPart[])` preserves multimodal parts and text projection.
- `controller.decide(id, 'approve' | 'deny', reason?)` uses `ChatConfig.decisionStore` to validate the current conversation before claiming, prioritizing live controller state over memory and preserving unsaved later turns and using the pending snapshot only as a fallback (`ToolDecisionStore`, `ToolDecisionRecord`). The store provides insert-if-absent `putPending`, linearizable `get`, atomic pending-to-claimed `claim`, and matching-claim terminal `settle`. Scope it to an application-authorized conversation. Same-decision terminal replay returns the recorded tool outcome; unknown IDs raise `AK_ACTION_NOT_FOUND`, competing or in-flight decisions raise `AK_ACTION_ALREADY_DECIDED`. Failed and claimed records are never automatically retried. Serialize different decisions per conversation to protect transcript writes.
- `controller.decide(id, 'approve' | 'deny', reason?)` uses `ChatConfig.decisionStore` to validate the current conversation before claiming, prioritizing live controller state over memory and preserving unsaved later turns and using the pending snapshot only as a fallback (`ToolDecisionStore`, `ToolDecisionRecord`). The store provides insert-if-absent `putPending`, linearizable `get`, atomic pending-to-claimed `claim`, and matching-claim terminal `settle`. Scope it to an application-authorized conversation. Idempotency compares only the decision, retaining the original denial reason. Same-decision terminal replay returns the recorded tool outcome; unknown IDs raise `AK_ACTION_NOT_FOUND`, conflicting decisions or targets that are no longer pending raise `AK_ACTION_ALREADY_DECIDED`. Failed and claimed records are never automatically retried. Serialize different decisions per conversation to protect transcript writes.
- Identical decisions are idempotent even while claimed: they return the recorded outcome, or the original call while execution is in flight, without invoking the tool or model again. Streamed confirmations are registered before `onToolCall`. After restart, streamed decisions require current `ChatMemory`: arrival snapshots are immutable and may omit later calls; without a current conversation, `AK_CONFIG_INVALID` rejects an incomplete streaming snapshot before claiming. A live controller does not require memory.
- `approve`/`deny` are deprecated. With a decision store they delegate to `decide`, including atomic claims and typed errors; without a store they preserve generation-local behavior.
- `ChatConfig.authorizeToolCall` — trusted host authorization before tool proposal and again before execution. Callback failures default to deny.
- `createInMemoryMemory` / `createLocalStorageMemory` — default
Expand Down
6 changes: 4 additions & 2 deletions apps/docs-next/content/docs/reference/packages/core.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ Configure `ChatConfig.decisionStore` with a conversation-scoped `ToolDecisionSto

`ToolDecisionRecord` stores `toolCallId`, `messages`, `status` (`pending`, `claimed`, `complete`, `failed`, `denied`), and optional `decision`, `reason`, and `outcome`. Implement `putPending` as insert-if-absent, `get` as a linearizable read, `claim` as an atomic pending-to-claimed transition returning the snapshot only to its winner, and `settle` as a durable terminal write for the matching claim. A database implementation can use `UPDATE ... WHERE status = 'pending' RETURNING ...`. The application must scope access to the authorized conversation.

Before claiming, the controller loads and validates the current conversation, prioritizing live state (including unsaved turns), then configured chat memory, then the stored snapshot when neither exists. It preserves later messages and patches the target call, revalidates and reauthorizes the tool, persists its outcome, and resumes the model. Same-decision terminal replay returns the stored tool outcome without another tool or model invocation. Unknown IDs raise `AK_ACTION_NOT_FOUND`; in-flight or conflicting decisions raise `AK_ACTION_ALREADY_DECIDED`. Tool failures become terminal `failed` records and reach the model as tool errors. A new attempt needs a new proposal ID.
Before claiming, the controller loads and validates the current conversation, prioritizing live state (including unsaved turns), then configured chat memory, then the stored snapshot when neither exists. It preserves later messages and patches the target call, revalidates and reauthorizes the tool, persists its outcome, and resumes the model only for the latest completed assistant message when all confirmations in that target assistant message are decided. Tool results follow their originating assistant message in call order. Idempotency compares the decision only; a repeated denial with a different reason retains the original reason and outcome. Same-decision terminal replay returns the stored tool outcome without another tool or model invocation. Unknown IDs raise `AK_ACTION_NOT_FOUND`; conflicting decisions or targets that are no longer pending raise `AK_ACTION_ALREADY_DECIDED`. Tool failures become terminal `failed` records and reach the model as tool errors. A new attempt needs a new proposal ID.

For a stuck `claimed` record, inspect `get(id)` and domain execution evidence, stop the original worker, and terminally `settle` the matching claim with a proven outcome or explicit indeterminate failure. Never reset or delete the claim. Reconcile the target call in current chat memory and continue with a new user turn; a retry requires a separately authorized new proposal ID. See [ADR 0042 operational recovery](https://github.com/AgentsKit-io/agentskit/blob/main/docs/architecture/adrs/0042-durable-tool-decisions.md#operational-recovery-of-a-claimed-decision). A crash after claim leaves an indeterminate claimed record requiring application reconciliation; core never automatically retries side effects. Serialize different decisions in one conversation to avoid lost transcript writes. Legacy `approve`/`deny` are deprecated: with a decision store they delegate to `decide`, including atomic claims and typed errors; otherwise they keep generation-local behavior. Core supplies the port, not HTTP transport or a database backend.

Expand Down Expand Up @@ -122,6 +122,8 @@ See [Type-safe tools with `defineTool`](#type-safe-tools-with-definetool) below.

`AdapterFactory`, `StreamSource`, `StreamChunk`, `Message`, `ToolDefinition`, `ToolCall`, `SkillDefinition`, `ChatMemory`, `Retriever`, `VectorMemory`, `Observer`, `AgentEvent`, and related types — full signatures in TypeDoc (below).

Identical decisions are idempotent even while claimed: they return the recorded outcome, or the original call while execution is in flight, without invoking the tool or model again. Streamed confirmations are registered before `onToolCall`. After restart, streamed decisions require current `ChatMemory`: arrival snapshots are immutable and may omit later calls; without a current conversation, `AK_CONFIG_INVALID` rejects an incomplete streaming snapshot before claiming. A live controller does not require memory.

## Example: inspect types in a custom tool

```ts
Expand Down Expand Up @@ -226,7 +228,7 @@ ErrorCodes.AK_TOOL_INVALID_INPUT // tool arguments or proposal are invalid
ErrorCodes.AK_TOOL_QUOTA_EXCEEDED // tool execution exceeds its quota
ErrorCodes.AK_TOOL_FORBIDDEN // tool execution is denied by policy
ErrorCodes.AK_ACTION_NOT_FOUND // durable tool decision ID is unknown
ErrorCodes.AK_ACTION_ALREADY_DECIDED // decision is claimed or conflicts with stored decision
ErrorCodes.AK_ACTION_ALREADY_DECIDED // decision conflicts or the target is no longer pending
ErrorCodes.AK_MEMORY_LOAD_FAILED // memory.load() failed
ErrorCodes.AK_MEMORY_SAVE_FAILED // memory.save() failed
ErrorCodes.AK_MEMORY_CLEAR_FAILED // memory.clear() failed
Expand Down
3 changes: 3 additions & 0 deletions apps/docs-next/lib/api-symbol-routes.json
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,7 @@
"api/rag/functions/bm25Score": "/docs/api/rag/functions#function-bm25score",
"api/rag/functions/chunkText": "/docs/api/rag/functions#function-chunktext",
"api/rag/functions/createHybridRetriever": "/docs/api/rag/functions#function-createhybridretriever",
"api/rag/functions/createPartitionedRAG": "/docs/api/rag/functions#function-createpartitionedrag",
"api/rag/functions/createRAG": "/docs/api/rag/functions#function-createrag",
"api/rag/functions/createRerankedRetriever": "/docs/api/rag/functions#function-creatererankedretriever",
"api/rag/functions/jinaReranker": "/docs/api/rag/functions#function-jinareranker",
Expand Down Expand Up @@ -602,6 +603,8 @@
"api/rag/interfaces/LoaderOptions": "/docs/api/rag/interfaces#interface-loaderoptions",
"api/rag/interfaces/NotionLoaderOptions": "/docs/api/rag/interfaces#interface-notionloaderoptions",
"api/rag/interfaces/OneDriveLoaderOptions": "/docs/api/rag/interfaces#interface-onedriveloaderoptions",
"api/rag/interfaces/PartitionSearchDiagnostics": "/docs/api/rag/interfaces#interface-partitionsearchdiagnostics",
"api/rag/interfaces/PartitionedRAGConfig": "/docs/api/rag/interfaces#interface-partitionedragconfig",
"api/rag/interfaces/PdfLoaderOptions": "/docs/api/rag/interfaces#interface-pdfloaderoptions",
"api/rag/interfaces/RAG": "/docs/api/rag/interfaces#interface-rag",
"api/rag/interfaces/RAGConfig": "/docs/api/rag/interfaces#interface-ragconfig",
Expand Down
6 changes: 3 additions & 3 deletions apps/docs-next/lib/deterministic-knowledge.generated.json
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@
{
"id": "claim.native-adapters",
"kind": "restricted-faq",
"label": "25 native adapters",
"label": "28 native adapters",
"match": {
"type": "exact",
"values": [
Expand All @@ -156,7 +156,7 @@
]
},
"answer": {
"markdown": "The verified AgentsKit ledger currently reports **25 native adapters**. The public conservative floor is 25.",
"markdown": "The verified AgentsKit ledger currently reports **28 native adapters**. The public conservative floor is 28.",
"citations": [
{
"id": "claim-native-adapters",
Expand Down Expand Up @@ -2444,5 +2444,5 @@
}
}
],
"contentHash": "sha256:0f0c04bee4e18b603df0f08ceab4eaffc6e88165420be8b2ea20da60973365d6"
"contentHash": "sha256:131edf7d35dd2646cd567cb15eda02cc7503b1fb59614d4ac7e73392b7aec1f7"
}
4 changes: 2 additions & 2 deletions apps/docs-next/lib/deterministic-site.generated.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
"version": 1,
"siteId": "agentskit-docs",
"artifact": {
"href": "/deterministic-knowledge/0f0c04bee4e18b603df0f08ceab4eaffc6e88165420be8b2ea20da60973365d6.json",
"contentHash": "sha256:0f0c04bee4e18b603df0f08ceab4eaffc6e88165420be8b2ea20da60973365d6"
"href": "/deterministic-knowledge/131edf7d35dd2646cd567cb15eda02cc7503b1fb59614d4ac7e73392b7aec1f7.json",
"contentHash": "sha256:131edf7d35dd2646cd567cb15eda02cc7503b1fb59614d4ac7e73392b7aec1f7"
},
"fallback": {
"mode": "backend"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@
{
"id": "claim.native-adapters",
"kind": "restricted-faq",
"label": "25 native adapters",
"label": "28 native adapters",
"match": {
"type": "exact",
"values": [
Expand All @@ -156,7 +156,7 @@
]
},
"answer": {
"markdown": "The verified AgentsKit ledger currently reports **25 native adapters**. The public conservative floor is 25.",
"markdown": "The verified AgentsKit ledger currently reports **28 native adapters**. The public conservative floor is 28.",
"citations": [
{
"id": "claim-native-adapters",
Expand Down Expand Up @@ -2444,5 +2444,5 @@
}
}
],
"contentHash": "sha256:0f0c04bee4e18b603df0f08ceab4eaffc6e88165420be8b2ea20da60973365d6"
"contentHash": "sha256:131edf7d35dd2646cd567cb15eda02cc7503b1fb59614d4ac7e73392b7aec1f7"
}
2 changes: 1 addition & 1 deletion docs/architecture/adrs/0042-durable-tool-decisions.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Add `ChatController.decide(toolCallId, decision, reason?)` and `ChatConfig.decis

The conversation-scoped store creates immutable pending records containing a serializable message snapshot, atomically transitions pending to claimed with its decision, and stores a terminal tool outcome. Claim returns the authoritative snapshot only to its winner. A database implementation can use `UPDATE ... WHERE status = 'pending' RETURNING ...`; creation must be insert-if-absent. Reads and writes must be durable and linearizable per tool call. Store access is an application-authorized conversation boundary, not an authorization token supplied by an untrusted client.

Before claiming, the controller loads and validates the current conversation: live state takes precedence over configured chat memory, and the pending snapshot is used only when no current conversation is available. Loading and sibling reconciliation operate on cloned messages so existing subscriber snapshots remain immutable. After claim, it stops the stream and merges only reconciled terminal sibling outcomes by message and call ID into the latest live state, preserving chunks, new confirmations and turns received during storage awaits. It rechecks that the target remains pending before execution; a removed or changed target terminally settles the claim as failed without executing or resuming the model. It patches the target call while preserving later turns, revalidates and reauthorizes execution through the existing tool pipeline, saves the terminal outcome before model continuation, and never automatically reclaims a claimed or failed record. Same-decision terminal replay returns the recorded tool outcome without invoking tool or model. Unknown IDs and competing decisions raise typed errors; an in-flight duplicate raises a conflict. Pending snapshots are persisted before proposal completion when the store is configured.
Before claiming, the controller loads and validates the current conversation: live state takes precedence over configured chat memory, and the pending snapshot is used only when no current conversation is available. Loading and sibling reconciliation operate on cloned messages so existing subscriber snapshots remain immutable. After claim, it rechecks the live target before merging only reconciled terminal sibling outcomes by message and call ID into the latest live state, preserving chunks, new confirmations and turns received during storage awaits. It rechecks that the target remains pending before execution; a removed or changed target terminally settles the claim as failed without executing or resuming the model. A rejected decision leaves any active stream untouched. It patches the target call while preserving later turns, revalidates and reauthorizes execution through the existing tool pipeline, saves the terminal outcome before model continuation (only from the latest completed assistant when all calls are decided, with tool results inserted immediately after their originating message in call order), and never automatically reclaims a claimed or failed record. Same-decision terminal replay returns the recorded tool outcome without invoking tool or model. Unknown IDs and competing decisions raise typed errors; an identical in-flight duplicate returns the original call without execution. Pending snapshots are persisted on streamed call arrival before callbacks, and before proposal completion when the store is configured. Their immutable arrival snapshots may omit later streamed calls, so reconstruction after restart requires current ChatMemory; an incomplete streaming fallback snapshot fails before claim with AK_CONFIG_INVALID.

## Consequences

Expand Down
Loading
Loading