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 pyagentspec and serialized to Agent Spec JSON.
  • The ag_ui_agentspec adapter 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_memory tool 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 Database

Memory: what's CopilotKit, what's Oracle#

Memory ownership across the stack: CopilotKit owns the chat UI and threads, the AG-UI transport, and the generative UI; your agent code is the seam — Agent Spec on LangGraph, the recall_memory tool, and persist + reconcile; Oracle Agent Memory owns the Oracle AI Database, fact extraction, and vector recall.

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 — oracleagentmemory ships 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 8000

Run the frontend#

cd frontend
cp .env.local.example .env.local     # optional; defaults to localhost:8000/run
npm install
npm run dev

Open 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.

  1. Tell it: "I'm vegetarian, I fly from SFO, and I prefer an aisle seat."
  2. Click "+ New thread" in the sidebar (instead of reloading), then ask: "Find me a flight to Amsterdam."
  3. 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:

agent/concierge/tools.py
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:

agent/concierge/agent.py
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 forward forwarded_props, so scope user_id via a FastAPI dependency / ContextVar (see agent/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_memories in agent/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-memoryagent/ (the Agent Spec agent) and frontend/ (the CopilotKit V2 chat).