Skip to content

Commit 0a485da

Browse files
authored
feat(memory): add actor and session lists (#1955)
1 parent ca6426d commit 0a485da

17 files changed

Lines changed: 708 additions & 121 deletions

File tree

src/components/Root.tsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ import { MemoryEventListScreen } from "../handlers/memory/event/list/screen.tsx"
5454
import { MemoryRecordScreen } from "../handlers/memory/record/screen.tsx";
5555
import { MemoryRecordGetScreen } from "../handlers/memory/record/get/screen.tsx";
5656
import { MemoryRecordListScreen } from "../handlers/memory/record/list/screen.tsx";
57+
import { MemoryActorScreen } from "../handlers/memory/actor/screen.tsx";
58+
import { MemoryActorListScreen } from "../handlers/memory/actor/list/screen.tsx";
59+
import { MemorySessionScreen } from "../handlers/memory/session/screen.tsx";
60+
import { MemorySessionListScreen } from "../handlers/memory/session/list/screen.tsx";
5761
import { IdentityScreen } from "../handlers/identity/screen.tsx";
5862
import { ApiKeyCredentialProviderScreen } from "../handlers/identity/api-key-credential-provider/screen.tsx";
5963
import { ApiKeyCredentialProviderListScreen } from "../handlers/identity/api-key-credential-provider/list/screen.tsx";
@@ -524,6 +528,34 @@ export function Root({ path, ctx, core, queryClient }: RootProps) {
524528
path="agentcore/memory/record/list/:memoryId/:scopeKind/:scope"
525529
element={<MemoryRecordListScreen ctx={ctx} core={core} />}
526530
/>
531+
<Route
532+
path="agentcore/memory/actor"
533+
element={<MemoryActorScreen ctx={ctx} core={core} />}
534+
/>
535+
<Route
536+
path="agentcore/memory/actor/list"
537+
element={<MemoryActorListScreen ctx={ctx} core={core} />}
538+
/>
539+
<Route
540+
path="agentcore/memory/actor/list/:memoryId"
541+
element={<MemoryActorListScreen ctx={ctx} core={core} />}
542+
/>
543+
<Route
544+
path="agentcore/memory/session"
545+
element={<MemorySessionScreen ctx={ctx} core={core} />}
546+
/>
547+
<Route
548+
path="agentcore/memory/session/list"
549+
element={<MemorySessionListScreen ctx={ctx} core={core} />}
550+
/>
551+
<Route
552+
path="agentcore/memory/session/list/:memoryId"
553+
element={<MemorySessionListScreen ctx={ctx} core={core} />}
554+
/>
555+
<Route
556+
path="agentcore/memory/session/list/:memoryId/:actorId"
557+
element={<MemorySessionListScreen ctx={ctx} core={core} />}
558+
/>
527559
<Route path="agentcore/identity" element={<IdentityScreen ctx={ctx} core={core} />} />
528560
<Route
529561
path="agentcore/identity/api-key-credential-provider"
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import { afterEach, describe, expect, test } from "bun:test";
2+
import type { ActorSummary, SessionSummary } from "@aws-sdk/client-bedrock-agentcore";
3+
import type { MemorySummary } from "@aws-sdk/client-bedrock-agentcore-control";
4+
import {
5+
cleanupScreens,
6+
renderScreen,
7+
TestCoreClient,
8+
waitFor,
9+
waitForText,
10+
} from "../../../testing";
11+
12+
afterEach(cleanupScreens);
13+
14+
function memorySummary(overrides: Partial<MemorySummary> = {}): MemorySummary {
15+
return {
16+
arn: "arn:aws:bedrock-agentcore:us-east-1:123456789012:memory/memory-1",
17+
id: "memory-1",
18+
status: "ACTIVE",
19+
createdAt: new Date("2026-07-19T01:02:03.000Z"),
20+
updatedAt: new Date("2026-07-20T12:34:56.000Z"),
21+
...overrides,
22+
};
23+
}
24+
25+
function actor(overrides: Partial<ActorSummary> = {}): ActorSummary {
26+
return {
27+
actorId: "actor-1",
28+
...overrides,
29+
};
30+
}
31+
32+
function session(overrides: Partial<SessionSummary> = {}): SessionSummary {
33+
return {
34+
sessionId: "session-1",
35+
actorId: "actor-1",
36+
createdAt: new Date("2026-08-02T10:20:30.000Z"),
37+
...overrides,
38+
};
39+
}
40+
41+
describe("Memory actor list flow", () => {
42+
test("uses the Memory picker, lists actors, then opens the actor's sessions", async () => {
43+
const memoryId = "memory/blue one";
44+
const actorId = "actor/blue one";
45+
const core = new TestCoreClient();
46+
core.memory.setListResponse({ memories: [memorySummary({ id: memoryId })] });
47+
core.memory.setListActorsResponse({ actorSummaries: [actor({ actorId })] });
48+
core.memory.setListSessionsResponse({
49+
sessionSummaries: [session({ actorId })],
50+
});
51+
const screen = renderScreen("/agentcore/memory/actor/list", { core });
52+
53+
await waitForText(screen.lastFrame, memoryId);
54+
await screen.press("return");
55+
await waitForText(screen.lastFrame, actorId);
56+
await screen.press("return");
57+
await waitForText(screen.lastFrame, "session-1");
58+
59+
expect(core.memory.calls.find((call) => call.method === "listActors")).toEqual({
60+
method: "listActors",
61+
args: [
62+
{
63+
memoryId,
64+
maxResults: expect.any(Number),
65+
nextToken: undefined,
66+
},
67+
{ region: "us-east-1" },
68+
],
69+
});
70+
expect(core.memory.calls.find((call) => call.method === "listSessions")).toEqual({
71+
method: "listSessions",
72+
args: [
73+
{
74+
memoryId,
75+
actorId,
76+
maxResults: expect.any(Number),
77+
nextToken: undefined,
78+
},
79+
{ region: "us-east-1" },
80+
],
81+
});
82+
83+
await screen.press("escape");
84+
await waitForText(screen.lastFrame, "choose an actor to list sessions for");
85+
await screen.press("escape");
86+
await waitForText(screen.lastFrame, "choose a Memory to list actors for");
87+
});
88+
89+
test("shows empty and retry states for actor lists", async () => {
90+
const empty = renderScreen("/agentcore/memory/actor/list/memory-1");
91+
await waitForText(empty.lastFrame, "No actors found for Memory memory-1.");
92+
empty.unmount();
93+
94+
const core = new TestCoreClient();
95+
core.memory.setError(new Error("actors unavailable"));
96+
const failed = renderScreen("/agentcore/memory/actor/list/memory-1", { core });
97+
98+
await waitForText(failed.lastFrame, "actors unavailable");
99+
core.memory.setError(undefined);
100+
core.memory.setListActorsResponse({ actorSummaries: [actor()] });
101+
await failed.write("r");
102+
await waitFor(() => failed.lastFrame()?.includes("actor-1") ?? false);
103+
});
104+
});
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { Router } from "../../../router";
2+
import { renderTui } from "../../../tui";
3+
import type { AppIO } from "../../../io";
4+
import type { Core } from "../../types";
5+
import { createListMemoryActorsHandler } from "./list";
6+
7+
export function createMemoryActorHandler(core: Core, io: AppIO): Router {
8+
return new Router("actor", "inspect actors in AgentCore Memories")
9+
.default(renderTui(core, io))
10+
.handler(createListMemoryActorsHandler(core));
11+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import z from "zod";
2+
import { InputValidationError } from "../../../../errors";
3+
import { createHandler, flag } from "../../../../router";
4+
import { JsonRendererKey } from "../../../../tui";
5+
import type { Core } from "../../../types";
6+
import { coreOptsFromCtx } from "../../../utils";
7+
8+
export const createListMemoryActorsHandler = (core: Core) =>
9+
createHandler({
10+
name: "list",
11+
description: "list actors in an AgentCore Memory",
12+
flags: [
13+
flag("memory", "the ID of the Memory", z.string().optional()),
14+
flag("max-results", "maximum number of actors to return", z.number().optional()),
15+
flag("next-token", "pagination token returned by a previous request", z.string().optional()),
16+
],
17+
handle: async (ctx, flags) => {
18+
if (!flags.memory) {
19+
throw new InputValidationError("required option '--memory <memory>' not specified");
20+
}
21+
22+
const response = await core.memory.listActors(
23+
{
24+
memoryId: flags.memory,
25+
maxResults: flags["max-results"],
26+
nextToken: flags["next-token"],
27+
},
28+
coreOptsFromCtx(ctx),
29+
);
30+
31+
ctx.require(JsonRendererKey).renderJson(response);
32+
},
33+
});
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { useNavigate, useParams } from "react-router";
2+
import { MemoryPicker } from "../../../../components/MemoryPicker";
3+
import type { ScreenProps } from "../../../types";
4+
import { MemoryActorPicker } from "../../listPickers";
5+
6+
export function MemoryActorListScreen(props: ScreenProps) {
7+
const navigate = useNavigate();
8+
const { memoryId } = useParams();
9+
10+
if (!memoryId) {
11+
return (
12+
<MemoryPicker
13+
{...props}
14+
breadcrumb={["agentcore", "memory", "actor", "list"]}
15+
description="choose a Memory to list actors for"
16+
onSelect={(id) => navigate(`/agentcore/memory/actor/list/${encodeURIComponent(id)}`)}
17+
/>
18+
);
19+
}
20+
21+
return (
22+
<MemoryActorPicker
23+
{...props}
24+
memoryId={memoryId}
25+
breadcrumb={["agentcore", "memory", "actor", "list", memoryId]}
26+
description="choose an actor to list sessions for"
27+
onSelect={(actorId) =>
28+
navigate(
29+
`/agentcore/memory/session/list/${encodeURIComponent(memoryId)}/${encodeURIComponent(actorId)}`,
30+
)
31+
}
32+
onBack={() => navigate(-1)}
33+
/>
34+
);
35+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { RouterScreen } from "../../../components/RouterScreen";
2+
import type { ScreenProps } from "../../types";
3+
4+
export function MemoryActorScreen(props: ScreenProps) {
5+
return <RouterScreen {...props} path={["agentcore", "memory", "actor"]} />;
6+
}

0 commit comments

Comments
 (0)