Build an Agentic Travel App with Oracle Agent Memory, Agent Spec, and CopilotKit
Define an agent once in Oracle Agent Spec, run it on LangGraph over AG-UI, give it long-term memory on Oracle AI Database, and render it in CopilotKit with human-in-the-loop.
Oracle Agent Spec is an open, framework-agnostic way to describe an agent as portable JSON — define it once, run it on any supported runtime. This recipe wires three things together: an Agent Spec agent running on LangGraph, served over the open AG-UI protocol, rendered in a CopilotKit chat, with long-term memory on Oracle AI Database so it remembers you across sessions.
The example is a personal travel concierge: it remembers your preferences across sessions, searches flights, and books them with a human-in-the-loop confirmation card rendered by CopilotKit's generative UI.
How it works#
- The agent is defined declaratively with
pyagentspecand serialized to Agent Spec JSON. - The
ag_ui_agentspecadapter loads that JSON and serves it as a FastAPI AG-UI endpoint on the LangGraph runtime. - CopilotKit consumes the AG-UI endpoint with an
HttpAgent— the same protocol as any AG-UI agent. - Memory is the glue: a
recall_memorytool reads durable preferences from Oracle Agent Memory, and each turn is persisted after the response streams — then a small reconcile pass supersedes outdated facts so an updated preference wins next time.
CopilotKit (Next.js, V2) ──/api/copilotkit──▶ CopilotRuntime (HttpAgent)
│ AG-UI (SSE)
▼
Agent Spec JSON → ag_ui_agentspec (LangGraph)
recall_memory · search_flights · book_flight (HITL ClientTool)
│ recall + persist
▼
oracleagentmemory → Oracle AI DatabaseMemory: what's CopilotKit, what's Oracle#
Oracle does the remembering, CopilotKit does the conversing, and your agent code is the seam between them. CopilotKit never touches the database — to it, recall_memory is just a tool that returns text — and Oracle never sees the chat protocol. Swap Oracle for another store and the frontend doesn't change a line.
Try it live#
Talk to the concierge below. Tell it a travel preference, then open a new thread with "+ New thread" and ask about your preferences again — it recalls what you told it from memory persisted in Oracle AI Database, not from the current conversation.
Prerequisites#
- Python 3.12 (required —
oracleagentmemoryships a cp312 wheel),uv, Node.js 18+ - Docker (for the local Oracle AI Database) or your own Oracle AI Database
OPENAI_API_KEY(defaults use OpenAI via litellm)
Pre-release dependencies
This frontend uses CopilotKit V2 pre-release builds so Agent Spec's human-in-the-loop renders, and the ag_ui_agentspec adapter is installed from the ag-ui repo (not PyPI). Both are pinned in the manifests.
Start the database and run the agent#
git clone https://github.com/CopilotKit/CopilotKit.git
cd CopilotKit/examples/showcases/oracle-agent-memory
docker compose up -d # wait for "DATABASE IS READY TO USE"
./db/setup-db.sh # create the cookbook DB user (idempotent)
cd agent
cp .env.example .env # add your OPENAI_API_KEY
uv sync
uv run uvicorn concierge.server:app --reload --port 8000Run the frontend#
cd frontend
cp .env.local.example .env.local # optional; defaults to localhost:8000/run
npm install
npm run devOpen http://localhost:3000.
Try it#
Frontend at a glance: the left panel lists your conversation threads with a "+ New thread" button. search_flights renders interactive flight-option cards with a "Select this flight" button, recall_memory shows a "🧠 Remembered your preferences" chip you can click to expand and see exactly which preferences it pulled, and booking surfaces a "Confirm your booking" card (Confirm / Cancel) that stamps into a boarding-pass ticket once confirmed.
- Tell it: "I'm vegetarian, I fly from SFO, and I prefer an aisle seat."
- Click "+ New thread" in the sidebar (instead of reloading), then ask: "Find me a flight to Amsterdam."
- It recalls your preferences from Oracle — home airport SFO, aisle seat, vegetarian meal — and surfaces flights like AMS-001 (KLM KL606, nonstop, $740) personalized to what it remembered, not what you said in this thread.
Book it: select a flight from the cards (or ask "Book me flight AMS-001 to Amsterdam"), then click Confirm & book on the confirmation card to receive the boarding pass. book_flight is implemented as a CopilotKit ClientTool (useHumanInTheLoop) executed on the frontend, so the confirm→book step resolves within a single agent run. Follow-up messages in the same thread work too — search, pick, confirm, and keep chatting (see the note below).
Recall is eventually consistent
Memory is written and indexed asynchronously, so a fact you just taught becomes recallable after a brief delay (typically seconds). In a normal "come back later" session that delay is invisible; only a teach-then-ask within the same few seconds can outrun indexing.
Multi-turn works via a server-side workaround
Follow-ups after a server-tool call would otherwise hit an upstream Agent Spec × AG-UI adapter bug (tool_call_id correlation) that corrupts the replayed history and 400s on the next turn. The cookbook works around it in agent/concierge/server.py by replacing the adapter's incremental message merge with a full-history replace each turn, so multi-turn conversations — and the confirm→book human-in-the-loop — work end to end. The "+ New thread" step above proves cross-session recall (memory is user-scoped, so a fresh thread still remembers you) — not a workaround for broken follow-ups. Details + the upstream fix we're tracking: agentspec-multiturn-toolcall-correlation.
The key pieces, in code#
Memory recall is exposed as an Agent Spec ServerTool, so the portable spec itself declares the capability; book_flight is a CopilotKit ClientTool so the confirmation card is rendered on the frontend via useHumanInTheLoop and the entire flow completes in a single agent run:
book_flight_tool = ClientTool(
name="book_flight",
description="Book the chosen flight by its id. The traveler confirms in the UI before it is finalized.",
inputs=[_str_prop("flight_id", "The id of the flight to book, e.g. 'AMS-001'.")],
outputs=[_str_prop("confirmation", "Human-readable booking confirmation.")],
)The agent is defined once and serialized to portable JSON:
return Agent(
name="travel_concierge",
llm_config=llm,
system_prompt=SYSTEM_PROMPT,
tools=TOOLS,
human_in_the_loop=True,
)The adapter has no post-run hook, so the server persists each turn to Oracle Agent Memory after the AG-UI stream drains (see agent/concierge/server.py).
Going further#
- Per-user memory — the cookbook defaults to a single
demo-user. The stock adapter does not forwardforwarded_props, so scopeuser_idvia a FastAPI dependency / ContextVar (seeagent/concierge/tools.py). - Swap the runtime — Agent Spec's adapter also supports Oracle's WayFlow runtime; the same spec runs on either.
- Memory reconciliation — after each turn a small LLM pass (
reconcile_durable_memoriesinagent/concierge/reconcile.py) prunes outdated or duplicate durable facts so an updated preference supersedes the old one on recall. Swap in your own policy (e.g. keep history, or reconcile on a schedule).
Get started with a coding agent#
Want to build this yourself? Paste this into your coding agent (Claude Code, Cursor, …):
Build a CopilotKit chat backed by a portable Oracle Agent Spec agent with
long-term memory on Oracle AI Database. Requirements:
- A Python FastAPI agent that defines an Oracle Agent Spec `Agent` (via
`pyagentspec`) with three tools: `recall_memory` (reads durable preferences from
Oracle Agent Memory via `oracleagentmemory`), `search_flights` (a mock flight
search returning cards like AMS-001 KLM KL606 $740 nonstop), and `book_flight`
(a CopilotKit `ClientTool` — Agent Spec `ClientTool` — gated in the UI via
`useHumanInTheLoop` for human-in-the-loop in a single agent run).
- Serialize the agent to Agent Spec JSON and serve it over AG-UI on the LangGraph
runtime with the `ag_ui_agentspec` adapter (`add_agentspec_fastapi_endpoint`).
The adapter has no post-run hook, so persist each turn to Oracle Agent Memory
after the AG-UI stream drains.
- A Next.js CopilotKit frontend that proxies to the agent over AG-UI with
`HttpAgent`, so the agent owns the LLM call (use CopilotKit's empty runtime
adapter). The frontend renders generative UI: `search_flights` → flight-option
cards, `recall_memory` → a "🧠 Remembered your preferences" chip, and
`book_flight` → a confirmation card that stamps into a boarding-pass ticket. A
collapsible left sidebar lists conversation threads with a "+ New thread" button.
- Use Oracle AI Database (Docker image `container-registry.oracle.com/database/free`)
as the memory store; connect with `oracledb` and litellm embeddings.
Walk me through it step by step, starting with the agent.Get the code#
Full source: examples/showcases/oracle-agent-memory — agent/ (the Agent Spec agent) and frontend/ (the CopilotKit V2 chat).