aws-samples / aws-samples/agents4energy
Render AgentCore-memory chat via AG-UI + CopilotKit (drop brittle double-parse)
- Dominant language
- TypeScript
- Stars
- 48
- Forks
- 68
- PR merge metrics
- No merged PRs in 30d
Description
## Problem
Rendering restored chat history from AgentCore Memory is currently brittle and error-prone. The same event payload is parsed **twice** with a lossy string round-trip in between:
1. **Lambda** ([`list-session-messages/handler.ts`](web/amplify/functions/list-session-messages/handler.ts)) `JSON.parse`s each event, flattens it to a joined `text` string — but only if top-level text exists. A pure `toolResult` message yields `''`, so it falls back to returning the **raw JSON string**.
2. **Client** ([`use-initial-messages.ts`](web/app/(with-auth)/chat/use-initial-messages.ts)) `JSON.parse`s again — but `e.text` is *sometimes* flattened text and *sometimes* raw JSON, so the try/catch is load-bearing and the two paths silently diverge.
3. A non-standard `type: 'toolResult'` UIMessage part is invented, forcing custom un-packing logic in the render loop ([`page.tsx:248-278`](web/app/(with-auth)/chat/page.tsx#L248-L278)).
We're fighting the AI SDK's part model instead of using a stable, documented message schema.
## Decision
Adopt the **AG-UI protocol** on the client and render with **CopilotKit v2** (``), while **keeping the managed AgentCore Harness**.
Investigation (CopilotKit + AG-UI source) confirmed:
- CopilotKit's official AgentCore integration expects a self-written Strands/LangGraph agent emitting AG-UI events. **We are not doing that** — we keep the Harness.
- Instead, we implement a **client-side `AbstractAgent` subclass** (`@ag-ui/client`) that wraps our existing `InvokeHarnessCommand` call and translates the Converse binary event stream into AG-UI events (`TEXT_MESSAGE_START/CONTENT/END`, `TOOL_CALL_*`).
- CopilotKit v2 accepts a purely client-side agent via the `selfManagedAgents` prop (`CopilotKitProvider`) — **no Node runtime/bridge and no new infra required**.
- Restored history does **not** come for free (that only happens when the *agent* uses `AgentCoreMemorySessionManager` server-side, which we don't). We keep the `ListEvents` Lambda fetch, but parse the Converse `ContentBlock[]` **once** into AG-UI `Message[]` and pass them via the agent's `initialMessages` config.
## Net effect
Collapses "parse in Lambda → re-serialize → parse again on client → invent custom part → hand-extract in render" down to **"parse Converse → AG-UI `Message[]` once."** The double-parse, the lossy string round-trip, the custom `toolResult` part, and the bespoke render extractor all go away. The AI SDK (`@ai-sdk/react`, `ai`) is removed from the chat path.
## Plan
- [ ] Add deps: `@ag-ui/client`, `@copilotkit/react-core`, `@copilotkit/react-ui`
- [ ] Implement `HarnessAgent extends AbstractAgent` — wrap `InvokeHarnessCommand`, emit AG-UI events; carry per-`Agent` config (systemPrompt/modelId/MCP servers) via `forwardedProps`
- [ ] Implement `Converse ListEvents → AG-UI Message[]` mapper (parse once), fed to `initialMessages`
- [ ] Rewrite chat page to `` + ``
- [ ] Remove obsolete `agentcore-transport.ts` AI-SDK glue, custom `toolResult` part, and render extractor
- [ ] Update `docs/` (agentic-architecture data-flow: transport layer now AG-UI)
- [ ] Test streaming + history rendering end-to-end
## Key files
- `web/lib/agentcore-transport.ts` — becomes the `HarnessAgent` AG-UI adapter
- `web/app/(with-auth)/chat/use-initial-messages.ts` — parse-once → AG-UI `Message[]`
- `web/app/(with-auth)/chat/page.tsx` — CopilotKit provider + ``
- `web/amplify/functions/list-session-messages/handler.ts` — return structured parts, not re-serialized strings
Contributor guide
Research direction
Start with web/lib/agentcore-transport.ts, use-initial-messages.ts, page.tsx, and the list-session-messages handler.ts to trace the current history and streaming paths. Review the planned AG-UI and CopilotKit integration, then run the existing web checks and the end-to-end streaming and history-rendering test coverage. Done means structured history is parsed once, streaming and restored messages render through CopilotChat, and the obsolete AI SDK glue is removed.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- aws, typescript
- Domain
- backend-api-design, full-stack
- Issue type
- Refactor
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 38/100