apps/docs/skills/recipe-embedded-app-agent/SKILL.md
Full recipe for embedding an agent in a Next.js app with streaming API routes, React hooks, progressive disclosure of reasoning steps, and error handling.
npx skillsauth add tylerjrbuell/reactive-agents-ts recipe-embedded-app-agentInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
A complete Next.js application with an agent embedded end-to-end: streaming API route on the server, React streaming hook on the client, progressive display of reasoning steps, and production-safe error handling. Adaptable to Vue and Svelte with equivalent packages.
ui-integration — AgentStream.toSSE(), useAgentStream, framework hooksreasoning-strategy-selection — adaptive strategy for interactive usecost-budget-enforcement — per-session budgetsapp/
api/
agent/
route.ts ← server: agent build + SSE streaming
components/
AgentChat.tsx ← client: useAgentStream hook + UI
page.tsx ← render AgentChat
import { ReactiveAgents, AgentStream } from "@reactive-agents/runtime";
export const runtime = "nodejs"; // required for streaming
export async function POST(req: Request) {
const { prompt } = await req.json();
if (!prompt || typeof prompt !== "string") {
return new Response(JSON.stringify({ error: "prompt required" }), {
status: 400,
headers: { "Content-Type": "application/json" },
});
}
const agent = await ReactiveAgents.create()
.withName("app-agent")
.withProvider("anthropic")
.withReasoning({
defaultStrategy: "adaptive",
maxIterations: 10,
})
.withTools({
allowedTools: ["web-search", "http-get", "checkpoint", "final-answer"],
})
.withCostTracking({ perSession: 0.25 })
.withObservability({ verbosity: "minimal" })
.build();
// Stream events with full density so the client can show tool calls
return AgentStream.toSSE(
agent.runStream(prompt, { density: "full" })
);
}
"use client";
import { useState } from "react";
import { useAgentStream } from "@reactive-agents/react";
export function AgentChat() {
const [input, setInput] = useState("");
const { text, events, status, error, output, run, cancel } = useAgentStream("/api/agent");
const toolEvents = events.filter(
(e) => e.type === "ToolCallStart" || e.type === "ToolCallResult"
);
return (
<div className="chat">
<div className="input-row">
<input
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={(e) => e.key === "Enter" && !e.shiftKey && run(input)}
placeholder="Ask anything..."
disabled={status === "streaming"}
/>
{status === "streaming" ? (
<button onClick={cancel}>Stop</button>
) : (
<button onClick={() => run(input)} disabled={!input.trim()}>
Send
</button>
)}
</div>
{/* Show tool calls as they happen */}
{toolEvents.length > 0 && (
<div className="tool-trace">
{toolEvents.map((e, i) => (
<div key={i} className="tool-step">
{e.type === "ToolCallStart" && `⚙ ${e.toolName}...`}
{e.type === "ToolCallResult" && `✓ ${e.toolName}`}
</div>
))}
</div>
)}
{/* Stream text as it arrives */}
{text && (
<div className="response">
{text}
{status === "streaming" && <span className="cursor">▋</span>}
</div>
)}
{error && (
<div className="error">
{error.message.includes("Budget") ? "Usage limit reached." : "Something went wrong."}
</div>
)}
</div>
);
}
<!-- components/AgentChat.vue -->
<script setup lang="ts">
import { ref } from "vue";
import { useAgentStream } from "@reactive-agents/vue";
const input = ref("");
const { text, status, error, run, cancel } = useAgentStream("/api/agent");
</script>
<template>
<div>
<input v-model="input" @keydown.enter="run(input)" :disabled="status === 'streaming'" />
<button @click="cancel" v-if="status === 'streaming'">Stop</button>
<button @click="run(input)" v-else>Send</button>
<p>{{ text }}</p>
<p v-if="error" style="color:red">{{ error.message }}</p>
</div>
</template>
<!-- src/routes/chat/+page.svelte -->
<script lang="ts">
import { createAgentStream } from "@reactive-agents/svelte";
const agent = createAgentStream("/api/agent");
</script>
<input
bind:value={$agent.input}
on:keydown={(e) => e.key === "Enter" && $agent.run($agent.input)}
/>
<button on:click={() => $agent.cancel()} disabled={$agent.status !== "streaming"}>Stop</button>
<p>{$agent.text}</p>
When streaming isn't needed (e.g., background processing):
// Server — returns JSON instead of SSE
export async function POST(req: Request) {
const { prompt } = await req.json();
const agent = await ReactiveAgents.create()
.withProvider("anthropic")
.withTools()
.build();
const result = await agent.run(prompt);
return Response.json({ output: result.output, cost: result.cost });
}
// Client
import { useAgent } from "@reactive-agents/react";
const { output, loading, error, run } = useAgent("/api/agent");
export const runtime = "nodejs" is required in Next.js App Router — edge runtime does not support all Node.js APIs used by the agentPOST(), not at module level — module-level agents share state across concurrent requestsdensity: "full" sends tool call events — the client must filter for TextDelta if only displaying textuseAgentStream manages AbortController internally — cancel() sends an abort signal to the serverAgentStream.toSSE() calls agent.dispose() automatically on stream completiondevelopment
Orient to the Reactive Agents framework, understand the builder API shape, and select the right capability skills for your task.
testing
Enable output verification (hallucination detection, semantic entropy, self-consistency), add post-run verification steps, and run LLM-scored evals across 5 quality dimensions.
data-ai
Configure per-provider behavior, understand streaming quirks, and use the 7-hook adapter system for optimal performance across LLM providers.
data-ai
Configure the 4-layer memory system with SQLite/FTS5/vec storage for persistent agent knowledge that survives sessions.