aws-samples / aws-samples/agents4energy

Render AgentCore-memory chat via AG-UI + CopilotKit (drop brittle double-parse)

Open
#192 2 comments 0 reactions 0 assignees View on GitHub
enhancement javascript
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.