Two hackathon-ready Next.js references for building task-backed agent applications with the latest Introspection Node SDK and AG-UI.
| Template | Best for | Included |
|---|---|---|
agent-chat |
One configured runtime | Concurrent chats, streaming, task history, refresh/resume, HITL, cancel, archive |
agent-workspace |
Several configured runtimes | Everything in Agent Chat plus runtime-grouped workspaces and concurrent runtime sessions |
| Agent Chat | Agent Workspace |
|---|---|
![]() |
![]() |
Start with Agent Chat unless users need to choose between different agent runtimes. It has the complete chat lifecycle without workspace configuration. Choose Agent Workspace when one product should expose several runtime-backed workspaces, such as coding and research agents.
The apps intentionally share the same chat core while keeping their route and authentication glue local. This makes either app independently copyable without hiding the important server boundary in a framework package.
pnpm install
cp apps/agent-chat/.env.example apps/agent-chat/.env.local
pnpm dev:chatRequired configuration:
INTROSPECTION_CP_URL=https://api.staging.introspection.dev
INTROSPECTION_TOKEN=intro_...
INTROSPECTION_RUNTIME=coding-agent
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=sb_publishable_...INTROSPECTION_PROJECT is optional when the API key already selects the project. In Supabase, enable email/password authentication and add http://localhost:3202/auth/callback or http://localhost:3203/auth/callback to the permitted redirect URLs. Use https://api.introspection.dev when targeting production.
INTROSPECTION_RUNTIME (Agent Chat) and INTROSPECTION_WORKSPACES_JSON (Agent Workspace) are now optional pins. Without them, the apps discover the project's active runtimes through the Node SDK (client.runtimes.list) and populate the sidebar from what is actually deployed.
Browser
-> protected same-origin Next.js route
-> authenticated Supabase user ID
-> Introspection Node SDK runner with that identity
-> task start or task run creation
-> NDJSON stream through the same route
-> AG-UI AbstractAgent reduces events into session state
-> React renders messages, tools, status, and HITL controls
New chats are task-first: the first prompt starts an Introspection task, and the UI promotes its local draft session to the returned task ID. Later messages create runs on that task. The user message appears optimistically, so sending is immediate even while the task or run starts.
Each active chat has an independent Zustand session. Switching chats does not stop background streams. React Query owns task-list caching and invalidation, while Introspection remains the source of truth for persisted tasks, transcripts, and run state. On refresh, the app loads the transcript and attaches to an active run when one exists.
Both templates use Supabase Auth for application users and a project API key used only by server-side Introspection code.
| State | Owner | Notes |
|---|---|---|
| Login session | Supabase Auth | The validated claim sub becomes the Introspection identity user ID. |
| Tasks, transcripts, runs | Introspection | A task is the chat record; no second chat database is required. |
| Live chat sessions | Zustand | One independent client store per open/draft task. |
| Task lists | React Query | Cached, polled while active, and invalidated after mutations. |
| Workspace membership | Introspection task metadata | starter_template.workspace_key groups tasks in Agent Workspace. |
The browser receives the Supabase publishable key, which is designed for public clients, but never receives an Introspection token, Data Plane URL, or Data Plane cookie. Every runner uses the authenticated Supabase user.id as its Introspection identity, so task history is user-scoped.
The API currently treats operation and message IDs as metadata rather than enforced idempotency keys. Applications that automatically retry ambiguous task-creation failures should reconcile those IDs before retrying.
Before a public deployment: add per-user rate limiting to the protected agent routes. Authentication prevents anonymous API-key use but does not by itself control how much an authenticated user can spend.
apps/
agent-chat/ # Single-runtime Next.js app
src/app/api/introspection # Protected task/run route handlers
src/app/auth # Supabase sign-in and callback
src/lib/runtime-targets.ts# Server-side runtime configuration
src/lib/template-user.ts # Supabase claim -> Introspection identity
agent-workspace/ # Multi-runtime Next.js app with the same boundaries
packages/
chat-core/src/
chat-app.tsx # Sidebar, composer, messages, HITL, task queries
pending-interrupt-panel.tsx # Approval/question panel shown in place of the composer
client.ts # Same-origin API client and NDJSON run handle
session.ts # Per-chat AG-UI/Zustand lifecycle
session-registry.ts # Concurrent session registry
transcript.ts # Introspection transcript -> AG-UI messages
server/node-api.ts # Server-only Node SDK task/run operations + runtime discovery
styles.css # Shared starter UI
introspection/
email-agent/ # Pi recipe: Gmail assistant, sends gated by a tool_call approval hook
multi-research-agent/ # Pi recipe: coordinator + researcher/writer subagents
meeting-actionables/ # Pi recipe: meetings -> structured actionables
.introspection/ # Deployment manifests for the three recipes above
tests/
e2e/chat.spec.ts # Browser user-flow coverage for both templates
fake-supabase.mjs # Local auth boundary used only by automated tests
docs/images/ # Real staging-backed screenshots
The four route handlers in each app are deliberately thin adapters around chat-core/server. runtime-targets.ts is the allowlist boundary: the browser sends a target key, never an arbitrary runtime name.
- Change runtime labels and runtime names in each app's
runtime-targets.tsor environment variables. - Restyle the shared experience in
packages/chat-core/src/styles.css, or replace the UI while retaining the session and server layers. - Extend tool rendering in
chat-app.tsxand the HITL experience inpending-interrupt-panel.tsxwithout changing stream transport. While interrupts are pending, the panel replaces the composer; approvals resolve with{ approved: true }or{ approved: false, feedback }, matching interrupts raised with reasonconfirmation(for example by the email-agent recipe's send-approval hook). - Add application data to Supabase when your product needs projects, teams, billing, or domain records. Chat persistence itself does not require another table.
- Add pagination, rate limits, observability, and deployment-specific duration limits before production scale.
The route handlers and SDK streams use a five-minute execution limit. The sidebar intentionally shows the 50 most recent chats per workspace; adapt pagination and deployment limits for longer-running production workloads.
pnpm lint
pnpm typecheck
pnpm test
pnpm build
pnpm test:e2eThe browser suite covers authentication, optimistic sending, concurrent chats, switching during streams, refresh/resume, deep links, cancellation, archive, HITL, workspace isolation, and mobile navigation. The screenshots above were captured from real staging-backed multi-turn sessions; the fake Supabase server is only an automated-test fixture.

