@aws-blocks/bb-agent 0.3.5 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/DESIGN.md +65 -17
  2. package/README.md +156 -8
  3. package/dist/agent.aws.d.ts +15 -1
  4. package/dist/agent.aws.d.ts.map +1 -1
  5. package/dist/agent.aws.js +49 -0
  6. package/dist/agent.d.ts +82 -14
  7. package/dist/agent.d.ts.map +1 -1
  8. package/dist/agent.js +238 -57
  9. package/dist/agentcore-bundle.d.ts +12 -0
  10. package/dist/agentcore-bundle.d.ts.map +1 -0
  11. package/dist/agentcore-bundle.js +150 -0
  12. package/dist/agentcore-bundle.test.d.ts +2 -0
  13. package/dist/agentcore-bundle.test.d.ts.map +1 -0
  14. package/dist/agentcore-bundle.test.js +46 -0
  15. package/dist/agentcore-entry.d.ts +21 -0
  16. package/dist/agentcore-entry.d.ts.map +1 -0
  17. package/dist/agentcore-entry.js +120 -0
  18. package/dist/agentcore-runtime.cdk.d.ts +27 -0
  19. package/dist/agentcore-runtime.cdk.d.ts.map +1 -0
  20. package/dist/agentcore-runtime.cdk.js +168 -0
  21. package/dist/index.aws.d.ts +1 -0
  22. package/dist/index.aws.d.ts.map +1 -1
  23. package/dist/index.cdk.d.ts +12 -6
  24. package/dist/index.cdk.d.ts.map +1 -1
  25. package/dist/index.cdk.js +31 -31
  26. package/dist/index.cdk.test.js +128 -51
  27. package/dist/index.hooks.d.ts +2 -2
  28. package/dist/index.hooks.d.ts.map +1 -1
  29. package/dist/index.mock.d.ts +1 -0
  30. package/dist/index.mock.d.ts.map +1 -1
  31. package/dist/index.test.js +426 -1
  32. package/dist/model-factory.d.ts +2 -2
  33. package/dist/model-factory.d.ts.map +1 -1
  34. package/dist/model-factory.js +2 -2
  35. package/dist/providers/canned.d.ts +8 -1
  36. package/dist/providers/canned.d.ts.map +1 -1
  37. package/dist/providers/canned.js +127 -42
  38. package/dist/types.d.ts +63 -1
  39. package/dist/types.d.ts.map +1 -1
  40. package/dist/version.d.ts +1 -1
  41. package/dist/version.js +1 -1
  42. package/package.json +24 -9
  43. package/src/agent.aws.ts +58 -1
  44. package/src/agent.ts +269 -56
  45. package/src/agentcore-bundle.test.ts +52 -0
  46. package/src/agentcore-bundle.ts +162 -0
  47. package/src/agentcore-entry.ts +134 -0
  48. package/src/agentcore-runtime.cdk.ts +203 -0
  49. package/src/index.aws.ts +3 -0
  50. package/src/index.cdk.test.ts +145 -53
  51. package/src/index.cdk.ts +33 -34
  52. package/src/index.hooks.ts +2 -2
  53. package/src/index.mock.ts +3 -0
  54. package/src/index.test.ts +473 -1
  55. package/src/model-factory.ts +3 -3
  56. package/src/providers/canned.ts +131 -36
  57. package/src/types.ts +64 -1
  58. package/src/version.ts +1 -1
  59. package/dist/job-event-source.d.ts +0 -19
  60. package/dist/job-event-source.d.ts.map +0 -1
  61. package/dist/job-event-source.js +0 -20
  62. package/src/job-event-source.ts +0 -21
package/DESIGN.md CHANGED
@@ -3,48 +3,95 @@
3
3
  Design document for the Agent Building Block. For usage, see [README.md](./README.md).
4
4
 
5
5
  **Package:** `@aws-blocks/bb-agent`
6
- **Type:** Composite (uses DistributedTable, Realtime, AsyncJob, FileBucket internally)
7
- **AWS Services:** Bedrock, DynamoDB, S3, SQS, AppSync Events
6
+ **Type:** Composite (uses DistributedTable, Realtime, FileBucket internally) + an AgentCore Runtime
7
+ **AWS Services:** Bedrock AgentCore Runtime, Bedrock, DynamoDB, S3, API Gateway (WebSocket)
8
8
  **Agent Framework:** [Strands Agents SDK](https://strandsagents.com/)
9
9
 
10
10
  ## Architecture
11
11
 
12
- The Agent BB is a composite Building Block it creates and manages 4 internal BBs:
12
+ The streaming agent loop runs on a **Bedrock AgentCore Runtime** (sessions up to 8h, warm,
13
+ managed) — not the shared Blocks request handler — and streams chunks to the browser over the
14
+ **Realtime** BB. The Agent BB composes these internal BBs plus the runtime:
13
15
 
14
- | Internal BB | Purpose | Created when |
16
+ | Internal BB / resource | Purpose | Created when |
15
17
  |-------------|---------|-------------|
16
18
  | **FileBucket** | Session persistence (Strands SessionManager) | Always |
17
19
  | **DistributedTable** | Frontend message history | `inferenceOnly: false` |
18
20
  | **Realtime** | Streaming chunks to caller | Always |
19
- | **AsyncJob** | Async agent execution (avoids 29s API Gateway timeout) | Always |
21
+ | **AgentCore Runtime** | Hosts the streaming loop (co-bundled backend + `serve()`) | Always (AWS) |
20
22
 
21
23
  ```
22
- stream() → AsyncJob.submit() → returns { channelId } immediately
24
+ stream()/resume() → InvokeAgentRuntime (returns immediately)
23
25
 
24
- AsyncJob consumer
26
+ AgentCore Runtime container (agentcore-entry.ts): starts the turn as a BACKGROUND
27
+ async task and returns an ack immediately
25
28
 
26
- runAgent() → Strands agent loop → publishes chunks to Realtime
29
+ runAgent() → Strands agent loop → publishes chunks to Realtime (as the shared execution role)
27
30
  → persists messages to DistributedTable
28
31
  → SessionManager saves state to FileBucket
29
32
  ```
30
33
 
34
+ The RPC handler (`stream`/`resume`) only kicks off the turn; it does not hold the connection —
35
+ running the loop as a background task lets `InvokeAgentRuntime` return in seconds. The microVM then
36
+ stays alive on AgentCore's own terms: AgentCore polls the container's health endpoint, and the SDK
37
+ reports `HealthyBusy` while a background task is in flight, so AgentCore keeps the runtime running
38
+ (up to the 8h max session) and returns it to `Healthy` — eligible for reclaim — once the task
39
+ completes. The browser subscribes to the Realtime channel by `channelId` and receives chunks as the
40
+ loop runs, so a turn is bounded by the AgentCore session (8h), not by the request handler's
41
+ per-invocation limit or API Gateway's ~29s cap.
42
+
43
+ **Compute model.** All AgentCore provisioning is kept self-contained in `AgentCoreRuntime`
44
+ (`agentcore-runtime.cdk.ts`) — the co-bundle, the `Runtime`, the shared role's AgentCore trust +
45
+ grants, the container env, and the handler's invoke permission — so it can later fold into a per-BB
46
+ compute abstraction (should one land) without touching call sites.
47
+
31
48
  ## Session Persistence
32
49
 
33
50
  Two storage backends, same FileBucket BB:
34
51
  - **AWS:** Strands' native `S3Storage` → FileBucket-provisioned S3 bucket
35
52
  - **Local:** Custom `FileBucketSnapshotStorage` → FileBucket mock (mirrors S3Storage key layout exactly)
36
53
 
54
+ ## Runaway-protection caps
55
+
56
+ `AgentConfig.maxLlmCalls` / `maxToolIterations` (default 20, `false` disables) bound runaway cost. They're enforced in `runAgent` by counting Strands' `BeforeModelCallEvent` / `BeforeToolCallEvent` hooks and calling `agent.cancel()` once a cap is exceeded; cancellation ends the stream normally (`stopReason: 'cancelled'`), which `runAgent` surfaces as an `error` chunk and then skips the final persist + `done`.
57
+
58
+ **Scope is the whole logical turn, including across HITL resumes.** The counters are stored in the Strands agent's `appState` (keys `__bbAgentModelCallCount` / `__bbAgentToolCallCount`), which the `SessionManager` persists with the session snapshot — the same mechanism the `trusted:<tool>` flags use. A turn that pauses on an interrupt and continues via `resume()` therefore keeps counting on its existing budget. Locals in `runAgent` would reset on every resume, letting an auto-approving or trusted-tool resume loop re-enter itself indefinitely — exactly the runaway these caps exist to stop.
59
+
60
+ **The reset is lazy, and it has to be.** The `SessionManager` restores the snapshot's `appState` *during* `stream()`, i.e. after `runAgent` has already set up its hooks — so zeroing the counters up front is silently overwritten by the previous turn's values and the budget leaks from turn to turn (a second message on the same conversation would start at the first turn's count and trip immediately). Instead, a fresh turn mints a `turnId` and the first cap hook to fire notices the stored `__bbAgentCapTurnId` is stale, zeroes the counters, and claims the turn; a resume mints no id, so it continues on the restored counts. Strands has no per-turn identifier to reuse here — `invocationState` is a caller-supplied bag, and a per-invocation id would change on every `resume()`, which is the opposite of what's needed.
61
+
62
+ A tool call cancelled by the tool cap still gets an `AfterToolCallEvent` (Strands reports the cancellation as the call's result), so the `tool-call` row already written to the message table keeps its `tool-result` partner — no dangling `tool_use` is left for the next turn to replay. `runAgent` additionally writes an `assistant` row carrying the stop reason in `metadata.error`, so a reloaded conversation explains why it ended.
63
+
37
64
  ## Infrastructure (CDK)
38
65
 
39
- The CDK class mirrors the runtime's BB creation:
40
- - **Bedrock IAM:** `InvokeModel` + `InvokeModelWithResponseStream` on all foundation models and inference profiles
41
- - **FileBucket:** `${id}-sessions` — session snapshot storage
42
- - **DistributedTable:** `${id}-messages` — conversation history (only when `inferenceOnly: false`)
66
+ The CDK class provisions:
67
+ - **FileBucket:** `${id}-sn` session snapshot storage
68
+ - **DistributedTable:** `${id}-convos` / `${id}-messages` conversation metadata + history (only when `inferenceOnly: false`)
43
69
  - **Realtime:** `${id}-rt` — streaming namespace `chunks`
44
- - **AsyncJob:** `${id}-job` — job payload: `{ message, conversationId?, channelId }`
45
- - Event source uses `batchSize: 1` / `maxBatchingWindowSeconds: 0`: the caller is blocked on the job starting, so the Agent opts out of AsyncJob's batching defaults (10 / 5s) rather than add up to 5s of latency to an interactive turn.
46
-
47
- > **Note:** Internal Building Blocks are created on the parent scope (not `this`) to ensure correct nested-scope resolution on AWS.
70
+ - **AgentCore Runtime** (`${id}-runtime`, via `AgentCoreRuntime`)the co-bundled backend + `serve()`
71
+ harness. It runs **as the shared Blocks execution role** (`Scope.executionRole` / `BlocksRole`)
72
+ the same role the Lambda handler runs as — so it **inherits every Building Block's grants**, including
73
+ **Realtime publish** (`execute-api:ManageConnections` + the connections table, granted to the handler
74
+ by the `${id}-rt` Realtime child), other BBs an agent's *tools* touch (KVStore, tables, etc.), and
75
+ this agent's own session bucket (S3) and conversation/message tables (DynamoDB). `AgentCoreRuntime`
76
+ adds to that shared role only what's AgentCore-specific and not already carried:
77
+ - **Trust:** the `bedrock-agentcore.amazonaws.com` assume-role statement, so the runtime can assume
78
+ the shared role. Scoped by `aws:SourceAccount` + `aws:SourceArn` (AWS's recommended AgentCore trust
79
+ policy). Added here — not in core — so a Realtime-only app never trusts AgentCore.
80
+ - **Bedrock:** `InvokeModel` + `InvokeModelWithResponseStream` on all foundation models and inference profiles
81
+ - **Handler grant:** the shared role is granted `bedrock-agentcore:InvokeAgentRuntime` (wildcard runtime
82
+ ARN, to avoid a role↔runtime dependency cycle) so the RPC handler can start the loop.
83
+
84
+ The container gets four environment variables: `BB_AGENT_ID`, `BLOCKS_STACK_NAME`, and the config
85
+ location `BLOCKS_CONFIG_BUCKET` / `BLOCKS_CONFIG_KEY`. `BB_AGENT_ID` + `BLOCKS_STACK_NAME` let the
86
+ co-bundled backend re-derive its resource names in-process (session bucket, conversation/message
87
+ tables) via the SDK-identifier registry — the same derivation the handler uses — so those names aren't
88
+ injected. `BLOCKS_CONFIG_BUCKET`/`BLOCKS_CONFIG_KEY` point the container at the shared config blob
89
+ (from core's `getConfigLocation()`); `loadConfigToProcessEnv()` loads the **same full app config the
90
+ handler does**, which is how the loop gets `BLOCKS_RT_CALLBACK_URL` (registered by the Realtime BB) and
91
+ every other `registerConfig()` value a tool's Building Block may read. IAM to read the blob is inherited
92
+ from the shared execution role.
93
+
94
+ > **Note:** The runtime (`agent.ts`) and CDK (`index.cdk.ts`) layers both create the internal BBs on the Agent scope (`this`) with the **same child ids** (`sn`, `convos`, `messages`, `rt`). Same id → same `fullId` → same derived physical name, so the deployed loop resolves the exact resources CDK provisioned.
48
95
 
49
96
  ## Model Providers
50
97
 
@@ -62,6 +109,7 @@ Custom Strands model provider for local development. No network, no API keys, no
62
109
 
63
110
  - Returns instant keyword-based responses (e.g., prompt contains "weather" → weather response, otherwise a default canned response)
64
111
  - Streams word by word, matching the same `ModelStreamEvent` protocol as Bedrock/OpenAI
65
- - Triggers tool calls when the prompt mentions a tool name — splits camelCase names into words (e.g., "weather" matches `getWeather`) and emits Strands `toolUse` events
112
+ - Triggers tool calls when the prompt mentions a tool name (or a `cannedTriggers` keyword) — splits camelCase names into words (e.g., "weather" matches `getWeather`) and emits Strands `toolUse` events. Matching is on word boundaries, not substrings, so "category" does not fire `getCat`.
113
+ - Derives tool input from, in order of preference: the tool's `cannedExamples`, the schema `default` (from Zod `.default()`), the first `enum` value (for enum fields), then a generic placeholder by type (`'sample'` / `1` / `true` / `[]`)
66
114
  - After Strands executes the tool and sends the result back, returns a fixed acknowledgment (`"I called the tool and got a result."`)
67
115
  - Token usage reports zeros (no real model call)
package/README.md CHANGED
@@ -48,7 +48,7 @@ const agent = new Agent(scope, id, config)
48
48
  | `getPendingInterrupts(conversationId)` | `Promise<Array<...>>` | Get unanswered interrupts (for reload support). |
49
49
  | `getChannel(channelId)` | `Promise<RealtimeChannel>` | Get a Realtime channel for subscribing to chunks. |
50
50
 
51
- `stream()` submits the message to AsyncJob and returns immediately — no API Gateway timeout risk. The agent runs asynchronously and publishes chunks to Realtime. The channel ID is resolved as `options.channelId || options.conversationId || crypto.randomUUID()` — empty strings are treated as unset and fall through to the next value.
51
+ `stream()` invokes the AgentCore Runtime (`InvokeAgentRuntime`) and returns immediately — no API Gateway timeout risk. The loop runs on the runtime (sessions up to 8h) and publishes chunks to Realtime as it goes. The channel ID is resolved as `options.channelId || options.conversationId || crypto.randomUUID()` — empty strings are treated as unset and fall through to the next value.
52
52
 
53
53
  **Important: Subscribe before sending.** The agent starts emitting chunks immediately after `stream()` is called. If you subscribe to the channel after calling `stream()`, early chunks may be dropped. Always subscribe first, await `established`, then send:
54
54
 
@@ -136,6 +136,8 @@ The `useChat` hook only surfaces `user`, `assistant`, and `approval` messages to
136
136
  | `inferenceOnly` | `boolean` | Skip persistence infra. Default: `false`. |
137
137
  | `conversation` | `ConversationManagerConfig` | How the agent trims message history (sliding-window or summarizing). |
138
138
  | `streamingMode` | `'token' \| 'block'` | How text chunks are published to the client. Default: `'block'`. |
139
+ | `maxLlmCalls` | `number \| false` | Max model invocations per turn before the turn is stopped; `false` disables. Default: `20`. See [Limiting runaway cost](#limiting-runaway-cost). |
140
+ | `maxToolIterations` | `number \| false` | Max tool calls per turn before the turn is stopped; `false` disables. Default: `20`. See [Limiting runaway cost](#limiting-runaway-cost). |
139
141
 
140
142
  ### Model Configuration
141
143
 
@@ -359,6 +361,38 @@ const agent = new Agent(scope, 'support', {
359
361
  });
360
362
  ```
361
363
 
364
+ ### Limiting runaway cost
365
+
366
+ An agent runs a reason→act loop: each iteration is one **model call**, optionally followed by tool calls, and a model call that requests no tools ends the turn. A misbehaving agent — or a prompt that induces one — can loop this cycle far longer than intended; an unbounded loop can run up unexpected cost.
367
+
368
+ > **These caps are a safety backstop, not a way to guide the agent.** The defaults exist only to stop a runaway from racking up cost — they are *not* tuned for your agent and should not be used to shape its behavior. An agent that legitimately needs more steps or tools will be cut off mid-task at the default. **Set these values deliberately for your own agent** based on how many steps and tool calls a healthy turn takes, so a normal turn always completes and only genuine runaways are stopped.
369
+
370
+ Two per-turn safety caps bound this, and **both default to `20`**:
371
+
372
+ - **`maxLlmCalls`** — the maximum number of model invocations in a single turn. This is the most direct spend guard (model calls are the billing unit), and because every tool round needs a model call it transitively bounds tool loops too.
373
+ - **`maxToolIterations`** — the maximum number of tool calls in a single turn (parallel tool batches count each call).
374
+
375
+ When either cap is hit, the turn is stopped and the client receives an `error` chunk (so `complete()` rejects) instead of `done`. The counts cover the whole turn, including across a [tool-approval interrupt](#tool-approval-human-in-the-loop): they are kept in the agent's session state, so a turn that pauses for approval and continues via `resume()` keeps its existing budget instead of starting a fresh one. Only a new message starts a new budget.
376
+
377
+ ```typescript
378
+ const agent = new Agent(scope, 'support', {
379
+ systemPrompt: '...',
380
+ maxLlmCalls: 40, // agent legitimately reasons over many steps
381
+ maxToolIterations: 60, // ...and chains many tools per turn
382
+ });
383
+
384
+ // Or disable a cap entirely with `false`:
385
+ const unbounded = new Agent(scope, 'batch', {
386
+ systemPrompt: '...',
387
+ maxLlmCalls: false, // no per-turn model-call limit
388
+ maxToolIterations: false,
389
+ });
390
+ ```
391
+
392
+ Raise the caps for agents that legitimately take many steps so they aren't cut off mid-task, or set a cap to `false` to disable it — tuning these to your agent is part of delivering a good agentic experience, not just a cost lever. The caps bound call *count*, not tokens or wall-clock — for real cost protection, also configure a [billing alarm](https://docs.aws.amazon.com/cost-management/latest/userguide/monitor-charges.html) or a CloudWatch alarm on Bedrock spend.
393
+
394
+ When sizing the caps for an agent that uses [tool approval](#tool-approval-human-in-the-loop), remember that approved and trusted tool calls both count: a `trustable` tool that's been trusted runs without interrupting, and a tool approved through `resume()` continues on the same budget, so a long approve-and-continue turn can still reach the cap.
395
+
362
396
  ## Tools
363
397
 
364
398
  Tools let the agent take actions during its reasoning — query a database, call an API, send an email. The model decides *when* to call a tool based on the user's message and the tool's description. You define the tool's schema and handler; the framework handles the rest.
@@ -640,9 +674,41 @@ The CannedProvider is a custom Strands model provider that requires no network o
640
674
 
641
675
  - Returns simple mock responses
642
676
  - Triggers tool calls when the prompt mentions a tool name (e.g., "get order" triggers `getOrderStatus`)
643
- - Generates valid tool inputs from Zod schemas using type-based placeholders
677
+ - Generates valid tool inputs from Zod schemas, respecting schema `default` values (from `.default()`) before falling back to type-based placeholders (`'sample'`, `1`, `true`, `[]`)
644
678
  - Streams responses word by word, matching the same protocol as real providers
645
679
 
680
+ #### Canned Hints — `cannedExamples` and `cannedTriggers`
681
+
682
+ Two optional tool fields make the canned provider more useful for local prototyping. Both are **ignored by the real bedrock/openai providers**, so they're safe to leave on production tools:
683
+
684
+ | Field | Type | Effect (canned provider only) |
685
+ | --- | --- | --- |
686
+ | `cannedExamples` | `Record<string, JSONValue>` | Realistic tool input, shallow-merged over the generated placeholder — your fields win, unspecified fields fall back to schema defaults / placeholders. The merge is one level deep: a nested-object example replaces that whole generated sub-object rather than deep-merging into it. |
687
+ | `cannedTriggers` | `string[]` | Extra keyword phrases that make the provider select this tool, beyond its name and camelCase words. Single and multi-word phrases match on word boundaries (so `'log in'` won't fire on `"backlog in"`); internal whitespace is flexible. |
688
+
689
+ Building on the [KnowledgeBase tool](#using-knowledgebase-with-the-agent) above: without hints the mock calls `searchDocs` with `{ query: 'sample' }`, which matches nothing in your documents, so local testing returns empty results. A `cannedExamples` query that actually appears in *your* docs makes the mock return real hits, and `cannedTriggers` lets natural phrasings fire the tool:
690
+
691
+ ```typescript
692
+ tools: (tool) => ({
693
+ searchDocs: tool({
694
+ description: 'Search product documentation for relevant information',
695
+ parameters: z.object({
696
+ query: z.string().describe('The search query'),
697
+ maxResults: z.number().optional().describe('Max results to return (default: 5)'),
698
+ }),
699
+ handler: async ({ input }) => kb.retrieve(input.query, { maxResults: input.maxResults ?? 5 }),
700
+
701
+ // Canned provider hints (ignored by real models):
702
+ // Without this the mock would search for the literal 'sample' and match nothing —
703
+ // use a query that hits YOUR documents so local runs return meaningful results.
704
+ cannedExamples: { query: 'how do I reset my password' },
705
+ // The name already matches "search"/"docs"/"searchDocs"; these add phrasings that don't
706
+ // contain the name, so "help me find the manual" or "look up the guide" also fire the tool.
707
+ cannedTriggers: ['find', 'look up'],
708
+ }),
709
+ }),
710
+ ```
711
+
646
712
 
647
713
  ## Client Hook — `useChat`
648
714
 
@@ -697,7 +763,8 @@ export const api = new ApiNamespace(scope, 'api', (context) => ({
697
763
  return { conversationId: await agent.createConversationId(userId) };
698
764
  },
699
765
  async sendMessage(conversationId: string, message: string, channelId: string, userId: string) {
700
- await agent.stream(message, { conversationId, channelId, userId });
766
+ const result = await agent.stream(message, { conversationId, channelId, userId });
767
+ return { channelId: result.channelId };
701
768
  },
702
769
  async getConversation(conversationId: string) {
703
770
  const messages = await agent.getConversation(conversationId);
@@ -737,7 +804,87 @@ await chat.sendMessage('Hello!');
737
804
  await chat.loadConversation('conv-123');
738
805
  ```
739
806
 
740
- ### 2. Support Agent with Tools
807
+ The example above is framework-agnostic on purpose — `useChat` has no React import and works with any UI layer. The two examples below show how to bridge it into a specific framework's reactivity.
808
+
809
+ ### 2. React: hold the instance once, drive `useState` from the callbacks
810
+
811
+ `useChat` is a factory, not a React hook, so it must **not** run on every render — recreating it drops the WebSocket subscription and conversation state each time. Hold the single instance in a `useRef` (created lazily so it survives re-renders), and turn the `onMessagesChange` / `onLoadingChange` / `onInterrupt` callbacks into `setState` calls so React re-renders when the mutable instance changes. This example keeps the `api` wiring minimal — it omits the `userId` that the End-to-End example (#1) threads through `createConversation` / `sendMessage`; thread it the same way here when your API needs it (or resolve the user server-side).
812
+
813
+ ```tsx
814
+ 'use client'; // Next.js only — see the note below. Plain React (Vite/CRA) can omit this.
815
+
816
+ import { useRef, useState, useEffect } from 'react';
817
+ import { useChat, type ChatMessage } from '@aws-blocks/bb-agent/client';
818
+ import { api } from './api'; // your generated aws-blocks API client
819
+
820
+ export function Chat() {
821
+ const [messages, setMessages] = useState<ChatMessage[]>([]);
822
+ const [isLoading, setIsLoading] = useState(false);
823
+ const [input, setInput] = useState('');
824
+
825
+ // Create the instance exactly once. The ref survives every re-render,
826
+ // so the subscription and conversation state are never torn down.
827
+ // Type the ref as `| undefined` and initialize with `undefined` — @types/react 19
828
+ // tightened the useRef overloads, so a bare useRef<T>() no longer compiles.
829
+ const chatRef = useRef<ReturnType<typeof useChat> | undefined>(undefined);
830
+ if (!chatRef.current) {
831
+ // eslint-disable-next-line react-hooks/rules-of-hooks -- useChat is a factory, not a hook; the use-prefix trips the linter's hook heuristic.
832
+ chatRef.current = useChat({
833
+ api: {
834
+ sendMessage: (convId, msg, chId) => api.sendMessage(convId, msg, chId),
835
+ createConversation: () => api.createConversation(),
836
+ getConversation: (id) => api.getConversation(id),
837
+ },
838
+ subscribe: async (channelId, handler) => {
839
+ const channel = await api.getChannel(channelId);
840
+ return channel.subscribe(handler);
841
+ },
842
+ // Bridge the mutable instance into React state — these fire on every change.
843
+ onMessagesChange: setMessages,
844
+ onLoadingChange: setIsLoading,
845
+ });
846
+ }
847
+ const chat = chatRef.current!; // guaranteed set by the block above
848
+
849
+ // Tear down the WebSocket subscription when the component unmounts.
850
+ useEffect(() => () => chat.destroy(), [chat]);
851
+
852
+ async function handleSend(e: React.FormEvent) {
853
+ e.preventDefault();
854
+ const text = input.trim();
855
+ if (!text || isLoading) return;
856
+ setInput('');
857
+ await chat.sendMessage(text);
858
+ }
859
+
860
+ return (
861
+ <div>
862
+ <ul>
863
+ {messages.map((m) => (
864
+ <li key={m.id} data-role={m.role}>
865
+ <strong>{m.role}:</strong> {m.content}
866
+ </li>
867
+ ))}
868
+ </ul>
869
+ <form onSubmit={handleSend}>
870
+ <input value={input} onChange={(e) => setInput(e.target.value)} disabled={isLoading} />
871
+ <button type="submit" disabled={isLoading}>Send</button>
872
+ </form>
873
+ </div>
874
+ );
875
+ }
876
+ ```
877
+
878
+ Key points:
879
+
880
+ - **One instance, held in a ref.** `useRef` + the lazy `if (!chatRef.current)` guard is the React idiom for "construct once." Because the identifier is `use`-prefixed, `eslint-plugin-react-hooks` (bundled in the default Next.js and CRA configs) flags the guarded call as a conditional hook (`react-hooks/rules-of-hooks`). `useChat` is a factory, not a hook, so this is a false positive — the inline `eslint-disable-next-line` above the call silences it. What you must **not** do is call `useChat(...)` unguarded on every render: that recreates the instance each time and is the footgun the factory note warns about.
881
+ - **Callbacks are your reactivity bridge.** `useChat` mutates its own message list in place; `onMessagesChange` / `onLoadingChange` hand you the new value so you can `setState` and trigger a render. Passing `setMessages` / `setIsLoading` directly is enough.
882
+ - **Clean up on unmount** with `chat.destroy()` in a `useEffect` cleanup, so the Realtime subscription is closed.
883
+ - **Approvals:** wire `resume: (chId, responses, convId) => api.resume(chId, responses, convId)` into the `api` object above (mirroring your backend's resume method — `respondToInterrupt` throws if it is absent), add `onInterrupt: setInterrupts` (with `const [interrupts, setInterrupts] = useState<Array<{ id: string; name: string; reason?: unknown }>>([])` — a bare `useState([])` infers `never[]` and rejects the payload) to render an approval UI, then call `chat.respondToInterrupt([{ interruptId, approved: true }])`.
884
+
885
+ **Next.js:** this is the same component — just keep the `'use client'` directive at the top of the file. `useChat` opens a browser WebSocket and holds client state, so it must run in a Client Component, never a Server Component. No other changes are needed.
886
+
887
+ ### 3. Support Agent with Tools
741
888
 
742
889
  Agent with tools that can look up orders and search documentation. Uses tool context to scope queries to the authenticated user.
743
890
 
@@ -800,7 +947,8 @@ The Agent BB composes several internal Building Blocks automatically:
800
947
  | `FileBucket` | S3 | Session snapshot storage (Strands agent state between turns) |
801
948
  | `DistributedTable` × 2 | DynamoDB | Conversations table + messages table |
802
949
  | `Realtime` | API Gateway WebSocket | Streaming chunks to connected clients |
803
- | `AsyncJob` | SQS + Lambda | Runs the agent asynchronously (no API Gateway timeout) |
950
+
951
+ The streaming loop itself runs on a **Bedrock AgentCore Runtime** (provisioned by `AgentCoreRuntime` — not a composed BB). It's invoked via `InvokeAgentRuntime`, runs the loop for the length of the session (up to 8h), and publishes chunks over the Realtime BB above.
804
952
 
805
953
  When `inferenceOnly: true`, the two DistributedTables are skipped (no conversation persistence).
806
954
 
@@ -809,9 +957,8 @@ When `inferenceOnly: true`, the two DistributedTables are skipped (no conversati
809
957
  - **Model:** Bedrock pay-per-token pricing. See [Bedrock pricing](https://aws.amazon.com/bedrock/pricing/).
810
958
  - **Persistence:** DynamoDB (DistributedTable) — PAY_PER_REQUEST, single-digit ms latency.
811
959
  - **Session storage:** S3 (FileBucket) — ~$0.023 per GB/month.
812
- - **Async execution:** SQS (AsyncJob)$0.40 per million messages.
813
- - **Streaming:** AppSync Events (Realtime) — $1.00 per million connection minutes.
814
- - **No timeout limit:** Agent runs in AsyncJob consumer Lambda (up to 15 min), not behind API Gateway.
960
+ - **Loop compute:** Bedrock AgentCore Runtime consumption-based (vCPU + memory while a session is active). See [AgentCore Runtime pricing](https://aws.amazon.com/bedrock/agentcore/pricing/).
961
+ - **Streaming:** API Gateway WebSocket (Realtime) — per-message + per-connection-minute pricing.
815
962
 
816
963
  ## Troubleshooting
817
964
 
@@ -827,4 +974,5 @@ When `inferenceOnly: true`, the two DistributedTables are skipped (no conversati
827
974
  - [Bedrock supported models](https://docs.aws.amazon.com/bedrock/latest/userguide/models-supported.html)
828
975
  - [Cross-region inference profiles](https://docs.aws.amazon.com/bedrock/latest/userguide/cross-region-inference.html)
829
976
  - [Bedrock pricing](https://aws.amazon.com/bedrock/pricing/)
977
+ - [Bedrock AgentCore Runtime pricing](https://aws.amazon.com/bedrock/agentcore/pricing/)
830
978
  - [Ollama model library](https://ollama.com/library)
@@ -2,7 +2,7 @@ import type { ScopeParent } from '@aws-blocks/core';
2
2
  import type { FileBucket } from '@aws-blocks/bb-file-bucket';
3
3
  import type { SnapshotStorage } from '@strands-agents/sdk';
4
4
  import type { S3StorageConfig } from '@strands-agents/sdk/session/s3-storage';
5
- import { AgentBase } from './agent.js';
5
+ import { AgentBase, type AgentTurnPayload } from './agent.js';
6
6
  import type { AgentConfig, DefaultToolContext } from './types.js';
7
7
  /**
8
8
  * Builds the deployed Agent's snapshot storage, pinning S3Storage to the Lambda
@@ -12,6 +12,20 @@ import type { AgentConfig, DefaultToolContext } from './types.js';
12
12
  */
13
13
  export declare function createDeployedSnapshotStorage(bucket: FileBucket, S3StorageImpl?: new (config: S3StorageConfig) => SnapshotStorage): SnapshotStorage;
14
14
  export declare class Agent<TContext = DefaultToolContext> extends AgentBase<TContext> {
15
+ private _agentCore?;
15
16
  constructor(scope: ScopeParent, id: string, config: AgentConfig<TContext>);
17
+ /**
18
+ * Run the turn on the AgentCore Runtime that hosts this agent's loop.
19
+ *
20
+ * Returns as soon as the runtime has ACCEPTED the turn: `agentcore-entry` starts `runAgent()`
21
+ * as a background async task (which streams chunks to Realtime under the runtime's own role)
22
+ * and responds immediately, so this `InvokeAgentRuntime` call does NOT hold the connection for
23
+ * the turn's duration — the loop keeps running server-side for up to the 8h session lifetime.
24
+ * `runtimeSessionId` is keyed by conversationId so a conversation's turns/resumes reuse one
25
+ * warm microVM.
26
+ *
27
+ * @internal Internal compute seam; not customer API.
28
+ */
29
+ protected dispatchTurn(payload: AgentTurnPayload<TContext>): Promise<void>;
16
30
  }
17
31
  //# sourceMappingURL=agent.aws.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"agent.aws.d.ts","sourceRoot":"","sources":["../src/agent.aws.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAC7D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAE3D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wCAAwC,CAAC;AAC9E,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAGlE;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAC5C,MAAM,EAAE,UAAU,EAClB,aAAa,GAAE,KAAK,MAAM,EAAE,eAAe,KAAK,eAA2B,GACzE,eAAe,CAEjB;AAED,qBAAa,KAAK,CAAC,QAAQ,GAAG,kBAAkB,CAAE,SAAQ,SAAS,CAAC,QAAQ,CAAC;gBAChE,KAAK,EAAE,WAAW,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,CAAC,QAAQ,CAAC;CAGzE"}
1
+ {"version":3,"file":"agent.aws.d.ts","sourceRoot":"","sources":["../src/agent.aws.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAE7D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAE3D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wCAAwC,CAAC;AAC9E,OAAO,EAAE,SAAS,EAAE,KAAK,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE9D,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAalE;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAC5C,MAAM,EAAE,UAAU,EAClB,aAAa,GAAE,KAAK,MAAM,EAAE,eAAe,KAAK,eAA2B,GACzE,eAAe,CAEjB;AAED,qBAAa,KAAK,CAAC,QAAQ,GAAG,kBAAkB,CAAE,SAAQ,SAAS,CAAC,QAAQ,CAAC;IAC5E,OAAO,CAAC,UAAU,CAAC,CAAyB;gBAEhC,KAAK,EAAE,WAAW,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,CAAC,QAAQ,CAAC;IAIzE;;;;;;;;;;;OAWG;cACsB,YAAY,CAAC,OAAO,EAAE,gBAAgB,CAAC,QAAQ,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;CA4BzF"}
package/dist/agent.aws.js CHANGED
@@ -1,8 +1,21 @@
1
1
  // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
2
  // SPDX-License-Identifier: Apache-2.0
3
+ import { createHash } from 'node:crypto';
4
+ import { getConfig } from '@aws-blocks/core';
5
+ import { BedrockAgentCoreClient, InvokeAgentRuntimeCommand } from '@aws-sdk/client-bedrock-agentcore';
3
6
  import { S3Storage } from '@strands-agents/sdk/session/s3-storage';
4
7
  import { AgentBase } from './agent.js';
8
+ import { AgentErrors, blocksAgentError } from './errors.js';
5
9
  import { BedrockModels } from './models.js';
10
+ /**
11
+ * AgentCore requires a `runtimeSessionId` of at least 33 characters. Conversation/channel ids
12
+ * are UUIDs (36 chars) in the normal path and pass through unchanged; anything shorter is hashed
13
+ * to a stable 64-char hex id — stable per input, so a conversation keeps routing to one warm
14
+ * microVM across turns/resumes.
15
+ */
16
+ function toRuntimeSessionId(base) {
17
+ return base.length >= 33 ? base : createHash('sha256').update(base).digest('hex');
18
+ }
6
19
  /**
7
20
  * Builds the deployed Agent's snapshot storage, pinning S3Storage to the Lambda
8
21
  * execution region (`AWS_REGION`) so non-us-east-1 deploys use the correct regional
@@ -13,7 +26,43 @@ export function createDeployedSnapshotStorage(bucket, S3StorageImpl = S3Storage)
13
26
  return new S3StorageImpl({ bucket: bucket.fullId, region: process.env.AWS_REGION });
14
27
  }
15
28
  export class Agent extends AgentBase {
29
+ _agentCore;
16
30
  constructor(scope, id, config) {
17
31
  super(scope, id, config, config.model?.deployed ?? BedrockModels.BALANCED, createDeployedSnapshotStorage);
18
32
  }
33
+ /**
34
+ * Run the turn on the AgentCore Runtime that hosts this agent's loop.
35
+ *
36
+ * Returns as soon as the runtime has ACCEPTED the turn: `agentcore-entry` starts `runAgent()`
37
+ * as a background async task (which streams chunks to Realtime under the runtime's own role)
38
+ * and responds immediately, so this `InvokeAgentRuntime` call does NOT hold the connection for
39
+ * the turn's duration — the loop keeps running server-side for up to the 8h session lifetime.
40
+ * `runtimeSessionId` is keyed by conversationId so a conversation's turns/resumes reuse one
41
+ * warm microVM.
42
+ *
43
+ * @internal Internal compute seam; not customer API.
44
+ */
45
+ async dispatchTurn(payload) {
46
+ const runtimeArnKey = `BB_AGENT_${this.fullId}_RUNTIME_ARN`;
47
+ const runtimeArn = await getConfig(runtimeArnKey);
48
+ if (!runtimeArn) {
49
+ throw blocksAgentError(AgentErrors.StreamFailed, `AgentCore Runtime ARN not found (config key ${runtimeArnKey}). Ensure the app build produced the AgentCore asset and the stack deployed the Runtime.`);
50
+ }
51
+ this._agentCore ??= new BedrockAgentCoreClient({});
52
+ const body = {
53
+ prompt: payload.message,
54
+ channelId: payload.channelId,
55
+ conversationId: payload.conversationId,
56
+ userId: payload.userId,
57
+ interruptResponses: payload.interruptResponses,
58
+ context: payload.context,
59
+ };
60
+ await this._agentCore.send(new InvokeAgentRuntimeCommand({
61
+ agentRuntimeArn: runtimeArn,
62
+ runtimeSessionId: toRuntimeSessionId(payload.conversationId ?? payload.channelId),
63
+ contentType: 'application/json',
64
+ accept: 'application/json',
65
+ payload: new TextEncoder().encode(JSON.stringify(body)),
66
+ }));
67
+ }
19
68
  }
package/dist/agent.d.ts CHANGED
@@ -4,15 +4,46 @@ import { FileBucket } from '@aws-blocks/bb-file-bucket';
4
4
  import type { ChildLogger } from '@aws-blocks/bb-logger';
5
5
  import type { SnapshotStorage } from '@strands-agents/sdk';
6
6
  import type { AgentConfig, AgentStreamResult, StreamOptions, Message, Conversation, ModelConfig, InterruptResponse, DefaultToolContext } from './types.js';
7
+ /**
8
+ * A single agent turn to dispatch (initial message or HITL resume). Passed to
9
+ * {@link AgentBase.dispatchTurn} — run in-process locally, or shipped to the AgentCore Runtime
10
+ * on AWS as the `InvokeAgentRuntime` payload.
11
+ *
12
+ * @internal Internal turn-dispatch seam — not part of the public API. Customers use
13
+ * `stream()` / `resume()`; this is the shape those hand to the compute layer.
14
+ */
15
+ export interface AgentTurnPayload<TContext = DefaultToolContext> {
16
+ /** User prompt. Empty on resume (interruptResponses drive the turn instead). */
17
+ message: string;
18
+ /** Conversation to persist to / restore the session from (undefined for inferenceOnly). */
19
+ conversationId?: string;
20
+ /** Realtime channel the client subscribes to for this turn's chunks. */
21
+ channelId: string;
22
+ /** Conversation owner. */
23
+ userId: string;
24
+ /** HITL resume: approval responses to apply instead of a new prompt. */
25
+ interruptResponses?: Array<{
26
+ interruptId: string;
27
+ response: string;
28
+ }>;
29
+ /** Per-call tool context, forwarded to tool invocations. JSON-serializable. */
30
+ context?: TContext;
31
+ }
32
+ /** @internal Register a live Agent instance so the AgentCore entrypoint can find it by fullId. */
33
+ export declare function registerAgentInstance(fullId: string, agent: AgentBase<any>): void;
34
+ /** @internal Look up a registered Agent instance by fullId (used by the AgentCore entrypoint). */
35
+ export declare function getAgentInstance(fullId: string): AgentBase<any> | undefined;
7
36
  /**
8
37
  * Base class for the Agent BB. Extended by agent.mock.ts (model.local) and agent.aws.ts (model.deployed).
9
38
  *
10
- * Creates up to 4 internal BBs depending on mode:
39
+ * Creates internal BBs depending on mode:
11
40
  * - FileBucket: session snapshot storage for Strands SessionManager (always)
12
41
  * - DistributedTable: frontend message history (when inferenceOnly = false)
13
- * - Realtime: streaming chunks to browser + AsyncJob result delivery (always)
14
- * - AsyncJob: runs Strands agent asynchronously (always)
15
- * - TODO logging
42
+ * - Realtime: streaming chunks to browser (always)
43
+ *
44
+ * The agent loop runs via {@link dispatchTurn}: in-process locally (mock), and on the
45
+ * AgentCore Runtime on AWS (the deployed subclass overrides dispatchTurn to invoke it). The
46
+ * AgentCore Runtime itself is provisioned by the CDK layer (index.cdk.ts → AgentCoreRuntime).
16
47
  */
17
48
  export declare class AgentBase<TContext = DefaultToolContext> extends Scope {
18
49
  /** Developer-facing agent configuration. */
@@ -25,8 +56,6 @@ export declare class AgentBase<TContext = DefaultToolContext> extends Scope {
25
56
  private messages?;
26
57
  /** Realtime pub/sub — streams chunks to browser. */
27
58
  private rt;
28
- /** Internal async job — runs the Strands agent in a separate execution context. */
29
- private job;
30
59
  /** Which model provider to use. */
31
60
  private modelConfig;
32
61
  /** Where to persist Strands agent state (snapshots). */
@@ -43,31 +72,70 @@ export declare class AgentBase<TContext = DefaultToolContext> extends Scope {
43
72
  * @param createSnapshotStorage - factory that receives the internal FileBucket and returns the appropriate SnapshotStorage
44
73
  */
45
74
  constructor(scope: ScopeParent, id: string, config: AgentConfig<TContext>, modelConfig: ModelConfig | ModelConfig[] | undefined, createSnapshotStorage: (bucket: FileBucket) => SnapshotStorage);
75
+ /**
76
+ * Run one agent turn (initial message or HITL resume) and publish its chunks to Realtime.
77
+ *
78
+ * This is the single execution entry the compute layer invokes: the AgentCore Runtime
79
+ * entrypoint (agentcore-entry.ts) calls it as a background async task on AWS, and locally
80
+ * {@link dispatchTurn} calls it in-process. Errors are caught and published as an `error`
81
+ * chunk (not re-thrown) so the client never hangs and a non-idempotent turn isn't retried.
82
+ *
83
+ * @param payload - the turn to run (see {@link AgentTurnPayload}).
84
+ * @internal Invoked by the compute layer (agentcore-entry / dispatchTurn), not customer API.
85
+ */
86
+ invokeTurn(payload: AgentTurnPayload<TContext>): Promise<void>;
87
+ /**
88
+ * Dispatch a turn to wherever the agent loop runs, returning promptly so `stream()`/`resume()`
89
+ * hand the client a `channelId` without waiting for the turn to finish.
90
+ *
91
+ * Base (local/mock): run the loop IN-PROCESS, fire-and-forget — chunks flow to the mock
92
+ * Realtime as the turn progresses. The deployed (AWS) subclass overrides this to invoke the
93
+ * AgentCore Runtime, which runs the loop as a background task and publishes to Realtime.
94
+ *
95
+ * @internal Internal compute seam (overridden by the AWS subclass); not customer API.
96
+ */
97
+ protected dispatchTurn(payload: AgentTurnPayload<TContext>): Promise<void>;
46
98
  /**
47
99
  * Executes the Strands agent, publishes chunks to Realtime, persists messages to DynamoDB.
48
100
  *
49
- * Called by: AsyncJob consumer.
50
- * NOT called directly stream() submits to AsyncJob, which invokes this.
101
+ * Called by {@link invokeTurn} (wherever the loop runs — locally in-process, or inside the
102
+ * AgentCore Runtime container on AWS). Publishes each event to the Realtime `chunks` channel.
103
+ *
104
+ * Flow: invokeTurn() → runAgent() → Strands agent.stream() → publishes chunks to Realtime BB
51
105
  *
52
- * Flow: AsyncJob handler runAgent() Strands agent.stream() publishes chunks to Realtime BB
53
- * TODO add comments for args
106
+ * @param message - the user message that starts the turn (ignored on the resume path)
107
+ * @param conversationId - conversation to load/persist history for; undefined means no persistence
108
+ * @param channelId - Realtime channel the stream chunks are published to
109
+ * @param userId - owner of the conversation, stored on every persisted message
110
+ * @param interruptResponses - approval responses when resuming a turn paused on a HITL interrupt
111
+ * @param context - per-call tool context, threaded to tool handlers via Strands invocationState
54
112
  */
55
113
  private runAgent;
56
114
  private createStrandsAgent;
57
115
  /**
58
116
  * Submit a message to the agent. Returns immediately with a channelId.
59
117
  *
60
- * Flow: stream() → AsyncJob.submit() → returns { channelId }
61
- * The AsyncJob consumer calls runAgent() separately.
62
- * Chunks are published to Realtime on the returned channelId.
118
+ * Flow: stream() → dispatchTurn() → returns { channelId }
119
+ * dispatchTurn runs the loop where the compute lives (in-process locally; on the AgentCore
120
+ * Runtime on AWS) and publishes chunks to Realtime on the returned channelId.
63
121
  *
64
122
  * Subscribe to chunks via result.channel, or await result.complete() for the final response.
123
+ *
124
+ * Errors surface on two paths. Once the turn is dispatched, loop failures arrive as an `error`
125
+ * chunk on the channel (and reject `complete()`). A failure to *dispatch* the turn — e.g. on AWS
126
+ * when the AgentCore Runtime can't be invoked (unresolved runtime ARN, or an `InvokeAgentRuntime`
127
+ * error) — rejects this `stream()` call itself rather than reaching the channel. Always `await`
128
+ * `stream()` so a dispatch failure isn't lost.
65
129
  */
66
130
  stream(message: string, options?: StreamOptions<TContext>): Promise<AgentStreamResult>;
67
131
  /**
68
132
  * Resume an interrupted agent with user's responses.
69
- * Submits a new AsyncJob that loads the session and continues from the interrupt point.
133
+ * Dispatches a new turn that loads the session and continues from the interrupt point.
70
134
  * Chunks are published to the same channelId — use the existing subscription or call complete() again to wait for the result.
135
+ *
136
+ * Like `stream()`, errors surface on two paths: loop failures arrive as an `error` chunk on the
137
+ * channel once the turn is dispatched, while a failure to *dispatch* (e.g. on AWS when the AgentCore
138
+ * Runtime can't be invoked) rejects this `resume()` call itself. Always `await` it.
71
139
  */
72
140
  resume(channelId: string, responses: Array<InterruptResponse>, options?: {
73
141
  conversationId?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"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"}