<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Anya Summers</title>
    <description>The latest articles on DEV Community by Anya Summers (@anya_summers_a5881a3d01ce).</description>
    <link>https://dev.to/anya_summers_a5881a3d01ce</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3876445%2F58b8ee11-40e1-4eaf-ad97-5a4089a51887.png</url>
      <title>DEV Community: Anya Summers</title>
      <link>https://dev.to/anya_summers_a5881a3d01ce</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/anya_summers_a5881a3d01ce"/>
    <language>en</language>
    <item>
      <title>We just launched a new course on how to Build Adaptive AI Agents</title>
      <dc:creator>Anya Summers</dc:creator>
      <pubDate>Fri, 28 Aug 2026 11:47:19 +0000</pubDate>
      <link>https://dev.to/oracledevs/we-just-launched-a-new-course-on-how-to-build-adaptive-ai-agents-2pkm</link>
      <guid>https://dev.to/oracledevs/we-just-launched-a-new-course-on-how-to-build-adaptive-ai-agents-2pkm</guid>
      <description>&lt;p&gt;Our new short course with DeepLearning.AI,&amp;nbsp;&lt;a href="https://www.deeplearning.ai/courses/building-adaptive-ai-agents" rel="noreferrer noopener"&gt;Building Adaptive AI Agents&lt;/a&gt;, is live today. It covers continual and adaptive learning: the&amp;nbsp;different ways&amp;nbsp;in which you can make an agent better over time, and how to do it on &lt;a href="https://www.oracle.com/database/" rel="noopener noreferrer"&gt;Oracle AI Database 26ai.&lt;/a&gt;&lt;/p&gt;





&lt;h2&gt;Key Takeaways&amp;nbsp;&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Agents forget because what one session works out rarely survives into the next: some setups carry a little project context, almost none carry the reasoning behind it.&lt;/li&gt;



&lt;li&gt;The cheapest fixes live in the&amp;nbsp;&lt;strong&gt;token&lt;/strong&gt;&amp;nbsp;space, turning yesterday's traces into reusable skills and restructuring memory, with zero training.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Structure beats keywords: we also explore Code Knowledge Graphs, a way to let agents find the correct context faster than simple regular expression matching.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Weight-space adaptation (retraining or finetuning) is the expensive tier you reach for only when context injection, processing and manipulation stops generalizing.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Best practice is an effort ladder: start&amp;nbsp;cheap, and&amp;nbsp;climb only when you must.&lt;/li&gt;
&lt;/ul&gt;





&lt;p&gt;&lt;/p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.oracle.com%2Fdevelopers%2Fwp-content%2Fuploads%2Fsites%2F129%2F2026%2F08%2F01-stateless-agent-throws-away-1024x373.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.oracle.com%2Fdevelopers%2Fwp-content%2Fuploads%2Fsites%2F129%2F2026%2F08%2F01-stateless-agent-throws-away-1024x373.png" alt="A stateless agent throws away the one thing worth keeping: Monday it builds&lt;br&gt;
a map of the codebase, nothing is written down, Tuesday it builds the same&lt;br&gt;
map again." width="799" height="291"&gt;&lt;/a&gt;A stateless AI agent rebuilds the same codebase map in a later session because it was never stored.&lt;p&gt;&lt;/p&gt;

&lt;h2&gt;Why does your agent keep paying the same bill twice?&amp;nbsp;&lt;/h2&gt;

&lt;p&gt;You ask your agent to add a field to the checkout flow. Before it writes a line, it greps for "checkout", opens six files to work out where the data layer&amp;nbsp;actually lives, and&amp;nbsp;reads through&amp;nbsp;your ORM setup. Twelve tool calls to build a map of a codebase that has not moved since Friday.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;It builds that map again on Tuesday. And on Wednesday.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;How much of last month's token spend went on your agent rediscovering things it already knew?&amp;nbsp;&lt;/p&gt;

&lt;p&gt;The failure everyone braces for with coding agents is the wrong answer. That is not the one that costs the most. The one that costs the most is the right answer, worked out correctly, thrown away when the session closed, and then worked out again from scratch the next morning.&amp;nbsp;&lt;/p&gt;





&lt;h2&gt;Watch it happen on something smaller&amp;nbsp;&lt;/h2&gt;

&lt;p&gt;Your agent writes a query against your own schema and hits an error:&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ORA-00904: "CREATED_AT": invalid identifier&amp;nbsp;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;So&amp;nbsp;it reads the schema, figures out the column it&amp;nbsp;actually needed&amp;nbsp;is&amp;nbsp;&lt;code&gt;DATE_CREATED&lt;/code&gt;, fixes the query and moves on. Fine. That is the agent doing its job.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Then the next day it is queried&amp;nbsp;again, and&amp;nbsp;starts a fresh session with a blank context window. Nothing reminds the new conversation that&amp;nbsp;&lt;code&gt;CREATED_AT&lt;/code&gt;&amp;nbsp;was wrong yesterday, so it figures it out all over again. Identical failure, identical token&amp;nbsp;spend, and nothing learned, so&amp;nbsp;you're&amp;nbsp;essentially wasting&amp;nbsp;tokens and time on unnecessary tool calls, thinking budget, context window buildup, etc.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;There is usually an absence of a feedback loop. The agent&amp;nbsp;&lt;em&gt;learned&lt;/em&gt;&amp;nbsp;something valuable yesterday and then threw it away with the context window after it was done.&amp;nbsp;&lt;strong&gt;Continual learning&lt;/strong&gt;&amp;nbsp;is the discipline of making an agent better over time, and it runs all the way from reusing yesterday's traces up to retraining the model itself. This piece, and most of the available wins, sit at the cheap end of that range: feeding the traces back so the agent stops paying for the same work twice.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.linkedin.com/in/jasperan/" rel="noreferrer noopener"&gt;Nacho&lt;/a&gt;&amp;nbsp;spent the past couple of months watching agents fail this&amp;nbsp;way, and&amp;nbsp;looking at how frontier labs fix it: finetuning models, building better agent harnesses, fresh training runs. All of it assumes compute most of us do not have, and all of it reaches for the most expensive item on the list to fix something that was rarely broken there in the first place.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;So&amp;nbsp;the question that started this course was a smaller one.&amp;nbsp;&lt;em&gt;"My agent is live and getting things wrong. What is the cheapest change that makes it better, and how do I do it?"&lt;/em&gt;&amp;nbsp;Put another way: is there anything we can do with the data we already have?&amp;nbsp;&lt;/p&gt;





&lt;h2&gt;Which kind of memory is your agent&amp;nbsp;actually missing?&amp;nbsp;&lt;/h2&gt;

&lt;p&gt;Every autonomous agent runs the same loop: perceive, decide, act,&amp;nbsp;observe, repeat. Each turn through that loop leaves traces, and those traces land in one of four memory forms:&amp;nbsp;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Memory form&lt;/strong&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;What it stores&lt;/strong&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;AI agent example&lt;/strong&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Working&lt;/strong&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;What the agent is thinking about right now&amp;nbsp;&lt;/td&gt;
&lt;td&gt;Scratchpad, AI agent notes, TODOs, etc.&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Episodic&lt;/strong&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;Specific past events&amp;nbsp;&lt;/td&gt;
&lt;td&gt;Previous&amp;nbsp;agent conversations, errors that happened, etc.&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Semantic&lt;/strong&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;Stable facts&amp;nbsp;&lt;/td&gt;
&lt;td&gt;Domain knowledge about the company,&amp;nbsp;private data, private database schemas, etc.&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Procedural&lt;/strong&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;How to do things&amp;nbsp;&lt;/td&gt;
&lt;td&gt;Skills, skill chains, workflows, notes to self, etc.&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The four forms decay at different speeds, and they have different degrees of permanence: working memory evaporates, episodic memory (usually) fades, semantic facts last years, and procedural skills are almost set in stone.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.oracle.com%2Fdevelopers%2Fwp-content%2Fuploads%2Fsites%2F129%2F2026%2F08%2F02-four-kinds-of-memory-1024x387.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.oracle.com%2Fdevelopers%2Fwp-content%2Fuploads%2Fsites%2F129%2F2026%2F08%2F02-four-kinds-of-memory-1024x387.png" alt="Four kinds of memory ordered by how long they last: working memory goes with&lt;br&gt;
the turn, episodic fades, semantic lasts years, procedural is almost set in&lt;br&gt;
stone." width="799" height="302"&gt;&lt;/a&gt;Four types of AI agent memory—working, episodic, semantic, and procedural—shown by how long they persist.&lt;p&gt;&lt;/p&gt;

&lt;p&gt;What your agent has at the end of Monday is episodic: the session where it grepped, opened six&amp;nbsp;files&amp;nbsp;and worked out where the data layer lives. What&amp;nbsp;it&amp;nbsp;needs on Tuesday&amp;nbsp;is&amp;nbsp;two other things. The map itself, which barely changes, is a semantic fact. How to find the next one is a procedural skill. Nothing in a default setup turns the episode into either, so it stays an episode and dies with the context window. Which form your agent is missing decides where in the course you should start.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;We built&amp;nbsp;&lt;a href="https://www.deeplearning.ai/courses/building-adaptive-ai-agents" rel="noreferrer noopener"&gt;Building Adaptive AI Agents&lt;/a&gt;&amp;nbsp;with Andrew Ng and the team at DeepLearning.AI.&amp;nbsp;&lt;a href="https://www.linkedin.com/in/jasperan/" rel="noreferrer noopener"&gt;Nacho Martinez&lt;/a&gt;&amp;nbsp;and&amp;nbsp;&lt;a href="https://www.linkedin.com/in/casiusslee/" rel="noreferrer noopener"&gt;Casius Lee&lt;/a&gt;&amp;nbsp;wrote it on the Oracle Developers side, and we are both genuinely happy about how this partnership came together. It goes through all four memory forms with the code: skill extraction from real agent traces, a Code Knowledge Graph built and queried, and&amp;nbsp;LoRA&amp;nbsp;adapters for the point where context injection stops generalizing.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;It is live today. Your agent left the traces this morning and they are still sitting there.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.deeplearning.ai/courses/building-adaptive-ai-agents" rel="noreferrer noopener"&gt;Take the course on DeepLearning.AI&lt;/a&gt;&amp;nbsp;&lt;/p&gt;





&lt;h2&gt;Frequently Asked Questions&amp;nbsp;&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What is continual learning?&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Continual learning is the practice of feeding what an agent worked out in one session back into the next one, so it stops rediscovering things it already knew. It covers everything from turning yesterday's traces into reusable skills, through restructuring how memory is retrieved, up to retraining the model itself.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is the token layer vs the weight layer?&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;The token layer is everything you can change without touching the model: what goes into the context window, which skills and memories get retrieved, and how they are structured. The weight layer means changing the model itself, through retraining or finetuning. Token-layer changes are cheap, take effect on the next run, and are where most of the available wins are. Weight-layer changes are expensive, and they are for the case where no amount of context injection produces the&amp;nbsp;behavior&amp;nbsp;reliably.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When do I need structure-aware retrieval?&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;When the right memory exists but retrieval misses it: especially multi-hop questions where the answer lives two edges away from the query. Build the graph, seed anchors, and let a PageRank walk find what keyword search&amp;nbsp;can't.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When do I&amp;nbsp;actually fine-tune&amp;nbsp;the embedder?&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;When retrieval fails because the embedder&amp;nbsp;doesn't&amp;nbsp;understand your domain's semantics: an agent needs "the code it will use next," not "the code that looks similar." Fine-tune on graded retrieval traces.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When do I need to fine-tune the weights?&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;When the fix must generalize across a whole class of task and no context injection reliably produces it: removing a default&amp;nbsp;behavior, installing a persona. In the course we explore the use of quantization and&amp;nbsp;LoRA&amp;nbsp;adapters, that can allow cheap finetuning.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's&amp;nbsp;the cheapest first step?&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Analyze&amp;nbsp;the traces you already have as&amp;nbsp;you've&amp;nbsp;been using your agents (Claude, Codex, etc.).&amp;nbsp;We'll&amp;nbsp;teach you how to make use of those traces your agent already leaves behind.&amp;nbsp;&lt;/p&gt;





&lt;h2&gt;Resources&amp;nbsp;&lt;/h2&gt;

&lt;p&gt;• &lt;strong&gt;Course:&lt;/strong&gt;&amp;nbsp;&lt;a href="https://www.deeplearning.ai/courses/building-adaptive-ai-agents" rel="noreferrer noopener"&gt;Building Adaptive AI Agents&lt;/a&gt;, a DeepLearning.AI short course built with Oracle, live 26 August 2026&amp;nbsp;&lt;/p&gt;

&lt;p&gt;• &lt;strong&gt;Agent memory:&lt;/strong&gt;&amp;nbsp;&lt;a href="https://docs.oracle.com/en/database/oracle/agent-memory/26.4/agmea/" rel="noreferrer noopener"&gt;Oracle AI Agent Memory documentation&lt;/a&gt;&amp;nbsp;and&amp;nbsp;&lt;a href="https://pypi.org/project/oracleagentmemory/" rel="noreferrer noopener"&gt;oracleagentmemory&lt;/a&gt;&amp;nbsp;26.6.0&amp;nbsp;&lt;/p&gt;

&lt;p&gt;• &lt;strong&gt;Database:&lt;/strong&gt;&amp;nbsp;&lt;a href="https://www.oracle.com/database/" rel="noreferrer noopener"&gt;Oracle AI Database 26ai&lt;/a&gt;: HNSW vector indexes,&amp;nbsp;VECTOR_DISTANCE, and property-graph (PGQ) traversal&amp;nbsp;&lt;/p&gt;

</description>
      <category>deeplearning</category>
      <category>agentskills</category>
      <category>ai</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>Build a Controlled Cursor MCP Workflow with Oracle AI Database</title>
      <dc:creator>Anya Summers</dc:creator>
      <pubDate>Fri, 28 Aug 2026 11:44:49 +0000</pubDate>
      <link>https://dev.to/oracledevs/build-a-controlled-cursor-mcp-workflow-with-oracle-ai-database-474n</link>
      <guid>https://dev.to/oracledevs/build-a-controlled-cursor-mcp-workflow-with-oracle-ai-database-474n</guid>
      <description>&lt;p&gt;&lt;strong&gt;Giving an AI assistant database access is easy. Making&amp;nbsp;that access&amp;nbsp;controlled, inspectable, and repeatable is the hard part. Here is the Cursor CLI and Oracle AI Database workflow that does it.&amp;nbsp;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Companion notebook: &lt;a href="https://github.com/oracle-devrel/oracle-ai-developer-hub/blob/main/notebooks/cursor_mcp_oracle_ai_database.ipynb" rel="noopener noreferrer"&gt;Cursor MCP with Oracle AI Database Workflow&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;





&lt;h2&gt;
&lt;strong&gt;Key Takeaways&lt;/strong&gt;&amp;nbsp;&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://modelcontextprotocol.io/docs/2026-07-28/getting-started/intro" rel="noreferrer noopener"&gt;MCP&lt;/a&gt;&amp;nbsp;turns AI-to-database access into an explicit tool contract instead of implicit system access.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.oracle.com/en/database/oracle/sql-developer-command-line/25.2/sqcug/sqlcl-mcp-server.html" rel="noreferrer noopener"&gt;Oracle SQLcl in MCP&lt;/a&gt;&amp;nbsp;mode,&amp;nbsp;sql&amp;nbsp;-mcp, is a practical way to connect Cursor CLI to Oracle AI Database through a local MCP server.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;ul&gt;
&lt;li&gt;Oracle AI Database provides the persistent storage and vector search layer for memory workloads, while&amp;nbsp;&lt;a href="https://docs.oracle.com/en/database/oracle/agent-memory/26.6/guide/api/agentmemory.html" rel="noreferrer noopener"&gt;Oracle AI Agent Memory&lt;/a&gt;&amp;nbsp;gives teams a Python API for threads, durable memories, scoped retrieval, and context assembly on top of it.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.langchain.com/oss/python/integrations/vectorstores/oracle" rel="noreferrer noopener"&gt;LangChain&lt;/a&gt;&amp;nbsp;can provide application-side wrappers and orchestration after the Oracle-backed memory and retrieval path is in place.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;ul&gt;
&lt;li&gt;A strong default is hybrid: Cursor CLI plus MCP for interactive database work, Oracle AI Database plus Oracle AI Agent Memory for durable memory, and&amp;nbsp;LangChain&amp;nbsp;only when the application needs reusable retrieval orchestration.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;





&lt;p&gt;Here is how those components connect in this pattern. The important visual point is that Cursor does not connect directly to Oracle AI Database. Cursor calls&amp;nbsp;SQLcl&amp;nbsp;MCP tools,&amp;nbsp;SQLcl&amp;nbsp;uses a saved Oracle connection, and Oracle AI Database&amp;nbsp;remains&amp;nbsp;the durable store for memory records, retrieval evidence, vectors, and tool traces. Oracle AI Agent Memory and&amp;nbsp;LangChain&amp;nbsp;sit in the application layer after that database-backed path is in place.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Production success depends less on clever prompting and more on boundaries, privileges, logging, scoped retrieval, and repeatable runbooks.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;This guide is for developers who want Cursor CLI to work with Oracle AI Database through explicit tools, durable memory, and reviewable retrieval evidence.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The developer path through this guide is simple:&amp;nbsp;&lt;/strong&gt;&lt;/p&gt;

&lt;ol start="1"&gt;
&lt;li&gt;Start with&amp;nbsp;one&amp;nbsp;approved Oracle connection and a read-only validation query.&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;

&lt;ol start="2"&gt;
&lt;li&gt;Put&amp;nbsp;SQLcl&amp;nbsp;MCP in front of that&amp;nbsp;connection&amp;nbsp;so Cursor sees tools, not raw database credentials.&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;

&lt;ol start="3"&gt;
&lt;li&gt;Check the audit and activity trail before adding more tool access.&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;

&lt;ol start="4"&gt;
&lt;li&gt;Add Oracle AI Agent Memory when the workflow needs durable thread context, scoped recall, or reusable context cards.&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;

&lt;ol start="5"&gt;
&lt;li&gt;Add&amp;nbsp;&lt;a href="https://docs.langchain.com/oss/python/integrations/vectorstores/oracle" rel="noopener noreferrer"&gt;LangChain&lt;/a&gt;&amp;nbsp;only when you need application-side&amp;nbsp;retrieval&amp;nbsp;orchestration beyond the MCP interaction loop.&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;/p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.oracle.com%2Fdevelopers%2Fwp-content%2Fuploads%2Fsites%2F129%2F2026%2F08%2FDiagram-2-1024x391.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.oracle.com%2Fdevelopers%2Fwp-content%2Fuploads%2Fsites%2F129%2F2026%2F08%2FDiagram-2-1024x391.png" alt="Architecture diagram showing a human operator using Cursor CLI and SQLcl MCP Server to access Oracle AI Database and Oracle AI Agent Memory, with memory tables, tool logs, vector retrieval, LangChain retrieval, and grounded context." width="799" height="305"&gt;&lt;/a&gt;Controlled Cursor MCP + Oracle AI Database Workflow&lt;p&gt;&lt;/p&gt;





&lt;h2&gt;
&lt;strong&gt;Why This Architecture Is Useful for Developers&lt;/strong&gt;&amp;nbsp;&lt;/h2&gt;

&lt;p&gt;Giving an AI assistant database access is easy. Making&amp;nbsp;that access&amp;nbsp;controlled, inspectable, and repeatable is the hard part.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Cursor is useful because it sits close to the developer's actual work: code, terminal commands, notebooks, configuration, and implementation details. A developer can move from "why is this failing?" to "inspect the database state" inside the same working loop. That closeness is powerful, but it also makes the&amp;nbsp;database&amp;nbsp;boundary more sensitive.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;The question is not whether Cursor can produce SQL-shaped text. The question is whether the database path is approved, observable, and easy to debug later. A useful workflow needs to preserve the request, the tool call, the database identity, the retrieved context, and the reason a risky action was allowed or blocked.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;By the end of this guide, you should know how to connect Cursor CLI to Oracle AI Database through a controlled MCP boundary, when Cursor context is enough and when Oracle-backed memory is needed, and how to build a retrieval path that can be queried, audited, and scaled.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;The companion notebook is intentionally practical. It validates&amp;nbsp;SQLcl&amp;nbsp;and Java discovery, writes a sanitized Cursor MCP config preview, checks the saved&amp;nbsp;SQLcl&amp;nbsp;connection alias, creates memory tables, inserts simulated Cursor/MCP teaching traces, tests lexical, vector, and hybrid retrieval, initializes Oracle AI Agent Memory with the current 26.6 configuration shape, and finishes with a validation snapshot.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;The workflow has four layers. Cursor CLI is the developer-facing agent interface.&amp;nbsp;SQLcl&amp;nbsp;MCP is the tool boundary. Oracle AI Database is the durable substrate for memory, traces, and retrieval. The notebook is the build-and-validation harness that proves the pieces are wired correctly before the workflow is handed to Cursor.&amp;nbsp;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Layer&lt;/strong&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;
&lt;span&gt;&lt;span&gt;Responsibility&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cursor CLI&amp;nbsp;&lt;/td&gt;
&lt;td&gt;Developer-facing MCP client and agent interface.&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SQLcl&amp;nbsp;MCP&amp;nbsp;&lt;/td&gt;
&lt;td&gt;Exposes declared Oracle tools to Cursor; it is the tool boundary.&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Oracle AI Database&amp;nbsp;&lt;/td&gt;
&lt;td&gt;Stores durable data, retrieval evidence, vectors, metadata, traces, and enforces database privileges.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Oracle AI Agent Memory&amp;nbsp;&lt;/td&gt;
&lt;td&gt;Provides application APIs for users, agents, threads, durable memories, scoped retrieval, and context assembly.&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LangChain&amp;nbsp;&lt;/td&gt;
&lt;td&gt;
&lt;span&gt;&amp;nbsp;&lt;/span&gt;Wraps Oracle-backed retrieval results as Document objects and supports application-side orchestration.&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;





&lt;h2&gt;
&lt;strong&gt;The Two Execution Loops&lt;/strong&gt;&amp;nbsp;&lt;/h2&gt;

&lt;p&gt;Building on that split between tool boundary and durable store, the system naturally forms two execution loops:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Loop A &lt;/strong&gt;is the operational interaction loop: Cursor CLI works with MCP to discover tools, inspect data, run bounded read-only queries, and return results&amp;nbsp;immediately.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;
&lt;strong&gt;Loop B&lt;/strong&gt; is the durable memory and retrieval loop: application code writes history, tool logs, memory records, chunks, and embeddings to Oracle AI Database, then retrieves context before a later answer or workflow step.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;/p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.oracle.com%2Fdevelopers%2Fwp-content%2Fuploads%2Fsites%2F129%2F2026%2F08%2FDiagram-1-1024x415.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.oracle.com%2Fdevelopers%2Fwp-content%2Fuploads%2Fsites%2F129%2F2026%2F08%2FDiagram-1-1024x415.png" alt="Diagram showing User to Cursor CLI to SQLcl MCP to Oracle AI Database to Answer, with a second loop where tool traces enter a memory store, Oracle AI Agent Memory, hybrid retrieval, and grounded context returned to Claude." width="799" height="324"&gt;&lt;/a&gt;Dual Execution Loop: MCP Interaction and Durable Memory&amp;nbsp;&lt;p&gt;&lt;/p&gt;

&lt;p&gt;SQLcl&amp;nbsp;MCP handles live tool use. Oracle AI Agent Memory handles durable memory and scoped recall. Most production setups need both loops, but they solve different problems.&lt;/p&gt;





&lt;h2&gt;
&lt;strong&gt;Reproducing the&amp;nbsp;SQLcl&amp;nbsp;MCP and Cursor CLI Workflow&lt;/strong&gt;&amp;nbsp;&lt;/h2&gt;

&lt;p&gt;The setup should be reproducible.&amp;nbsp;SQLcl&amp;nbsp;runs in MCP mode with&amp;nbsp;&lt;code&gt;sql&amp;nbsp;-mcp&lt;/code&gt;. Cursor CLI launches it as an MCP server and talks to Oracle through declared tools, not through direct access. Connections come from saved&amp;nbsp;SQLcl&amp;nbsp;profiles that you create and test before Cursor uses them.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Cursor does not invent them at runtime; it reuses profiles you have already created and validated.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prerequisites before you connect Cursor CLI:&amp;nbsp;&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Oracle&amp;nbsp;SQLcl&amp;nbsp;25.2.0 or higher.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;ul&gt;
&lt;li&gt;Oracle JRE 17 or 21.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;ul&gt;
&lt;li&gt;Cursor IDE or Cursor CLI if you want to use the generated MCP configuration outside the notebook.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;ul&gt;
&lt;li&gt;At least one saved&amp;nbsp;SQLcl&amp;nbsp;connection profile under&amp;nbsp;&lt;code&gt;~/.dbtools&lt;/code&gt;, created with password persistence for MCP use.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;ul&gt;
&lt;li&gt;A database user with the minimum permissions&amp;nbsp;required&amp;nbsp;for the workflow.&amp;nbsp;Start with read-only access and a sanitized development or replica environment where possible.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The notebook deliberately treats the saved&amp;nbsp;SQLcl&amp;nbsp;connection alias as a first-class artifact. In local development, that alias is what lets&amp;nbsp;SQLcl&amp;nbsp;MCP connect without forcing the agent to assemble credentials dynamically.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;The notebook then generates a sanitized Cursor MCP config preview.&amp;nbsp;The preview is intentionally safe: it shows the server command and arguments without exposing secrets.&amp;nbsp;It does not overwrite your real Cursor MCP configuration.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;For the saved connection itself, the important detail is&amp;nbsp;&lt;code&gt;-savepwd&lt;/code&gt;. MCP cannot stop and ask a human for the password every time Cursor invokes a database tool. The saved alias becomes the repeatable local path Cursor can use after you have reviewed it.&amp;nbsp;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;{ 
  "mcpServers": { 
    "sqlcl": { 
      "command": "/absolute/path/to/sql", 
      "args": ["-mcp"] 
    } 
  } 
} &lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;That small JSON block defines the connection between Cursor and&amp;nbsp;SQLcl&amp;nbsp;MCP Server. Cursor interacts with the database through the tools and permissions exposed by the MCP server, using the saved&amp;nbsp;SQLcl&amp;nbsp;connection profile you created and tested first. Save this configuration in&amp;nbsp;.cursor/mcp.json&amp;nbsp;for a project-scoped setup or&amp;nbsp;~&lt;code&gt;/.cursor/mcp.json&lt;/code&gt;&amp;nbsp;globally, restart Cursor or Cursor CLI, then run:&amp;nbsp;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;cursor-agent&amp;nbsp;mcp&amp;nbsp;list&amp;nbsp;
cursor-agent&amp;nbsp;mcp&amp;nbsp;list-tools&amp;nbsp;sqlcl&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;A useful first prompt is intentionally constrained:&amp;nbsp;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Use&amp;nbsp;SQLcl&amp;nbsp;MCP to list available saved Oracle connections. Do not run DML or DDL.&amp;nbsp;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Validation checklist before expanding access:&amp;nbsp;Run&amp;nbsp;&lt;code&gt;sql&amp;nbsp;-mcp&lt;/code&gt;&amp;nbsp;locally and confirm the server starts.&amp;nbsp;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Restart Cursor CLI and confirm the&amp;nbsp;SQLcl&amp;nbsp;tools are discoverable.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;ul&gt;
&lt;li&gt;Run one read-only query against an approved schema.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;ul&gt;
&lt;li&gt;Check database-side MCP activity logs and session metadata.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;ul&gt;
&lt;li&gt;Document the connection alias, database user, grant scope, restrict level, and troubleshooting owner.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Good first proof looks like this:&amp;nbsp;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The MCP server starts without a Java or path error.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;ul&gt;
&lt;li&gt;Cursor lists the&amp;nbsp;SQLcl&amp;nbsp;MCP tools after&amp;nbsp;restart.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;ul&gt;
&lt;li&gt;A read-only query succeeds against the expected schema.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;ul&gt;
&lt;li&gt;The notebook audit trail records the expected tool interaction in&amp;nbsp;&lt;code&gt;cursor_tool_logs&lt;/code&gt;.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;ul&gt;
&lt;li&gt;For live Cursor CLI +&amp;nbsp;SQLcl&amp;nbsp;MCP validation, confirm the database/session activity through your normal Oracle monitoring path.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;ul&gt;
&lt;li&gt;A denied query fails because of the database role, not because a prompt asked nicely.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A useful MCP boundary is more than&amp;nbsp;tool&amp;nbsp;discovery. The notebook models read-only defaults, confirmation requirements, scope checks, and controlled failure examples so&amp;nbsp;denied&amp;nbsp;and warning states are visible.&amp;nbsp;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Read-only default: start with inspection and diagnostics before allowing changes.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;ul&gt;
&lt;li&gt;Confirmation gate: require explicit approval for medium-risk, write-like, or destructive actions.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;ul&gt;
&lt;li&gt;Scope control: keep user, tenant, and schema filters close to the database query.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;ul&gt;
&lt;li&gt;Failure trace: store denied calls and warnings as evidence instead of hiding them.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;





&lt;h2&gt;
&lt;strong&gt;What a Successful Notebook Run Shows&lt;/strong&gt;&amp;nbsp;&lt;/h2&gt;

&lt;p&gt;The notebook is not just setup prose. It produces concrete checkpoints that make the workflow inspectable.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;The first useful result is a deterministic Cursor/MCP timeline. The sample data uses explicit event sequence values and simulated event&amp;nbsp;timestamps&amp;nbsp;so the workflow order is stable every time the notebook is rerun:&amp;nbsp;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;step  event_kind    actor                              result 
1     CONVERSATION  user                               initial support-job request 
2     CONVERSATION  assistant                          SQLcl MCP read-only plan 
3     MCP_TOOL      sqlcl.connections/list_connections SUCCESS 
4     MCP_TOOL      sqlcl.sql/query                    SUCCESS 
5     MCP_TOOL      sqlcl.sql/query                    DENIED / PRIVILEGE_SCOPE 
6     CONVERSATION  assistant                          grounded summary&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;That ordering matters because operational memory is only useful if the answer can be traced back to the request, the tool calls, and the permission boundary that shaped the result.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;The grounding package also returns visible evidence before the assistant answer is assembled:&amp;nbsp;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Status: READY&amp;nbsp;

Top evidence:&amp;nbsp;

- Saved&amp;nbsp;SQLcl&amp;nbsp;connections for MCP&amp;nbsp;

-&amp;nbsp;SQLcl&amp;nbsp;MCP execution boundary&amp;nbsp;

- Tool logging baseline&amp;nbsp;

-&amp;nbsp;LangChain&amp;nbsp;as orchestration glue&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If retrieval is empty or too weak, the notebook returns&amp;nbsp;&lt;code&gt;INSUFFICIENT_CONTEXT&lt;/code&gt;&amp;nbsp;and displays a safe empty-result message instead of trying to select columns from a missing evidence table.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;The final snapshot should show every local layer that is ready:&amp;nbsp;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Cursor MCP config&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; generated&amp;nbsp;
SQLcl&amp;nbsp;MCP runtime&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ready&amp;nbsp;
SQLcl&amp;nbsp;saved connection&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ready&amp;nbsp;
Oracle AI Database memory&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ready&amp;nbsp;
Oracle AI Agent Memory package&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ready&amp;nbsp;
Lexical search&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ready&amp;nbsp;
Native VECTOR execution path&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ready&amp;nbsp;
Demo embeddings&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; demo ready&amp;nbsp;
Hybrid retrieval&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ready&amp;nbsp;
LangChain&amp;nbsp;wrapper&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ready&amp;nbsp;
validation_action_needed&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; 0&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;That is the practical bar for this demo: Cursor setup artifacts are generated,&amp;nbsp;SQLcl&amp;nbsp;MCP prerequisites are&amp;nbsp;validated, Oracle memory tables are populated, retrieval works, Agent Memory initializes, and the notebook separates native VECTOR readiness from deterministic demo embeddings.&amp;nbsp;&lt;/p&gt;





&lt;h2&gt;
&lt;strong&gt;Simulated Teaching Data, Not Live Cursor Telemetry&lt;/strong&gt;&amp;nbsp;&lt;/h2&gt;

&lt;p&gt;One important boundary in the companion notebook is that the operational records are simulated teaching data. The notebook inserts sample conversation rows and sample tool-log&amp;nbsp;rows to show what a production workflow should preserve: the user's request, Cursor's plan, tool calls, outcomes, controlled failures, and retrieval evidence.&amp;nbsp;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Those rows are not live telemetry captured from Cursor CLI, and the notebook does not automatically&amp;nbsp;observe, scrape, or stream Cursor CLI activity. Live Cursor validation still happens through Cursor's MCP configuration and the&amp;nbsp;SQLcl&amp;nbsp;MCP server. The notebook proves the database-backed memory, retrieval, and validation pattern around that&amp;nbsp;workflow&amp;nbsp;so the pieces are inspectable and repeatable.&lt;/p&gt;





&lt;h2&gt;&lt;strong&gt;Why Put Application Memory Records in Oracle AI Database, Not Just Outputs&amp;nbsp;&lt;/strong&gt;&lt;/h2&gt;

&lt;p&gt;Once the first MCP tool calls work, the next challenge is continuity. This is where long-term memory for AI agents becomes different from&amp;nbsp;short-lived chat context.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;If memory lives only in chat context, the system is fragile. If memory is scattered across files without structure, retrieval and auditing become expensive over time.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;A database-backed memory model is usually cleaner and more scalable. The companion notebook builds this memory layer from&amp;nbsp;scratch&amp;nbsp;so the mechanics are visible, then shows how Oracle AI Agent Memory sits on top of it once the substrate is working.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Memory categories that matter in practice:&amp;nbsp;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Conversation memory keeps the important&amp;nbsp;user&amp;nbsp;and assistant&amp;nbsp;turns&amp;nbsp;that future sessions may need.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Operational memory keeps tool calls, outcomes, warnings, and failures so a team can debug what happened.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Semantic memory adds embeddings so the system can find relevant context even when the user asks in different words.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In practice, hybrid retrieval for agent memory usually combines exact operational terms, such as&amp;nbsp;&lt;code&gt;sql&amp;nbsp;-mcp&lt;/code&gt;&amp;nbsp;or&amp;nbsp;&lt;code&gt;cursor_mc&lt;/code&gt;p, with semantic search over memory records.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;The notebook shows the lower-level mechanics&amp;nbsp;first&amp;nbsp;so the storage and retrieval path is visible. This is also a context engineering problem: the application&amp;nbsp;has to&amp;nbsp;decide which memories, tool traces, and retrieval results should be assembled before Cursor or another assistant answers. Oracle AI Agent Memory then gives application code a cleaner&amp;nbsp;package&amp;nbsp;API over that same database-backed idea.&amp;nbsp;&lt;/p&gt;





&lt;h2&gt;
&lt;strong&gt;Where Oracle AI Agent Memory Fits&lt;/strong&gt;&amp;nbsp;&lt;/h2&gt;

&lt;p&gt;Oracle AI Agent Memory sits between your application code and Oracle AI Database. The package manages conversation threads, durable memory records, scoped retrieval, and context assembly while Oracle AI Database&amp;nbsp;remains&amp;nbsp;the storage layer underneath.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;The notebook includes an abbreviated package-backed memory pattern. It initializes&amp;nbsp;OracleAgentMemory&amp;nbsp;with a database connection pool and a custom local deterministic embedder.&amp;nbsp;&lt;code&gt;LocalCursorEmbedder&lt;/code&gt;&amp;nbsp;is notebook code, not a built-in Oracle AI Agent Memory embedder.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;The local embedder is intentionally billing-free, which makes the notebook runnable for people who do not want to attach paid model usage to a tutorial.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;In Oracle AI Agent Memory 26.6, use&amp;nbsp;&lt;code&gt;MemoryExtractionConfig(extract_memories=False)&lt;/code&gt;&amp;nbsp;instead of the older &lt;code&gt;inline&amp;nbsp;extract_memories=False&lt;/code&gt;&amp;nbsp;parameter, and use&amp;nbsp;&lt;code&gt;memory_store_id&lt;/code&gt;&amp;nbsp;instead of&amp;nbsp;&lt;code&gt;table_name_prefix&lt;/code&gt;.&amp;nbsp;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;from oracleagentmemory.apis.searchscope import SearchScope 
from oracleagentmemory.core import MemoryExtractionConfig 
from oracleagentmemory.core.oracleagentmemory import OracleAgentMemory 
  
db_pool = oracledb.SessionPool( 
    user=CONFIG["ORACLE_USER"], 
    password=CONFIG["ORACLE_PASSWORD"], 
    dsn=CONFIG["ORACLE_DSN"], 
    min=1, 
    max=4, 
    increment=1, 
) 
  
agent_memory = OracleAgentMemory( 
    connection=db_pool, 
    embedder=LocalCursorEmbedder(dimensions=32), 
    llm=None, 
    memory_extraction_config=MemoryExtractionConfig(extract_memories=False), 
    schema_policy="create_if_necessary", 
    memory_store_id="cursor_oam_local", 
) &lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;br&gt;Use oracleagentmemory from your application layer when you need package-managed users, agents, memories, threads, scoped retrieval, and context assembly. Keep systems of record separate from memory records: memory helps provide context, but application logic and authoritative data sources should still decide what is true, allowed, and final. &lt;br&gt;&lt;br&gt;Implementation note: use a schema whose default tablespace supports the JSON objects created by Agent Memory. If a local SYSTEM schema sits on a tablespace that rejects JSON object creation, the better production answer is a dedicated application schema. &lt;br&gt;&lt;br&gt;Production note: the package-managed expired-record purge job needs CREATE JOB or an equivalent scheduler-job privilege, or a managed schema setup flow. Without that privilege, expired messages and memories will not be purged automatically. For a local notebook demo, this is acceptable as long as the limitation is visible. &lt;/p&gt;





&lt;h2&gt;Store and Search: What a Realistic Memory Looks Like &lt;/h2&gt;

&lt;p&gt;A realistic Cursor memory is not&amp;nbsp;generic&amp;nbsp;trivia about a user. For this workflow, memory should capture how a developer actually works: the connection name they used, the&amp;nbsp;SQLcl&amp;nbsp;path that succeeded, the MCP config location, the failed privilege boundary, the retrieval query that helped, and the final fix that should be reused later.&amp;nbsp;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;thread = agent_memory.create_thread( 
    user_id=AGENT_MEMORY_USER_ID, 
    agent_id=AGENT_MEMORY_AGENT_ID, 
) 
  
thread.add_memory( 
    "Developer validated Cursor CLI with SQLcl MCP alias cursor_mcp " 
    "against local Oracle AI Database service FREEPDB1." 
) 
  
results = agent_memory.search( 
    query="Cursor SQLcl MCP alias validation and Agent Memory setup", 
    scope=SearchScope(user_id=AGENT_MEMORY_USER_ID, agent_id=AGENT_MEMORY_AGENT_ID), 
) &lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;That kind of memory pays off because it is operational. It can help Cursor answer the next question with context from the&amp;nbsp;previous&amp;nbsp;debugging session, but it is still scoped and retrievable through a database-backed API.&amp;nbsp;&lt;/p&gt;





&lt;h2&gt;
&lt;strong&gt;Vector Search, Native VECTOR, and Demo Embeddings&lt;/strong&gt;&amp;nbsp;&lt;/h2&gt;

&lt;p&gt;Vector search is part of the Oracle AI Database memory story. In a real application, embeddings usually come from a model and are indexed with Oracle AI&amp;nbsp;Database&amp;nbsp;vector capabilities.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;The notebook separates two ideas that are easy to accidentally blur:&amp;nbsp;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Native VECTOR readiness means the database can store and search vectors through the Oracle AI Database vector path.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Deterministic demo embeddings are local, repeatable vectors used so the notebook can run without external model billing.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The deterministic embeddings are useful for portability and inspection, but they should not be described as&amp;nbsp;productionsemantic embeddings. For production, replace the notebook's&amp;nbsp;&lt;code&gt;demo_embed()&lt;/code&gt;&amp;nbsp;or&amp;nbsp;&lt;code&gt;LocalCursorEmbedder&lt;/code&gt;&amp;nbsp;with a supported embedding model after cost, latency, privacy, and retrieval-quality review.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;The final notebook snapshot makes this separation explicit with two rows:&amp;nbsp;Native VECTOR execution path&amp;nbsp;and&amp;nbsp;Demo embeddings.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.oracle.com%2Fdevelopers%2Fwp-content%2Fuploads%2Fsites%2F129%2F2026%2F08%2FDiagram-3-1-386x1024.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.oracle.com%2Fdevelopers%2Fwp-content%2Fuploads%2Fsites%2F129%2F2026%2F08%2FDiagram-3-1-386x1024.png" alt="Vertical flow from user query through tenant filter, lexical search using Oracle Text, vector search using Oracle VECTOR, hybrid scoring, grounding package, and final answer." width="386" height="1024"&gt;&lt;/a&gt;Hybrid search pipeline combining tenant filtering, Oracle Text lexical search, Oracle VECTOR search, scoring, and a grounding package.&lt;p&gt;&lt;/p&gt;





&lt;h2&gt;
&lt;strong&gt;Where&amp;nbsp;&lt;a href="https://docs.langchain.com/oss/python/integrations/vectorstores/oracle" rel="noopener noreferrer"&gt;LangChain&lt;/a&gt;&amp;nbsp;Adds Value&lt;/strong&gt;&amp;nbsp;&lt;/h2&gt;

&lt;p&gt;LangChain&amp;nbsp;should not be treated as the source of truth. Cursor CLI does not call&amp;nbsp;LangChain&amp;nbsp;directly in this architecture, and&amp;nbsp;LangChain&amp;nbsp;is not the permission boundary, memory store, or audit layer.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;In this notebook,&amp;nbsp;LangChain&amp;nbsp;is used as a compatibility layer. The custom Oracle-backed&amp;nbsp;&lt;code&gt;hybrid_search()&lt;/code&gt; path performs retrieval, then the results are wrapped as&amp;nbsp;LangChain&amp;nbsp;Document&amp;nbsp;objects&amp;nbsp;so applications that already expect&amp;nbsp;LangChain&amp;nbsp;interfaces can consume them.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;By the time&amp;nbsp;LangChain&amp;nbsp;is introduced, the database tables, package memory, retrieval scores, and validation&amp;nbsp;snapshot already exist.&amp;nbsp;LangChain&amp;nbsp;becomes a wrapper around evidence, not a substitute for evidence.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Use it when the consuming application already expects retrievers, documents, chains, or&amp;nbsp;tool&amp;nbsp;orchestration. If the application only needs direct SQL, package-backed Agent Memory search, or a simple evidence table, the extra abstraction can make debugging harder.&amp;nbsp;&lt;/p&gt;





&lt;h2&gt;
&lt;strong&gt;How to Move This from Demo to Production&lt;/strong&gt;&amp;nbsp;&lt;/h2&gt;

&lt;p&gt;The difference between demo success and production success is disciplined operations.&amp;nbsp;In this workflow, the first failures to check are usually integration issues:&amp;nbsp;SQLcl&amp;nbsp;discovery, Java runtime, saved connection aliases, database permissions, and retrieval configuration.&amp;nbsp;A local notebook can prove the wiring. A production workflow needs smaller database roles, managed secrets, clear MCP approval policy, repeatable environment setup, and&amp;nbsp;monitoring aroundmemory writes and tool calls.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Access and privilege model:&amp;nbsp;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use a dedicated application schema where possible, then tighten grants with least-privilege roles and quotas.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;ul&gt;
&lt;li&gt;Keep saved&amp;nbsp;SQLcl&amp;nbsp;aliases separate by role and environment instead of sharing one broad connection.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;ul&gt;
&lt;li&gt;Start read-only wherever possible and&amp;nbsp;gate write&amp;nbsp;operations with explicit confirmation workflows.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;ul&gt;
&lt;li&gt;Use schema allowlists and separate accounts for development,&amp;nbsp;test, and production.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Observability model:&amp;nbsp;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Log tool name, thread ID, timestamp, status, and sanitized inputs and outputs.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;ul&gt;
&lt;li&gt;Classify failures into runtime, discovery, connection, permission, query, and retrieval categories.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;ul&gt;
&lt;li&gt;Keep a troubleshooting playbook in the&amp;nbsp;repo&amp;nbsp;so setup issues do not become tribal knowledge.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;ul&gt;
&lt;li&gt;Check whether&amp;nbsp;retrieval&amp;nbsp;quality changes as more data and memory records are added.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Reliability model:&amp;nbsp;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prefer deterministic SQL patterns with bounded result sets.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;ul&gt;
&lt;li&gt;Use retrieval-first context assembly for memory-heavy tasks.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;ul&gt;
&lt;li&gt;Avoid giant context stuffing as a substitute for memory design.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;ul&gt;
&lt;li&gt;Review and prune tool surfaces periodically.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;ul&gt;
&lt;li&gt;Move from the local deterministic embedder to a supported embedding model after cost, latency, and privacy review.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;





&lt;h2&gt;
&lt;strong&gt;What to Check When the Workflow Fails&lt;/strong&gt;&amp;nbsp;&lt;/h2&gt;

&lt;p&gt;When this workflow fails, start with the integration points below.&amp;nbsp;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Runtime failure:&amp;nbsp;sql&amp;nbsp;-mcp&amp;nbsp;does not start.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check the absolute&amp;nbsp;SQLcl&amp;nbsp;path, confirm Java is available, and run&amp;nbsp;sql&amp;nbsp;-mcp&amp;nbsp;outside Cursor first. Resolve runtime issues before checking assistant behavior.&amp;nbsp;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Discovery failure: Cursor does not see tools.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check the Cursor MCP configuration, confirm the configured command points to the&amp;nbsp;SQLcl&amp;nbsp;executable, and restart or reload Cursor CLI after edits.&amp;nbsp;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Connection failure: tools are&amp;nbsp;present&amp;nbsp;but queries fail&amp;nbsp;immediately.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check the saved&amp;nbsp;SQLcl&amp;nbsp;connection alias, confirm the profile lives under the expected&amp;nbsp;SQLcl&amp;nbsp;connection store, and verify password persistence for the MCP workflow. Then test the same connection outside Cursor.&amp;nbsp;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Permission failure: queries execute selectively and fail on specific objects.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check the database role first. A selective failure can be the right outcome when&amp;nbsp;least&amp;nbsp;privilege is working. Add grants intentionally and keep read-write access separate from the&amp;nbsp;initial&amp;nbsp;validation path.&amp;nbsp;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Retrieval&amp;nbsp;quality failure: answers are fluent but weakly grounded.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Inspect the retrieved records before blaming the model. Check chunk size, metadata filters, embedding choice, top-k settings, and whether the query is asking for exact history, semantic similarity, or operational logs. The&amp;nbsp;notebook's grounding package includes an&amp;nbsp;INSUFFICIENT_CONTEXT&amp;nbsp;path and&amp;nbsp;guards&amp;nbsp;empty evidence before displaying result columns.&amp;nbsp;&lt;/p&gt;





&lt;h2&gt;
&lt;strong&gt;Why the Hybrid Model Is Usually the Best Long-Term Design&lt;/strong&gt;&amp;nbsp;&lt;/h2&gt;

&lt;p&gt;No single layer handles both execution and memory well. Trying to force everything into Cursor context gets messy fast: you either lose control over execution, or you stuff too much state into prompts just to keep things working. On the other&amp;nbsp;side, if you only build backend memory systems, you lose the speed and usability that makes an assistant useful during development.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;The hybrid approach works because it does not force one layer to do everything. MCP handles live tool execution, Oracle AI Database keeps durable evidence, Oracle AI Agent Memory provides the memory API, and&amp;nbsp;LangChain&amp;nbsp;is added only when the application needs that shape.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;In real&amp;nbsp;teams&amp;nbsp;this usually evolves over time. Start with Cursor CLI,&amp;nbsp;SQLcl&amp;nbsp;MCP, read-only access, and strong logging. Once people rely on the workflow, the gaps become visible: lost context, weak traceability, repeated setup work, or retrieval that is hard to explain. That is when database-backed memory and structured retrieval become worth adding.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;For Cursor specifically, the value is that the assistant can stay in the developer loop without becoming an unreviewed database actor.&amp;nbsp;Cursor&amp;nbsp;can help plan, inspect, and explain.&amp;nbsp;SQLcl&amp;nbsp;MCP exposes the database path as tools. Oracle AI Database keeps the durable evidence. That is the combination that makes the workflow useful after the demo.&amp;nbsp;&lt;/p&gt;





&lt;h2&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;A Cursor and&amp;nbsp;SQLcl&amp;nbsp;MCP workflow&amp;nbsp;becomes&amp;nbsp;useful when it is treated as an engineering pattern, not just a setup trick. Cursor keeps the developer moving,&amp;nbsp;SQLcl&amp;nbsp;MCP keeps database access explicit, and Oracle AI Database keeps the evidence durable enough to inspect later.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;The result is a workflow a team can inspect. You can see what Cursor asked for, which tool path ran, what the database allowed, which memory records were retrieved, and how the&amp;nbsp;final answer&amp;nbsp;was assembled.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;That is the shift that matters: from assistant access that is implicit and hard to audit, to explicit boundaries, durable memory, and evidence a developer can&amp;nbsp;actually debug. For database-connected development, that is what turns Cursor from a helpful local assistant into part of a controlled engineering workflow.&amp;nbsp;&lt;/p&gt;





&lt;h2&gt;
&lt;strong&gt;Frequently Asked Questions&lt;/strong&gt;&amp;nbsp;&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What is MCP in this context?&amp;nbsp;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;MCP is the protocol boundary that lets Cursor call explicit tools exposed by a server instead of accessing systems implicitly.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What does MCP protect, and what does it not protect?&amp;nbsp;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;MCP makes the tool interface explicit and reviewable: Cursor can&amp;nbsp;call only&amp;nbsp;the tools exposed by the server. It does not replace database security. The saved&amp;nbsp;SQLcl&amp;nbsp;connection profile, database user, grants, roles, network controls, and database policies&amp;nbsp;determine&amp;nbsp;what those tools can&amp;nbsp;actually access&amp;nbsp;or change.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why use&amp;nbsp;SQLcl&amp;nbsp;for Oracle MCP?&amp;nbsp;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;SQLcl&amp;nbsp;already understands Oracle workflows and can run as the Oracle&amp;nbsp;SQLcl&amp;nbsp;MCP server&amp;nbsp;with sql -mcp, making the Oracle integration practical and direct.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is this Cursor IDE or Cursor CLI?&amp;nbsp;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The workflow is Cursor CLI oriented because the notebook&amp;nbsp;validates&amp;nbsp;the&amp;nbsp;SQLcl&amp;nbsp;MCP path using a local MCP&amp;nbsp;serverdefinition and manual Cursor CLI checks. The same MCP server can be configured in project-scoped .cursor/mcp.json or global ~/.cursor/mcp.json, depending on how your team uses Cursor.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why include Oracle AI Database if MCP already works?&amp;nbsp;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;MCP handles the execution boundary. Oracle AI Database handles durable memory, retrieval, vector search, concurrency, observability, and governance.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do I need an OpenAI API key?&amp;nbsp;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;An external model-provider API key is only needed if you change the notebook to use a provider-backed embedding or LLM service. The default notebook path uses a local deterministic&amp;nbsp;embedder&amp;nbsp;so people can run the Agent Memory package section without adding billing details.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why include&amp;nbsp;LangChain&amp;nbsp;if Oracle already stores memory?&amp;nbsp;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Because many teams already use&amp;nbsp;LangChain-shaped retrievers and chains. The notebook shows how Oracle-backed retrieval can fit that interface.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is this RAG vs agent memory?&amp;nbsp;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not exactly. RAG retrieves external knowledge for a response, while agent memory preserves useful context, decisions, tool traces, and workflow state across sessions. In practice, production systems often use both.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is the&amp;nbsp;minimum&amp;nbsp;viable&amp;nbsp;setup?&amp;nbsp;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;SQLcl&amp;nbsp;MCP configured in Cursor CLI, one safe saved Oracle connection, and a read-only validation flow.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Should production start with read-write permissions?&amp;nbsp;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Usually&amp;nbsp;no. Start read-only, log everything important, and add write scopes gradually with explicit approvals.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is the best rollout strategy?&amp;nbsp;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Pilot in development with read-only access and strong logging, then expand capabilities in controlled phases as the team learns which memory and tool paths are&amp;nbsp;actually useful.&amp;nbsp;&lt;/p&gt;





&lt;h2&gt;
&lt;strong&gt;Companion Troubleshooting Appendix&lt;/strong&gt;&amp;nbsp;&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Minimum&amp;nbsp;viable&amp;nbsp;setup:&lt;/strong&gt;&amp;nbsp;SQLcl&amp;nbsp;MCP configured in Cursor CLI, one approved Oracle connection, read-only validation, and database-side activity logging.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First checks: &lt;/strong&gt;confirm&amp;nbsp;sql&amp;nbsp;-mcp&amp;nbsp;starts, Cursor sees the tools after&amp;nbsp;restart, and the saved&amp;nbsp;SQLcl&amp;nbsp;connection alias resolves.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Environment model:&lt;/strong&gt; use separate credentials and policies for dev, test, and prod, with stricter controls as capability expands.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Logging model:&lt;/strong&gt; capture tool name, timestamp, thread ID, status, sanitized input/output summaries, and relevant&amp;nbsp;SQLcl&amp;nbsp;MCP log records.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Retrieval quality:&lt;/strong&gt; tune chunk size, enrich metadata, review embedding choice, and evaluate retrieval against representative queries.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Common anti-pattern:&lt;/strong&gt; expanding tool surfaces before ownership, logging standards, and runbooks are in place.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rollout path: &lt;/strong&gt;pilot in dev with read-only access and strong logging, then expand capabilities in controlled phases.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Schema:&lt;/strong&gt; Agent Memory package tables need a schema and tablespace that can create the package's JSON-backed objects.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scheduler job: &lt;/strong&gt;package-managed expiry purge needs scheduler-job privilege or managed setup for production.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Model calls:&lt;/strong&gt; an API key can be present but still fail if the model provider account has no quota.&amp;nbsp;&lt;/p&gt;





&lt;h2&gt;
&lt;strong&gt;Resources&lt;/strong&gt;&amp;nbsp;&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.oracle.com/en/database/oracle/agent-memory/26.6/guide/get-started.html" rel="noreferrer noopener"&gt;Oracle AI Agent Memory documentation&lt;/a&gt;&amp;nbsp;&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.oracle.com/en/database/oracle/oracle-database/26/" rel="noreferrer noopener"&gt;Oracle&amp;nbsp;AI&amp;nbsp;Database 26ai documentation&lt;/a&gt;&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://pypi.org/project/oracleagentmemory/" rel="noreferrer noopener"&gt;oracleagentmemory on PyPI&lt;/a&gt;&amp;nbsp;&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.oracle.com/en/database/oracle/sql-developer-command-line/25.2/sqcug/sqlcl-mcp-server.html" rel="noreferrer noopener"&gt;SQLcl MCP Server&lt;/a&gt;&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/oracle-devrel/oracle-ai-developer-hub/blob/main/notebooks/cursor_mcp_oracle_ai_database.ipynb" rel="noopener noreferrer"&gt;End-to-end&amp;nbsp;Companion notebook&lt;/a&gt;&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cursor</category>
      <category>mcp</category>
      <category>oracle</category>
      <category>database</category>
    </item>
    <item>
      <title>Hybrid Retrieval for Agent Memory: Vector, Lexical, and Metadata Together</title>
      <dc:creator>Anya Summers</dc:creator>
      <pubDate>Fri, 28 Aug 2026 11:42:47 +0000</pubDate>
      <link>https://dev.to/oracledevs/hybrid-retrieval-for-agent-memory-vector-lexical-and-metadata-together-3com</link>
      <guid>https://dev.to/oracledevs/hybrid-retrieval-for-agent-memory-vector-lexical-and-metadata-together-3com</guid>
      <description>&lt;p&gt;&lt;strong&gt;Hybrid retrieval for agent memory is a multi-stage pipeline. Pure vector search is the prototype.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The companion repo implements the full pipeline against the schema and two-layer pattern from &lt;a href="https://blogs.oracle.com/developers/persistent-memory-and-derived-context-a-two-layer-pattern-for-agents" rel="noopener noreferrer"&gt;&lt;em&gt;Persistent Memory and Derived Context&lt;/em&gt;&lt;/a&gt;, &lt;a href="https://blogs.oracle.com/developers/from-prompt-to-persistence-part-1-designing-multi-tenant-agent-memory-schemas-for-saas" rel="noopener noreferrer"&gt;&lt;em&gt;From Prompt to Persistence part 1&lt;/em&gt;&lt;/a&gt;, and &lt;a href="https://blogs.oracle.com/developers/from-prompt-to-persistence-part-2-putting-the-multi-tenant-agent-memory-schema-to-work" rel="noopener noreferrer"&gt;&lt;em&gt;part 2&lt;/em&gt;&lt;/a&gt;: hybrid SQL with in-database PREDICTION reranking, a &lt;a href="https://pypi.org/project/langchain-oracledb/" rel="noopener noreferrer"&gt;LangChain&lt;/a&gt; retriever, a &lt;a href="https://pypi.org/project/langgraph-oracledb/" rel="noopener noreferrer"&gt;LangGraph&lt;/a&gt; retrieval graph, and the evaluation suite with its golden-set labeling flow, runnable end to end against an Oracle AI Database 26ai Free instance on the 23-document research corpus built for this article. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Companion notebook: &lt;a href="https://github.com/oracle-devrel/oracle-ai-developer-hub/blob/main/notebooks/hybrid_retrieval_pipeline.ipynb" rel="noopener noreferrer"&gt;Hybrid Retrieval Pipeline&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;





&lt;h2&gt;
&lt;a&gt;&lt;/a&gt;Key takeaways&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Hybrid retrieval for agent memory starts with database-enforced metadata filters. It then combines vector and lexical candidates through rank fusion, with optional reranking.&lt;/li&gt;



&lt;li&gt;Vector search finds semantic matches. Lexical search pins identifiers and exact strings. SQL and JSON metadata constrain scope and lifecycle before applying source and type restrictions. Those filters run before either search mode ranks a row.&lt;/li&gt;



&lt;li&gt;Every stage has to earn its place on a labeled set. In the companion experiment, equal-weight hybrid fusion underperformed vector search on the small corpus, while reranking improved ordering at a material latency cost.&lt;/li&gt;
&lt;/ul&gt;





&lt;p&gt;Jane's research assistant at Acme has good days and bad days. Some answers are sharp, cited, exactly right. Others are vaguely on-topic in a way that's hard to complain about but impossible to trust. There's no obvious pattern. Same model, same prompt template, same memory schema underneath. The team suspects the model, because the model is the part everyone watches.&lt;/p&gt;

&lt;p&gt;Retrieval is often the first place to look. When the right rows reach the context window, the model has a better chance of producing a grounded answer. When they do not, even a capable model may answer confidently from the wrong material. The model cannot recover a passage the retrieval pipeline never supplied.&lt;/p&gt;

&lt;p&gt;This is the third piece in a series. &lt;a href="https://blogs.oracle.com/developers/from-prompt-to-persistence-part-1-designing-multi-tenant-agent-memory-schemas-for-saas" rel="noopener noreferrer"&gt;From Prompt to Persistence part 1&lt;/a&gt; and &lt;a href="https://blogs.oracle.com/developers/from-prompt-to-persistence-part-2-putting-the-multi-tenant-agent-memory-schema-to-work" rel="noopener noreferrer"&gt;part 2&lt;/a&gt; built the multi-tenant schema: eight typed memory tables, four scope columns, row-level security on the tenant boundary, and provenance on every durable row. &lt;a href="https://blogs.oracle.com/developers/persistent-memory-and-derived-context-a-two-layer-pattern-for-agents" rel="noopener noreferrer"&gt;Persistent Memory and Derived Context&lt;/a&gt; separated the canonical layer from the derived layer so every retrieved row still points to something true.&lt;/p&gt;

&lt;p&gt;Part 2 ended with vector and lexical scores side by side, deliberately unfused. This article picks up there. The schema stores the memory. The two-layer pattern keeps it honest. This pipeline reads it and assembles the working set the agent uses.&lt;/p&gt;





&lt;h2&gt;
&lt;a&gt;&lt;/a&gt;Why your agent feels slightly off&lt;/h2&gt;

&lt;p&gt;The question we'll use throughout comes from the research assistant scenario the whole series has been building. Jane asks:&lt;/p&gt;

&lt;p&gt;"What did the Letta paper say about memory eviction, and how does that compare to what the AgentCore docs recommend?"&lt;/p&gt;

&lt;p&gt;This one sentence stresses every part of a retrieval system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Letta" and "AgentCore" are exact-match identifiers. Embedding models under-weight rare proper nouns, so vector search will happily return chunks about memory management from papers that never mention either name.&lt;/li&gt;



&lt;li&gt;"Memory eviction" is a concept. The Letta paper might call it context pruning, message eviction, or recursive summarization. Lexical search will miss every paraphrase.&lt;/li&gt;



&lt;li&gt;"The docs the user has access to" is a scope constraint. Only ingested papers and vendor docs in collections Jane's agent is allowed to read, inside Acme's tenant boundary, should be candidates at all. No similarity score enforces that. Only a filter does.&lt;/li&gt;



&lt;li&gt;Comparing two sources means the context window needs good chunks from both documents rather than five redundant chunks from whichever one embeds closer to the query.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Run it through pure vector top-k against the research corpus the companion notebook builds and the answer surfaces AgentCore only incidentally, never the passage that answers the question. Run it through hybrid retrieval and both sources appear, with the first AgentCore retention passage at fused rank eleven. Reranking brings that passage to rank five, after four Letta passages. Context budgeting then preserves coverage from both sources so the agent can make the comparison Jane asked for.&lt;/p&gt;

&lt;p&gt;Same model every time. The thing that moved was retrieval. That's the thesis: retrieval is an architecture.&lt;/p&gt;





&lt;h2&gt;
&lt;a&gt;&lt;/a&gt;Vector, lexical, metadata: three retrieval modes, three failure modes&lt;/h2&gt;

&lt;p&gt;This pipeline uses three retrieval modes, and each one covers a failure mode the other two leave exposed. Whether both ranking modes earn a place depends on the corpus and query mix.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vector retrieval&lt;/strong&gt; ranks by semantic similarity. It's the mode that handles paraphrase, synonyms, conceptual matches, the reason &lt;em&gt;memory eviction&lt;/em&gt; can find a chunk about "pruning stale messages from the context window." Its failure mode is specificity. Rare tokens, product names, error strings, version numbers, benchmark identifiers: all of them get diluted into an embedding that mostly encodes the surrounding topic. Ask a vector index about "Letta" and it hears "a paper about agent memory," which describes half the corpus.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lexical retrieval&lt;/strong&gt; ranks by term matching using Oracle Text relevance scores. It's the mode that nails exact identifiers, code fragments, error messages typed verbatim. A user who pastes &lt;code&gt;ORA-51805&lt;/code&gt; into the chat deserves the document containing &lt;code&gt;ORA-51805&lt;/code&gt;, and no embedding model should be trusted with that job. Its failure mode is the inverse: any vocabulary shift between the query and the document breaks it. "Memory eviction" finds nothing in a paper that only ever says "context pruning."&lt;/p&gt;

&lt;p&gt;Metadata retrieval does not rank. It filters on scope, lifecycle, time, source, collection, and type. Those predicates become a security boundary only when the database enforces them with row-level security or an equivalent policy. An application filter is useful for retrieval scope, but it is not sufficient tenant isolation because a caller can forget it. A relevance score is an opinion. A database-enforced scope predicate is a boundary.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Mode&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Strong at&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Fails at&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Role in the pipeline&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Vector&lt;/td&gt;
&lt;td&gt;Paraphrase, synonyms, concepts&lt;/td&gt;
&lt;td&gt;Rare terms, identifiers, exact strings&lt;/td&gt;
&lt;td&gt;Candidate generation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lexical&lt;/td&gt;
&lt;td&gt;Identifiers, code, verbatim phrases&lt;/td&gt;
&lt;td&gt;Rephrasing, vocabulary drift&lt;/td&gt;
&lt;td&gt;Candidate generation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Metadata&lt;/td&gt;
&lt;td&gt;Scope, time, policy, source, type&lt;/td&gt;
&lt;td&gt;Ranking anything&lt;/td&gt;
&lt;td&gt;Filtering; security when policy-enforced&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Jane's question needs all three at once: metadata to bound the search to her tenant and collections, lexical to pin "Letta" and "AgentCore," vector to find eviction discussed in other words. Only the filter is mandatory for correctness — a missing scope predicate is a security bug, while a missing retriever is only a quality one.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.oracle.com%2Fdevelopers%2Fwp-content%2Fuploads%2Fsites%2F129%2F2026%2F08%2FPicture1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.oracle.com%2Fdevelopers%2Fwp-content%2Fuploads%2Fsites%2F129%2F2026%2F08%2FPicture1.png" alt="Diagram comparing vector, lexical, and metadata search. Vector ranks semantic similarity, lexical ranks term matches, and metadata filters scope, time, source, type, tenant ID, deletion, and validity; all contribute to candidate generation or filtering." width="364" height="1194"&gt;&lt;/a&gt;&lt;em&gt;Figure 1: Three retrieval modes and the failure mode each one covers for the others.&lt;/em&gt;&lt;p&gt;&lt;/p&gt;





&lt;h2&gt;
&lt;a&gt;&lt;/a&gt;The hybrid retrieval pipeline, stage by stage&lt;/h2&gt;

&lt;p&gt;The order of stages follows one principle borrowed directly from query optimization: &lt;strong&gt;filter early, rerank late&lt;/strong&gt;. Cheap, high-selectivity operations run first and shrink the candidate set. Expensive, high-precision operations run last, on the smallest set that still contains the answer. Databases have applied this thinking to query plans for fifty years. Retrieval pipelines are query plans, and most of them are written by people who've never looked at one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stage 1: query understanding.&lt;/strong&gt; Before anything touches an index, the raw question gets decomposed: extract entities ("Letta", "AgentCore"), expand the concept vocabulary (memory eviction to context pruning, message eviction, summarization), and classify intent. A factual lookup, a procedural recall, and an open-ended comparison want different candidate mixes from different tables. A production implementation can use one fast-model call that returns a small JSON object with entities and expanded terms, plus intent and time bounds. The companion notebook uses fixed rules and a small thesaurus for the same step. This keeps the demo repeatable and removes model-response variance from the retrieval comparison. The plan must still yield a valid lexical expression when a question has no proper nouns or thesaurus hits; the fallback is the question's own content words.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stage 2: metadata filtering.&lt;/strong&gt; Apply scope and policy before any ranking happens. In the schema from &lt;a href="https://blogs.oracle.com/developers/from-prompt-to-persistence-part-1-designing-multi-tenant-agent-memory-schemas-for-saas" rel="noopener noreferrer"&gt;&lt;em&gt;From Prompt to Persistence (Part 1)&lt;/em&gt;&lt;/a&gt;, the tenant predicate is appended by row-level security, so it's enforced even if every other stage of the pipeline has a bug. The remaining predicates are the working scope block from part 1 (&lt;code&gt;user_id&lt;/code&gt;/&lt;code&gt;agent_id&lt;/code&gt;/&lt;code&gt;thread_id &lt;/code&gt;with &lt;code&gt;NULL&lt;/code&gt; inheritance, &lt;code&gt;deleted_at IS NULL&lt;/code&gt;, &lt;code&gt;valid_until &lt;/code&gt;still open) plus whatever the query understanding stage produced: collection restrictions, time windows, source types. This stage is typically cheaper than ranking and can shrink the candidate set substantially when the predicates are selective. Every row it removes is a row the expensive stages never touch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stage 3: hybrid candidate generation.&lt;/strong&gt; Vector search and lexical search run against the filtered set, each producing its own ranked top-k (50 is a reasonable default for each side). They disagree with each other constantly, and that's the point. The vector pool has the paraphrases. The lexical pool has the identifiers. The answer to Jane's question lives in the union.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stage 4: score fusion.&lt;/strong&gt; Vector distances and lexical scores live on incompatible scales, so don't combine the scores. Combine the ranks. Reciprocal rank fusion (RRF) gives each candidate &lt;code&gt;1/(60 + rank)&lt;/code&gt; from each list it appears in and sums them. RRF avoids score calibration and has one constant, conventionally set to 60. A calibrated linear combination can outperform it when measured score distributions support the added complexity, so treat RRF as a strong baseline rather than a guaranteed winner.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stage 5: reranking.&lt;/strong&gt; A cross-encoder scores the query and candidate together, which can improve ordering after candidate generation. It is also the most expensive stage, so it runs on 40 candidates instead of the full corpus. In the companion evaluation, this stage produced the largest NDCG gain, along with roughly 2.2 seconds of p50 added latency.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stage 6: context budgeting.&lt;/strong&gt; The token budget is fixed before retrieval starts, and the reranked list gets cut to fit it. The notebook uses a greedy budget pass instead of simple truncation: prefer diversity across source documents for a comparison question, drop near-duplicate chunks even when they score well, and stop early when scores fall off a cliff. More tokens of bad context is just more expensive bad context, and evaluations of long-context models show that task performance can drop when relevant material appears in the middle, exactly where a lazy pipeline dumps its marginal hits. What you didn't include is a decision too. The output of this stage is the working set: the ranked, budgeted, provenance-tagged slice of memory that the agent loop's Infer &amp;amp; Act phase actually consumes. How that working set gets ordered and laid out inside the prompt is the next article in this series; this pipeline's job ends at deciding what earned a seat.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.oracle.com%2Fdevelopers%2Fwp-content%2Fuploads%2Fsites%2F129%2F2026%2F08%2FPicture2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.oracle.com%2Fdevelopers%2Fwp-content%2Fuploads%2Fsites%2F129%2F2026%2F08%2FPicture2.png" alt="Vertical workflow showing query understanding, metadata filtering, candidate generation from vector and lexical pools, score fusion, reranking, and context budgeting. Candidate counts shrink from all documents to 136, 100, 40, 40, and 7." width="586" height="1228"&gt;&lt;/a&gt;&lt;em&gt;Figure 2: The six-stage pipeline. Candidate counts shrink left to right; per-candidate cost grows.&lt;/em&gt;&lt;p&gt;&lt;/p&gt;

&lt;p&gt;One thing worth making explicit, because it connects this article to the last one: the pipeline reads the derived layer, and only the derived layer, for ranking. Embeddings, text indexes, materialized projections: all of it is derived context, rebuilt from canonical memory under the sync policies from the two-layer article. The metadata filters run against canonical scope and lifecycle columns. Every candidate that survives to the working set carries its provenance (&lt;code&gt;source_event_id&lt;/code&gt;, document ID, version) back to a canonical row. Fast path for ranking with true path for citation. The arrow between them points one direction.&lt;/p&gt;





&lt;h2&gt;
&lt;a&gt;&lt;/a&gt;The hybrid query in one SQL statement&lt;/h2&gt;

&lt;p&gt;Now the centerpiece. Stages 2 through 5 (metadata filtering, both candidate generators, RRF fusion, and the cross-encoder rescore) as one SQL statement against the knowledge base tables from part 1. Not an integration project. A query.&lt;/p&gt;

&lt;p&gt;One piece of DDL is needed on top of the part 1 schema. Entity and summarization memory already carry Oracle Text indexes for their lexical halves; the knowledge base chunks need the same:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;CREATE INDEX idx_kb_chunk_text ON knowledge_base_chunk (content)
  INDEXTYPE IS CTXSYS.CONTEXT PARAMETERS ('SYNC (ON COMMIT)');&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;With that in place, here's Jane's question as a query plan. &lt;code&gt;:query&lt;/code&gt; is the natural-language question; &lt;code&gt;:lex_query&lt;/code&gt; is the Oracle Text expression the query understanding stage built from the extracted entities and expanded terms (something like &lt;code&gt;{Letta} OR {AgentCore} OR {memory eviction} OR {context pruning}&lt;/code&gt;. Brace every term. Oracle Text reserves &lt;code&gt;ABOUT&lt;/code&gt;, &lt;code&gt;ACCUM&lt;/code&gt;, &lt;code&gt;AND&lt;/code&gt;, &lt;code&gt;NEAR&lt;/code&gt;, &lt;code&gt;NOT&lt;/code&gt; and &lt;code&gt;WITHIN&lt;/code&gt; as operators, so an ordinary English word lifted from a user's question is a syntax error unescaped. The question "where do models attend least within a long context?" fails on &lt;code&gt;within&lt;/code&gt;. Braces make the content literal, which also handles hyphens, and it saves maintaining a reserved-word list that has to track Oracle's):&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;WITH
-- Stage 3a: vector candidates over the metadata-filtered set
vec_pool AS (
  SELECT c.id,
         ROW_NUMBER() OVER (
           ORDER BY VECTOR_DISTANCE(c.embedding,
             VECTOR_EMBEDDING(ALL_MINILM_L12_V2 USING :query AS DATA),
             COSINE)) AS vec_rank
    FROM knowledge_base_chunk c
    JOIN knowledge_base_document d ON d.id = c.document_id
   WHERE d.collection IN ('ingested-papers', 'vendor-docs')     -- Stage 2 starts here
     AND d.valid_until IS NULL AND d.deleted_at IS NULL
     AND c.deleted_at IS NULL
     AND (d.user_id IS NULL OR d.user_id = :user_id)
     AND JSON_VALUE(c.metadata, '$.section_type') &amp;lt;&amp;gt; 'references'
   ORDER BY VECTOR_DISTANCE(c.embedding,
             VECTOR_EMBEDDING(ALL_MINILM_L12_V2 USING :query AS DATA),
             COSINE)
   FETCH FIRST 50 ROWS ONLY
),
-- Stage 3b: lexical candidates over the same filtered set
lex_pool AS (
  SELECT c.id,
         DENSE_RANK() OVER (ORDER BY SCORE(1) DESC) AS lex_rank
    FROM knowledge_base_chunk c
    JOIN knowledge_base_document d ON d.id = c.document_id
   WHERE CONTAINS(c.content, :lex_query, 1) &amp;gt; 0
     AND d.collection IN ('ingested-papers', 'vendor-docs')
     AND d.valid_until IS NULL AND d.deleted_at IS NULL
     AND c.deleted_at IS NULL
     AND (d.user_id IS NULL OR d.user_id = :user_id)
     AND JSON_VALUE(c.metadata, '$.section_type') &amp;lt;&amp;gt; 'references'
   ORDER BY SCORE(1) DESC
   FETCH FIRST 50 ROWS ONLY
),
-- Stage 4: reciprocal rank fusion across the two pools
fused AS (
  SELECT COALESCE(v.id, l.id) AS id,
         COALESCE(1 / (60 + v.vec_rank), 0)
       + COALESCE(1 / (60 + l.lex_rank), 0) AS rrf_score
    FROM vec_pool v
    FULL OUTER JOIN lex_pool l ON v.id = l.id
),
candidates AS (
  SELECT id, rrf_score
    FROM fused
   ORDER BY rrf_score DESC
   FETCH FIRST 40 ROWS ONLY
)
SELECT c.id, c.content, c.chunk_index, c.metadata,
       d.id AS document_id, d.title, d.source_uri, d.version,
       k.rrf_score,
       PREDICTION(BGE_RERANKER USING :query AS FIRST_INPUT,
                  d.title || '. ' || c.content AS SECOND_INPUT) AS rerank_score
  FROM candidates k
  JOIN knowledge_base_chunk c    ON c.id = k.id
  JOIN knowledge_base_document d ON d.id = c.document_id
 ORDER BY rerank_score DESC;
-- tenant_id predicate appended automatically by RLS on every table reference
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Walking the clauses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This example demonstrates the inherited &lt;code&gt;user_id&lt;/code&gt; scope predicate inside each pool. The tenant predicate does not appear because it cannot be forgotten: RLS appends it to every table reference, in both pools and on the final join, every time. Apply corresponding &lt;code&gt;agent_id&lt;/code&gt; or &lt;code&gt;thread_id&lt;/code&gt; predicates when knowledge-base documents use those scopes; this shared research corpus is tenant- and user-scoped. That holds only if the policy loop from part 1 matches &lt;code&gt;KNOWLEDGE_BASE%&lt;/code&gt; as well as &lt;code&gt;%_MEMORY&lt;/code&gt;. Both knowledge-base tables carry &lt;code&gt;tenant_id&lt;/code&gt; but neither name ends in &lt;code&gt;_MEMORY&lt;/code&gt;, so an obvious loop leaves precisely the two tables this query reads without a policy, and the query has no predicate of its own to fall back on.&lt;/li&gt;



&lt;li&gt;
&lt;code&gt;JSON_VALUE(c.metadata, '$.section_type')&lt;/code&gt; is a derived-metadata filter, here excluding bibliography chunks that match "Letta" forty times without saying anything. Part 1's functional index pattern on JSON paths makes this predicate indexable rather than a per-row parse.&lt;/li&gt;



&lt;li&gt;
&lt;code&gt;VECTOR_DISTANCE&lt;/code&gt; with an in-database &lt;code&gt;VECTOR_EMBEDDING&lt;/code&gt; means the query text is embedded by the engine in the statement itself, with the HNSW (&lt;a href="https://en.wikipedia.org/wiki/Hierarchical_navigable_small_world" rel="noopener noreferrer"&gt;&lt;em&gt;Hierarchical Navigable Small World&lt;/em&gt;&lt;/a&gt;) index from part 1 (&lt;code&gt;ORGANIZATION INMEMORY NEIGHBOR GRAPH&lt;/code&gt;) available to the optimizer. On this 137-chunk run, Oracle chose a different physical plan.&lt;/li&gt;



&lt;li&gt;
&lt;code&gt;CONTAINS ... SCORE(1)&lt;/code&gt; is the Oracle Text half, running against the index we just created. Note that both pools repeat the same metadata predicates: each generator ranks only what the filters allow, which is what &lt;em&gt;filter early&lt;/em&gt; means in practice. Note also that the lexical pool ranks with &lt;code&gt;DENSE_RANK&lt;/code&gt; rather than &lt;code&gt;ROW_NUMBER&lt;/code&gt;. Oracle Text &lt;code&gt;SCORE() &lt;/code&gt;is a coarse integer, and on short chunks ties are the norm rather than the exception: one query against the companion notebook's corpus matched 80 chunks across ten distinct scores. &lt;code&gt;ROW_NUMBER&lt;/code&gt; would order those tied chunks arbitrarily, and the next stage would read that arbitrary order as signal. This is the sharp edge on rank fusion generally: discarding the scores and trusting the order is what makes RRF insensitive to score scaling, and equally what makes it credulous when the order is partly noise.&lt;/li&gt;



&lt;li&gt;The &lt;code&gt;FULL OUTER JOIN&lt;/code&gt; plus &lt;code&gt;COALESCE&lt;/code&gt; arithmetic is RRF, in four lines. A chunk found by both generators gets both reciprocal terms and rises; a chunk found by only one still competes. This is the fusion that part 2's query deferred: where that statement returned &lt;code&gt;vec_score&lt;/code&gt; and &lt;code&gt;lex_score&lt;/code&gt; side by side and left the ranking to a later stage, this is the later stage.&lt;/li&gt;



&lt;li&gt;The &lt;code&gt;candidates&lt;/code&gt; CTE caps the set at 40 rows before reranking, which is Stage 5's input contract.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The CTEs describe the logical stages. The optimizer still chooses the physical plan. In the companion run, the 137-chunk corpus was small enough that Oracle used the collection and chunk indexes, window sorts, and a full outer hash join without an HNSW step. A larger corpus may produce a different plan. Read the plan Oracle chose before tuning the plan you expected.&lt;/p&gt;

&lt;p&gt;The plan below is condensed from the companion notebook's &lt;code&gt;DBMS_XPLAN&lt;/code&gt; output:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;SORT ORDER BY STOPKEY
 NESTED LOOPS
  HASH JOIN FULL OUTER (RRF fusion)
   WINDOW SORT PUSHED RANK (vector pool)
    INDEX RANGE SCAN IDX_KB_DOC_COLLECTION
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And one property that's easy to miss because nothing visibly does it: the whole statement reads &lt;strong&gt;one transactional snapshot&lt;/strong&gt;. The vector pool, the lexical pool, the final join: all of them see the same instant of the database. If a document is being superseded mid-query by a Promote step on another connection, this query sees it entirely old or entirely new, never a chunk from each. After the two-layer article made the case for keeping canonical and derived in sync, this is the read-side payoff: consistency you don't write code for.&lt;/p&gt;

&lt;h3&gt;
&lt;a&gt;&lt;/a&gt;The polyglot version, for contrast&lt;/h3&gt;

&lt;p&gt;The same pipeline with a dedicated vector store, Elasticsearch for lexical, and Postgres for scope looks like this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;# 1. Resolve scope: which document IDs may this user see? (Postgres)
doc_ids = pg.fetch_allowed_documents(tenant_id, user_id,
                                 collections=['ingested-papers', 'vendor-docs'])

# 2. Vector candidates (vector store), filtered to allowed docs
vec_hits = vector_store.query(vector=embed(query), top_k=50,
                              filter={'tenant_id': tenant_id,
                                      'document_id': {'$in': doc_ids}})

# 3. Lexical candidates (Elasticsearch), same filter re-expressed in its DSL
lex_hits = es.search(index='kb_chunks', size=50, query={
    'bool': {'must':   {'query_string': {'query': lex_query}},
             'filter': [{'term':  {'tenant_id': tenant_id}},
                        {'terms': {'document_id': doc_ids}}]}})

# 4. RRF, in application code
fused = rrf_merge(vec_hits, lex_hits, k=60)[:40]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Four numbered steps, three systems, and every number hides a cost. Three round trips run serially because each depends on the last, so network and service latency accumulate before the application can fuse the results. The tenant boundary is enforced three times in three different filter dialects, and each one is a place it can be forgotten; the schema articles made the case that this is the failure mode that matters most in multi-tenant SaaS. The $in filter ships potentially thousands of document IDs over the wire because the systems can't join. There's no shared snapshot, so a document superseded between step 2 and step 3 can contribute its old chunks to one pool and its new chunks to the other. And the RRF merge is now application code you own and debug at 2 AM.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Concern&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Converged SQL pipeline&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Three-system pipeline&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Application round trips&lt;/td&gt;
&lt;td&gt;One statement through reranking&lt;/td&gt;
&lt;td&gt;Three serial calls before application fusion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tenant enforcement&lt;/td&gt;
&lt;td&gt;One database policy model&lt;/td&gt;
&lt;td&gt;Repeated in three filter dialects&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data crossing boundaries&lt;/td&gt;
&lt;td&gt;Candidate data stays in the database through fusion&lt;/td&gt;
&lt;td&gt;IDs and candidates cross service boundaries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Read consistency&lt;/td&gt;
&lt;td&gt;One transactional snapshot&lt;/td&gt;
&lt;td&gt;No shared snapshot across systems&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fusion code&lt;/td&gt;
&lt;td&gt;SQL in the query&lt;/td&gt;
&lt;td&gt;Application code to build and operate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Latency and service cost&lt;/td&gt;
&lt;td&gt;Measure the deployed database and reranker&lt;/td&gt;
&lt;td&gt;Measure network, service, and merge costs together&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;None of these are exotic failures. They are the ordinary tax of putting a join across a network boundary. When retrieval can live in SQL, that tax disappears into the engine, and there's no merge layer to build or operate. Sidecars and dedicated stores can still be the right choice when hardware, latency, or existing infrastructure justifies the additional boundary.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.oracle.com%2Fdevelopers%2Fwp-content%2Fuploads%2Fsites%2F129%2F2026%2F08%2FPicture3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.oracle.com%2Fdevelopers%2Fwp-content%2Fuploads%2Fsites%2F129%2F2026%2F08%2FPicture3.png" alt="Comparison diagram: the converged approach applies row-level scope once, combines vector and lexical pools with a full outer join and prediction rerank in one round trip; the polyglot approach filters Postgres, a vector store, and Elasticsearch separately before application-level merging." width="390" height="1198"&gt;&lt;/a&gt;&lt;em&gt;Figure 3: One statement, one snapshot, one security model versus three systems, three filters, an application-layer merge.&lt;/em&gt;&lt;p&gt;&lt;/p&gt;





&lt;h2&gt;
&lt;a&gt;&lt;/a&gt;Reranking: precision after candidate generation&lt;/h2&gt;

&lt;p&gt;Everything so far uses bi-encoders: the query and every chunk were embedded separately, at different times, with no knowledge of each other, and relevance was approximated by the distance between those independent points. That's what makes candidate generation cheap and scalable, and it's also the ceiling on its precision.&lt;/p&gt;

&lt;p&gt;A cross-encoder can improve on that approximation by scoring the pair together. Query and candidate go through the model in one pass, attention flows between their tokens, and the output is a single relevance score computed with full knowledge of both texts. It can see that a chunk mentioning &lt;em&gt;message eviction policy in MemGPT-style agents&lt;/em&gt; answers a question about &lt;em&gt;memory eviction in the Letta paper&lt;/em&gt; (same lineage, different names), and that a chunk merely containing the word "Letta" in a citation list answers nothing. Bi-encoders can't make either judgment. This is also why cross-encoders can't generate candidates: scoring every chunk in the corpus against every query is a full scan of the most expensive kind. They only work as the late, narrow stage of a funnel that something cheaper built.&lt;/p&gt;

&lt;p&gt;Placement and sizing follow from that. The reranker runs after fusion and before budgeting, on the fused top-N. Size N generously enough to recover from candidate-generation mistakes (the right chunk at fused rank 35 can still be rescued) and small enough to afford: The notebook uses 40. Choose a smaller or larger candidate set from measured recall and latency on the deployed workload. Reranking cannot fix candidate-set recall. If the answer isn't in the candidate set, no amount of rescoring surfaces it, which is why the generators cast a wide net and the reranker exists to clean it up.&lt;/p&gt;

&lt;p&gt;One implementation detail earns its own paragraph, because getting it wrong is silent. Rerank the chunk with its document title prepended. Chunks are short, and a cross-encoder handed a bare sentence has nothing telling it which document that sentence came from. On the companion notebook's corpus, scoring the chunk text alone lost document context the model needed. For one tenant-isolation question, the best Multi-Tenant chunk ranked 11th; the next two landed at 21st and 24th. Prepending the title moved all three into the top three. The fix is &lt;code&gt;d.title || '. ' || c.content&lt;/code&gt; where you would otherwise have passed &lt;code&gt;c.content&lt;/code&gt;. Nothing in the output announces when you have this wrong, which is the first argument for the evaluation section below.&lt;/p&gt;

&lt;p&gt;For the reference implementation, run a small open-source cross-encoder in the database as a &lt;code&gt;PREDICTION&lt;/code&gt; expression so stages 2 through 5 remain one SQL statement and candidate text stays inside the database boundary. The companion notebook uses &lt;code&gt;BAAI/bge-reranker-base&lt;/code&gt; because it exported and loaded successfully in the tested Oracle AI Database 26ai and OML4Py 2.1.1 environment. The notebook also demonstrates the documented &lt;a href="https://docs.oracle.com/en/database/oracle/oracle-database/26/vecse/utl_to_rerank-dbms_vector_chain.html" rel="noopener noreferrer"&gt;&lt;code&gt;DBMS_VECTOR_CHAIN.UTL_TO_RERANK&lt;/code&gt;&lt;/a&gt; interface. Treat model compatibility and size limits as release-specific.&lt;/p&gt;

&lt;p&gt;In the latest clean 36-query evaluation, the reranked path took 2,121 ms at p50 on the tested CPU, compared with 8 ms for pure vector search: 2,113 ms of added latency. Recent standalone 40-candidate runs took about 2.9–3.1 seconds after model warmup. These measurements cover different query sets and are not latency guarantees. &lt;code&gt;BAAI/bge-reranker-v2-m3&lt;/code&gt; exported to a 2.2 GB artifact and failed to load in the tested environment. Report those observed errors with the environment and release instead of turning them into a permanent product limit. Hosted rerankers send candidate chunks to a third party. A sidecar can be a valid latency or hardware choice, but it moves tenant text across an additional trust boundary and needs its own isolation, access controls, and observability.&lt;/p&gt;

&lt;p&gt;On the companion notebook's illustrative, purpose-built corpus of 23 documents and 137 chunks, with a labeled golden set built the way the evaluation section below describes, the three configurations produce the following results. The passages are paraphrases written for the notebook, so treat this as a worked retrieval experiment rather than a general benchmark:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Strategy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;NDCG@10&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Recall@20&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;p50 added latency&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Pure vector top-k&lt;/td&gt;
&lt;td&gt;0.61&lt;/td&gt;
&lt;td&gt;0.76&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hybrid (RRF)&lt;/td&gt;
&lt;td&gt;0.56&lt;/td&gt;
&lt;td&gt;0.69&lt;/td&gt;
&lt;td&gt;+10 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hybrid + rerank&lt;/td&gt;
&lt;td&gt;0.71&lt;/td&gt;
&lt;td&gt;0.78&lt;/td&gt;
&lt;td&gt;~+2.1 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
&lt;a&gt;&lt;/a&gt;Experiment scope&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;23 documents and 137 chunks, or roughly six chunks per document on average&lt;/li&gt;



&lt;li&gt;Short, paraphrased passages written for the notebook&lt;/li&gt;



&lt;li&gt;36 labeled queries and 106 graded labels&lt;/li&gt;



&lt;li&gt;One embedding model, one fusion policy, one reranker, and one database environment&lt;/li&gt;



&lt;li&gt;Per-query NDCG@10 variation large enough that small metric differences should be treated as noise&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Read the table columnwise. On this small, short-chunk corpus, hybrid costs more than it returns: the lexical pool adds a weak, tie-heavy signal that equal-weight RRF then uses to dilute the vector ranking. That configuration result does not establish a universal verdict on hybrid retrieval. Run the same ablation on your own labeled corpus, with the same lexical tie policy in the hybrid and hybrid-plus-rerank paths, before attributing a gain to reranking alone. Reranking cannot improve candidate recall at 40 because it only reorders the set it receives. It can improve Recall@20 by moving relevant candidates above that cutoff, and it can improve NDCG because ordering is what the context budget consumes. Two seconds is a serious cost in an interactive agent, and it is the price of keeping tenant text inside the database on CPU. If that budget does not fit, a GPU or sidecar is a deployment trade-off; either introduces an additional trust boundary that needs its own tenant controls.&lt;/p&gt;





&lt;h2&gt;Integrating the pipeline with &lt;a href="https://pypi.org/project/langchain-oracledb/" rel="noopener noreferrer"&gt;LangChain&lt;/a&gt; and &lt;a href="https://pypi.org/project/langgraph-oracledb/" rel="noopener noreferrer"&gt;LangGraph&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;A framework doesn't write your retrieval pipeline. It gives you a place to put it. A well-defined place to put things is useful, as long as nobody mistakes the socket for the appliance.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://pypi.org/project/langchain-oracledb/" rel="noopener noreferrer"&gt;LangChain&lt;/a&gt;'s retriever interface is the right entry point, and the integration follows the same pattern as the OracleVS integration in the Oracle &lt;a href="https://pypi.org/project/langchain-oracledb/" rel="noopener noreferrer"&gt;LangChain&lt;/a&gt; modules: a custom retriever that wraps the SQL pipeline and goes through the Memory Manager, because part 2's rule hasn't changed. The manager is the only code allowed to touch the schema, and a framework adapter doesn't get an exemption:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;from langchain_core.retrievers import BaseRetriever
from langchain_core.documents import Document

class OracleHybridRetriever(BaseRetriever):
    """LangChain entry point for the SQL retrieval pipeline."""

    manager: object     # from part 2: the one door into the schema
    user_id: str                 # request scope passed to the manager
    k: int = 8                 # final results after budgeting
    candidates: int = 40       # fused candidates reranked in the SQL statement

    def _get_relevant_documents(self, query: str, *, run_manager) -&amp;gt; list[Document]:
        plan = self.manager.understand_query(query)                 # stage 1
        rows = self.manager.search_knowledge_base(                  # stages 2-5: one SQL statement
            plan, user_id=self.user_id, top_k=self.candidates)
        working_set, _ = budget_context(                        # stage 6
            rows, max_tokens=plan["token_budget"], diversify_by=plan["diversify_by"])
        return [Document(page_content=r["content"],
                         metadata={"title": r["title"], "source": r["source_uri"],
                                   "document_id": r["document_id"],
                                   "version": r["version"],
                                   "score": r["rerank_score"]})
                for r in working_set[: self.k]]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;That class drops into anything expecting a &lt;a href="https://pypi.org/project/langchain-oracledb/" rel="noopener noreferrer"&gt;LangChain&lt;/a&gt; retriever. The consuming chain does not change when the retrieval strategy changes; whether the answers improve is a result for the evaluation suite rather than an assumption built into the integration.&lt;/p&gt;

&lt;p&gt;When the pipeline needs observable stages (and in production it does) &lt;a href="https://pypi.org/project/langgraph-oracledb/" rel="noopener noreferrer"&gt;LangGraph&lt;/a&gt; is the better frame. One node per stage, explicit state between nodes, and a graph you can checkpoint and instrument:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;from typing import TypedDict
from langgraph.graph import StateGraph, START, END

class RetrievalState(TypedDict):
    query: str
    plan: dict          # stage 1 output: entities, expanded terms, intent, budget
    candidates: list    # stages 2-5 output: reranked, provenance-tagged rows
    working_set: list   # stage 6 output: what Infer &amp;amp; Act consumes

g = StateGraph(RetrievalState)
g.add_node('understand', understand_query)      # stage 1: one fast-model call
g.add_node('retrieve',   retrieve_and_rerank)   # stages 2-5: one SQL statement
g.add_node('budget',     budget_context)        # stage 6: pure function

g.add_edge(START, 'understand')
g.add_edge('understand', 'retrieve')
g.add_edge('retrieve', 'budget')
g.add_edge('budget', END)

retrieval = g.compile()
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Notice the graph has three nodes for a six-stage pipeline. Stages 2 through 5 are one node because they are one SQL statement; the database already turned stages 2 through 5 into a single plan. Re-separating them in the orchestration layer would add round trips to recreate a boundary the engine deliberately erased. Oracle AI Database does the retrieval and reranking. &lt;a href="https://pypi.org/project/langgraph-oracledb/" rel="noopener noreferrer"&gt;LangGraph&lt;/a&gt; orchestrates the state around it.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.oracle.com%2Fdevelopers%2Fwp-content%2Fuploads%2Fsites%2F129%2F2026%2F08%2FPicture4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.oracle.com%2Fdevelopers%2Fwp-content%2Fuploads%2Fsites%2F129%2F2026%2F08%2FPicture4.png" alt="Flow diagram from LangGraph StateGraph through understand, retrieve, and budget nodes. Query understanding is stage 1; retrieval runs metadata filtering, candidate generation, score fusion, and reranking as stages 2 to 5 in one SQL statement; context budgeting is stage 6." width="800" height="1055"&gt;&lt;/a&gt;&lt;em&gt;Figure 4: The LangGraph pipeline. Three nodes, six stages, because the database collapsed stages 2 through 5.&lt;/em&gt;&lt;p&gt;&lt;/p&gt;





&lt;h2&gt;
&lt;a&gt;&lt;/a&gt;Evaluating retrieval quality&lt;/h2&gt;

&lt;p&gt;Every number in this article came from somewhere specific, because unmeasured retrieval is unmanaged retrieval. Teams that eyeball a few answers after each change are tuning by anecdote, and anecdotes have terrible recall.&lt;/p&gt;

&lt;p&gt;The metrics worth knowing, and why they’re important:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Recall@k measures how many truly relevant items made the top k. It is the candidate-generation metric. If recall@50 is poor, measure it first and at the fused-candidate boundary because downstream stages cannot recover missing candidates.&lt;/li&gt;



&lt;li&gt;Precision@k measures how many of the top k were relevant. It matters most at the working-set boundary, where every irrelevant item consumes context budget.&lt;/li&gt;



&lt;li&gt;MRR measures how high the first relevant item lands. Use it when one good hit is enough, such as an exact-fact lookup against entity memory.&lt;/li&gt;



&lt;li&gt;NDCG@10 rewards putting the most relevant items highest, using graded rather than binary relevance, over roughly the number of results that fit a context budget. For agent memory retrieval, it captures the ordering that the budget stage consumes. If you track one metric, start with this one.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Warm the reranker before you time it, the same as you would any model server. In recent notebook runs, the first scoring call took roughly 0.9–1.1 seconds. Report that one-time model load separately from warm-path query latency. Assert the range while you are at it. NDCG is normalized to [0, 1], so a value above 1 exposes an evaluation bug, most often an ideal DCG computed from the label list instead of from every gradeable chunk in the corpus. A metric with defined bounds provides a free correctness check on the measurement, and that check is worth more than the metric on the day it fires.&lt;/p&gt;

&lt;p&gt;The golden set is the part that sounds hard and isn't, because the schema already did the work. &lt;code&gt;conversation_memory&lt;/code&gt; is the flight recorder: every question, retrieval, answer, and downstream signal (a follow-up, a thumbs-down, a correction) sits in the trace, scoped and timestamped. Log the candidate IDs as a &lt;code&gt;retrieval_result&lt;/code&gt; event alongside the tool calls. Harvesting question/relevant-chunk pairs is a query against tables you already operate, followed by a labeling pass where a human (or a strong model, with spot checks) marks which retrieved chunks actually supported a good answer. Two hundred labeled questions can be a useful starting target, but the number you need depends on query variance and the regression size you need to detect. The companion notebook runs on a 36-question demonstration set, which is enough to see a large effect and not enough to split hairs. Production traffic is the test set you already have. You just have to harvest it.&lt;/p&gt;

&lt;p&gt;Cadence matters as much as metrics. Once per release is the minimum. On every PR that touches the pipeline is better, and it's cheap once the golden set exists, because the whole evaluation is a few hundred SQL statements and a scoring script. On every embedding model change it's mandatory, and this is non-negotiable in a way the others aren't: new embeddings re-shape the entire vector space, the two-layer article made the derived-layer rebuild an afternoon's work, and an afternoon is exactly enough time to ship a model that's better on the benchmark and worse on your corpus without noticing.&lt;/p&gt;

&lt;p&gt;LLM-as-judge deserves an accurate framing: it's a directional signal, useful for catching large regressions between releases and for triaging where to spend labeling effort, and it is not a benchmark. Judges drift with their prompts and inherit the biases of whatever model is judging. Use one to point the flashlight. Use the labeled golden set to make claims.&lt;/p&gt;





&lt;h2&gt;
&lt;a&gt;&lt;/a&gt;Beyond hybrid: where agentic retrieval comes in&lt;/h2&gt;

&lt;p&gt;Everything to this point is one-shot: a question goes in, the pipeline runs once, a working set comes out. The next evolution is agentic retrieval, where the model itself issues queries, reads intermediate results, decides what to fetch next, and stops when it has enough. This is the direction serious production systems are converging, and it reframes classical single-pass RAG as the legacy pattern: not wrong, just the special case where one round trip happens to be enough.&lt;/p&gt;

&lt;p&gt;Look back at Jane's question and you can see why. "What did Letta say about memory eviction, and how does that compare to AgentCore" is two retrievals wearing one question mark. The one-shot pipeline handles it, with the budget stage working to keep both sources represented. An agentic retriever handles it more naturally: query for the Letta paper's eviction discussion, read the result, notice the paper frames eviction as recursive summarization, then query the AgentCore docs for that specific mechanism, with vocabulary learned from the first result. The second query is better than anything Stage 1 could have written up front, because it was informed by an actual intermediate read.&lt;/p&gt;

&lt;p&gt;The part that matters architecturally: agentic retrieval doesn't replace the hybrid pipeline, it composes it. Every query the model issues (through the retriever tool the &lt;a href="https://pypi.org/project/langchain-oracledb/" rel="noopener noreferrer"&gt;LangChain&lt;/a&gt; section just built) runs through the same six stages, with the same scope filters, RLS boundary, and reranker. The agent decides what to ask. The pipeline decides what to return, and enforces what may be returned. Getting that separation wrong, letting the model's queries bypass the pipeline and hit indexes directly, reopens every tenant-isolation hole the schema articles closed.&lt;/p&gt;

&lt;p&gt;Cost draws the boundary for when to use it. Each retrieval round is a model call plus a pipeline run, so an N-hop agentic retrieval multiplies both latency and spend by N, and N isn't known in advance. Compositional, multi-hop, exploratory questions warrant it. "What's our refund threshold" does not, and an agent that spends four round trips on it is burning money to feel thorough. The practical pattern is an escalation policy: the intent classification from Stage 1 routes simple lookups through one pipeline pass and releases the multi-hop loop only when the question's structure demands it, under an explicit budget of rounds and tokens.&lt;/p&gt;

&lt;p&gt;One question remains open at the end of this pipeline, and it's the next article. The working set this article produces still has to become a context window, and that assembly (what order, what framing, what gets the model's strongest attention) is a deterministic construction problem rather than another retrieval problem. There's a tempting non-answer that would skip it: context windows are a million tokens now, why not retrieve loosely and let the model sort it out? The lost-in-the-middle problem is the counterargument, and the next piece makes it fully: evaluations show that task performance can drop when relevant material sits in the middle of a long context. A larger window changes what fits, while position can still affect performance.&lt;/p&gt;





&lt;h2&gt;
&lt;a&gt;&lt;/a&gt;Where this leaves you&lt;/h2&gt;

&lt;p&gt;The schema articles established tenant-isolated memory with typed records. The two-layer piece separated canonical memory from derived context. This article shows how the agent retrieves from it: filter first, generate vector and lexical candidates, fuse and rerank them, then budget the working set.&lt;/p&gt;

&lt;p&gt;If you take one action from this piece, run the ablation yourself. Pull real questions from your traces and hold the model and prompt fixed. Compare the current pipeline against a vector-only baseline, then test hybrid fusion and reranking separately. Measure ranking quality and answer outcomes beside p50 and p95 latency. Keep only the stages that earn their cost on your corpus.&lt;/p&gt;





&lt;h2&gt;
&lt;a&gt;&lt;/a&gt;Frequently asked questions&lt;/h2&gt;

&lt;h3&gt;
&lt;a&gt;&lt;/a&gt;How do I implement hybrid search for AI agent memory?&lt;/h3&gt;

&lt;p&gt;Apply database-enforced metadata scope first, then generate vector and lexical candidate pools from the allowed rows. Fuse their ranks and optionally rerank the survivors before cutting the result to a context budget. Keep provenance on every returned chunk.&lt;/p&gt;

&lt;h3&gt;
&lt;a&gt;&lt;/a&gt;When should hybrid search be used instead of vector search alone?&lt;/h3&gt;

&lt;p&gt;Test it when queries contain identifiers, error codes, product names, or vocabulary that embeddings tend to blur. Do not assume it wins: on a small corpus with short chunks and a noisy lexical signal, vector search alone may rank better.&lt;/p&gt;

&lt;h3&gt;
&lt;a&gt;&lt;/a&gt;How do vector search, lexical search, and metadata filtering work together?&lt;/h3&gt;

&lt;p&gt;Metadata determines which rows may compete. Vector search finds semantic matches and paraphrases. Lexical search finds exact terms. Rank fusion combines the candidate lists, and reranking can improve their final order.&lt;/p&gt;

&lt;h3&gt;
&lt;a&gt;&lt;/a&gt;How do you evaluate whether hybrid retrieval improves AI agent memory?&lt;/h3&gt;

&lt;p&gt;Build a labeled set from real agent questions and hold the model and prompt fixed. Compare ablations with NDCG and recall, while tracking MRR, answer quality, and p50/p95 latency. Report corpus size and chunking details alongside candidate counts and uncertainty.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>vectordatabase</category>
      <category>oracle</category>
      <category>agentskills</category>
    </item>
    <item>
      <title>What we learned about letting agents into a production database</title>
      <dc:creator>Anya Summers</dc:creator>
      <pubDate>Mon, 24 Aug 2026 10:48:04 +0000</pubDate>
      <link>https://dev.to/oracledevs/what-we-learned-about-letting-agents-into-a-production-database-4ehc</link>
      <guid>https://dev.to/oracledevs/what-we-learned-about-letting-agents-into-a-production-database-4ehc</guid>
      <description>&lt;p&gt;Oracle is bringing its agent memory and Model Context Protocol work to &lt;a href="https://events.linuxfoundation.org/agntcon-mcpcon-europe/" rel="noopener noreferrer"&gt;AGNTCon + MCPCon Europe&lt;/a&gt;, including a workshop showing developers how to use Oracle AI Database as durable memory for AI agents, and how to let agents query enterprise data without generating their own SQL.&lt;/p&gt;





&lt;p&gt;Someone in your business asks their agent what discount rate you should offer in APAC next quarter. It thinks for a moment and says 25%.&lt;/p&gt;

&lt;p&gt;Would you be confident to ship that answer?&lt;/p&gt;

&lt;p&gt;That question came up when we sat down with &lt;a href="https://www.linkedin.com/in/thatjeffsmith/" rel="noopener noreferrer"&gt;Jeff Smith&lt;/a&gt;, who leads much of our Model Context Protocol work at Oracle, to talk about what our customers are running into. We had expected a conversation about protocols and plumbing. It was mostly about trust.&lt;/p&gt;





&lt;h2&gt;Why does an agent need a database at all?&lt;/h2&gt;

&lt;p&gt;Start with the thing that makes agents hard to build. Language models are stateless. Ask one a question today and it answers as though it has never met you, because unless you send the entire history back with every request, it has no durable awareness of anything that came before.&lt;/p&gt;

&lt;p&gt;Fine for a chat window. It falls apart the moment you want an agent that runs for weeks, remembers what it decided in March, and can explain why. &lt;a href="https://blogs.oracle.com/developers/what-is-agent-memory-a-beginners-guide-for-ai-developers" rel="noopener noreferrer"&gt;Memory&lt;/a&gt; is what turns a stateless model into an application that survives contact with a real business.&lt;/p&gt;

&lt;p&gt;Agents need somewhere to keep what they learn, and once that memory holds real company information it belongs where the rest of your data already lives, under the same access rules, backups and auditing. That makes it a database problem, and we have been working on those since 1979, when Oracle shipped the first commercially available SQL database.&lt;/p&gt;

&lt;p&gt;It is why &lt;a href="https://blogs.oracle.com/database/introducing-oracle-ai-agent-memory-a-unified-memory-core-for-enterprise-ai-systems" rel="noopener noreferrer"&gt;Oracle AI Agent Memory&lt;/a&gt; extends Oracle AI Database into a persistent memory core for agents: working memory for the task in hand, long-term factual memory, and procedural memory for how a job actually gets done. Our researchers published &lt;a href="https://arxiv.org/abs/2607.13157" rel="noopener noreferrer"&gt;a paper on it&lt;/a&gt; in July, and teams across the company are building the connective tissue that lets agents reach it.&lt;/p&gt;

&lt;p&gt;Which brings you back to the question we opened with. Once an agent can reach your data, how far do you trust what it does with it?&lt;/p&gt;





&lt;h2&gt;What actually goes wrong when an agent talks to a database?&lt;/h2&gt;

&lt;p&gt;The failure people brace for is the agent writing broken SQL. That is not the failure that hurts.&lt;/p&gt;

&lt;p&gt;The one that hurts is syntactically perfect SQL returning a number that is confidently wrong. The agent goes through your schema, finds an employees table, a sales table, a partners table, works out how it thinks they relate, and comes back with an answer. The query runs clean. Nothing errors. The number is just wrong, because the agent decided “month” meant days since first purchase rather than a calendar month, and nobody catches it for a quarter.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.oracle.com%2Fdevelopers%2Fwp-content%2Fuploads%2Fsites%2F129%2F2026%2F08%2Fagntcon-sql-accuracy-1024x376.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.oracle.com%2Fdevelopers%2Fwp-content%2Fuploads%2Fsites%2F129%2F2026%2F08%2Fagntcon-sql-accuracy-1024x376.png" alt="A bar chart of execution accuracy on the BIRD text-to-SQL benchmark, as published on 21 August 2026. The best published model reaches 80.04 per cent and human performance sits at 92.96 per cent." width="800" height="294"&gt;&lt;/a&gt;BIRD benchmark graphic comparing best published execution accuracy with human performance.&lt;p&gt;&lt;/p&gt;

&lt;p&gt;Ask again tomorrow and it may write a different query. So you end up engineering your prompts harder and harder to coax good SQL out of a model that has no idea which of your forty tables finance actually trusts. On the &lt;a href="https://bird-bench.github.io/" rel="noopener noreferrer"&gt;BIRD benchmark&lt;/a&gt;, as published on 21 August 2026, the best model on the leaderboard reaches 80.04% execution accuracy against 92.96% for humans. Respectable engineering, and worth rechecking before you quote it, because leaderboard positions move. Either way, not a number you want underneath a pricing decision. &lt;/p&gt;





&lt;h2&gt;What if the agent did not write the query at all? &lt;/h2&gt;

&lt;p&gt;This is the part we keep coming back to at Oracle. Your business already solved this, years ago.&lt;/p&gt;

&lt;p&gt;Somewhere in your organisation, there are two dozen reports your analysts use to answer exactly these questions, and behind each is a query somebody wrote, argued about, checked and eventually trusted enough to put in front of the business.&lt;/p&gt;

&lt;p&gt;So our managed service lets you &lt;a href="https://docs.oracle.com/en-us/iaas/database-tools/doc/database-tools-sql-reports.html" rel="noopener noreferrer"&gt;publish those reports to the agent as governed MCP tools&lt;/a&gt;. Each one arrives with the same contract any other tool on the protocol gets: a name, a description the agent can reason about, typed inputs it has to fill, and access control deciding who may call it at all.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.oracle.com%2Fdevelopers%2Fwp-content%2Fuploads%2Fsites%2F129%2F2026%2F08%2Fagntcon-report-as-tool-1024x418.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.oracle.com%2Fdevelopers%2Fwp-content%2Fuploads%2Fsites%2F129%2F2026%2F08%2Fagntcon-report-as-tool-1024x418.png" alt="Three panels. The analyst's report is a query already argued about and trusted. It is published as a governed MCP tool with typed inputs, access control and fixed SQL underneath. The agent picks the report and fills in the inputs, and is never asked to write the query." width="800" height="327"&gt;&lt;/a&gt;Governed SQL tool design: an agent selects a trusted analyst report and supplies typed inputs.&lt;p&gt;&lt;/p&gt;

&lt;p&gt;The agent sees something like a sales trends report with clearly typed inputs for quarter, region and business unit. It picks one and fills in the inputs. It does not write a line of SQL. The query underneath is fixed, so the same question follows the same trusted query path every time. If the number moves, it is because the data moved, not because the model phrased the question differently on a Tuesday. And the path it followed is one your analysts already stand behind.&lt;/p&gt;

&lt;p&gt;The shift sounds small but is significant in practice. The agent no longer has to be clever enough to work out the right SQL. Only clever enough to pick the right report.&lt;/p&gt;

&lt;p&gt;We should be straight about how new this is. Not many people work this way yet and we are still learning where it breaks. Natural language to SQL works perfectly well on our stack and plenty of teams will keep using it. But reusing work your analysts already trust, rather than asking a model to reinvent it live, is worth arguing about in public. &lt;/p&gt;





&lt;h2&gt;Who is the agent, and what is it allowed to see?&lt;/h2&gt;

&lt;p&gt;The other half of every customer conversation is about security, and the industry has not been subtle about why.&lt;/p&gt;

&lt;p&gt;In May the United States National Security Agency published &lt;a href="https://media.defense.gov/2026/Jun/02/2003943289/-1/-1/0/CSI_MCP_SECURITY.PDF" rel="noopener noreferrer"&gt;seventeen pages on Model Context Protocol security&lt;/a&gt;, finding that adoption has outpaced the safeguards. More than forty security vulnerabilities have been filed against implementations of the protocol this year. As &lt;a href="https://www.linkedin.com/in/vipinsamar/" rel="noopener noreferrer"&gt;Vipin Samar&lt;/a&gt;, our senior vice president for database security, &lt;a href="https://blogs.oracle.com/database/oracle-deep-data-security-is-now-available-in-oracle-ai-database-26ai" rel="noopener noreferrer"&gt;put it&lt;/a&gt;, agents are increasingly acting as autonomous insiders.&lt;/p&gt;

&lt;p&gt;We treat an agent as what it is: another application that proves who it is before it gets anything. It signs in through the corporate identity system your staff already use, and it inherits that person’s permissions rather than getting a set of its own.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.oracle.com%2Fdevelopers%2Fwp-content%2Fuploads%2Fsites%2F129%2F2026%2F08%2Fagntcon-identity-and-reach-1024x378.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.oracle.com%2Fdevelopers%2Fwp-content%2Fuploads%2Fsites%2F129%2F2026%2F08%2Fagntcon-identity-and-reach-1024x378.png" alt="Three panels. The agent signs in through the corporate identity system and holds no standing credential of its own. Its permissions are inherited from the person it acts for, not granted separately. Row, column and cell policy is declared once in SQL and enforced whether the request comes from a person or an agent." width="799" height="295"&gt;&lt;/a&gt;AI agent access controls: corporate sign-in, inherited permissions, and SQL-enforced policy.&lt;p&gt;&lt;/p&gt;

&lt;p&gt;Underneath sits the database security we have been building for more than four decades. &lt;a href="https://www.oracle.com/security/database-security/features/deep-data-security/" rel="noopener noreferrer"&gt;Oracle Deep Data Security&lt;/a&gt; extends the access controls our customers already rely on rather than replacing them, expressing them as policies you declare in SQL instead of code you maintain. It reaches individual rows, columns and cells, so two people asking an agent the same question see only what each is cleared to see. Salaries stay hidden from those who should not see them, whether the request comes from a person or an agent acting for them.&lt;/p&gt;

&lt;p&gt;None of that makes prompt injection go away. It does not. What it decides is how much of your data is reachable when something does go wrong, which is the question your security team will actually ask.&lt;/p&gt;





&lt;h2&gt;Four ways to connect&lt;/h2&gt;

&lt;p&gt;There is no single Oracle connector for this, because there is no single way people run Oracle. &lt;a href="https://blogs.oracle.com/database/introducing-mcp-server-for-oracle-database" rel="noopener noreferrer"&gt;One runs locally on a developer’s machine&lt;/a&gt;, which is how most people meet this first. &lt;a href="https://www.oracle.com/autonomous-database/mcp-server/" rel="noopener noreferrer"&gt;One is built into Autonomous Database&lt;/a&gt; with nothing to install. &lt;a href="https://docs.oracle.com/en-us/iaas/releasenotes/database-tools/mcp.htm" rel="noopener noreferrer"&gt;One is a managed service in Oracle Cloud&lt;/a&gt; that also reaches Oracle databases running inside Amazon, Microsoft and Google’s clouds, at no extra charge. And where data stays in a customer’s own data centre, there is &lt;a href="https://docs.oracle.com/en/database/oracle/oracle-rest-data-services/26.2/ordig/configuring-model-context-protocol-mcp.html" rel="noopener noreferrer"&gt;a version they run themselves&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Alongside those we publish an &lt;a href="https://github.com/oracle/mcp" rel="noopener noreferrer"&gt;open repository of reference connectors&lt;/a&gt; for the wider Oracle estate, from MySQL to a toolkit that reads your database driver logs and tells you why last night got slow.&lt;/p&gt;





&lt;h2&gt;Come and talk to us in Amsterdam&lt;/h2&gt;

&lt;p&gt;Oracle is a Gold member of the &lt;a href="https://www.linuxfoundation.org/press/linux-foundation-announces-the-formation-of-the-agentic-ai-foundation" rel="noopener noreferrer"&gt;Agentic AI Foundation&lt;/a&gt;, the Linux Foundation body that now hosts the Model Context Protocol, and we are sponsoring &lt;a href="https://events.linuxfoundation.org/agntcon-mcpcon-europe/" rel="noopener noreferrer"&gt;AGNTCon + MCPCon Europe&lt;/a&gt; at the RAI in Amsterdam on 17 and 18 September.&lt;/p&gt;

&lt;p&gt;We’re also bringing a hands-on workshop. It walks through giving an agent durable memory with Oracle AI Database underneath and connecting the agent to that memory over the Model Context Protocol. If you are building AI applications and have not yet worked out where the database fits, that session is built for you.&lt;/p&gt;

&lt;p&gt;We will be in the Solutions Showcase for the rest of the event. Come and tell us what you are building, and we will talk through how to put a database behind it. If you think letting agents call pre-approved reports is the wrong shape, tell us that too. We are early enough in this to still be changing our minds.&lt;/p&gt;

&lt;p&gt;Learn more: &lt;a href="https://www.oracle.com/mcp/" rel="noopener noreferrer"&gt;oracle.com/mcp&lt;/a&gt; and &lt;a href="https://github.com/oracle/mcp" rel="noopener noreferrer"&gt;github.com/oracle/mcp&lt;/a&gt;&lt;/p&gt;





&lt;h2&gt;References&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Oracle AI Agent Memory, a unified memory core for enterprise AI systems: &lt;a href="https://blogs.oracle.com/database/introducing-oracle-ai-agent-memory-a-unified-memory-core-for-enterprise-ai-systems" rel="noopener noreferrer"&gt;https://blogs.oracle.com/database/introducing-oracle-ai-agent-memory-a-unified-memory-core-for-enterprise-ai-systems&lt;/a&gt;
&lt;/li&gt;



&lt;li&gt;Oracle Agent Memory as an Enterprise Memory Substrate for Long-Horizon AI Agents, July 2026: &lt;a href="https://arxiv.org/abs/2607.13157" rel="noopener noreferrer"&gt;https://arxiv.org/abs/2607.13157&lt;/a&gt;
&lt;/li&gt;



&lt;li&gt;What is agent memory, a beginner’s guide for AI developers: &lt;a href="https://blogs.oracle.com/developers/what-is-agent-memory-a-beginners-guide-for-ai-developers" rel="noopener noreferrer"&gt;https://blogs.oracle.com/developers/what-is-agent-memory-a-beginners-guide-for-ai-developers&lt;/a&gt;
&lt;/li&gt;



&lt;li&gt;MCP Server for Oracle Database, announced 16 July 2025: &lt;a href="https://blogs.oracle.com/database/introducing-mcp-server-for-oracle-database" rel="noopener noreferrer"&gt;https://blogs.oracle.com/database/introducing-mcp-server-for-oracle-database&lt;/a&gt;
&lt;/li&gt;



&lt;li&gt;Autonomous AI Database MCP Server: &lt;a href="https://www.oracle.com/autonomous-database/mcp-server/" rel="noopener noreferrer"&gt;https://www.oracle.com/autonomous-database/mcp-server/&lt;/a&gt;
&lt;/li&gt;



&lt;li&gt;Managed MCP servers in Oracle Cloud Infrastructure, 12 May 2026: &lt;a href="https://docs.oracle.com/en-us/iaas/releasenotes/database-tools/mcp.htm" rel="noopener noreferrer"&gt;https://docs.oracle.com/en-us/iaas/releasenotes/database-tools/mcp.htm&lt;/a&gt;
&lt;/li&gt;



&lt;li&gt;Running reports as agent tools: &lt;a href="https://docs.oracle.com/en-us/iaas/database-tools/doc/database-tools-sql-reports.html" rel="noopener noreferrer"&gt;https://docs.oracle.com/en-us/iaas/database-tools/doc/database-tools-sql-reports.html&lt;/a&gt;
&lt;/li&gt;



&lt;li&gt;Self-managed streaming endpoint, Oracle REST Data Services 26.2: &lt;a href="https://docs.oracle.com/en/database/oracle/oracle-rest-data-services/26.2/ordig/configuring-model-context-protocol-mcp.html" rel="noopener noreferrer"&gt;https://docs.oracle.com/en/database/oracle/oracle-rest-data-services/26.2/ordig/configuring-model-context-protocol-mcp.html&lt;/a&gt;
&lt;/li&gt;



&lt;li&gt;National Security Agency, Model Context Protocol security design considerations, 20 May 2026: &lt;a href="https://media.defense.gov/2026/Jun/02/2003943289/-1/-1/0/CSI_MCP_SECURITY.PDF" rel="noopener noreferrer"&gt;https://media.defense.gov/2026/Jun/02/2003943289/-1/-1/0/CSI_MCP_SECURITY.PDF&lt;/a&gt;
&lt;/li&gt;



&lt;li&gt;BIRD benchmark leaderboard, figures as published on 21 August 2026: &lt;a href="https://bird-bench.github.io/" rel="noopener noreferrer"&gt;https://bird-bench.github.io/&lt;/a&gt;
&lt;/li&gt;



&lt;li&gt;Agentic AI Foundation: &lt;a href="https://www.linuxfoundation.org/press/linux-foundation-announces-the-formation-of-the-agentic-ai-foundation" rel="noopener noreferrer"&gt;https://www.linuxfoundation.org/press/linux-foundation-announces-the-formation-of-the-agentic-ai-foundation&lt;/a&gt;
&lt;/li&gt;



&lt;li&gt;AGNTCon + MCPCon Europe: &lt;a href="https://events.linuxfoundation.org/agntcon-mcpcon-europe/" rel="noopener noreferrer"&gt;https://events.linuxfoundation.org/agntcon-mcpcon-europe/&lt;/a&gt;
&lt;/li&gt;



&lt;li&gt;Oracle MCP servers: &lt;a href="https://www.oracle.com/mcp/" rel="noopener noreferrer"&gt;https://www.oracle.com/mcp/&lt;/a&gt;
&lt;/li&gt;



&lt;li&gt;Oracle MCP reference connectors on GitHub: &lt;a href="https://github.com/oracle/mcp" rel="noopener noreferrer"&gt;https://github.com/oracle/mcp&lt;/a&gt;
&lt;/li&gt;



&lt;li&gt;Oracle Deep Data Security: &lt;a href="https://www.oracle.com/security/database-security/features/deep-data-security/" rel="noopener noreferrer"&gt;https://www.oracle.com/security/database-security/features/deep-data-security/&lt;/a&gt; and &lt;a href="https://blogs.oracle.com/database/oracle-deep-data-security-is-now-available-in-oracle-ai-database-26ai" rel="noopener noreferrer"&gt;https://blogs.oracle.com/database/oracle-deep-data-security-is-now-available-in-oracle-ai-database-26ai&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>techtalks</category>
      <category>oracle</category>
      <category>amsterdam</category>
      <category>agentcon</category>
    </item>
    <item>
      <title>How to Build a REST API for an AI Application</title>
      <dc:creator>Anya Summers</dc:creator>
      <pubDate>Fri, 21 Aug 2026 13:46:30 +0000</pubDate>
      <link>https://dev.to/oracledevs/how-to-build-a-rest-api-for-an-ai-application-2afl</link>
      <guid>https://dev.to/oracledevs/how-to-build-a-rest-api-for-an-ai-application-2afl</guid>
      <description>&lt;p&gt;Build a REST API for an AI application that stays reliable when
inference is slow, retried, or connected to real data. Use a thin
FastAPI layer to validate and authenticate requests, create a durable
prediction job, return &lt;code&gt;202 Accepted&lt;/code&gt; with a status URL, and
let a worker handle model execution outside the HTTP request.&lt;/p&gt;

&lt;p&gt;This guide shows how to keep the public API provider-neutral, persist
job state with Oracle AI Database in the production path, stream
interactive responses when needed, and enforce tenant-aware access. It
is grounded in maintained Oracle AI Developer Hub proof cases for SSE
delivery, retrieval orchestration, and enterprise data access.&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Illustrative patterns vs. maintained proof cases&lt;/strong&gt;&lt;br&gt;The code in this guide explains the API boundaries. The maintained proof cases later in the article show those boundaries in working applications. For production, use Oracle AI Database persistence, tenant-scoped access, a separate worker or durable queue, approved identity, retention, monitoring, and distributed rate limiting. The SQL, VPD, streaming, and &lt;code&gt;BackgroundTasks&lt;/code&gt; snippets are design guidance, not copy-and-paste deployment code.&lt;/p&gt;&lt;/blockquote&gt;

&lt;h2 id="key-takeaways"&gt;Key takeaways&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Build the REST API around a resource lifecycle: submit work, return
&lt;code&gt;202 Accepted&lt;/code&gt;, and expose a status URL.&lt;/li&gt;
&lt;li&gt;Validate bounded JSON with Pydantic before calling a model, and
generate an OpenAPI contract from the implementation.&lt;/li&gt;
&lt;li&gt;Keep model-provider credentials server-side and isolate
provider-specific behavior behind an adapter.&lt;/li&gt;
&lt;li&gt;Persist job state durably, scope every read to the authenticated
tenant, and support idempotent retries.&lt;/li&gt;
&lt;li&gt;Use a worker and durable queue for variable or long-running
inference; use synchronous responses only when the timing is
predictable.&lt;/li&gt;
&lt;li&gt;Treat FastAPI, Oracle AI Database, ORDS, OpenAI Responses API,
streaming, and queues as separate architectural boundaries with
different jobs.&lt;/li&gt;
&lt;li&gt;Test authentication, tenant isolation, input rejection, rate
limiting, provider failures, worker transitions, and the public OpenAPI
contract before deployment.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="what-will-you-design-and-verify"&gt;What will you design and
verify?&lt;/h2&gt;

&lt;p&gt;You will design a small FastAPI service that accepts a prediction
request, authenticates the caller, returns &lt;code&gt;202 Accepted&lt;/code&gt;
with a &lt;code&gt;Location&lt;/code&gt; header, processes the request outside the
HTTP request, and exposes final state through
&lt;code&gt;GET /v1/predictions/{id}&lt;/code&gt;. The same service generates
&lt;code&gt;/docs&lt;/code&gt; and &lt;code&gt;/openapi.json&lt;/code&gt; automatically.&lt;/p&gt;

&lt;p&gt;Use the three maintained proof cases in the Developer Hub section to
validate the boundary that matters to your application: interactive
streaming, retrieval and orchestration, or identity-aware enterprise
data access.&lt;/p&gt;

&lt;p&gt;The rest of the article gives you the design checklist: thin routes,
clear resource contracts, provider adapters, asynchronous work,
streaming, identity, and governed durable state. The proof cases show
how those pieces behave together in maintained applications.&lt;/p&gt;

&lt;h2 id="what-are-the-best-practices-for-building-a-rest-api-for-an-ai-application"&gt;What
are the best practices for building a REST API for an AI
application?&lt;/h2&gt;

&lt;p&gt;Use a clear resource contract, validate inputs before model calls,
keep provider credentials server-side, choose synchronous or
asynchronous execution deliberately, persist job state, scope reads to
authenticated tenants, and expose OpenAPI plus safe errors. Test the
failure path as carefully as the successful model response.&lt;/p&gt;

&lt;p&gt;The recommended implementation applies those practices through five
boundaries:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;HTTP boundary:&lt;/strong&gt; FastAPI, Pydantic, OpenAPI,
authentication, rate limiting, and request IDs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Job boundary:&lt;/strong&gt; &lt;code&gt;POST /v1/predictions&lt;/code&gt;
creates a durable resource and returns &lt;code&gt;202 Accepted&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Worker boundary:&lt;/strong&gt; a separate process claims work and
calls a model adapter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data boundary:&lt;/strong&gt; Oracle AI Database stores the
request lifecycle and applies tenant scope.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Provider boundary:&lt;/strong&gt; the model integration is
replaceable and must not expose provider credentials to clients.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id="how-do-you-design-a-thin-rest-api-layer-for-an-ai-app"&gt;How do
you design a thin REST API layer for an AI app?&lt;/h2&gt;

&lt;p&gt;Keep the REST API thin: authenticate and validate at the HTTP edge,
create or read a job resource, and delegate model calls, retrieval, and
durable state to dedicated boundaries. The route should not contain
provider-specific logic, long-running inference, or authorization rules
that the data layer must enforce again.&lt;/p&gt;

&lt;p&gt;Treat inference as a resource lifecycle rather than a single
long-running HTTP function. The API accepts and records work, the worker
performs inference, and the client reads the resulting resource using a
stable status URL. In the production path, FastAPI owns the
application-facing boundary and Oracle AI Database owns durable
prediction-job state.&lt;/p&gt;

&lt;h3 id="which-endpoints-should-a-chatbot-rest-api-expose-in-fastapi"&gt;Which
endpoints should a chatbot REST API expose in FastAPI?&lt;/h3&gt;

&lt;p&gt;A chatbot REST API should separate conversation resources, messages,
prediction jobs, and optional streams. This keeps chat history
addressable, makes slow inference observable, and lets authorization
apply to each resource instead of hiding the entire lifecycle behind one
&lt;code&gt;/chat&lt;/code&gt; request.&lt;/p&gt;

&lt;p&gt;For a chatbot-style REST API, expose endpoints that map cleanly to
the same resource lifecycle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;POST /v1/conversations/{conversation_id}/messages&lt;/code&gt; —
create a message and schedule the underlying prediction job.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /v1/conversations/{conversation_id}&lt;/code&gt; — return
conversation metadata and current state.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /v1/conversations/{conversation_id}/messages&lt;/code&gt; —
return authorized message history.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /v1/predictions/{id}&lt;/code&gt; — poll the durable job until
the model response is ready.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /v1/conversations/{conversation_id}/messages/{message_id}/stream&lt;/code&gt;
— optionally stream incremental output with Server-Sent Events
(SSE).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DELETE /v1/conversations/{conversation_id}&lt;/code&gt; — delete
conversation data according to the retention policy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The client-facing flow stays consistent: FastAPI authenticates and
creates work, a worker updates durable job state in Oracle AI Database,
and the client reads the response through a status URL or an authorized
stream.&lt;/p&gt;

&lt;p&gt;The pattern is useful when model inference may outlive the request
timeout, when a client needs reliable status information, or when an AI
application must retain request context beside governed business
data.&lt;/p&gt;

&lt;h2 id="why-oracle-ai-database-for-the-production-path"&gt;Why Oracle AI Database for the production path?&lt;/h2&gt;

&lt;p&gt;Oracle AI Database gives the production path one place to keep durable prediction-job state beside governed application data. It can scope work to a tenant, support reliable status reads and idempotency records, and apply data-access controls close to the rows that the API exposes.&lt;/p&gt;

&lt;h2 id="proof-cases-apply-the-api-pattern-to-a-real-ai-developer-job"&gt;Proof cases: apply the API pattern to a real AI developer job&lt;/h2&gt;

&lt;p&gt;The REST API pattern is useful only when it connects to work a developer actually has to ship. These three Oracle AI Developer Hub assets are not substitutes for this tutorial; each is a working proof case for one boundary in the design.&lt;/p&gt;

&lt;h3 id="job-ship-an-interactive-ai-experience-without-turning-the-route-into-the-agent"&gt;Job: ship an interactive AI experience without turning the route into the agent&lt;/h3&gt;

&lt;p&gt;Use &lt;a href="https://github.com/oracle-devrel/oracle-ai-developer-hub/tree/74f7b01b3d85c1bfc94a66c72011da55fd316d61/apps/appbooks/agent_harness_palo_stack" rel="noopener noreferrer"&gt;Total Recall&lt;/a&gt; when you need to move from a &lt;code&gt;POST /v1/chat&lt;/code&gt; prototype to an interactive application that streams progress while keeping the agent harness outside the browser. It is a FastAPI backend and browser app that streams over SSE, then exposes the surrounding concerns—context, retrieval, memory, tools, and traces—as separate layers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What this proves for an API design:&lt;/strong&gt; streaming is a delivery mechanism at the HTTP boundary; it does not eliminate the need for a stable resource model, server-side provider credentials, or durable state. Use this proof case when the product requirement is “show useful progress now” rather than “return one blocking response later.”&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Run it now:&lt;/strong&gt; prepare Python 3.11+, a reachable Oracle AI Database with its required embedding model, and an approved model-provider credential; then follow the appbook setup and run &lt;code&gt;./run.sh&lt;/code&gt;. Its documented success signal is a local browser application with the FastAPI service available and status indicators for the database, harness, reranker fallback or availability, and configured model provider. Use the live trace and streamed chat to check that your route delivers incremental output without moving the harness into the client.&lt;/p&gt;

&lt;h3 id="job-make-a-retrieval-backed-ai-api-answer-from-the-right-evidence"&gt;Job: make a retrieval-backed AI API answer from the right evidence&lt;/h3&gt;

&lt;p&gt;Use the &lt;a href="https://github.com/oracle-devrel/oracle-ai-developer-hub/tree/74f7b01b3d85c1bfc94a66c72011da55fd316d61/workshops/from_rag_to_agents_workshop" rel="noopener noreferrer"&gt;From RAG to Agents workshop&lt;/a&gt; when the API must do more than call a model. The workshop builds a research-paper assistant from data loading and retrieval through RAG, agent tools, multi-agent orchestration, and persistent session memory.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What this proves for an API design:&lt;/strong&gt; retrieval and orchestration belong behind a provider-neutral service boundary. Keep &lt;code&gt;POST /v1/predictions&lt;/code&gt; or &lt;code&gt;POST /v1/chat&lt;/code&gt; responsible for validation, identity, status, and response shape; let a worker or agent runtime assemble retrieval, tool calls, and model execution. Use this proof case when the developer job is “turn grounded retrieval into an application feature without exposing the internal pipeline as the public API.”&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Run it now:&lt;/strong&gt; install Docker, Python, and Jupyter; start the workshop’s Oracle AI Database container, install its requirements, and open &lt;code&gt;workshop/notebook_student.ipynb&lt;/code&gt;. The learning path moves from data loading and retrieval through RAG, tools, orchestration, and session memory. The proof is not only that a model answers—it is that you can inspect which retrieval and orchestration boundary produced the answer.&lt;/p&gt;

&lt;h3 id="job-expose-enterprise-data-without-bypassing-identity-and-access-policy"&gt;Job: expose enterprise data without bypassing identity and access policy&lt;/h3&gt;

&lt;p&gt;Use the &lt;a href="https://github.com/oracle-devrel/oracle-ai-developer-hub/tree/74f7b01b3d85c1bfc94a66c72011da55fd316d61/workshops/enterprise-data-agent-harness-workshop" rel="noopener noreferrer"&gt;Enterprise Data Agent workshop&lt;/a&gt; when the API serves people who should see different data. It pairs a notebook with a running application and demonstrates identity-aware row and column policies, retrieval, memory, tools, and a live chat interface against the same database.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What this proves for an API design:&lt;/strong&gt; authentication at the route is necessary but not sufficient. Derive the caller identity in FastAPI, carry that identity into the database session through an approved pattern, and make retrieval, conversation reads, and tool access apply the same scope. Use this proof case when the developer job is “add an AI capability to enterprise data without creating a second, ungoverned access path.”&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Run it now:&lt;/strong&gt; use the workshop’s GitHub Codespaces path for the lowest-friction setup, or follow its local Docker, Python, and Node.js setup to start the notebook and companion application. The proof to look for is identity-dependent data access: the same application and agent workflow should be constrained by the active identity and the data policy, rather than trusting an unscoped identifier supplied in a chat request.&lt;/p&gt;

&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Evidence boundary:&lt;/strong&gt; These links are pinned to a specific Developer Hub commit so their code and setup instructions remain reviewable. They demonstrate an API boundary in a working application: Total Recall demonstrates FastAPI and SSE; From RAG to Agents demonstrates retrieval and orchestration; Enterprise Data Agent demonstrates identity-aware access. They do not benchmark the latency, throughput, availability, or security of your deployment. Measure those against your model, data, identity provider, and traffic before release.&lt;/p&gt;&lt;/blockquote&gt;

&lt;h2 id="how-do-you-keep-your-public-api-independent-of-the-ai-provider"&gt;How
do you keep your public API independent of the AI provider?&lt;/h2&gt;

&lt;p&gt;Define a provider-neutral request, result, and error contract at the
application boundary. Put each model SDK behind an adapter that
translates the internal request into the provider call and maps the
result back into your API’s stable schema. Clients should not receive
provider response objects, provider-specific error payloads, or
credentials.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;from typing import Protocol

class ModelResult(BaseModel):
    text: str
    provider_request_id: str | None = None

class ModelAdapter(Protocol):
    def generate(self, *, prompt: str, temperature: float) -&amp;gt; ModelResult: ...&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Select the adapter through server-side configuration. This lets the
HTTP contract, job schema, authorization, and tests remain stable when
the provider or model changes.&lt;/p&gt;

&lt;h2 id="how-do-fastapi-docs-and-openapi-json-help-during-development"&gt;How do FastAPI &lt;code&gt;/docs&lt;/code&gt; and &lt;code&gt;/openapi.json&lt;/code&gt; help during development?&lt;/h2&gt;

&lt;p&gt;FastAPI generates an interactive &lt;code&gt;/docs&lt;/code&gt; page and a machine-readable &lt;code&gt;/openapi.json&lt;/code&gt; contract from the request and response models. Use them to try authenticated development requests, inspect validation rules, share an exact contract with frontend or platform teams, generate clients, and add contract tests. They improve developer feedback; they do not replace authentication, authorization, rate limits, or gateway controls.&lt;/p&gt;

&lt;p&gt;Before deployment, confirm that &lt;code&gt;/openapi.json&lt;/code&gt; exposes only intentional fields and responses. Decide whether &lt;code&gt;/docs&lt;/code&gt; should remain available outside development, and protect it according to the deployment’s operational policy.&lt;/p&gt;

&lt;h2 id="should-ai-inference-be-synchronous-or-asynchronous"&gt;Should AI
inference be synchronous or asynchronous?&lt;/h2&gt;

&lt;p&gt;Use a synchronous response only when completion time is predictable
and fits the API contract. Use an asynchronous job resource when
inference can take an unpredictable amount of time, requires retries, or
must be audited independently of the HTTP request.&lt;/p&gt;

&lt;p&gt;An AI response may not be ready within a predictable HTTP deadline. A
job resource lets the API acknowledge valid work quickly, prevents a
request thread from waiting on inference, and gives the client a stable
resource to poll.&lt;/p&gt;

&lt;p&gt;Use these resources:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;colgroup&gt;
&lt;col&gt;
&lt;col&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Endpoint&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;POST /v1/predictions&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Validate a prompt and submit a prediction job. Return
&lt;code&gt;202 Accepted&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GET /v1/predictions/{id}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Read the job’s queued, running, succeeded, or failed state.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GET /healthz&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Let platform health checks confirm that the API process is
available.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Do not make a URL such as &lt;code&gt;/generateText&lt;/code&gt; the primary
interface. The prediction is a resource with a lifecycle; the API should
make that lifecycle visible.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;client → FastAPI → prediction_jobs in Oracle AI Database
                  ↓
             durable worker → approved model adapter
                  ↓
client ← GET /v1/predictions/{id}&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;An in-process background task can be useful for a local prototype.
For production, run a separate worker process and use a durable queue or
equivalent platform service so accepted work can survive a web-process
restart.&lt;/p&gt;

&lt;h3 id="how-do-i-add-streaming-responses-for-chat-in-fastapi"&gt;How do I
add streaming responses for chat in FastAPI?&lt;/h3&gt;

&lt;p&gt;Use Server-Sent Events when the client benefits from incremental
response content. Streaming is an output-delivery choice, not a
replacement for durable job state: persist the authoritative result and
expose a status resource even when the user interface receives partial
content.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;import asyncio
import json

from fastapi import Request
from fastapi.responses import StreamingResponse

@app.get("/v1/conversations/{conversation_id}/messages/{message_id}/stream")
async def stream_message(request: Request, conversation_id: str, message_id: str):
    async def events():
        try:
            async for chunk in worker.stream(message_id):
                if await request.is_disconnected():
                    return
                yield "event: message\\ndata: " + json.dumps({"delta": chunk}) + "\\n\\n"
            yield "event: done\\ndata: {}\\n\\n"
        except asyncio.CancelledError:
            return
        except Exception:
            yield 'event: error\\ndata: {"code":"stream_failed"}\\n\\n'

    return StreamingResponse(events(), media_type="text/event-stream")&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Define event types and payload shapes as part of the public contract.
Serialize each chunk as JSON rather than interpolating raw model output
into an SSE frame; then check client disconnects, handle cancellation,
keep provider errors server-side, and finalize the durable result for
later retrieval.&lt;/p&gt;

&lt;h2 id="how-do-i-build-an-ai-rest-api-with-fastapi"&gt;How do I build an AI
REST API with FastAPI?&lt;/h2&gt;

&lt;p&gt;Define bounded Pydantic request and response models, reject unknown
fields, use FastAPI dependencies for authentication, return an explicit
status code, and let FastAPI generate the OpenAPI contract from the
implementation.&lt;/p&gt;

&lt;p&gt;The next two snippets show the request model and submission boundary.
Treat them as patterns to adapt to your identity provider, job store,
and worker runtime; the maintained proof cases show the surrounding
application behavior.&lt;/p&gt;

&lt;h3 id="step-1-define-a-bounded-request-schema"&gt;Step 1: Define a bounded
request schema&lt;/h3&gt;

&lt;p&gt;Use Pydantic to reject unknown fields and bound input before the
request reaches an expensive model call.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;class PredictionRequest(BaseModel):
    model_config = ConfigDict(extra="forbid")

    prompt: Annotated[str, Field(min_length=5, max_length=8_000)]
    temperature: Annotated[float, Field(default=0.2, ge=0.0, le=1.0)]&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The limits are application decisions, not universal defaults. Set
them from the model context window, your abuse policy, and your expected
payload shape.&lt;/p&gt;

&lt;h3 id="step-2-submit-work-and-return-202-accepted"&gt;Step 2: Submit work
and return &lt;code&gt;202 Accepted&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The endpoint creates a prediction job before it schedules work. It
returns a &lt;code&gt;Location&lt;/code&gt; header and a JSON status URL.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;from fastapi import BackgroundTasks, Depends, Header, Request, status
from fastapi.responses import JSONResponse

@app.post("/v1/predictions")
async def create_prediction(
    payload: PredictionRequest,
    background_tasks: BackgroundTasks,
    request: Request,
    identity: VerifiedIdentity = Depends(require_identity),
    idempotency_key: str | None = Header(default=None, alias="Idempotency-Key"),
):
    job_id = str(uuid4())
    status_url = f"/v1/predictions/{job_id}"
    repository.create(
        job_id=job_id,
        tenant_id=identity.tenant_id,
        client_id=identity.user_id,
        prompt=payload.prompt,
        temperature=payload.temperature,
        request_id=request.state.request_id,
        idempotency_key=idempotency_key,
    )
    background_tasks.add_task(worker.process_once)
    return JSONResponse(
        status_code=status.HTTP_202_ACCEPTED,
        headers={"Location": status_url},
        content={"id": job_id, "status": "queued", "status_url": status_url},
    )&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Use &lt;code&gt;202 Accepted&lt;/code&gt; when the API has accepted work but has
not completed it. Do not choose a fixed time threshold such as two
seconds; use a job resource whenever the result cannot be completed
reliably within the request contract.&lt;/p&gt;

&lt;p&gt;This &lt;code&gt;BackgroundTasks&lt;/code&gt; example is illustrative, not a
production runtime pattern. For work that is heavy, retried, or must
survive a process restart, use a separate worker and durable queue;
FastAPI makes the same distinction in its &lt;a href="https://fastapi.tiangolo.com/tutorial/background-tasks/" rel="noopener noreferrer"&gt;background-task guidance&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id="how-would-i-persist-ai-jobs-with-oracle-ai-database-in-production"&gt;How would I
persist AI jobs with Oracle AI Database in production?&lt;/h2&gt;

&lt;p&gt;Store the job request, tenant scope, lifecycle status, safe result or
error code, request correlation ID, and idempotency fingerprint in
Oracle AI Database. Keep transactions short and use bind variables
through a connection pool.&lt;/p&gt;

&lt;h3 id="step-3-persist-prediction-status-in-oracle-ai-database"&gt;Step 3:
Persist prediction status in Oracle AI Database&lt;/h3&gt;

&lt;p&gt;The service records the request, client scope, status, result, safe
error code, and request ID in a &lt;code&gt;prediction_jobs&lt;/code&gt; table. This
keeps API state close to the application data that may shape the model
request or govern who can retrieve the result.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;CREATE TABLE prediction_jobs (
    id             VARCHAR2(36) PRIMARY KEY,
    tenant_id      VARCHAR2(255) NOT NULL,
    client_id      VARCHAR2(255) NOT NULL,
    status         VARCHAR2(16) NOT NULL,
    prompt         CLOB NOT NULL,
    temperature    NUMBER(3,2) NOT NULL,
    generated_text CLOB,
    error_code     VARCHAR2(128),
    request_id     VARCHAR2(64) NOT NULL,
    idempotency_key VARCHAR2(255),
    request_fingerprint VARCHAR2(64),
    created_at     TIMESTAMP WITH TIME ZONE NOT NULL,
    updated_at     TIMESTAMP WITH TIME ZONE NOT NULL,
    CONSTRAINT prediction_jobs_idempotency_uq
      UNIQUE (tenant_id, idempotency_key)
);&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Use bind variables for database calls. A production implementation
should use &lt;code&gt;oracledb.create_pool&lt;/code&gt;, then acquire a connection
only for the short transaction that creates, claims, or completes a
job.&lt;/p&gt;

&lt;h3 id="step-4-let-a-worker-handle-model-inference"&gt;Step 4: Let a worker
handle model inference&lt;/h3&gt;

&lt;p&gt;The worker claims one queued job, calls a model adapter, and records
either the result or a safe error code.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;def process_once(self) -&amp;gt; bool:
    job = self._repository.claim_next()
    if not job:
        return False
    try:
        result = self._model.generate(prompt=job.prompt, temperature=job.temperature)
        self._repository.mark_succeeded(job.id, result)
    except Exception:
        self._repository.mark_failed(job.id, "model_inference_failed")
    return True&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Keep raw provider errors out of public responses. Log only redacted,
correlated diagnostics, then use &lt;code&gt;request_id&lt;/code&gt; to investigate
the failure internally.&lt;/p&gt;

&lt;h2 id="how-do-i-build-a-minimal-fastapi-chat-endpoint-using-the-openai-sdk"&gt;How do I build a minimal FastAPI chat endpoint using the OpenAI SDK?&lt;/h2&gt;

&lt;p&gt;For a minimal, synchronous prototype, validate the message, call the OpenAI SDK from the server, and return your own stable JSON shape. Keep this route small: it demonstrates the request boundary, not a durable production job system.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;import os
from fastapi import FastAPI
from openai import AsyncOpenAI
from pydantic import BaseModel, Field

app = FastAPI()
client = AsyncOpenAI(api_key=os.environ["OPENAI_API_KEY"])

class ChatRequest(BaseModel):
    message: str = Field(min_length=1, max_length=8_000)

@app.post("/v1/chat")
async def create_chat(payload: ChatRequest):
    response = await client.responses.create(
        model=os.environ["OPENAI_MODEL"],
        input=payload.message,
    )
    return {"output_text": response.output_text}&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;For variable, costly, or auditable inference, replace the direct SDK call with the &lt;code&gt;202&lt;/code&gt; prediction-job pattern described above. Add the authenticated &lt;code&gt;Depends&lt;/code&gt; pattern in the security section before using this route outside a local prototype. Do not return the provider’s raw response object or expose provider credentials to the client.&lt;/p&gt;

&lt;h3 id="how-do-i-connect-a-rest-api-to-the-openai-responses-api"&gt;How do I connect a REST API to the OpenAI Responses API?&lt;/h3&gt;

&lt;p&gt;Keep the model provider behind a server-side adapter. Load &lt;code&gt;OPENAI_API_KEY&lt;/code&gt; from a secret manager or environment, set &lt;code&gt;OPENAI_MODEL&lt;/code&gt; explicitly, call the Responses API from the worker, capture the provider request ID for diagnostics, and return only your API’s stable response contract.&lt;/p&gt;

&lt;h3 id="how-should-you-store-and-load-the-openai-api-key-securely"&gt;How
should you store and load the OpenAI API key securely?&lt;/h3&gt;

&lt;p&gt;Store the OpenAI API key in an approved secret manager and inject it
into the server or worker at runtime. Environment variables are suitable
as the process-level delivery mechanism, but the value should originate
from managed secret storage rather than source code, a Docker image, a
notebook, browser JavaScript, or a committed &lt;code&gt;.env&lt;/code&gt; file.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use a different secret for each environment and workload
identity.&lt;/li&gt;
&lt;li&gt;Restrict who and what can read the secret.&lt;/li&gt;
&lt;li&gt;Fail startup when the required secret is missing instead of
accepting it from a client request.&lt;/li&gt;
&lt;li&gt;Do not print the key in logs, traces, exceptions, health responses,
or configuration dumps.&lt;/li&gt;
&lt;li&gt;Rotate the key according to the organization’s credential policy and
after suspected exposure.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Load the value server-side and pass it directly to the provider
client:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;import os
from openai import OpenAI

api_key = os.environ["OPENAI_API_KEY"]
openai_client = OpenAI(api_key=api_key)&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This is an illustrative provider boundary, not a downloadable
implementation or configuration recipe. The current &lt;a href="https://developers.openai.com/api/reference/overview" rel="noopener noreferrer"&gt;OpenAI API
reference&lt;/a&gt; describes Responses as the direct model-request API,
requires server-side bearer credentials, and recommends logging request
IDs and reviewing rate limits before production.&lt;/p&gt;

&lt;p&gt;Do not put the provider key in browser JavaScript or accept it from
the request body. Return a stable internal error instead of a raw
provider error. The API’s caller authentication and the model provider’s
authentication are separate trust boundaries.&lt;/p&gt;

&lt;h2 id="how-do-i-authenticate-and-secure-an-ai-rest-api"&gt;How do I
authenticate and secure an AI REST API?&lt;/h2&gt;

&lt;p&gt;Authenticate before expensive work, resolve tenant identity from
verified credentials, apply authorization to every job read and update,
enforce request limits, bound payloads, support idempotent retries,
redact provider errors, and define retention for prompts and generated
responses.&lt;/p&gt;

&lt;h3 id="how-do-you-store-conversations-and-enforce-user-ownership-checks"&gt;How
do you store conversations and enforce user ownership checks?&lt;/h3&gt;

&lt;p&gt;Store each conversation with a server-derived &lt;code&gt;tenant_id&lt;/code&gt;
and &lt;code&gt;owner_id&lt;/code&gt;, then carry the conversation identifier into
its messages and prediction jobs. Every read, update, stream, and delete
operation must include the authenticated ownership scope; knowing a
conversation ID is not authorization.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;SELECT conversation_id, status, updated_at
FROM ai_conversations
WHERE conversation_id = :conversation_id
  AND tenant_id = :authenticated_tenant_id
  AND owner_id = :authenticated_user_id;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Apply the same scope when loading message history or returning
generated content. For defense in depth, enforce the tenant predicate in
Oracle AI Database with an approved policy such as VPD, and test that
one user cannot read, stream, update, or delete another user’s
conversation.&lt;/p&gt;

&lt;p&gt;For chatbot-style endpoints, store conversations and messages with an
owner or tenant identifier and enforce that ownership on every endpoint
that returns context, history, or generated content. Also account for
prompt injection, unsafe generated output, unintended tool calls,
sensitive-data disclosure, and cost exhaustion. Mitigate these risks
with strict schema validation, tool allowlists, least-privilege data
access, timeouts, token budgets, redaction, and human approval for
consequential actions.&lt;/p&gt;

&lt;h3 id="how-do-i-add-user-authentication-with-fastapi-depends"&gt;How do I
add user authentication with FastAPI &lt;code&gt;Depends&lt;/code&gt;?&lt;/h3&gt;

&lt;p&gt;Inject a verified identity object before creating an inference job or
calling a provider. Derive &lt;code&gt;tenant_id&lt;/code&gt; and
&lt;code&gt;user_id&lt;/code&gt; from a validated JWT, OAuth session, or equivalent
credential; do not accept them as authoritative request-body fields.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;from fastapi import Depends, HTTPException, status
from pydantic import BaseModel

class VerifiedIdentity(BaseModel):
    tenant_id: str
    user_id: str

async def require_identity() -&amp;gt; VerifiedIdentity:
    identity = await verify_request_credentials()
    if identity is None:
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
    return identity

@app.post("/v1/chat")
async def chat(request: ChatRequest,
               identity: VerifiedIdentity = Depends(require_identity)):
    # Scope reads and writes with identity.tenant_id and identity.user_id.
    return await create_scoped_job(request, identity)&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This dependency is illustrative. Replace
&lt;code&gt;verify_request_credentials()&lt;/code&gt; with the approved
authentication mechanism for the deployment and enforce the same
identity in the database policy.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Authentication and authorization:&lt;/strong&gt; Require
authenticated callers and scope every job to a tenant or user. An opaque
prediction ID is not authorization.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate limiting:&lt;/strong&gt; A process-local guard is suitable
only for a demonstration. Enforce distributed limits with an API
gateway, Redis-backed service, or equivalent platform control in
multi-instance deployments.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Input controls:&lt;/strong&gt; Bound prompt length, reject unknown
JSON fields, and validate media uploads separately from text
requests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Idempotency:&lt;/strong&gt; Add an idempotency key for clients
that may retry &lt;code&gt;POST&lt;/code&gt; after network failures.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Observability:&lt;/strong&gt; Return a request ID, record the job
ID and tenant-safe correlation data, and measure queue delay, inference
duration, errors, and abandonment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retention:&lt;/strong&gt; Define when prompts and generated
responses expire or must be deleted; do not retain them indefinitely by
default.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id="oracle-ai-database-pattern-tenant-scope-and-request-correlation"&gt;Oracle
AI Database pattern: tenant scope and request correlation&lt;/h3&gt;

&lt;p&gt;The API must authenticate the caller before it opens a database
session. The following is &lt;strong&gt;simplified Oracle pseudocode&lt;/strong&gt;,
based on &lt;a href="https://docs.oracle.com/en/database/oracle/oracle-database/26/dbseg/using-oracle-vpd-to-control-data-access.html" rel="noopener noreferrer"&gt;application
contexts and Oracle Virtual Private Database (VPD)&lt;/a&gt; and &lt;a href="https://docs.oracle.com/en/database/oracle/oracle-database/26/arpls/DBMS_APPLICATION_INFO.html" rel="noopener noreferrer"&gt;&lt;code&gt;DBMS_APPLICATION_INFO&lt;/code&gt;&lt;/a&gt;.
It is not a copy-and-paste production policy: configure a trusted
context-setting package and review the VPD licensing and security
requirements for the target deployment. &lt;a href="https://docs.oracle.com/en/database/oracle/oracle-database/26/arpls/DBMS_RLS.html" rel="noopener noreferrer"&gt;&lt;code&gt;DBMS_RLS&lt;/code&gt;&lt;/a&gt;, which implements VPD, is available with Oracle AI Database Enterprise Edition only; confirm the edition and deployment controls before adopting this pattern.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;-- The API derives these values from authenticated identity; do not accept a
-- tenant ID or client identity as an untrusted request-body field.
BEGIN
  trusted_ai_api_context.set_identity(
    tenant_id  =&amp;gt; :authenticated_tenant_id,
    client_id  =&amp;gt; :authenticated_client_id
  );

  -- Safe correlation only: do not place prompts, tokens, or secrets here.
  DBMS_APPLICATION_INFO.SET_MODULE(
    module_name =&amp;gt; 'ai_predictions_api',
    action_name =&amp;gt; 'create_prediction'
  );
  DBMS_APPLICATION_INFO.SET_CLIENT_INFO(:request_id);
END;
/

-- A VPD policy function can add this predicate whenever PREDICTION_JOBS is
-- queried or changed. The application query stays simple; the database
-- enforces the current tenant scope.
CREATE OR REPLACE FUNCTION prediction_tenant_predicate(
  object_schema VARCHAR2,
  object_name   VARCHAR2
) RETURN VARCHAR2 AS
BEGIN
  RETURN q'[tenant_id = SYS_CONTEXT('AI_API_CTX', 'TENANT_ID')]';
END;
/

BEGIN
  DBMS_RLS.ADD_POLICY(
    object_schema   =&amp;gt; 'APP_SCHEMA', -- replace with the schema that owns PREDICTION_JOBS
    object_name     =&amp;gt; 'PREDICTION_JOBS',
    policy_name     =&amp;gt; 'PREDICTION_TENANT_SCOPE',
    policy_function =&amp;gt; 'PREDICTION_TENANT_PREDICATE',
    statement_types =&amp;gt; 'SELECT,INSERT,UPDATE,DELETE',
    update_check    =&amp;gt; TRUE
  );
END;
/

-- Idempotency belongs in durable state, not only in the API process.
-- The PREDICTION_JOBS table definition above already enforces it with
-- UNIQUE (tenant_id, idempotency_key); do not add the constraint twice.&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Use &lt;code&gt;DBMS_APPLICATION_INFO&lt;/code&gt; to correlate a safe request ID
with database work; its &lt;code&gt;CLIENT_INFO&lt;/code&gt; field is not a location
for secret or sensitive prompt data. For systems that use VPD, the
policy function is attached to the protected table and Oracle applies
the predicate to the configured statement types. Treat this entire SQL
section as illustrative architecture guidance and have it reviewed for
the target edition, schema design, session-pooling behavior, and identity
model.&lt;/p&gt;

&lt;h2 id="when-should-i-use-fastapi-ords-streaming-or-a-queue"&gt;When
should I use FastAPI, ORDS, streaming, or a queue?&lt;/h2&gt;

&lt;p&gt;Use FastAPI for application-facing validation and model
orchestration, ORDS for a governed database-owned resource, SSE for
incremental interactive output, and a durable queue for production
worker delivery. These choices can coexist, but they solve different
boundaries.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;colgroup&gt;
&lt;col&gt;
&lt;col&gt;
&lt;col&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Need&lt;/th&gt;
&lt;th&gt;Best-fit boundary&lt;/th&gt;
&lt;th&gt;Example or resource&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Short predictable inference&lt;/td&gt;
&lt;td&gt;Synchronous REST response&lt;/td&gt;
&lt;td&gt;Direct route with bounded input and timeout&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Long-running inference&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;202&lt;/code&gt; job resource plus worker&lt;/td&gt;
&lt;td&gt;Job-resource and worker pattern described above&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Token-by-token interaction&lt;/td&gt;
&lt;td&gt;SSE or another streaming protocol&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://github.com/oracle-devrel/oracle-ai-developer-hub/tree/74f7b01b3d85c1bfc94a66c72011da55fd316d61/apps/appbooks/agent_harness_palo_stack" rel="noopener noreferrer"&gt;Total
Recall appbook&lt;/a&gt;, as an advanced path&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database-owned status resource&lt;/td&gt;
&lt;td&gt;ORDS&lt;/td&gt;
&lt;td&gt;&lt;a href="https://docs.oracle.com/en/database/oracle/oracle-rest-data-services/" rel="noopener noreferrer"&gt;Oracle
REST Data Services documentation&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Retrieval-backed application&lt;/td&gt;
&lt;td&gt;Separate retrieval API&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/oracle-devrel/oracle-ai-developer-hub/tree/74f7b01b3d85c1bfc94a66c72011da55fd316d61/workshops/from_rag_to_agents_workshop" rel="noopener noreferrer"&gt;From
RAG to Agents workshop&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;ORDS is useful when a database-owned resource needs a governed REST
interface. It does not need to replace FastAPI as the public API
boundary for model orchestration. In this pattern, FastAPI owns request
validation and model lifecycle; ORDS can provide a narrowly scoped,
read-only integration for job-status data when that is useful to an
existing database-facing application.&lt;/p&gt;

&lt;h2 id="how-do-i-test-and-deploy-an-ai-rest-api"&gt;How do I test and
deploy an AI REST API?&lt;/h2&gt;

&lt;p&gt;Test the public contract, authentication, tenant isolation,
idempotency, rate limiting, worker transitions, provider failures, and
OpenAPI output before deployment. Run the worker separately in
production and configure provider and database credentials outside
source control.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Start with the existing &lt;a href="https://github.com/oracle-devrel/oracle-ai-developer-hub/tree/74f7b01b3d85c1bfc94a66c72011da55fd316d61/apps/appbooks/agent_harness_palo_stack" rel="noopener noreferrer"&gt;Total
Recall appbook&lt;/a&gt; for the FastAPI/SSE boundary, then use the &lt;a href="https://github.com/oracle-devrel/oracle-ai-developer-hub/tree/74f7b01b3d85c1bfc94a66c72011da55fd316d61/workshops/from_rag_to_agents_workshop" rel="noopener noreferrer"&gt;From
RAG to Agents workshop&lt;/a&gt; for database-backed orchestration
patterns.&lt;/li&gt;
&lt;li&gt;Implement the resource contract with bounded Pydantic schemas and an
authenticated identity dependency.&lt;/li&gt;
&lt;li&gt;Add durable job state, tenant scope, idempotency, and a worker
before exposing a provider call.&lt;/li&gt;
&lt;li&gt;Add Oracle AI Database persistence and connection pooling through an
approved repository implementation.&lt;/li&gt;
&lt;li&gt;Run contract, authentication, tenant-isolation, streaming, worker,
and provider-adapter tests before deployment.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The OpenAPI contract should be available at
&lt;code&gt;/openapi.json&lt;/code&gt;. Use it to generate client SDKs, run contract
tests, and review request/response schemas during integration. During
development, FastAPI’s &lt;code&gt;/docs&lt;/code&gt; page can help inspect
endpoints and validated models, but neither endpoint is a security
control: authorization, tenant scoping, and rate limiting must still be
enforced in API logic and gateway policies.&lt;/p&gt;

&lt;h3 id="troubleshooting-an-ai-rest-api"&gt;Troubleshooting an AI REST API&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;401 unauthorized&lt;/code&gt;:&lt;/strong&gt; Verify that the API derives identity from approved credentials and that the client presents the required credential.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;422 Unprocessable Entity&lt;/code&gt;:&lt;/strong&gt; Check the
prompt length, temperature range, JSON field names, and
&lt;code&gt;Content-Type: application/json&lt;/code&gt; header.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;409 idempotency_conflict&lt;/code&gt;:&lt;/strong&gt; Reuse an
&lt;code&gt;Idempotency-Key&lt;/code&gt; only for the same logical request body;
generate a new key for changed work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;404 not_found&lt;/code&gt; while polling:&lt;/strong&gt; Scope the status lookup to the same authenticated tenant that created the job.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Work disappears after a restart:&lt;/strong&gt; Move accepted work into durable state and process it through a separate worker or queue.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;/docs&lt;/code&gt; loads but requests fail:&lt;/strong&gt; OpenAPI
describes the contract; it does not supply authentication or
configuration. Inspect the request credential and JSON error code.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="production-checklist"&gt;Production checklist&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Validate and reject unknown request
fields.&lt;/li&gt;
&lt;li&gt;Authenticate before creating a job
or calling a provider.&lt;/li&gt;
&lt;li&gt;Scope every status read by
authenticated tenant identity.&lt;/li&gt;
&lt;li&gt;Support &lt;code&gt;Idempotency-Key&lt;/code&gt;
for safe client retries.&lt;/li&gt;
&lt;li&gt;Use a durable worker and queue for
production inference.&lt;/li&gt;
&lt;li&gt;Record request, queue, inference,
and provider correlation IDs without logging prompts or
secrets.&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;OPENAI_MODEL&lt;/code&gt; or the
equivalent provider model explicitly.&lt;/li&gt;
&lt;li&gt;Define retention and deletion
behavior for prompts and outputs.&lt;/li&gt;
&lt;li&gt;Run contract and failure-path tests
before publishing.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="faq"&gt;FAQ&lt;/h2&gt;

&lt;h3 id="how-do-i-build-a-rest-api-for-an-ai-application"&gt;How do I build
a REST API for an AI application?&lt;/h3&gt;

&lt;p&gt;Use FastAPI to validate and authenticate requests, persist an
inference job, return &lt;code&gt;202 Accepted&lt;/code&gt; with a status URL, and
let a worker call the model provider. Store job state in a durable
database such as Oracle AI Database, scope reads to the authenticated
tenant, and return stable JSON status and error responses.&lt;/p&gt;

&lt;h3 id="what-are-the-best-practices-for-an-ai-rest-api"&gt;What are the
best practices for an AI REST API?&lt;/h3&gt;

&lt;p&gt;The core practices are bounded input validation, server-side provider
authentication, rate limiting, durable asynchronous jobs, tenant-aware
authorization, idempotency, structured errors, request correlation,
retention controls, and tests for failure paths—not only successful
inference.&lt;/p&gt;

&lt;h3 id="how-do-i-authenticate-an-openai-powered-rest-api"&gt;How do I
authenticate an OpenAI-powered REST API?&lt;/h3&gt;

&lt;p&gt;Authenticate the API caller at your service boundary, then
authenticate separately with the model provider from the server or
worker. Keep the provider key in a secret manager or protected
environment variable, set the model explicitly, capture a safe provider
request ID, and do not send the key or raw provider error to the
client.&lt;/p&gt;

&lt;h3 id="should-i-use-fastapi-or-ords-for-an-ai-application-api"&gt;Should I
use FastAPI or ORDS for an AI application API?&lt;/h3&gt;

&lt;p&gt;Use FastAPI for application-facing validation, authentication,
orchestration, and model lifecycle management. Use ORDS when a
database-owned resource needs a governed REST interface. They can
coexist; ORDS does not need to replace FastAPI as the public
model-orchestration boundary.&lt;/p&gt;

&lt;h3 id="should-an-ai-rest-api-return-a-result-synchronously"&gt;Should an
AI REST API return a result synchronously?&lt;/h3&gt;

&lt;p&gt;It can for predictable, short operations. Use a job resource and
&lt;code&gt;202 Accepted&lt;/code&gt; when an operation may not finish reliably in
the request window or when you need durable retry, audit, and status
behavior.&lt;/p&gt;

&lt;h3 id="is-fastapi-enough-for-a-production-ai-application"&gt;Is FastAPI
enough for a production AI application?&lt;/h3&gt;

&lt;p&gt;FastAPI is a useful HTTP framework, but it is only one layer.
Production behavior also needs durable persistence, a worker or queue,
authentication, limits, observability, and retention controls.&lt;/p&gt;

&lt;h3 id="which-maintained-proof-case-should-i-run-for-an-ai-rest-api"&gt;Which maintained proof case should I run for an AI REST API?&lt;/h3&gt;

&lt;p&gt;Run &lt;a href="https://github.com/oracle-devrel/oracle-ai-developer-hub/tree/74f7b01b3d85c1bfc94a66c72011da55fd316d61/apps/appbooks/agent_harness_palo_stack" rel="noopener noreferrer"&gt;Total Recall&lt;/a&gt; for FastAPI and SSE, the &lt;a href="https://github.com/oracle-devrel/oracle-ai-developer-hub/tree/74f7b01b3d85c1bfc94a66c72011da55fd316d61/workshops/from_rag_to_agents_workshop" rel="noopener noreferrer"&gt;From RAG to Agents workshop&lt;/a&gt; for retrieval and orchestration, or the &lt;a href="https://github.com/oracle-devrel/oracle-ai-developer-hub/tree/74f7b01b3d85c1bfc94a66c72011da55fd316d61/workshops/enterprise-data-agent-harness-workshop" rel="noopener noreferrer"&gt;Enterprise Data Agent workshop&lt;/a&gt; for identity-aware, governed data access. Choose the proof case that matches the boundary you need to validate first.&lt;/p&gt;

&lt;h3 id="how-does-oracle-ai-database-help-with-an-ai-api"&gt;How does Oracle
AI Database help with an AI API?&lt;/h3&gt;

&lt;p&gt;It can persist job state and application data in the same governed
data platform. The application API still validates and orchestrates
requests; Oracle AI Database provides the durable state and data-access
layer behind it.&lt;/p&gt;

&lt;h2 id="next-steps"&gt;Next steps&lt;/h2&gt;

&lt;p&gt;Choose one maintained proof case, then apply the API contract in this guide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/oracle-devrel/oracle-ai-developer-hub/tree/74f7b01b3d85c1bfc94a66c72011da55fd316d61/apps/appbooks/agent_harness_palo_stack" rel="noopener noreferrer"&gt;Total Recall&lt;/a&gt; for FastAPI, SSE, traces, and the agent harness boundary.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/oracle-devrel/oracle-ai-developer-hub/tree/74f7b01b3d85c1bfc94a66c72011da55fd316d61/workshops/from_rag_to_agents_workshop" rel="noopener noreferrer"&gt;From RAG to Agents&lt;/a&gt; for retrieval, tools, orchestration, and session memory.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/oracle-devrel/oracle-ai-developer-hub/tree/74f7b01b3d85c1bfc94a66c72011da55fd316d61/workshops/enterprise-data-agent-harness-workshop" rel="noopener noreferrer"&gt;Enterprise Data Agent&lt;/a&gt; for identity-aware, governed access to enterprise data.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="documentation"&gt;Documentation&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.oracle.com/en/database/oracle/oracle-rest-data-services/" rel="noopener noreferrer"&gt;Oracle
REST Data Services documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://python-oracledb.readthedocs.io/en/latest/" rel="noopener noreferrer"&gt;python-oracledb
documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://fastapi.tiangolo.com/tutorial/body/" rel="noopener noreferrer"&gt;FastAPI
request-body documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://fastapi.tiangolo.com/tutorial/security/" rel="noopener noreferrer"&gt;FastAPI
security documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://owasp.org/API-Security/editions/2023/en/0x11-t10/" rel="noopener noreferrer"&gt;OWASP
API Security Top 10&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.openai.com/api/reference/overview" rel="noopener noreferrer"&gt;OpenAI API
reference&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>api</category>
      <category>ai</category>
      <category>oracle</category>
      <category>restapi</category>
    </item>
    <item>
      <title>How to Build a Controlled MCP Workflow for Codex and Oracle AI Database</title>
      <dc:creator>Anya Summers</dc:creator>
      <pubDate>Wed, 12 Aug 2026 15:28:59 +0000</pubDate>
      <link>https://dev.to/oracledevs/how-to-build-a-controlled-mcp-workflow-for-codex-and-oracle-ai-database-5e90</link>
      <guid>https://dev.to/oracledevs/how-to-build-a-controlled-mcp-workflow-for-codex-and-oracle-ai-database-5e90</guid>
      <description>&lt;p&gt;&lt;strong&gt;Connect Codex CLI to Oracle AI Database through&amp;nbsp;SQLcl&amp;nbsp;MCP, then Add Oracle AI agent memory and&amp;nbsp;LangChain&amp;nbsp;Retrieval.&amp;nbsp;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Companion notebook:&lt;/strong&gt;&amp;nbsp;&lt;a href="https://github.com/oracle-devrel/oracle-ai-developer-hub/blob/main/notebooks/codex_mcp_oracle_ai_database_memory_langchain.ipynb" rel="noopener noreferrer"&gt;Codex MCP Oracle AI Database&lt;/a&gt;&lt;/p&gt;





&lt;h2&gt;
&lt;strong&gt;Key Takeaways&lt;/strong&gt;&amp;nbsp;&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://modelcontextprotocol.io/docs/getting-started/intro" rel="noopener noreferrer"&gt;MCP&lt;/a&gt; turns AI-to-database access into an explicit tool contract instead of implicit system access. &amp;nbsp;&amp;nbsp;&lt;/li&gt;



&lt;li&gt;
&lt;a href="https://docs.oracle.com/en/database/oracle/sql-developer-command-line/25.4/sqcug/using-oracle-sqlcl-mcp-server.html" rel="noopener noreferrer"&gt;Oracle SQLcl in MCP&lt;/a&gt; mode (sql -mcp) is a direct, documented way to connect Codex CLI to Oracle AI Database through an MCP server. &amp;nbsp;&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Oracle AI Database provides the persistent storage and vector search layer for memory workloads, while &lt;a href="https://docs.oracle.com/en/database/oracle/agent-memory/26.4/agmea/get-started.html" rel="noopener noreferrer"&gt;Oracle AI Agent Memory&lt;/a&gt; gives teams a Python API for threads, durable memory records, scoped retrieval, and context assembly on top of it.&amp;nbsp;&amp;nbsp;&lt;/li&gt;



&lt;li&gt;
&lt;a href="https://docs.langchain.com/oss/python/integrations/vectorstores/oracle" rel="noopener noreferrer"&gt;LangChain&lt;/a&gt; can provide application-side wrappers and orchestration after the Oracle-backed memory and retrieval path is in place.&lt;/li&gt;



&lt;li&gt;A strong default is hybrid: Codex CLI plus MCP for interactive database work, Oracle AI Database plus Oracle AI Agent Memory for durable memory, and&amp;nbsp;LangChain&amp;nbsp;only when the application needs reusable&amp;nbsp;retrieval&amp;nbsp;orchestration.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Here is how those components connect in this pattern. Codex works with Oracle through SQLcl MCP’s explicit tools. The saved SQLcl profile, database user, grants, and database policies remain the enforcement boundary. Oracle AI Agent Memory is the Python package your application uses to manage memory records on Oracle AI Database. LangChain is optional application-side orchestration after the database-backed memory and evidence path is in place; it is not the permission boundary or source of truth.&lt;/p&gt;

&lt;p&gt;Production success depends less on clever prompting and more on boundaries, privileges, logging, scoped retrieval, and repeatable runbooks.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;This guide is for&amp;nbsp;developers&amp;nbsp;who&amp;nbsp;want Codex CLI to work with Oracle AI Database through explicit tools, durable memory, and reviewable retrieval evidence.&amp;nbsp;It also shows an AI agent memory architecture where Codex handles the&amp;nbsp;developer&amp;nbsp;interaction,&amp;nbsp;SQLcl MCP provides the tool boundary for database access; the saved SQLcl profile and database user permissions determine what Codex can actually do, and Oracle AI Database stores durable memory and retrieval evidence.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The developer path through this guide is simple:&lt;/strong&gt;&amp;nbsp;&amp;nbsp;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Start with&amp;nbsp;one&amp;nbsp;approved Oracle connection and a read-only validation query. &amp;nbsp;&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Put&amp;nbsp;SQLcl&amp;nbsp;MCP in front of that&amp;nbsp;connection&amp;nbsp;so Codex sees tools, not raw database credentials. &amp;nbsp;&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Check the audit and activity trail before adding more tool access. &amp;nbsp;&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Add Oracle AI Agent Memory when the workflow needs durable thread context, scoped recall, or reusable context cards. &amp;nbsp;&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Add&amp;nbsp;LangChain&amp;nbsp;only when you need application-side&amp;nbsp;retrieval&amp;nbsp;orchestration beyond the MCP interaction loop.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;/p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.oracle.com%2Fdevelopers%2Fwp-content%2Fuploads%2Fsites%2F129%2F2026%2F07%2FImage-2-3-1024x391.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.oracle.com%2Fdevelopers%2Fwp-content%2Fuploads%2Fsites%2F129%2F2026%2F07%2FImage-2-3-1024x391.png" alt="Architecture diagram showing a human operator using Codex CLI through a controlled SQLcl MCP tool path to Oracle AI Database and Oracle AI Agent Memory. Agent Memory connects to memory tables, tool logs, and vector retrieval. A separate LangChain retrieval path queries the database and returns grounded context to Codex CLI, forming the memory and retrieval loop." width="799" height="305"&gt;&lt;/a&gt;Controlled&amp;nbsp;Codex MCP + Oracle AI Database Workflow&amp;nbsp;&lt;p&gt;&lt;/p&gt;





&lt;h2&gt;
&lt;strong&gt;Why This&amp;nbsp;Architecture&amp;nbsp;Is&amp;nbsp;Useful&amp;nbsp;for&amp;nbsp;Developers&lt;/strong&gt;&amp;nbsp;&lt;/h2&gt;

&lt;p&gt;Giving an AI assistant database access is easy.&amp;nbsp;But making&amp;nbsp;that access&amp;nbsp;controlled, inspectable, and repeatable is the hard part.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Codex is useful because it sits close to the developer’s actual work: files, commands, notebooks, configuration, and implementation details. That closeness becomes more sensitive when the workflow touches a database.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;The question is not whether Codex can produce SQL-shaped text. The question is whether the database path is approved, observable, and easy to debug later. A useful workflow needs to preserve the request, the tool call, the database identity, the retrieved context, and the reason a risky action was allowed or blocked.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;By the end of this guide, you should know how to connect Codex CLI to Oracle AI Database through a controlled MCP boundary, when Codex context is enough and when Oracle-backed memory is needed, and how to build a retrieval path that can be queried, audited, and scaled.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;The workflow has four layers. Codex CLI is the developer-facing agent interface.&amp;nbsp;SQLcl&amp;nbsp;MCP is the tool boundary. Oracle AI Database is the durable substrate for memory, traces, and retrieval. The notebook is the build-and-validation harness that proves the pieces are wired correctly before the workflow is handed to Codex.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;That separation matters. Codex does not need raw credentials in its prompt.&amp;nbsp;SQLcl&amp;nbsp;MCP can be configured once using the saved alias. Oracle AI Database&amp;nbsp;owns&amp;nbsp;persistence and retrieval. The notebook shows the setup in a way that can be rerun, inspected, and debugged.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;A simple way&amp;nbsp;to divide the responsibilities:&amp;nbsp;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Layer&lt;/td&gt;
&lt;td&gt;Responsibility&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SQLcl MCP&lt;/td&gt;
&lt;td&gt;Exposes declared Oracle tools to Codex; it is the tool boundary.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Oracle AI Database&lt;/td&gt;
&lt;td&gt;Stores durable data, retrieval evidence, vectors, metadata, traces, and enforces database privileges.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Oracle AI Agent Memory&lt;/td&gt;
&lt;td&gt;Provides application APIs for users, agents, threads, durable memories, scoped retrieval, and context assembly.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LangChain&lt;/td&gt;
&lt;td&gt;Wraps Oracle-backed retrieval results as&amp;nbsp;&lt;code&gt;Document&lt;/code&gt;&amp;nbsp;objects and supports application-side orchestration.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;





&lt;h2&gt;
&lt;strong&gt;The Two&amp;nbsp;Execution&amp;nbsp;Loops&lt;/strong&gt;&amp;nbsp;&lt;/h2&gt;

&lt;p&gt;Building on the separation of responsibilities above, the system naturally forms two execution loops:&amp;nbsp;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Loop A is the operational interaction loop: Codex CLI works with MCP to discover tools, inspect data, run bounded read-only queries, and return results&amp;nbsp;immediately.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Loop B is the durable memory and retrieval loop: application code writes history, tool logs, memory records, chunks, and embeddings to Oracle AI Database, then retrieves context before a later answer or workflow step.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;/p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.oracle.com%2Fdevelopers%2Fwp-content%2Fuploads%2Fsites%2F129%2F2026%2F07%2FImage-1-3-1024x415.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.oracle.com%2Fdevelopers%2Fwp-content%2Fuploads%2Fsites%2F129%2F2026%2F07%2FImage-1-3-1024x415.png" alt="Diagram showing two linked loops. Loop A handles real-time interaction from user through Codex CLI, SQLcl MCP, Oracle AI Database, and answer. Loop B records tool traces into a memory store and Oracle AI Agent Memory, then uses hybrid retrieval to produce grounded context. Retrieved context returns to the agent for later interactions." width="799" height="324"&gt;&lt;/a&gt;Dual Execution Loop: MCP Interaction and Durable Memory &amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;p&gt;&lt;/p&gt;

&lt;p&gt;SQLcl&amp;nbsp;MCP is for Codex&amp;nbsp;operating&amp;nbsp;interactively through declared tools. Oracle AI Agent Memory is for application code that stores turns, retrieves history, and assembles context before Codex or another assistant sees a prompt. You can drop either one for a narrower use case, but most production setups need both loops.&amp;nbsp;&lt;/p&gt;





&lt;h2&gt;
&lt;strong&gt;Reproducing the&amp;nbsp;SQLcl&amp;nbsp;MCP and Codex CLI Workflow&lt;/strong&gt;&amp;nbsp;&lt;/h2&gt;

&lt;p&gt;The setup should be&amp;nbsp;reproducible.&amp;nbsp;SQLcl&amp;nbsp;runs in MCP mode with&amp;nbsp;sql&amp;nbsp;-mcp. Codex CLI launches it as an MCP server and talks to Oracle through declared tools, not through direct access. Connections come from saved&amp;nbsp;SQLcl&amp;nbsp;profiles that you create and test before Codex uses them.&amp;nbsp;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Codex does not invent them at runtime; it reuses profiles you have already created and&amp;nbsp;validated.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prerequisites before you connect Codex CLI:&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Oracle&amp;nbsp;SQLcl&amp;nbsp;25.2.0 or higher. &amp;nbsp;&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Oracle JRE 17 or 21. &amp;nbsp;&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Codex CLI&amp;nbsp;or another MCP-capable client you are explicitly configuring and testing. &amp;nbsp;&amp;nbsp;&lt;/li&gt;



&lt;li&gt;At least one saved&amp;nbsp;SQLcl&amp;nbsp;connection profile under&amp;nbsp;&lt;em&gt;~/.dbtools&lt;/em&gt;, created with password persistence for MCP use. &amp;nbsp;&amp;nbsp;&lt;/li&gt;



&lt;li&gt;A database user with the minimum permissions&amp;nbsp;required&amp;nbsp;for the workflow.&amp;nbsp;Start with read-only access and a sanitized development or replica environment where possible. &amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The notebook deliberately treats the saved&amp;nbsp;SQLcl&amp;nbsp;connection alias as a first-class artifact. In local development, that alias is what lets&amp;nbsp;SQLcl&amp;nbsp;MCP connect without forcing the agent to assemble credentials dynamically.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;The notebook then generates a sanitized Codex MCP config preview.&amp;nbsp;The preview is intentionally safe: it shows the server command and arguments without exposing secrets.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;For the saved connection itself, the important detail is &lt;code&gt;-savepwd&lt;/code&gt;. MCP cannot stop and ask a human for the password every time Codex invokes a database tool. The saved alias becomes the repeatable local path Codex can use after you have reviewed it.&amp;nbsp;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;

```toml 
[mcp_servers.sqlcl]
command = "/absolute/path/to/sql"
args = ["-mcp"]
```

 &lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;That small TOML block defines the connection between Codex and&amp;nbsp;SQLcl&amp;nbsp;MCP Server. Codex interacts with the database through the tools and permissions exposed by the MCP server, using the saved&amp;nbsp;SQLcl&amp;nbsp;connection profile you created and tested first.&amp;nbsp;Save this configuration in&amp;nbsp;&lt;code&gt;~/.codex/config.toml&lt;/code&gt;, restart Codex, then run&amp;nbsp;&lt;code&gt;codex mcp list&lt;/code&gt;&amp;nbsp;to confirm that the SQLcl MCP server and its tools are discoverable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Validation checklist before expanding access&lt;/strong&gt;&amp;nbsp;&amp;nbsp;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run&amp;nbsp;&lt;em&gt;sql&amp;nbsp;-mcp&lt;/em&gt;&amp;nbsp;locally and confirm the server starts. &amp;nbsp;&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Restart Codex CLI and confirm the&amp;nbsp;SQLcl&amp;nbsp;tools are discoverable. &amp;nbsp;&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Run one read-only query against an approved schema. &amp;nbsp;&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Check database-side MCP activity logs and session metadata. &amp;nbsp;&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Document the connection alias, database user, grant scope, restrict level, and troubleshooting owner. &amp;nbsp;&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Good first proof looks like this:&lt;/strong&gt;&amp;nbsp;&amp;nbsp;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The MCP server starts without a Java or path error. &amp;nbsp;&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Codex lists the&amp;nbsp;SQLcl&amp;nbsp;MCP tools after&amp;nbsp;restart. &amp;nbsp;&amp;nbsp;&lt;/li&gt;



&lt;li&gt;A read-only query succeeds against the expected schema. &amp;nbsp;&amp;nbsp;&lt;/li&gt;



&lt;li&gt;The notebook audit trail records the expected tool interaction in &lt;code&gt;codex_tool_logs&lt;/code&gt;.&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/li&gt;



&lt;li&gt;For live Codex CLI +&amp;nbsp;SQLcl&amp;nbsp;MCP validation, confirm the database/session activity through your normal Oracle monitoring path.&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/li&gt;



&lt;li&gt;A denied query fails because of the database role, not because a prompt asked nicely.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A useful MCP boundary is more than&amp;nbsp;tool&amp;nbsp;discovery. It is an operating contract. The notebook models that contract with risk levels, read-only defaults, confirmation requirements, scope checks, and controlled failure examples.&amp;nbsp;Denied and&amp;nbsp;warning states are useful because they prove the workflow can say no.&amp;nbsp;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Read-only default: start with inspection and diagnostics before allowing changes.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Confirmation gate: require explicit approval for medium-risk, write-like, or destructive actions.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Scope control: keep user, tenant, and schema filters close to the database query.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Failure trace: store denied calls and warnings as evidence instead of hiding them.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;





&lt;h2&gt;
&lt;strong&gt;Why put application memory records in Oracle AI Database, not just outputs &amp;nbsp;&lt;/strong&gt;&amp;nbsp;&lt;/h2&gt;

&lt;p&gt;Once the first MCP tool calls work, the next challenge is continuity. This is where long-term memory for AI agents becomes different from&amp;nbsp;short-lived chat context.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;If memory lives only in chat context, the system is fragile. If memory is scattered across files without structure, retrieval and auditing become expensive over time.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;A database-backed memory model is usually cleaner and more scalable. The companion notebook builds this memory layer from&amp;nbsp;scratch&amp;nbsp;so the mechanics are visible, then shows how Oracle AI Agent Memory sits on top of it once the substrate is working.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Memory Categories That Matter in Practice:&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;You do not need to start by memorizing table names or column names.&amp;nbsp;The useful idea is simpler:&amp;nbsp;different kinds&amp;nbsp;of memory serve different jobs.&amp;nbsp;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Conversation memory: Keeps the important&amp;nbsp;user&amp;nbsp;and assistant&amp;nbsp;turns&amp;nbsp;that future sessions may need.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Operational memory: Keeps tool calls, outcomes, warnings, and failures so a team can debug what happened.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Semantic memory: Adds embeddings so the system can find relevant context even when the user asks in different words.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In practice, hybrid retrieval for agent memory usually combines exact operational terms, such as &lt;code&gt;sql -mcp&lt;/code&gt; or &lt;code&gt;codex_mcp&lt;/code&gt;, with semantic search over memory records.&lt;/p&gt;

&lt;p&gt;The notebook shows&amp;nbsp;the lower-level mechanics&amp;nbsp;first&amp;nbsp;so the storage and retrieval path is visible.&amp;nbsp;This is also a context engineering problem: the application&amp;nbsp;has to&amp;nbsp;decide which memories, tool traces, and retrieval results should be assembled before Codex or another assistant answers.&amp;nbsp;Oracle AI Agent Memory then gives application code a cleaner&amp;nbsp;package&amp;nbsp;API over that same database-backed idea.&amp;nbsp;&lt;/p&gt;





&lt;h2&gt;
&lt;strong&gt;Where Oracle AI Agent Memory Fits&lt;/strong&gt;&amp;nbsp;&lt;/h2&gt;

&lt;p&gt;Oracle AI Agent Memory sits between your application code and Oracle AI Database. The package manages conversation threads, durable memory records, scoped retrieval, and context assembly. Oracle AI Database&amp;nbsp;remains&amp;nbsp;the storage and enforcement layer underneath; the package does not replace&amp;nbsp;that,&amp;nbsp;it makes working with it faster and more consistent.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;The notebook includes an abbreviated package-backed memory pattern. It initializes&amp;nbsp;&lt;code&gt;OracleAgentMemory&lt;/code&gt;&amp;nbsp;with a database connection pool and a custom local deterministic embedder.&amp;nbsp;&lt;code&gt;LocalCodexEmbedder&lt;/code&gt;&amp;nbsp;is notebook code, not a built-in Oracle AI Agent Memory embedder. &lt;/p&gt;

&lt;p&gt;The local embedder is intentionally billing-free, which makes the notebook runnable for people who do not want to attach paid model usage to a tutorial.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Abbreviated setup pattern: define&amp;nbsp;&lt;code&gt;CONFIG&lt;/code&gt;, import and initialize&amp;nbsp;&lt;code&gt;oracledb&lt;/code&gt;, implement&amp;nbsp;&lt;code&gt;LocalCodexEmbedder&lt;/code&gt;, and set stable values for&amp;nbsp;&lt;code&gt;AGENT_MEMORY_USER_ID&lt;/code&gt;&amp;nbsp;and&amp;nbsp;&lt;code&gt;AGENT_MEMORY_AGENT_ID&lt;/code&gt;&amp;nbsp;before running this example.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;

```python 
from oracleagentmemory.core.oracleagentmemory import OracleAgentMemory 
from oracleagentmemory.apis.searchscope import SearchScope 
 
db_pool = oracledb.SessionPool( 
    user=CONFIG["ORACLE_USER"], 
    password=CONFIG["ORACLE_PASSWORD"], 
    dsn=CONFIG["ORACLE_DSN"], 
    min=1, 
    max=4, 
    increment=1, 
) 
 
agent_memory = OracleAgentMemory( 
    connection=db_pool, 
    embedder=LocalCodexEmbedder(dimensions=32), 
    llm=None, 
    extract_memories=False, 
    schema_policy="create_if_necessary", 
) 
```

 &lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Use &lt;code&gt;oracleagentmemory&lt;/code&gt; from your application layer when you need package-managed users, agents, memories, threads, scoped retrieval, and context assembly.&amp;nbsp;Keep systems of record separate from memory records: memory helps provide context, but application logic and authoritative data sources should still decide what is true, allowed, and final.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Implementation&amp;nbsp;note:&amp;nbsp;Use a schema whose default tablespace supports the JSON objects created by Agent Memory. If a local SYSTEM schema sits on a tablespace that rejects JSON object creation, the better production answer is a dedicated application schema. The notebook can still explain the issue clearly instead of hiding it.&amp;nbsp;&lt;/p&gt;





&lt;h2&gt;
&lt;strong&gt;Store and Search: What a Realistic Memory Looks Like&lt;/strong&gt;&amp;nbsp;&lt;/h2&gt;

&lt;p&gt;A realistic Codex memory is not&amp;nbsp;generic&amp;nbsp;trivia about a user. For this workflow, memory should capture how a developer actually works: the connection name they used, the&amp;nbsp;SQLcl&amp;nbsp;path that succeeded, the MCP config location, the failed privilege boundary, the retrieval query that helped, and the final fix that should be reused later.&amp;nbsp;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;

```python 
thread = agent_memory.create_thread(
    user_id=AGENT_MEMORY_USER_ID,
    agent_id=AGENT_MEMORY_AGENT_ID,
)
 
thread.add_memory( 
    "Developer validated Codex CLI with SQLcl MCP alias codex_mcp " 
    "against local Oracle AI Database service FREEPDB1." 
) 
 
results = agent_memory.search(
    query="Codex SQLcl MCP alias validation and Agent Memory setup", 
    scope=SearchScope(user_id=AGENT_MEMORY_USER_ID), 
) 
```

 &lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;That kind of memory pays off because it is operational. It can help Codex answer the next question with context from the&amp;nbsp;previous&amp;nbsp;debugging session, but it is still scoped and retrievable through a database-backed API.&amp;nbsp;&lt;/p&gt;





&lt;h2&gt;
&lt;strong&gt;Where&amp;nbsp;LangChain&amp;nbsp;Adds Value&lt;/strong&gt;&amp;nbsp;&lt;/h2&gt;

&lt;p&gt;LangChain&amp;nbsp;should not be treated as the source of truth. Codex CLI does not call&amp;nbsp;LangChain&amp;nbsp;directly in this architecture.&amp;nbsp;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;LangChain&amp;nbsp;runs in your application layer, where it can format context, wrap retrieval behavior, expose a familiar retriever shape, and plug Oracle-backed memory into chains or agents that already expect&amp;nbsp;LangChain&amp;nbsp;interfaces. In this notebook,&amp;nbsp;LangChain is used as a compatibility layer: the notebook’s custom Oracle-backed&amp;nbsp;&lt;code&gt;hybrid_search()&lt;/code&gt;&amp;nbsp;path performs hybrid retrieval, and its results are wrapped as LangChain&amp;nbsp;&lt;code&gt;Document&lt;/code&gt;&amp;nbsp;objects so they can flow into applications that already expect&amp;nbsp;LangChain&amp;nbsp;interfaces.&amp;nbsp;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use Oracle AI Database for persistence, retrieval, tenancy, validation evidence, and tool traces.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Use Oracle AI Agent Memory for package-backed memory threads, durable memories, and scoped search.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Use&amp;nbsp;LangChain&amp;nbsp;for retriever wrappers and orchestration compatibility when the application already expects that shape.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Use Codex CLI for developer interaction, planning, and guided execution.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The notebook's&amp;nbsp;LangChain&amp;nbsp;section is valuable because it comes late. By the time it appears, the database tables, package memory, retrieval scores, and validation snapshot already exist.&amp;nbsp;LangChain&amp;nbsp;becomes a wrapper around evidence, not a substitute for evidence.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Do not add&amp;nbsp;LangChain&amp;nbsp;just to make the workflow sound more agentic. If the application only needs direct SQL, package-backed Agent Memory search, or a simple evidence table, the extra abstraction can make debugging harder.&amp;nbsp;LangChain&amp;nbsp;is a good fit when the consuming application already expects retrievers, documents, chains, or&amp;nbsp;tool&amp;nbsp;orchestration. It is not the permission boundary, the memory store, or the audit layer.&amp;nbsp;&lt;/p&gt;





&lt;h2&gt;
&lt;strong&gt;Engineering Guidance&lt;/strong&gt;&amp;nbsp;&lt;/h2&gt;

&lt;p&gt;The difference between demo success and production success is disciplined operations. Most failures at this stage come from integration gaps, not model behavior. A local notebook can prove the wiring. A production workflow needs smaller database roles, managed secrets, clear MCP approval policy, repeatable environment setup, and&amp;nbsp;monitoring around&amp;nbsp;memory writes and tool calls.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Access and privilege model&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use a dedicated application schema where possible, then tighten grants with least-privilege roles and quotas.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Keep saved&amp;nbsp;SQLcl&amp;nbsp;aliases separate by role and environment instead of sharing one broad connection.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Start read-only wherever possible and&amp;nbsp;gate write&amp;nbsp;operations with explicit confirmation workflows.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Use schema allowlists and separate accounts for development,&amp;nbsp;test, and production.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Observability model&amp;nbsp;&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Log tool name, thread ID, timestamp, status, and sanitized inputs and outputs.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Classify failures into runtime, discovery, connection, permission, query, and retrieval categories.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Keep a troubleshooting playbook in the&amp;nbsp;repo&amp;nbsp;so setup issues do not become tribal knowledge.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Check whether&amp;nbsp;retrieval&amp;nbsp;quality changes as more data and memory records are added.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Reliability model&amp;nbsp;&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prefer deterministic SQL patterns with bounded result sets.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Use retrieval-first context assembly for memory-heavy tasks.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Avoid giant context stuffing as a substitute for memory design.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Review and prune tool surfaces periodically.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Move from the local deterministic embedder to a supported embedding model after cost, latency, and privacy review.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;





&lt;h2&gt;
&lt;strong&gt;Typical Failure Modes&lt;/strong&gt;&amp;nbsp;&lt;/h2&gt;

&lt;p&gt;Most teams hit a predictable set of issues.&amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Runtime failure:&amp;nbsp;sql&amp;nbsp;-mcp&amp;nbsp;does not start&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Check the absolute&amp;nbsp;SQLcl&amp;nbsp;path, confirm Java is available, and run&amp;nbsp;sql&amp;nbsp;-mcp&amp;nbsp;outside Codex first. Resolve runtime issues before checking assistant behavior.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Discovery failure: Codex does not see tools&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Check the Codex MCP configuration, confirm the configured command points to the&amp;nbsp;SQLcl&amp;nbsp;executable, and restart or reload Codex CLI after edits.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Connection failure: tools are&amp;nbsp;present&amp;nbsp;but queries fail&amp;nbsp;immediately&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Check the saved&amp;nbsp;SQLcl&amp;nbsp;connection alias, confirm the profile lives under the expected&amp;nbsp;SQLcl&amp;nbsp;connection store, and verify password persistence for the MCP workflow. Then test the same connection outside Codex.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Permission failure: queries execute selectively and fail on specific objects&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Check the database role first. A selective failure can be the right outcome when&amp;nbsp;least&amp;nbsp;privilege is working. Add grants intentionally and keep read-write access separate from the&amp;nbsp;initial&amp;nbsp;validation path.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Retrieval quality failure: answers are fluent but weakly grounded&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Inspect the retrieved records before blaming the model. Check chunk size, metadata filters, embedding choice, top-k settings, and whether the query is asking for exact history, semantic similarity, or operational&amp;nbsp;logs.&amp;nbsp;&lt;/p&gt;





&lt;h2&gt;
&lt;strong&gt;Why the Hybrid Model Is Usually the Best Long-Term Design&lt;/strong&gt;&amp;nbsp;&lt;/h2&gt;

&lt;p&gt;No single layer handles both execution and memory well. Trying to force everything into Codex context gets messy fast: you either lose control over execution, or you stuff too much state into prompts just to keep things working. On the other&amp;nbsp;side, if you only build backend memory systems, you lose the speed and usability that makes an assistant useful during development.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;The hybrid approach works because it does not try to solve everything in one place. Execution stays controlled through MCP. Memory stays durable and&amp;nbsp;queryable&amp;nbsp;in Oracle AI Database. Oracle AI Agent Memory gives application code a cleaner memory API.&amp;nbsp;LangChain&amp;nbsp;adds orchestration only after evidence exists.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;In real&amp;nbsp;teams&amp;nbsp;this usually evolves over time. Start with Codex CLI,&amp;nbsp;SQLcl&amp;nbsp;MCP, read-only access, and strong logging. Once people rely on the workflow, the gaps become visible: lost context, weak traceability, repeated setup work, or retrieval that is hard to explain. That is when database-backed memory and structured retrieval become worth adding.&amp;nbsp;&lt;/p&gt;





&lt;h2&gt;
&lt;strong&gt;Conclusion&lt;/strong&gt;&amp;nbsp;&lt;/h2&gt;

&lt;p&gt;A Codex and&amp;nbsp;SQLcl&amp;nbsp;MCP workflow&amp;nbsp;becomes&amp;nbsp;useful when it is treated as an engineering pattern, not just a setup trick. Codex keeps the developer moving. MCP makes tool access&amp;nbsp;explicit.&amp;nbsp;SQLcl&amp;nbsp;connects those tools to&amp;nbsp;reviewed&amp;nbsp;Oracle profiles. Oracle AI Database stores durable memory records, traces, and retrieval evidence. Oracle AI Agent Memory gives applications a practical memory API.&amp;nbsp;LangChain&amp;nbsp;helps when retrieval needs to become&amp;nbsp;reusable&amp;nbsp;orchestration.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;The result is a workflow a team can inspect. You can see what Codex asked for, which tool path ran, what the database allowed, which memory records were retrieved, and how the&amp;nbsp;final answer&amp;nbsp;was assembled.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;That is the shift that matters: from assistant access that is implicit and hard to audit, to explicit boundaries, durable memory, and evidence a developer can&amp;nbsp;actually debug.&amp;nbsp;&lt;/p&gt;





&lt;h2&gt;
&lt;strong&gt;Frequently Asked Questions&lt;/strong&gt;&amp;nbsp;&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What is MCP in this context?&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;MCP is the protocol boundary that lets Codex call explicit tools exposed by a server instead of accessing systems implicitly.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What does MCP protect, and what does it not protect?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;MCP makes the tool interface explicit and reviewable: Codex can call only the tools exposed by the server. It does not replace database security. The saved SQLcl connection profile, database user, grants, roles, network controls, and database policies determine what those tools can actually access or change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why use&amp;nbsp;SQLcl&amp;nbsp;for Oracle MCP?&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;SQLcl&amp;nbsp;already understands Oracle workflows and can run as the Oracle&amp;nbsp;SQLcl&amp;nbsp;MCP server with &lt;code&gt;sql&amp;nbsp;–mcp&lt;/code&gt;, making the Oracle integration practical and direct.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is this Codex CLI or Codex app?&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;The workflow should be described as Codex CLI/TUI oriented because the MCP configuration is written as a local Codex config&amp;nbsp;preview&amp;nbsp;and the&amp;nbsp;SQLcl&amp;nbsp;MCP server is launched as a command.&amp;nbsp;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why include Oracle AI Database if MCP already works?&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;MCP handles the execution boundary. Oracle AI Database handles durable memory, retrieval, vector search, concurrency, observability, and governance.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do I need an OpenAI API key?&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;An external model-provider API key is only needed if you change the notebook to use a provider-backed embedding or LLM service. The default notebook path uses a local deterministic embedder&amp;nbsp;so people can run the Agent Memory package section without adding billing details.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why use&amp;nbsp;SQLcl&amp;nbsp;MCP instead of connecting directly from Codex?&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;MCP gives the workflow a defined tool boundary. Direct connections can be useful in notebooks, but Codex should call tools through a contract that can be logged, scoped, and reviewed.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why include&amp;nbsp;LangChain&amp;nbsp;if Oracle already stores memory?&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Because many teams already have&amp;nbsp;LangChain-shaped retrievers and chains. The notebook shows how to expose Oracle-backed retrieval through that shape while keeping memory and enforcement in the database.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is this RAG vs agent memory?&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Not exactly. RAG retrieves external knowledge for a response, while agent memory preserves useful context, decisions, tool traces, and workflow state across sessions. In practice, production systems often use both.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is the&amp;nbsp;minimum&amp;nbsp;viable&amp;nbsp;setup?&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;SQLcl&amp;nbsp;MCP configured in Codex CLI, one safe saved Oracle connection, and a read-only validation flow.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Should production start with read-write permissions?&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Usually&amp;nbsp;no. Start read-only, log everything important, and add write scopes gradually with explicit approvals.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is the best rollout strategy?&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Pilot in development with read-only access and strong logging, then expand capabilities in controlled phases as the team learns which memory and tool paths are&amp;nbsp;actually useful.&amp;nbsp;&lt;/p&gt;





&lt;h2&gt;
&lt;strong&gt;Companion Troubleshooting Appendix&lt;/strong&gt;&amp;nbsp;&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Minimum&amp;nbsp;viable&amp;nbsp;setup:&lt;/strong&gt;&amp;nbsp;SQLcl&amp;nbsp;MCP configured in Codex CLI, one approved Oracle connection, read-only validation, and database-side activity logging. &amp;nbsp;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First checks:&lt;/strong&gt;&amp;nbsp;confirm&amp;nbsp;&lt;em&gt;sql&amp;nbsp;-mcp&lt;/em&gt;&amp;nbsp;starts, Codex sees the tools after&amp;nbsp;restart, and the saved&amp;nbsp;SQLcl&amp;nbsp;connection alias resolves. &amp;nbsp;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Environment model:&lt;/strong&gt;&amp;nbsp;use separate credentials and policies for dev, test, and prod, with stricter controls as capability expands. &amp;nbsp;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Logging model:&lt;/strong&gt;&amp;nbsp;capture tool name, timestamp, thread ID, status, sanitized input/output summaries, and relevant&amp;nbsp;SQLcl&amp;nbsp;MCP log records. &amp;nbsp;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Retrieval quality:&lt;/strong&gt;&amp;nbsp;tune chunk size, enrich metadata, review embedding choice, and evaluate retrieval against representative queries. &amp;nbsp;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Common anti-pattern:&lt;/strong&gt;&amp;nbsp;expanding tool surfaces before ownership, logging standards, and runbooks are in place. &amp;nbsp;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rollout path:&lt;/strong&gt;&amp;nbsp;pilot in dev with read-only access and strong logging, then expand capabilities in controlled phases. &amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Schema&lt;/strong&gt;: Agent Memory package tables need a schema and tablespace that can create the package's JSON-backed objects.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Model calls&lt;/strong&gt;: an API key can be present but still fail if the model provider account has no quota.&amp;nbsp;&lt;/p&gt;

&lt;h2&gt;
&lt;strong&gt;Resources&amp;nbsp;&lt;/strong&gt;&amp;nbsp;&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.oracle.com/en/database/oracle/agent-memory/26.4/" rel="noreferrer noopener"&gt;Oracle AI Agent Memory documentation&lt;/a&gt;&amp;nbsp;&lt;/li&gt;



&lt;li&gt;&lt;a href="https://docs.oracle.com/en/database/oracle/oracle-database/26/" rel="noopener noreferrer"&gt;Oracle Database 26ai documentation&lt;/a&gt;&lt;/li&gt;



&lt;li&gt;
&lt;a href="https://pypi.org/project/oracleagentmemory/" rel="noreferrer noopener"&gt;oracleagentmemory on PyPI&lt;/a&gt;&amp;nbsp;&lt;/li&gt;



&lt;li&gt;&lt;a href="https://docs.oracle.com/en/database/oracle/sql-developer-command-line/25.2/sqcug/sqlcl-mcp-server.html" rel="noopener noreferrer"&gt;SQLcl MCP Server&lt;/a&gt;&lt;/li&gt;



&lt;li&gt;&lt;a href="https://github.com/oracle-devrel/oracle-ai-developer-hub/blob/main/notebooks/codex_mcp_oracle_ai_database_memory_langchain.ipynb" rel="noopener noreferrer"&gt;End-to-end support notebook&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;



</description>
      <category>codex</category>
      <category>oracle</category>
      <category>database</category>
      <category>ai</category>
    </item>
    <item>
      <title>Agent memory is a database problem: Oracle research makes the case</title>
      <dc:creator>Anya Summers</dc:creator>
      <pubDate>Tue, 11 Aug 2026 15:16:38 +0000</pubDate>
      <link>https://dev.to/oracledevs/agent-memory-is-a-database-problem-oracle-research-makes-the-case-1p0l</link>
      <guid>https://dev.to/oracledevs/agent-memory-is-a-database-problem-oracle-research-makes-the-case-1p0l</guid>
      <description>&lt;p&gt;&lt;em&gt;A technical report on arXiv sets out a database-native memory substrate for long-horizon agents: 93.8% on LongMemEval, roughly 10.7× fewer input tokens than flat conversation history, and governance treated as a design constraint, not an afterthought.&lt;/em&gt;&lt;/p&gt;





&lt;h2&gt;Key Takeaways&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Agent memory is fundamentally a data-management problem. Long-horizon agents need persistent, scoped, retrievable, and governable memory, not just larger context windows or more conversation history.&lt;/li&gt;



&lt;li&gt;Oracle Agent Memory manages working memory, long-term factual memory, and procedural memory directly on Oracle AI Database, with a lifecycle spanning extraction, consolidation, retrieval, summarisation, and revision.&lt;/li&gt;



&lt;li&gt;In the report, Oracle Agent Memory achieved 93.8% on LongMemEval and kept estimated input context to about 1,300 tokens per request at turn 80, compared with roughly 13,900 for flat conversation history.&lt;/li&gt;



&lt;li&gt;Keeping agent memory in the database keeps it close to the enterprise data it reasons over, while bringing transactions, relational, JSON and vector capabilities, scoped retrieval, and database-enforced governance into the same substrate.&lt;/li&gt;
&lt;/ul&gt;





&lt;p&gt;Prompt and context engineering emerged for a reason: to elicit specific behaviours from large language models whilst maintaining a high signal-to-noise ratio in the context window, through systematic curation of the data injected into it. But as models grow more capable at reasoning, extensive context-window management is becoming less necessary.&lt;/p&gt;

&lt;p&gt;The real job to be done sits elsewhere: adapting agents to new information, and making them dependable on long-horizon tasks. That job is the subject of a new technical report from Oracle's AI teams, &lt;a href="https://arxiv.org/pdf/2607.13157" rel="noopener noreferrer"&gt;&lt;em&gt;Oracle Agent Memory as an Enterprise Memory Substrate for Long-Horizon AI Agents&lt;/em&gt;&lt;/a&gt;, published this month on arXiv.&lt;/p&gt;





&lt;p&gt;&lt;strong&gt;Agent memory has emerged as the solution: the capability that lets agentic systems learn, adapt, and personalise from continuous interaction between the agent, its users, and its environment.&lt;/strong&gt; The report grounds this in recognisable enterprise use cases: support agents recalling incident history across weeks, coding agents reusing prior design decisions, analytics agents that know what "average spending" means in the business.&lt;/p&gt;

&lt;p&gt;The core problem investigated is not recall; it is maintaining heterogeneous memory under explicit scope, retention, and retrieval constraints; this is effective memory management. Yet although many memory-management solutions have emerged, few have met the demanding requirements of enterprise AI workloads. Most are variations on "just add a vector store": fragments assembled around the model (reflection buffers, framework add-ons, standalone memory services) that each work in a demo, and that together complicate governance, security, data locality, and reliability in an enterprise setting.&lt;/p&gt;

&lt;p&gt;The gap is enterprise-grade memory: a substrate where governance and security are entry requirements rather than roadmap items, and where scale, reliability, and data locality come built in rather than bolted on. The report is written squarely at that gap. Its starting observation is simple: the list of things agent memory demands (persistence, indexing, scoped retrieval, transactions, governance) is a list of database requirements.&lt;/p&gt;





&lt;p&gt;&lt;strong&gt;Oracle Agent Memory is a database-native memory substrate that treats agent memory as a managed lifecycle, spanning ingestion, extraction, consolidation, retrieval, summarisation, and revision, built directly on Oracle AI Database.&lt;/strong&gt; The report organises that memory into three operational categories:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Working memory&lt;/strong&gt;: the active state of a task, materialised as thread summaries and context cards compact enough for prompt injection.&lt;/li&gt;



&lt;li&gt;
&lt;strong&gt;Long-term factual memory&lt;/strong&gt;: durable facts, preferences, and profile attributes that persist across sessions under user and agent scope.&lt;/li&gt;



&lt;li&gt;
&lt;strong&gt;Procedural memory&lt;/strong&gt;: lessons, strategies, and guidelines drawn from prior outcomes; what the agent has learned to do, not just what it knows.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An active memory core does the opinionated work: synchronising threads, extracting durable facts, refreshing summaries and context cards, orchestrating search. Beneath it sits a passive memory-store contract (insert, search, retrieve, enforce scope) that stays reusable even for third-party memory libraries wanting Oracle AI Database as their backend.&lt;/p&gt;

&lt;p&gt;The report measures the substrate across long-horizon memory benchmarks, a flat-history baseline, and estimated token behaviour:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;93.8% on LongMemEval&lt;/strong&gt; (469 of 500), including 100% on single-session assistant recall and 96.2% on temporal reasoning. Multi-session reasoning is the honest low point at 88%, and the report says so.&lt;/li&gt;



&lt;li&gt;
&lt;strong&gt;Roughly 10.7× fewer estimated input tokens&lt;/strong&gt; than a flat-history baseline by turn 80 of a scripted conversation: about 1,300 tokens per request against roughly 13,900.&lt;/li&gt;



&lt;li&gt;
&lt;strong&gt;48 wins, 13 losses, 19 ties&lt;/strong&gt; in pairwise judging against that same flat-history baseline, a 3.7× win ratio among non-tied turns, even though the baseline saw the entire transcript. Bounded, relevant context beat complete, unfocused context.&lt;/li&gt;



&lt;li&gt;
&lt;strong&gt;0.680 on BEAM at the 1M-token scale&lt;/strong&gt; under event-presence scoring, ahead of previously reported external results, with the stricter order-sensitive score (0.630) and an early 10M stress test (0.510) published alongside it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;For enterprise leaders&lt;/strong&gt;, the argument is about where institutional memory should live. Agent memory holds private conversations, inferred preferences, learned procedures, and retrieved enterprise facts. Store it in a side service with its own authorisation model and you replicate identity, role, and policy outside your primary data platform. Place it in the database and memory stays close to the enterprise data it reasons over, inheriting the controls you already trust: with database-enforced access control through Oracle Deep Data Security, a denied record is absent from retrieval because the acting identity was never allowed to see it, not because an application filter caught it afterwards. &lt;strong&gt;Memory stops being another shadow data store to govern.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For developers, the report reads like an engineering document, because it is one. Threads are the integration primitive, and the workflow has four recurring stages:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Initialise the memory client with a database connection, an embedder, and an optional LLM.&lt;/li&gt;



&lt;li&gt;Create or reopen a thread, scoped to a user and an agent.&lt;/li&gt;



&lt;li&gt;Add messages so the system persists history and runs extraction and summarisation.&lt;/li&gt;



&lt;li&gt;Retrieve context via scoped search, context cards, or thread summaries before the next turn.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Defaults are conservative: bounded reads, bounded search, explicit scope with exact-match controls, no hidden schema side effects. The &lt;a href="https://www.oracle.com/database/ai-agent-memory/" rel="noopener noreferrer"&gt;&lt;code&gt;oracleagentmemory&lt;/code&gt;&lt;/a&gt; Python client is model-agnostic at the embedder and LLM layer, slots into harnesses such as LangGraph and WayFlow, and supports three integration patterns (middleware-centric, tool-centric, or hybrid). The appendices cover setup, thread lifecycle, and search semantics in runnable detail.&lt;/p&gt;

&lt;p&gt;The report also stakes out an evaluation position both audiences should note: downstream accuracy alone is not enough. A correct answer can hide noisy retrieval; a wrong one can hide a memory layer that did its job. It measures memory as a system instead, separating evidence retrieval, evidence use, task outcome, and operational efficiency, and applies that standard to itself, publishing configuration caveats, threshold sweeps, and failure modes in plain sight.&lt;/p&gt;

&lt;p&gt;Every team building agents eventually rediscovers the same requirements list, and then rebuilds it around a vector store. The position behind Oracle Agent Memory is that the list already has an answer. Give agents a substrate that speaks transactions, vectors, JSON, and policy natively, and the effort moves to where it actually matters: managing the lifecycle of what an agent remembers, not plumbing where it stores it. &lt;strong&gt;The storage question is settled. Remembering is the work.&lt;/strong&gt;

The full report is available on arXiv: https://arxiv.org/pdf/2607.13157

&lt;/p&gt;

&lt;h2&gt;FAQs&lt;/h2&gt;

&lt;h3&gt;What is agent memory?&lt;/h3&gt;

&lt;p&gt;Agent memory is the persistent state that lets an AI agent retain and retrieve useful information across interactions. That can include active task context, facts and preferences learned over time, and procedures or strategies derived from previous outcomes. Unlike the context window, that information does not have to disappear when the current conversation ends.&lt;/p&gt;

&lt;h3&gt;How is agent memory different from RAG or a vector database?&lt;/h3&gt;

&lt;p&gt;Vector retrieval can be part of agent memory, but retrieval alone is not memory management. An agent also needs to decide what to retain, how that information is scoped, when it should be updated or summarised, how long it should persist, and who is allowed to retrieve it. The Oracle report treats those as lifecycle and data-management problems rather than just search problems.&lt;/p&gt;

&lt;h3&gt;Why use a database for AI agent memory?&lt;/h3&gt;

&lt;p&gt;Agent memory needs many of the capabilities databases already provide: persistence, indexing, structured and semantic retrieval, transactions, access control, and governance. A database-native approach also allows relational data, JSON, vectors, and memory state to live on the same managed substrate rather than being distributed across separate memory services.&lt;/p&gt;

&lt;h3&gt;How does Oracle Agent Memory integrate with agent applications?&lt;/h3&gt;

&lt;p&gt;Developers create or reopen a thread scoped to a user and agent, add messages, and retrieve relevant context before subsequent turns. The &lt;code&gt;oracleagentmemory&lt;/code&gt; Python client is model-agnostic at the embedding and LLM layers and supports middleware-centric, tool-centric, and hybrid integration patterns, including with frameworks such as LangGraph and WayFlow.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>agentskills</category>
      <category>database</category>
      <category>oracle</category>
    </item>
    <item>
      <title>Custom Memory Extraction for AI Agents: Turning Conversations into Useful Facts</title>
      <dc:creator>Anya Summers</dc:creator>
      <pubDate>Tue, 11 Aug 2026 15:11:32 +0000</pubDate>
      <link>https://dev.to/oracledevs/custom-memory-extraction-for-ai-agents-turning-conversations-into-useful-facts-3752</link>
      <guid>https://dev.to/oracledevs/custom-memory-extraction-for-ai-agents-turning-conversations-into-useful-facts-3752</guid>
      <description>&lt;p&gt;Learn how &lt;a href="https://docs.oracle.com/en/database/oracle/agent-memory/26.6/guide/" rel="noreferrer noopener"&gt;Oracle AI Agent Memory&lt;/a&gt; uses custom extraction instructions, thread-level overrides, and tool-result metadata to turn support conversations into durable, scoped memory.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Companion notebook: &lt;/strong&gt;&lt;a href="https://github.com/oracle-devrel/oracle-ai-developer-hub/blob/main/notebooks/agent_memory/custom_memory_extraction_agent_memory.ipynb" rel="noreferrer noopener"&gt;custom_memory_extraction_agent_memory.ipynb&lt;/a&gt;&amp;nbsp;&lt;/p&gt;





&lt;h2&gt;
&lt;strong&gt;Key Takeaways&lt;/strong&gt;&amp;nbsp;&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Oracle AI Agent Memory can transform raw user-agent messages into durable memories stored in Oracle AI Database.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Default extraction provides a general memory baseline; custom extraction instructions make memory formation reflect a domain-specific policy.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;A customer-support agent should preserve durable facts such as order IDs, return requests, delivery issues, escalation commitments, tool-confirmed statuses, and stable communication preferences.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Good memory is selective: the extractor should ignore greetings, speculation, credentials, payment details, one-time codes, and temporary conversational wording.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Client-level custom instructions define the default extraction behavior, while thread-level overrides allow narrower policies for special workflows such as escalations.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Tool-result metadata can be inherited into memories, making later search more scoped, auditable, and aligned with enterprise retrieval boundaries.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;The notebook verifies database connectivity and table-creation permissions before running memory operations.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;





&lt;p&gt;&lt;/p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.oracle.com%2Fdevelopers%2Fwp-content%2Fuploads%2Fsites%2F129%2F2026%2F08%2F1-Custom-Extraction-Flow.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.oracle.com%2Fdevelopers%2Fwp-content%2Fuploads%2Fsites%2F129%2F2026%2F08%2F1-Custom-Extraction-Flow.png" alt="Flowchart showing support messages and tool results entering an Oracle AI Agent Memory extraction workflow governed by a custom policy that preserves durable support facts and ignores transient details. The workflow produces durable memories such as facts, preferences, and commitments plus inherited tenant, source, and tag metadata. Scoped retrieval then selects relevant memory for prompt-ready support context." width="800" height="699"&gt;&lt;/a&gt; Custom extraction turns raw conversation into governed memory&amp;nbsp;&lt;p&gt;&lt;/p&gt;





&lt;h2&gt;
&lt;strong&gt;Why Agents Need Selective Memory&lt;/strong&gt;&amp;nbsp;&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.oracle.com/en/database/oracle/agent-memory/26.6/guide/" rel="noreferrer noopener"&gt;Oracle AI Agent Memory&lt;/a&gt; provides a database-backed memory layer for AI agents, so applications can store messages, durable memories, metadata, summaries, and prompt-ready context in &lt;a href="https://www.oracle.com/database/ai-database/" rel="noreferrer noopener"&gt;Oracle AI Database&lt;/a&gt;.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;AI agents are most useful when they can carry context forward. A support agent may need to remember that a customer reported a damaged item, prefers SMS updates, or was promised an escalation by Friday. Without persistent memory, the agent has to rediscover these facts from chat history or ask the user again.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;But remembering everything is not the same as remembering well. Real conversations contain greetings, clarifications, temporary codes, apologies, repeated facts, speculation, and sometimes sensitive information. If every detail becomes durable memory, future retrieval becomes noisier and harder to govern.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Custom memory extraction addresses this by turning extraction into an application policy. Instead of only asking the model to summarize a conversation, developers can define what their workflow considers useful memory and what should be ignored.&amp;nbsp;&lt;/p&gt;





&lt;h2&gt;
&lt;strong&gt;The Customer-Support Use Case&lt;/strong&gt;&amp;nbsp;&lt;/h2&gt;

&lt;p&gt;The companion notebook uses a customer-support workflow because it has a realistic mix of natural language, exact identifiers, tool results, and policy boundaries. This is the kind of workflow where a generic transcript summary is not enough.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;The support conversation includes durable facts such as order identifiers, return intent, delivery problems, replacement commitments, and customer preferences. It also includes details that should not become long-term memory, such as temporary wording, credentials, payment details, and speculation.&amp;nbsp;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Preserve exact identifiers such as order IDs, return IDs, and case IDs.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Preserve confirmed support facts such as delivery issues, product defects, return requests, and replacement commitments.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Preserve stable preferences that should influence future interactions.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Preserve tool-confirmed facts when a tool result is the source of truth.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Ignore small talk, unconfirmed guesses, secrets, payment details, and one-time codes.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;/p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.oracle.com%2Fdevelopers%2Fwp-content%2Fuploads%2Fsites%2F129%2F2026%2F08%2F2-Support-Policy-Boundary.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.oracle.com%2Fdevelopers%2Fwp-content%2Fuploads%2Fsites%2F129%2F2026%2F08%2F2-Support-Policy-Boundary.png" alt="Flowchart showing a support conversation processed by custom extraction instructions. The policy preserves order IDs, return requests, delivery issues, commitments, and stable preferences while ignoring small talk, speculation, credentials, payment secrets, and one-time codes. Preserved information becomes concise, exact, reusable durable memory." width="800" height="732"&gt;&lt;/a&gt;Customer-support extraction policy&lt;p&gt;&lt;/p&gt;





&lt;h2&gt;
&lt;strong&gt;From Conversation Text to Durable Memory&lt;/strong&gt;&amp;nbsp;&lt;/h2&gt;

&lt;p&gt;Oracle AI Agent Memory stores the conversation and uses an extraction workflow to form higher-level memories. In a support case, those memories should be concise, durable, and useful for later retrieval. A memory like "Customer prefers morning delivery windows for replacement shipments" is more reusable than several raw turns where the user mentioned their preference indirectly.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;The notebook first establishes a baseline extraction path. This baseline is useful because it shows the general extraction behavior before any domain-specific policy is added. The custom path then uses the same kind of support conversation but adds explicit extraction instructions.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;SUPPORT_EXTRACTION_INSTRUCTIONS = """
Extract only durable customer-support memory that can help future support interactions.

Preserve confirmed order IDs, return requests, delivery issues, escalation reasons,
stable customer preferences, and tool-derived support facts.

Ignore greetings, small talk, speculation, credentials, payment secrets,
one-time verification codes, and temporary conversational wording.
""".strip()
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;These instructions guide the LLM-assisted extraction step. They help shape what should become durable memory, but they should not be treated as deterministic filtering, guaranteed redaction, or a replacement for application-level security controls.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;This instruction block is intentionally written like a product policy. It does not ask the extractor to remember more; it asks the extractor to remember better. The policy defines durable support memory, instructs the extractor to preserve exact identifiers, and excludes information that should not influence future agent behavior.&amp;nbsp;&lt;/p&gt;





&lt;h2&gt;
&lt;strong&gt;Baseline Extraction vs. Custom Extraction&lt;/strong&gt;&amp;nbsp;&lt;/h2&gt;

&lt;p&gt;The notebook shows the comparison directly. A baseline client uses the package's general extraction behavior. A custom client uses the same support scenario with custom extraction instructions. This makes the effect of the policy visible rather than theoretical.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;from oracleagentmemory.core import MemoryExtractionConfig, OracleAgentMemory

base_memory = OracleAgentMemory(
    store=base_store,
    llm=memory_llm,
    memory_extraction_config=MemoryExtractionConfig(
        memory_extraction_frequency=1,
        enable_context_summary=False,
    ),
)

custom_memory = OracleAgentMemory(
    store=custom_store,
    llm=memory_llm,
    memory_extraction_config=MemoryExtractionConfig(
        memory_extraction_frequency=1,
        enable_context_summary=False,
        memory_extraction_custom_instructions=SUPPORT_EXTRACTION_INSTRUCTIONS,
    ),
)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The important design point is that the application owns the memory policy. The model helps extract memories, but the application defines what kinds of facts should survive, which exact identifiers matter, and what kinds of text should be excluded.&amp;nbsp;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Because this policy is evaluated by an LLM-assisted extraction step, it should be treated as guidance rather than deterministic filtering, guaranteed redaction, or a replacement for application-level security controls.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;For the reader, the expected outcome is straightforward: baseline extraction demonstrates general memory formation; custom extraction demonstrates memory formation shaped by support workflow priorities.&lt;/p&gt;





&lt;h2&gt;
&lt;strong&gt;What to Look for in the Output&lt;/strong&gt;&amp;nbsp;&lt;/h2&gt;

&lt;p&gt;The most important signal is not that the custom path creates more memory records. The stronger signal is that the memory records are better shaped for the workflow. The custom extraction path should preserve durable support facts while avoiding temporary or sensitive details that should not guide future conversations.&amp;nbsp;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The order identifier remains available as an exact support reference.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;The return or replacement context is preserved as durable case state.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;The customer's stable delivery or communication preference is retained.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Temporary details, such as a one-time lobby or access code, are not treated as reusable memory.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Tool-confirmed status can be preserved with metadata that describes the source and workflow tag.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This before-and-after comparison is what makes custom extraction practical: it shows how memory can become more useful, searchable, and governable without turning the full transcript into long-term state.&amp;nbsp;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Signal in the notebook&lt;/strong&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Baseline extraction&lt;/strong&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Custom extraction&lt;/strong&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exact support identifiers&amp;nbsp;&lt;/td&gt;
&lt;td&gt;May preserve the identifier if it is generally salient.&amp;nbsp;&lt;/td&gt;
&lt;td&gt;Explicitly preserves order, return, and case identifiers as durable support references.&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Return or replacement state&amp;nbsp;&lt;/td&gt;
&lt;td&gt;May summarize the conversation at a general level.&amp;nbsp;&lt;/td&gt;
&lt;td&gt;Keeps confirmed return, replacement, delivery, and escalation facts as reusable case state.&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Customer preference&amp;nbsp;&lt;/td&gt;
&lt;td&gt;May capture the preference if it appears important in the conversation.&amp;nbsp;&lt;/td&gt;
&lt;td&gt;Preserves stable communication and delivery preferences that should influence future interactions.&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Temporary detail&amp;nbsp;&lt;/td&gt;
&lt;td&gt;May not have a domain-specific reason to exclude it.&amp;nbsp;&lt;/td&gt;
&lt;td&gt;Treats one-time codes, temporary instructions, and conversational noise as non-durable.&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tool-confirmed fact&amp;nbsp;&lt;/td&gt;
&lt;td&gt;May treat tool output like another message.&amp;nbsp;&lt;/td&gt;
&lt;td&gt;Preserves tool-derived support facts and selected source metadata for scoped retrieval.&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;





&lt;h2&gt;
&lt;strong&gt;Client-Level Defaults and Thread-Level Overrides&lt;/strong&gt;&amp;nbsp;&lt;/h2&gt;

&lt;p&gt;Not every conversation in an application needs the same extraction policy. Most support threads may follow a broad support-memory policy, while an escalation thread may need a narrower policy focused only on shipment commitments and customer-facing follow-up deadlines.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Oracle AI Agent Memory supports this pattern by allowing custom extraction instructions at the client level and overrides at the thread level. The client-level policy becomes the default. A thread-level override can refine the policy for one conversation.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;escalation_thread = custom_memory.create_thread( 
    thread_id=f"escalation_support_{RUN_ID}", 
    user_id=USER_ID, 
    agent_id=AGENT_ID, 
    memory_extraction_config=MemoryExtractionConfig( 
        memory_extraction_frequency=1, 
        enable_context_summary=False, 
        memory_extraction_custom_instructions=ESCALATION_EXTRACTION_INSTRUCTIONS, 
    ), 
) &lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The notebook also shows lifecycle control for thread-level instructions. A workflow can update the thread policy when the support state changes, or clear the override to return to the client-level default.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;updated_escalation_thread = custom_memory.update_thread( 
    escalation_thread.thread_id, 
    memory_extraction_config=MemoryExtractionConfig( 
        memory_extraction_custom_instructions=( 
            "Extract only shipment escalation commitments and customer-facing follow-up deadlines." 
        ) 
    ), 
) 
 
cleared_escalation_thread = custom_memory.update_thread( 
    escalation_thread.thread_id, 
    memory_extraction_config=MemoryExtractionConfig( 
        memory_extraction_custom_instructions=None 
    ), 
) &lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;/p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.oracle.com%2Fdevelopers%2Fwp-content%2Fuploads%2Fsites%2F129%2F2026%2F08%2FGroup-2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.oracle.com%2Fdevelopers%2Fwp-content%2Fuploads%2Fsites%2F129%2F2026%2F08%2FGroup-2.png" alt="Comparison of baseline and custom memory extraction for the same support conversation containing an order ID, replacement request, delivery preference, and temporary code. Baseline extraction may capture salient facts without support-specific rules. Custom extraction keeps the ID and preference while ignoring the temporary code, producing better-shaped memory for future support turns." width="800" height="735"&gt;&lt;/a&gt;Client-level policy and thread-level override&lt;p&gt;&lt;/p&gt;





&lt;h2&gt;
&lt;strong&gt;Common Patterns for Custom Extraction Instructions&lt;/strong&gt;&amp;nbsp;&lt;/h2&gt;

&lt;p&gt;Custom extraction instructions are flexible by design. Developers do not need a different memory API for every workflow. Instead, they can express the workflow's memory policy through instructions passed into the extraction configuration.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;The useful pattern is to write instructions as a boundary: what to preserve, what to ignore, and which source or metadata signals should influence the extracted memory. The following patterns are not separate package objects; they are practical ways to structure `memory_extraction_custom_instructions` for common agent workflows.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Pattern&lt;/strong&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Use when&lt;/strong&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Instruction focus&lt;/strong&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Support memory policy&amp;nbsp;&lt;/td&gt;
&lt;td&gt;A support agent needs durable case context across future interactions.&amp;nbsp;&lt;/td&gt;
&lt;td&gt;Preserve order IDs, return IDs, delivery issues, replacement commitments, escalation reasons, and stable preferences. Ignore greetings, credentials, payment details, one-time codes, and temporary wording.&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Escalation-only policy&amp;nbsp;&lt;/td&gt;
&lt;td&gt;One thread needs a narrower policy than the default client-level behavior.&amp;nbsp;&lt;/td&gt;
&lt;td&gt;Preserve escalation reason, owner or team, customer-facing follow-up deadline, and confirmed commitments. Ignore general troubleshooting and repeated background context.&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tool-result policy&amp;nbsp;&lt;/td&gt;
&lt;td&gt;The agent receives tool outputs that should be treated as authoritative workflow state.&amp;nbsp;&lt;/td&gt;
&lt;td&gt;Preserve tool-confirmed facts, keep exact identifiers, inherit selected metadata such as tenant, source, and tags, and distinguish tool-confirmed facts from user-reported claims.&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;





&lt;h2&gt;
&lt;strong&gt;Tool-Aware Extraction with Metadata&lt;/strong&gt;&amp;nbsp;&lt;/h2&gt;

&lt;p&gt;Tool-aware extraction is especially useful in enterprise workflows because many agents do not rely only on user messages. They call tools, retrieve operational state, and produce tool results that may be more authoritative than the natural-language conversation.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;In the support notebook, a tool result can carry metadata such as tenant, source, and tags. The extraction policy can tell the memory layer to preserve tool-derived support facts, and selected metadata can be inherited into the extracted memory.&amp;nbsp;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;tool_thread = custom_memory.create_thread( 
    thread_id=f"tool_support_{RUN_ID}", 
    user_id=USER_ID, 
    agent_id=AGENT_ID, 
    memory_extraction_config=MemoryExtractionConfig( 
        memory_extraction_frequency=1, 
        enable_context_summary=False, 
        memory_extraction_custom_instructions=TOOL_AWARE_EXTRACTION_INSTRUCTIONS, 
        memory_extraction_inherit_message_metadata=["tenant", "source", "tags"], 
    ), 
) &lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This pattern lets the application preserve not only the memory text, but also where the remembered fact came from. Later, scoped retrieval can use both natural-language query text and metadata filters.&amp;nbsp;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;tool_filtered_results = await tool_thread.search_async( 
    "replacement shipment delayed backordered hinge escalate", 
    max_results=5, 
    exact_thread_match=True, 
    record_types=["memory", "fact", "preference", "guideline"], 
    metadata_filter={ 
        "tenant": "acme", 
        "source": "support-copilot", 
        "tags": ["support", "tool:order_status", "replacement"], 
    }, 
) &lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;/p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.oracle.com%2Fdevelopers%2Fwp-content%2Fuploads%2Fsites%2F129%2F2026%2F08%2FGroup-12.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.oracle.com%2Fdevelopers%2Fwp-content%2Fuploads%2Fsites%2F129%2F2026%2F08%2FGroup-12.png" alt="Diagram showing two controls on support memory. Tool results inherit selected tenant, source, and tag metadata before becoming tool-confirmed memories used in scoped search. Separately, a client-level default memory policy can be overridden for a thread, such as an escalation-only policy, then updated or cleared as workflow state changes." width="800" height="710"&gt;&lt;/a&gt;Tool-result metadata and scoped retrieval&lt;p&gt;&lt;/p&gt;





&lt;h2&gt;
&lt;strong&gt;Why Metadata Matters for Memory Governance&lt;/strong&gt;&amp;nbsp;&lt;/h2&gt;

&lt;p&gt;Memory retrieval should be relevant, but it also needs boundaries. In enterprise systems, an application may need to search only within a tenant, case, workflow, source system, or review state. Similarity ranking alone is not an authorization or governance model. Metadata filters complement, but do not replace, application authentication and authorization.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Metadata gives the application a way to constrain which memories are eligible before ranking. In this article's use case, metadata can distinguish support-copilot facts from raw conversation messages, tag memories that came from an order-status tool, or keep tenant-specific facts scoped to the right customer context.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;This is also why custom extraction and metadata inheritance belong together. Custom extraction shapes the content of the memory. Metadata inheritance shapes how that memory can be retrieved, filtered, reviewed, and governed later.&lt;/p&gt;





&lt;h2&gt;
&lt;strong&gt;Running the Companion Notebook&lt;/strong&gt;&amp;nbsp;&lt;/h2&gt;

&lt;p&gt;The companion notebook starts with a practical database setup path. The setup cells connect to an Oracle AI Database schema, verify a simple query, and verify table-creation permissions before the memory store is initialized.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;For a lightweight trial environment, developers can use &lt;a href="https://freesql.com/" rel="noreferrer noopener"&gt;FreeSQL&lt;/a&gt; to get an Oracle AI Database schema before moving to a managed development or production database environment.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;That table-permission check matters because Oracle AI Agent Memory manages database-backed storage objects. If a developer can connect but cannot create tables, the memory workflow will fail later during schema setup. Surfacing that issue early makes the notebook more practical.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;The notebook also sets the Developer Hub program identifier before creating the database connection pool. This follows the Developer Hub convention for technical assets that connect to Oracle AI Database and makes the notebook identifiable in database telemetry.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;For environments with a database-resident embedding model, the same pattern can use Oracle AI Database-backed embeddings and Oracle AI Vector Search. For the custom extraction article, the main focus remains memory formation: how raw conversations become durable, domain-specific memories with the right policy and metadata.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;For database-level retrieval background, see the &lt;a href="https://docs.oracle.com/en/database/oracle/oracle-database/26/vecse/overview-ai-vector-search.html" rel="noreferrer noopener"&gt;Oracle AI Vector Search documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What the Companion Notebook Demonstrates&amp;nbsp;&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Connect to Oracle AI Database and verify table permissions.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Set the Developer Hub program identifier before opening the database connection.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Configure Oracle AI Agent Memory for a support workflow.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Add a support conversation containing exact identifiers, support commitments, preferences, and details that should not become memory.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Run baseline extraction as a general-purpose reference point.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Apply support-specific custom extraction instructions.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Compare extracted memories before and after customization.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Override extraction instructions for one escalation thread.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Update and clear thread-level extraction instructions.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Extract tool-derived support facts and inherit selected metadata.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Search with metadata filters to retrieve scoped memory results.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Clean up memory records and database objects after the run.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;





&lt;h2&gt;
&lt;strong&gt;Security and Deployment Notes&lt;/strong&gt;&amp;nbsp;&lt;/h2&gt;

&lt;p&gt;Custom extraction instructions are not a substitute for application security controls. Oracle AI Agent Memory persists data in the database schema selected by the application owner, while the surrounding application remains responsible for authenticating users, enforcing access control, and passing the correct user, agent, and thread scope into memory operations.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Applications should avoid sending secrets or unnecessary sensitive information into memory ingestion flows unless that behavior is explicitly intended and reviewed. Extraction instructions can guide what the model should preserve or ignore, but they should be treated as policy guidance rather than a hard redaction guarantee.&amp;nbsp;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep credentials, payment details, and one-time verification codes out of memory ingestion when possible.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Use exact user, agent, thread, and tenant scope for retrieval.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Use metadata filters for workflow boundaries such as source system, review status, or tool tag.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Review custom extraction instructions with domain owners, not only with developers.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Use cleanup steps in notebooks and development schemas to avoid leaving test data behind.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;





&lt;h2&gt;
&lt;strong&gt;Conclusion&lt;/strong&gt;&amp;nbsp;&lt;/h2&gt;

&lt;p&gt;Custom memory extraction makes agent memory more useful by making it selective. Instead of treating every conversation as durable knowledge, developers can define what their application should preserve, which identifiers matter, what metadata should follow extracted memories, and when a specific thread needs a narrower policy.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;For customer-support agents, this means future interactions can draw on the right facts: order IDs, delivery issues, return requests, escalation commitments, stable preferences, and tool-confirmed statuses. The agent gets memory that is easier to retrieve, easier to scope, and better aligned with the workflow it serves.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;A practical way to start is with a narrow extraction policy, inspect the extracted memories, and then expand the policy and metadata taxonomy with domain owners.&lt;/p&gt;





&lt;h2&gt;
&lt;strong&gt;Frequently Asked Questions&lt;/strong&gt;&amp;nbsp;&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What is custom memory extraction?&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Custom memory extraction lets developers guide what Oracle AI Agent Memory should preserve as durable memory and what it should ignore when processing conversation messages.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why should an agent not remember everything?&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Full transcripts contain temporary wording, repeated details, speculation, and sometimes sensitive data. Durable memory should preserve reusable facts, preferences, commitments, and workflow state.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is the difference between baseline and custom extraction?&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Baseline extraction shows general-purpose memory formation. Custom extraction adds domain-specific instructions so the memory output reflects the application's workflow policy.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is a thread-level extraction override?&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;A thread-level override is a custom extraction policy applied to one thread. It can narrow or change the default client-level policy for a specific workflow such as an escalation.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How does tool metadata help?&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Tool metadata records where a memory came from and how it should be scoped. It can help retrieve only memories from a specific tenant, source system, or tool result.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does the notebook require FreeSQL?&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;No. FreeSQL is a lightweight trial setup path. The same memory APIs can run against another Oracle AI Database environment with the required credentials and permissions.&lt;/p&gt;





&lt;h2&gt;
&lt;strong&gt;Resources&lt;/strong&gt;&amp;nbsp;&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/oracle-devrel/oracle-ai-developer-hub/blob/main/notebooks/agent_memory/custom_memory_extraction_agent_memory.ipynb" rel="noreferrer noopener"&gt;Companion notebook&lt;/a&gt;&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://pypi.org/project/oracleagentmemory/26.6.0/" rel="noreferrer noopener"&gt;Oracle AI Agent Memory package&lt;/a&gt;&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.oracle.com/en/database/oracle/agent-memory/26.6/guide/" rel="noreferrer noopener"&gt;Oracle AI Agent Memory documentation&lt;/a&gt;&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://freesql.com/" rel="noreferrer noopener"&gt;FreeSQL&lt;/a&gt;&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.oracle.com/en/database/oracle/oracle-database/26/vecse/overview-ai-vector-search.html" rel="noreferrer noopener"&gt;Oracle AI Vector Search documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>database</category>
      <category>ai</category>
      <category>agents</category>
      <category>agentskills</category>
    </item>
    <item>
      <title>Hybrid Search for Oracle AI Agent Memory: Combining Semantic Recall with Exact Match</title>
      <dc:creator>Anya Summers</dc:creator>
      <pubDate>Tue, 11 Aug 2026 14:59:04 +0000</pubDate>
      <link>https://dev.to/oracledevs/hybrid-search-for-oracle-ai-agent-memory-combining-semantic-recall-with-exact-match-29aj</link>
      <guid>https://dev.to/oracledevs/hybrid-search-for-oracle-ai-agent-memory-combining-semantic-recall-with-exact-match-29aj</guid>
      <description>&lt;p&gt;&lt;strong&gt;Use&amp;nbsp;Oracle AI Agent Memory hybrid search when persistent AI agent memory needs both semantic similarity and exact text precision.&amp;nbsp;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Companion Notebook:&lt;/strong&gt; &lt;a href="https://github.com/oracle-devrel/oracle-ai-developer-hub/blob/main/notebooks/agent_memory/hybrid_search_agent_memory.ipynb" rel="noopener noreferrer"&gt;Hybrid Search for Oracle AI Agent Memory: Combining Semantic Recall with Exact Match&lt;/a&gt;&lt;/p&gt;





&lt;h2&gt;Key Takeaways&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.oracle.com/en/database/oracle/oracle-database/26/vecse/overview-ai-vector-search.html" rel="noreferrer noopener"&gt;Vector search&lt;/a&gt; is strong for semantic recall, but agent memory often contains exact strings that should remain first-class retrieval signals.&lt;/li&gt;



&lt;li&gt;Hybrid search combines semantic retrieval and exact text matching so agents can recall both the named item and the surrounding context.&lt;/li&gt;



&lt;li&gt;Scoped retrieval and hybrid search solve different parts of the problem: scope controls which memories are eligible to be searched, while hybrid search controls how those eligible memories are ranked.&lt;/li&gt;



&lt;li&gt;
&lt;a href="https://docs.oracle.com/en/database/oracle/agent-memory/26.4/agmea/get-started.html" rel="noreferrer noopener"&gt;Oracle AI Agent Memory&lt;/a&gt; uses `SearchStrategy.HYBRID` with `OracleDBEmbedder`, while `SearchIndexSyncMode` controls managed search-index refresh behavior.&lt;/li&gt;



&lt;li&gt;The companion notebook validates hybrid retrieval across five exact, semantic, and mixed-query scenarios using deliberately similar memories and confirms the expected memory at rank one in every scenario.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This guide shows how to build&amp;nbsp;hybrid retrieval for agent memory&amp;nbsp;with Oracle AI Agent Memory, combining semantic recall with exact text matching.&amp;nbsp;&lt;/p&gt;





&lt;h2&gt;
&lt;strong&gt;The retrieval problem hybrid search solves&lt;/strong&gt;&amp;nbsp;&lt;/h2&gt;

&lt;p&gt;A useful agent memory system&amp;nbsp;has to&amp;nbsp;do two things at once. It must remember by meaning when the user paraphrases, and it must stay exact when the user refers to a specific business object, system event, or operational handle. Many retrieval misses happen in the gap between those two needs: the query is semantically close to several memories, but only one memory&amp;nbsp;contains&amp;nbsp;the exact identifier that should anchor the answer.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;Hybrid search closes that gap. Instead of treating semantic recall and exact text matching as separate retrieval systems, it gives the memory layer a combined path for vector and keyword search. The agent can retrieve the surrounding context and preserve the literal token that makes the memory trustworthy.&amp;nbsp;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;That is the core value of agent memory hybrid search: long-term agent memory can stay flexible without losing the exact text that enterprise workflows depend on.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;This article starts with a simple retrieval problem: a user may ask about a business event in natural language, then later refer to the same event by an invoice number, issue ID, error code, customer alias, or filename. A useful memory system should handle both query styles.&lt;/p&gt;





&lt;h2&gt;
&lt;strong&gt;Setting up Oracle&amp;nbsp;AI&amp;nbsp;Agent Memory&lt;/strong&gt;&amp;nbsp;&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.oracle.com/en/database/oracle/agent-memory/26.4/agmea/" rel="noreferrer noopener"&gt;Oracle AI Agent Memory&lt;/a&gt;&amp;nbsp;provides a durable memory layer for AI agents. It stores scoped memories, retrieves relevant context for later turns, and helps applications separate what the agent is allowed to&amp;nbsp;remember&amp;nbsp;from how memories are ranked.&amp;nbsp;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Hybrid search expands that retrieval model so an agent can recall by semantic meaning and by exact text in the same workflow, while memory governance&amp;nbsp;remains&amp;nbsp;tied to scope and application policy.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;The setup has four main components:&amp;nbsp;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;`OracleAgentMemory`&amp;nbsp;is the durable memory layer. It stores memories, applies scope, and exposes search.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;`OracleDBEmbedder`&amp;nbsp;connects retrieval to Oracle-backed embedding&amp;nbsp;behavior&amp;nbsp;used by the memory store.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;`SearchStrategy.HYBRID`&amp;nbsp;tells the memory layer to combine semantic and exact-text retrieval signals.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;`SearchIndexSyncMode`&amp;nbsp;controls when the managed search index is refreshed after memories are written.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Start by installing the&amp;nbsp;oracleagentmemory&amp;nbsp;package in the notebook or application environment. The hybrid-search API is configured in application code; the database schema and index setup are handled through the Oracle-backed memory store.&amp;nbsp;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;This makes Oracle AI Database agent memory a practical pattern for applications that need durable memories, scoped retrieval, and database-managed search.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;

```bash 
pip install oracleagentmemory==26.6.0 
```

 &amp;nbsp;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The core imports for a hybrid-search configuration look like this:&amp;nbsp;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;

```python 
from oracleagentmemory.apis.searchscope import SearchScope 
from oracleagentmemory.core import ( 
    MemoryExtractionConfig, 
    OracleAgentMemory, 
    SchemaPolicy, 
    SearchIndexSyncMode, 
    SearchStrategy, 
) 
from oracleagentmemory.core.embedders import OracleDBEmbedder 
```

 &lt;/code&gt;&lt;/pre&gt;





&lt;h2&gt;
&lt;strong&gt;Why vector memory is not always enough&lt;/strong&gt;&amp;nbsp;&lt;/h2&gt;

&lt;p&gt;Agent memory is not only a semantic archive of&amp;nbsp;previous&amp;nbsp;conversations. In enterprise systems, memory records often include short strings that carry operational meaning.&amp;nbsp;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;A single identifier can point to the correct incident, invoice, customer, model, file, run, or database error. If retrieval treats that identifier like ordinary prose, the agent can return a memory that is semantically related but operationally wrong.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Vector search helps when users paraphrase. It is especially useful when a user asks about a topic without repeating the exact words stored in memory.&amp;nbsp;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;But vector similarity is&amp;nbsp;not the same as&amp;nbsp;exactness. A short token can be diluted by the rest of the sentence, especially when the surrounding context is broad or when several memories are about similar workflows.&lt;/p&gt;





&lt;h2&gt;
&lt;strong&gt;Where exact text matters&lt;/strong&gt;&amp;nbsp;&lt;/h2&gt;

&lt;p&gt;Exact text matters whenever a memory must point to a specific object rather than a general topic. Common examples include:&amp;nbsp;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Issue IDs and ticket references used in engineering and support workflows.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Invoice, order, contract, and purchase-order numbers used in business workflows.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Database, application, and integration error codes used in troubleshooting.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Customer aliases, account IDs, and tenant names used for scoped recall.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Product SKUs, model numbers, filenames, branch names, and run IDs used by operational agents.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These values are small, but they often carry more authority than nearby prose. A robust agent memory layer should preserve that literal signal while still supporting semantic lookup and exact-match retrieval.&lt;/p&gt;





&lt;h2&gt;
&lt;strong&gt;Types of&amp;nbsp;search&lt;/strong&gt;&amp;nbsp;&lt;/h2&gt;


&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Vector search&lt;/td&gt;
&lt;td&gt;Keyword search&amp;nbsp;&lt;/td&gt;
&lt;td&gt;Hybrid search&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Finds by meaning and paraphrase&amp;nbsp;&lt;/td&gt;
&lt;td&gt;Finds by exact text (literal identifiers)&amp;nbsp;&lt;/td&gt;
&lt;td&gt;Combines semantic&amp;nbsp;and exact-text signals in one ranked retrieval path&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Example query: “What blocked the renewal?”&amp;nbsp;&lt;/td&gt;
&lt;td&gt;Example query: “INV-48291”&amp;nbsp;&lt;/td&gt;
&lt;td&gt;Example queries: “INV-48291” and “What blocked the renewal?”&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;
Table&amp;nbsp;1:&amp;nbsp;Conceptual&amp;nbsp;search-mode comparison&amp;nbsp;


&lt;p&gt;The differences become clearer when you compare what each mode&amp;nbsp;optimizes&amp;nbsp;for.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;The following comparison explains the retrieval modes conceptually. The companion notebook executes the hybrid configuration and evaluates it across exact, semantic, and mixed queries; it is not a benchmark of three separately executed strategies.&amp;nbsp;&lt;/p&gt;


&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Mode&amp;nbsp;&lt;/td&gt;
&lt;td&gt;Best at&lt;/td&gt;
&lt;td&gt;Weak spot&lt;/td&gt;
&lt;td&gt;Agent memory fit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vector search&lt;/td&gt;
&lt;td&gt;Paraphrase, semantic similarity, concept recall&amp;nbsp;&lt;/td&gt;
&lt;td&gt;Short identifiers may be&amp;nbsp;low-signal&amp;nbsp;&lt;/td&gt;
&lt;td&gt;Good for natural-language recall&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Keyword search&lt;/td&gt;
&lt;td&gt;Literal identifiers, error codes, aliases, filenames&amp;nbsp;&lt;/td&gt;
&lt;td&gt;Brittle when wording changes&amp;nbsp;&lt;/td&gt;
&lt;td&gt;Good when exact strings matter&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hybrid search&lt;/td&gt;
&lt;td&gt;Natural language plus exact handles&amp;nbsp;&lt;/td&gt;
&lt;td&gt;Requires database embedding and managed index setup&amp;nbsp;&lt;/td&gt;
&lt;td&gt;Strong fit when both semantic and exact-text retrieval matter&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;
Table&amp;nbsp;2: Search mode comparison&amp;nbsp;


&lt;p&gt;Use hybrid search when:&amp;nbsp;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Users ask in natural language but refer to exact business objects.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Memories include identifiers, aliases, filenames, error codes, or transaction IDs.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Several memories are semantically similar, but one exact token should decide the result.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;The agent needs scoped retrieval across user, agent, tenant, or thread boundaries.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;





&lt;h2&gt;
&lt;strong&gt;How Oracle AI Agent Memory supports hybrid retrieval for agent memory&lt;/strong&gt;&amp;nbsp;&lt;/h2&gt;

&lt;p&gt;Oracle AI Agent Memory exposes hybrid retrieval as a memory-store configuration. The application supplies an Oracle-backed embedder, selects&amp;nbsp;`SearchStrategy.HYBRID`, and chooses an index synchronization mode. The result is one memory layer that can retrieve by meaning and exact text over the same stored memories, while still respecting user, agent, and thread scope.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;`OracleDBEmbedder`&amp;nbsp;is important because hybrid search relies on the database-managed embedding and index path.&amp;nbsp;`SearchStrategy.HYBRID`&amp;nbsp;selects the hybrid retrieval backend.&amp;nbsp;`SearchIndexSyncMode`&amp;nbsp;defines when new or updated memory content becomes searchable through the managed index. Together, these settings make retrieval&amp;nbsp;behavior&amp;nbsp;explicit in application code rather than hiding it behind an ad hoc query pipeline.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;The configuration below shows the&amp;nbsp;minimum&amp;nbsp;pattern: choose an Oracle-backed embedder, set&amp;nbsp;`SearchStrategy.HYBRID`, and choose how the search index should refresh:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;

```python 
db_embedder = OracleDBEmbedder( 
    connection=connection, 
    model=CONFIG["ORACLE_DB_EMBEDDING_MODEL"], 
    embedding_dimension=CONFIG["ORACLE_DB_EMBEDDING_DIMENSION"], 
) 
memory = OracleAgentMemory( 
    connection=connection, 
    embedder=db_embedder, 
    memory_extraction_config=MemoryExtractionConfig( 
        extract_memories=False 
    ), 
    schema_policy=SchemaPolicy.CREATE_IF_NECESSARY, 
    search_strategy=SearchStrategy.HYBRID, 
    search_index_sync=SearchIndexSyncMode.ON_COMMIT, 
    memory_store_id="hybrid_blog", 
) 
```

 &lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Memory extraction is disabled because the example inserts controlled durable memories directly and focuses specifically on retrieval&amp;nbsp;behavior.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;In a typical workflow, the agent writes durable memories with user, agent, or thread scope. Later, search requests use&amp;nbsp;`SearchScope`&amp;nbsp;to restrict eligible records before ranking happens. That separation matters: scope decides what the agent is allowed to remember, while hybrid search decides how eligible memories are ranked.&lt;/p&gt;





&lt;h2&gt;
&lt;strong&gt;Hybrid retrieval flow&lt;/strong&gt;&amp;nbsp;&lt;/h2&gt;

&lt;p&gt;In an agent workflow, hybrid search is most useful when it sits between memory scoping and context assembly:&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.oracle.com%2Fdevelopers%2Fwp-content%2Fuploads%2Fsites%2F129%2F2026%2F07%2FContainer-538x1024.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.oracle.com%2Fdevelopers%2Fwp-content%2Fuploads%2Fsites%2F129%2F2026%2F07%2FContainer-538x1024.png" alt="Flowchart titled &amp;quot;Scoped Hybrid Retrieval.&amp;quot; A user query passes through a SearchScope filter to select eligible durable memories. Those memories are searched using hybrid retrieval, combining semantic and exact matching. The ranked results are then assembled into the prompt context for the language model. Each stage is connected by downward arrows, showing a linear retrieval pipeline." width="538" height="1024"&gt;&lt;/a&gt;Figure&amp;nbsp;1.&amp;nbsp;Scoped hybrid retrieval flow&lt;p&gt;&lt;/p&gt;

&lt;p&gt;`SearchScope`&amp;nbsp;and hybrid search are complementary. Scope&amp;nbsp;determines&amp;nbsp;which records are eligible for retrieval; hybrid search ranks only those eligible records. In the notebook, each evaluation query uses the same generated finance-user and support-agent scope, and retrieval is restricted to durable memory records by passing&amp;nbsp;`record_types=["memory"]`.&amp;nbsp;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;This flow is the reason hybrid search fits agent memory well. The application first narrows the memory universe with scope. Hybrid retrieval then combines semantic and exact-text signals only over eligible records. The final output is not just a matching row; it is a ranked context package the agent can use in the next step.&amp;nbsp;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;

```python 
user_id = "finance_user_123" 
agent_id = "support_finance_agent" 
  
scope = SearchScope(user_id=user_id, agent_id=agent_id) 
  
memory.add_memory( 
    content=( 
        "Northstar Renewals has a renewal blocker: invoice INV-48291 "
        "failed reconciliation after ORA-27102 during month-end processing." 
    ), 
    user_id=user_id, 
    agent_id=agent_id, 
) 
 
async def search_memory(query, scope, max_results=5): 
    return await memory.search_async( 
        query=query, 
        scope=scope, 
        max_results=max_results, 
        record_types=["memory"], 
    ) 
 
exact_results = await search_memory("INV-48291", scope) 
 
semantic_results = await search_memory( 
    "What blocked the Northstar renewal?", 
    scope, 
) 
 ```

 &lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Both calls use the same `SearchScope`, hybrid strategy, and managed hybrid index. Only the query style changes.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;For example, a user might ask “INV-48291” or “What blocked the renewal?” over the same scoped memories.&amp;nbsp;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;A vector-only search may understand the renewal-blocker question but underweight the exact invoice or error code. A keyword-only search may find INV-48291 or ORA-27102 but miss a paraphrased question such as “What blocked the renewal?”&amp;nbsp;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Hybrid search gives both signals a chance to influence ranking, so the agent can retrieve the Northstar Renewals memory with the exact invoice ID, the ORA-27102 error code, and the natural-language explanation.&amp;nbsp;&lt;/p&gt;





&lt;h2&gt;
&lt;strong&gt;Testing hybrid retrieval with deliberately similar memories&lt;/strong&gt;&amp;nbsp;&lt;/h2&gt;

&lt;p&gt;The companion notebook does not test hybrid retrieval against a single obvious memory. It creates a deliberately similar set of&amp;nbsp;memories,&amp;nbsp;so the retrieval path&amp;nbsp;has to&amp;nbsp;choose between records that share overlapping business, invoice, customer, renewal, and error-code vocabulary.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;The notebook stores five durable memories:&amp;nbsp;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A target renewal-blocker memory for Northstar Renewals,&amp;nbsp;containing&amp;nbsp;invoice INV-48291, Oracle error ORA-27102, and the failed reconciliation that blocked the renewal.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;A&amp;nbsp;neighboring&amp;nbsp;invoice memory for Northstar Renewals,&amp;nbsp;containing&amp;nbsp;invoice INV-48290, which is marked as paid and does not require follow-up.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;A different customer renewal-delay memory for Milan Office Supplies, where the delay is caused by an approval workflow rather than reconciliation.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;A general ORA-27102 troubleshooting memory that explains the database error without tying it to the Northstar invoice.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;A Northstar customer-alias memory that maps Northstar Renewals to its enterprise account name.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This setup makes the demonstration more realistic. The expected result must outrank memories that are partially similar, such as another Northstar invoice, another renewal delay, or another ORA-27102-related record. That is the&amp;nbsp;behavior&amp;nbsp;an enterprise agent memory system needs: not just finding something&amp;nbsp;related but&amp;nbsp;retrieving the memory that preserves the right operational detail.&amp;nbsp;&lt;/p&gt;





&lt;h2&gt;
&lt;strong&gt;Evaluating exact, semantic, and mixed queries&lt;/strong&gt;&amp;nbsp;&lt;/h2&gt;

&lt;p&gt;The notebook then evaluates the same hybrid-search configuration across five query scenarios. Each query uses the same&amp;nbsp;`SearchScope`, the same&amp;nbsp;`SearchStrategy.HYBRID`&amp;nbsp;configuration, and the same managed hybrid index. Only the query wording changes.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;The five query categories are:&amp;nbsp;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Exact invoice identifier: a query for INV-48291.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Exact Oracle error code: a query for ORA-27102.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Semantic renewal-blocker question: a natural-language question about what blocked the Northstar renewal.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Semantic customer question: a natural-language question about the Milan customer renewal delay.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Mixed customer and invoice question: a question that combines customer context with invoice reconciliation.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The validation checks whether the expected memory appears as the top-ranked result.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;


&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Query type&amp;nbsp;&lt;/td&gt;
&lt;td&gt;Expected memory&amp;nbsp;&lt;/td&gt;
&lt;td&gt;Rank&lt;/td&gt;
&lt;td&gt;Validation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exact invoice ID&amp;nbsp;&lt;/td&gt;
&lt;td&gt;Renewal blocker&amp;nbsp;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;PASS&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exact error code&amp;nbsp;&lt;/td&gt;
&lt;td&gt;Error reference&amp;nbsp;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;PASS&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Semantic renewal question&amp;nbsp;&lt;/td&gt;
&lt;td&gt;Renewal blocker&amp;nbsp;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;PASS&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Semantic customer question&amp;nbsp;&lt;/td&gt;
&lt;td&gt;Milan renewal&amp;nbsp;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;PASS&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mixed customer and invoice question&amp;nbsp;&lt;/td&gt;
&lt;td&gt;Renewal blocker&amp;nbsp;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;PASS&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;
Table&amp;nbsp;3: Evaluation results table&amp;nbsp;


&lt;p&gt;This is a controlled functional evaluation, not a statistical benchmark comparing vector, keyword, and hybrid search. The purpose is to show that one hybrid-search configuration can retrieve the expected durable memory across exact, semantic, and mixed query styles.&lt;/p&gt;





&lt;h2&gt;
&lt;strong&gt;Reading the notebook results&lt;/strong&gt;&amp;nbsp;&lt;/h2&gt;

&lt;p&gt;The results show that exact identifiers remained useful retrieval signals. The invoice query returned the renewal-blocker memory, while the ORA-27102 query returned the general error-reference memory. This matters because both the target renewal memory and the general troubleshooting memory&amp;nbsp;contain&amp;nbsp;the same error code, but they serve different user intents.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;The semantic queries also returned the intended memories without requiring the user to repeat the exact stored wording. For example, the renewal-blocker question retrieved the Northstar renewal memory, while the Milan customer question retrieved the separate Milan renewal-delay memory.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;The mixed query is the most representative enterprise case. It combines business context with an implied operational object: the user asks which invoice failed reconciliation for Northstar. The expected result is the memory&amp;nbsp;containing&amp;nbsp;INV-48291, ORA-27102, and the failed reconciliation context.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;The notebook also displays distance values for the ranked results. In this result set, smaller distance values&amp;nbsp;indicate&amp;nbsp;stronger matches. However, distance should be treated as a retrieval signal for inspection, not as a universal accuracy score. The more important validation is whether the expected memory was returned at rank one for each query.&amp;nbsp;&lt;/p&gt;





&lt;h2&gt;How hybrid search improves agent memory retrieval&lt;/h2&gt;

&lt;p&gt;Hybrid search can improve retrieval behavior because it matches how enterprise memory is actually written. A memory record rarely contains only natural-language explanation or only an identifier. It usually contains both: a named handle plus the context that explains why the handle matters.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Higher precision for identifier-driven questions because exact strings can influence ranking.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Better recall for paraphrased questions because semantic search still finds related memory records.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;More useful context packages because the agent can retrieve both the handle and the surrounding explanation.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Lower risk of plausible but wrong recall when several memories are semantically similar.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;Developers can inspect the ranked memories, metadata, and distances to verify whether the expected operational context was retrieved.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is especially useful for support agents, finance assistants, operations copilots, developer agents, and workflow agents that must remember prior decisions, tool outputs, system errors, or customer-specific context.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;The practical benefit is not only better search quality; it is better agent&amp;nbsp;behavior.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Better retrieval can improve grounding, but the model still needs normal validation and application controls.&amp;nbsp;Hybrid search gives the retrieval layer a stronger chance of selecting the memory that a human operator would have recognized&amp;nbsp;immediately.&amp;nbsp;&lt;/p&gt;





&lt;h2&gt;
&lt;strong&gt;Production notes: schema and index setup&lt;/strong&gt;&amp;nbsp;&lt;/h2&gt;

&lt;p&gt;For production, treat hybrid search as part of the memory-store design, not as a last-minute query&amp;nbsp;option. The schema should be owned by a dedicated application user with the right privileges, tablespace quota, and deployment controls. Memory scope should also match the application boundary, such as user, agent, tenant, or thread.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;`SchemaPolicy.CREATE_IF_NECESSARY` is useful during first-time setup or when upgrading a schema so it can support hybrid search. For a large existing memory store, the first hybrid index build may take time because Oracle needs to prepare the managed search structures over stored memory content. Treat that step as a planned migration rather than a normal application startup task.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;After the schema and index are ready, production applications may prefer `SchemaPolicy.REQUIRE_EXISTING`. That lets startup&amp;nbsp;validate&amp;nbsp;the expected schema instead of creating or&amp;nbsp;modifying&amp;nbsp;database objects.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;`SearchIndexSyncMode` is a freshness and operations&amp;nbsp;tradeoff:&amp;nbsp;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;`ON_COMMIT` is best for notebooks, demos, and interactive applications because newly committed memories become searchable&amp;nbsp;immediately.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;&amp;nbsp;`MANUAL` is useful for bulk loads, backfills, and migrations where teams want to ingest many records first and refresh the index on a controlled schedule.&amp;nbsp;&lt;/li&gt;



&lt;li&gt;`AUTO` delegates background maintenance to Oracle-managed&amp;nbsp;behavior, which can fit production workloads where some freshness lag is acceptable. `AUTO` is supported for `SearchStrategy.HYBRID`; keyword-only search does not support `AUTO`.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The database-resident embedding model is also part of the production contract. The same model and embedding dimension should be used by `OracleDBEmbedder` and the managed hybrid index so query embeddings and indexed memory vectors&amp;nbsp;remain&amp;nbsp;compatible.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Before publishing an agent that depends on durable memory, test the retrieval path with representative records. Include exact-identifier queries, semantic queries, mixed queries, scoped retrieval checks, and index freshness checks after writes. If the application has many similar records, include distractor memories in the test set so the expected result&amp;nbsp;has to&amp;nbsp;outrank nearby alternatives.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Teams can also pair hybrid retrieval with custom memory extraction when they need more predictable stored facts. Extraction controls what gets remembered; scope controls which records are eligible; hybrid search controls how eligible memories are ranked.&amp;nbsp;&lt;/p&gt;





&lt;h2&gt;
&lt;strong&gt;Conclusion&lt;/strong&gt;&amp;nbsp;&amp;nbsp;&lt;/h2&gt;

&lt;p&gt;AI agent memory needs more than semantic similarity. Vector search helps agents remember by meaning, but enterprise workflows often depend on exact strings such as invoice numbers, error codes, customer aliases, file names, and transaction IDs. Keyword search helps with those strings, but it can be brittle when users ask in natural language.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Hybrid search gives the memory layer a practical middle ground. It preserves literal identifiers while still supporting semantic recall, so an agent can retrieve both the named object and the context that explains why it matters.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;In the companion notebook, one `SearchStrategy.HYBRID` configuration retrieves the expected durable memory across exact, semantic, and mixed query styles. The deliberately similar memory set makes the demo more realistic: the target memory&amp;nbsp;has to&amp;nbsp;outrank records that overlap on customer, invoice, renewal, or error-code vocabulary.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;For Oracle AI Agent Memory, `SearchStrategy.HYBRID`, `OracleDBEmbedder`, `SearchScope`, and `SearchIndexSyncMode`&amp;nbsp;provide the core application controls for this pattern. Together, they help developers build memory systems that are scoped, durable, searchable by meaning, and precise when exact text matters.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Run the companion notebook to configure Oracle AI Agent Memory hybrid search, load the deliberately similar memory set, and inspect the ranked results for exact, semantic, and mixed queries.&amp;nbsp;&lt;/p&gt;





&lt;h2&gt;
&lt;strong&gt;Frequently Asked Questions&lt;/strong&gt;&amp;nbsp;&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What is Oracle AI Agent Memory?&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Oracle AI Agent Memory is a durable memory layer for AI agents. It lets applications store memories, retrieve relevant context later, and use scope controls such as user, agent, and thread scope so the agent searches only eligible memories before ranking results.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;strong&gt;Why use hybrid search for agent memory&lt;/strong&gt;?&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Hybrid search is useful for agent memory because many memories&amp;nbsp;contain&amp;nbsp;both meaning and exact text. Vector search helps with paraphrased questions, while keyword search helps with identifiers such as issue IDs, SKUs, invoice numbers, aliases, and error codes. Hybrid search combines both signals so the agent can retrieve the right memory and the surrounding context.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;strong&gt;Is vector search still useful for agent memory?&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Yes. Vector search&amp;nbsp;remains&amp;nbsp;essential for paraphrase, concept recall, and long natural-language memories. Hybrid search adds exact-text strength instead of replacing vector search.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I build hybrid retrieval for agent memory?&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Configure&amp;nbsp;OracleAgentMemory with `OracleDBEmbedder`, set&amp;nbsp;`SearchStrategy.HYBRID`, choose&amp;nbsp;`SearchIndexSyncMode`, store scoped memories, then test exact, semantic, and mixed queries.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When should I use keyword-only search?&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Use keyword-only search when retrieval is&amp;nbsp;almost entirely&amp;nbsp;literal text matching and embeddings are unnecessary. Many agent memory workloads need both exact strings and semantic context, which makes hybrid search a better default.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is the difference between scoped retrieval and hybrid search?&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Scoped retrieval controls which memories are eligible to be searched. Hybrid search controls how those eligible memories are ranked using both semantic and exact-text signals.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;strong&gt;Why does hybrid search require&amp;nbsp;`OracleDBEmbedder`?&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;The hybrid path uses Oracle-managed indexing and database-side embedding metadata.&amp;nbsp;`OracleDBEmbedder`&amp;nbsp;keeps the application embedder aligned with the database-backed retrieval path.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;strong&gt;Which&amp;nbsp;`SearchIndexSyncMode`&amp;nbsp;should I start with?&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;For notebooks and interactive demos,&amp;nbsp;`ON_COMMIT`&amp;nbsp;is the simplest starting point. For bulk ingestion,&amp;nbsp;`MANUAL`&amp;nbsp;gives more control.&amp;nbsp;`AUTO`&amp;nbsp;is useful when Oracle-managed hybrid index maintenance fits the workload.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;strong&gt;What should I test before publishing an agent memory workflow?&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Test exact-identifier queries, semantic queries, scoped retrieval, index freshness after writes, and&amp;nbsp;behavior&amp;nbsp;when multiple similar memories exist. The strongest demos show both exact and natural-language retrieval over the same records.&amp;nbsp;&lt;/p&gt;





&lt;h2&gt;
&lt;strong&gt;Resources&amp;nbsp;&lt;/strong&gt;&amp;nbsp;&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.oracle.com/en/database/oracle/agent-memory/26.4/" rel="noreferrer noopener"&gt;Oracle AI Agent Memory documentation&lt;/a&gt;&amp;nbsp;&lt;/li&gt;



&lt;li&gt;&lt;a href="https://docs.oracle.com/en/database/oracle/oracle-database/26/" rel="noopener noreferrer"&gt;Oracle Database 26ai documentation&lt;/a&gt;&lt;/li&gt;



&lt;li&gt;
&lt;a href="https://docs.oracle.com/en/database/oracle/agent-memory/26.4/agmea/store-search.html" rel="noreferrer noopener"&gt;Store and Search Memory&amp;nbsp;documentation&lt;/a&gt;&amp;nbsp;&lt;/li&gt;



&lt;li&gt;
&lt;a href="https://pypi.org/project/oracleagentmemory/" rel="noreferrer noopener"&gt;oracleagentmemory on PyPI&lt;/a&gt;&amp;nbsp;&lt;/li&gt;



&lt;li&gt;
&lt;a href="https://docs.oracle.com/en/database/oracle/oracle-database/26/vecse/index.html" rel="noreferrer noopener"&gt;Oracle AI Vector Search User’s Guide&lt;/a&gt;&amp;nbsp;&lt;/li&gt;



&lt;li&gt;&lt;a href="https://blogs.oracle.com/developers/whats-new-in-oracle-ai-agent-memory-custom-extraction-hybrid-search-and-more-control" rel="noopener noreferrer"&gt;What’s New in Oracle AI Agent Memory: Custom Extraction, Hybrid Search, and More Control&lt;/a&gt;&lt;/li&gt;



&lt;li&gt;&lt;a href="https://github.com/oracle-devrel/oracle-ai-developer-hub/blob/main/notebooks/agent_memory/hybrid_search_agent_memory.ipynb" rel="noopener noreferrer"&gt;End-to-end support notebook&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;



</description>
      <category>agentskills</category>
      <category>agents</category>
      <category>oracle</category>
      <category>database</category>
    </item>
    <item>
      <title>Building Scalable Multi-Language Applications with Oracle Globally Distributed Database Using OCI-Based Drivers</title>
      <dc:creator>Anya Summers</dc:creator>
      <pubDate>Tue, 11 Aug 2026 14:30:21 +0000</pubDate>
      <link>https://dev.to/oracledevs/building-scalable-multi-language-applications-with-oracle-globally-distributed-database-using-2je7</link>
      <guid>https://dev.to/oracledevs/building-scalable-multi-language-applications-with-oracle-globally-distributed-database-using-2je7</guid>
      <description>&lt;p&gt;Modern enterprise applications are rarely built using a single programming language. A cloud-native application might use Python for AI and analytics, Node.js for REST APIs and C for latency-sensitive services. Despite the diversity of languages, the database-access goals are familiar: efficient connection pooling, intelligent routing, high availability, and predictable scalability.&lt;/p&gt;

&lt;p&gt;Oracle Globally Distributed Database (GDD) enables applications to scale horizontally by distributing data across shards while presenting a single logical database to the application. To make these capabilities available consistently across languages, Oracle provides OCI-based (Oracle Call Interface) drivers that expose the same distributed database features through language-native APIs.&lt;/p&gt;

&lt;p&gt;In this article, we'll explore how Python, Node.js and C applications use the same OCI foundation to build scalable, shard-aware applications.&lt;/p&gt;





&lt;h2&gt;Key Takeaways&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Python and Node.js in Thick mode and C through OCI(Oracle Call Interface) give developers a consistent core workflow for Oracle Globally Distributed Database: create or reuse sessions, supply shard keys when needed, and release connections predictably. The APIs and feature coverage remain language-specific.&lt;/li&gt;



&lt;li&gt;Shard-aware connection routing is consistent across languages. Applications acquire connections using sharding keys (and optionally super sharding keys), allowing Oracle Globally Distributed Database to automatically route requests to the appropriate shard.&lt;/li&gt;



&lt;li&gt;Thoughtful pool sizing and high-availability configuration are essential in multi-shard systems: they support session reuse, help avoid uneven connection distribution, and can improve recovery behavior when the required services and client configuration are in place.&lt;/li&gt;
&lt;/ul&gt;





&lt;p&gt;&lt;/p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.oracle.com%2Fdevelopers%2Fwp-content%2Fuploads%2Fsites%2F129%2F2026%2F07%2FOracle-GDD-OCI-Based-Drivers-Architecture-v4-1024x576.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblogs.oracle.com%2Fdevelopers%2Fwp-content%2Fuploads%2Fsites%2F129%2F2026%2F07%2FOracle-GDD-OCI-Based-Drivers-Architecture-v4-1024x576.png" alt="Flowchart showing database sharding request routing. Python Thick, Node.js Thick, and C/OCI clients feed into drivers and a connection pool. A sharding key and super sharding key determine routing, followed by direct routing or connection checkout to the data-owning shard. The request executes on that shard, then the connection is released." width="800" height="450"&gt;&lt;/a&gt;&lt;em&gt;Use Python, Node.js, and C for shard-aware connection pooling and resilient database access.&lt;/em&gt;&lt;p&gt;&lt;/p&gt;





&lt;h2&gt;A Common Foundation&lt;/h2&gt;

&lt;p&gt;One of the strengths of Oracle's language drivers is that they share a common implementation stack. Although the programming APIs differ, they ultimately leverage the same Oracle Call Interface (OCI) capabilities for connection management, sharding, and high availability. In Thick mode, the Python and Node.js drivers use Oracle Client libraries and Oracle Call Interface (OCI). C applications call OCI directly. &lt;/p&gt;

&lt;p&gt;This means that developers can choose the programming language best suited to their workload while relying on consistent database behavior.&lt;/p&gt;

&lt;h4&gt;Why Thick Mode?&lt;/h4&gt;

&lt;p&gt;Thick mode is the Python and Node.js deployment mode that uses Oracle Client libraries and the Oracle Call Interface (OCI). It gives those applications access to the OCI-based connection behavior that C applications use directly. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;High-performance session pooling enables session reuse, reducing connection-creation overhead under load and helping applications manage demand across shards.&lt;/li&gt;



&lt;li&gt;Sharding and super sharding keys let the client request a connection for the data-owning shard, without embedding physical shard locations in application logic.&lt;/li&gt;



&lt;li&gt;Oracle Net and the native-client or provider capabilities supply the connection behavior needed for enterprise deployments, including the routing information used to establish the right connection.&lt;/li&gt;



&lt;li&gt;High availability is configured separately from driver selection. FAN, Application Continuity or Transparent Application Continuity (TAC), and Transparent Application Failover (TAF) can improve failure response and continuity during planned or unplanned events, but their availability and defaults depend on the driver or provider type, client version, database service, and pool or connection configuration.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Initializing Oracle Client enables Thick mode for Python and Node.js only. It does not, by itself, enable high-availability behavior. The same principle applies across C/OCI: configure and test the relevant services, client settings, and pool behavior for the driver in use.&lt;/em&gt;&lt;/p&gt;





&lt;h2&gt;Creating a Scalable Session Pool&lt;/h2&gt;

&lt;p&gt;Connection pooling is the first step toward scalable access. Instead of creating a new database connection for every request, applications reuse existing sessions, lowering latency and improving throughput. In a multi-shard system, pool configuration also matters because demand may not be evenly distributed across shards. Monitor pool utilization and shard-level workload patterns, then tune capacity for the traffic you actually observe.&lt;/p&gt;

&lt;p&gt;The pool sizes below are illustrative only, not recommended defaults. Size pools using expected concurrency, request duration, database capacity, shard distribution, and service-level objectives.&lt;/p&gt;

&lt;h5&gt;Python&lt;/h5&gt;

&lt;pre&gt;&lt;code&gt;import oracledb

oracledb.init_oracle_client()

pool = oracledb.create_pool(
    user=user,
    password=password,
    dsn=connect_string,
    min=5,
    max=50,
    increment=5,
    getmode=oracledb.POOL_GETMODE_WAIT
)&lt;/code&gt;&lt;/pre&gt;

&lt;h5&gt;Node.js&lt;/h5&gt;

&lt;pre&gt;&lt;code&gt;const oracledb = require('oracledb');

oracledb.initOracleClient();

const pool = await oracledb.createPool({
    user,
    password,
    connectString,
    poolMin: 5,
    poolMax: 50,
    poolIncrement: 5
});&lt;/code&gt;&lt;/pre&gt;

&lt;h5&gt;C &lt;/h5&gt;

&lt;pre&gt;&lt;code&gt;OCIEnvCreate(...);

OCIHandleAlloc(...);

OCISessionPoolCreate(
    envhp,
    errhp,
    poolhp,
    &amp;amp;poolName,
    &amp;amp;poolNameLen,
    connectString,
    strlen(connectString),
    minSessions,
    maxSessions,
    increment,
    username,
    strlen(username),
    password,
    strlen(password),
    OCI_SPC_HOMOGENEOUS
);&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;Acquiring Shard-Aware Connections&lt;/h3&gt;

&lt;p&gt;The key benefit of shard-aware access is not that the application learns where shards live. It is that the client can direct work to the shard that owns the requested data. Supplying the sharding key and, where the data model uses one, the super sharding key helps avoid unnecessary data movement and keeps physical topology out of business logic. The following snippets are abbreviated examples. They omit credentials, error handling, variable declarations, pool shutdown, and full OCI environment and handle management. Production code should add those concerns and should release every acquired connection&lt;/p&gt;

&lt;h5&gt;Python&lt;/h5&gt;

&lt;pre&gt;&lt;code&gt;# The context manager releases the connection to the pool.
with pool.acquire(
    supershardingkey=[region],
    shardingkey=[customer_id]
) as connection:
    with connection.cursor() as cursor:
        cursor.execute(sql)&lt;/code&gt;&lt;/pre&gt;

&lt;h5&gt;Node.js&lt;/h5&gt;

&lt;pre&gt;&lt;code&gt;let connection;
try {
  connection = await pool.getConnection({
    superShardingKey: [region],
    shardingKey: [customerId]
  });
  await connection.execute(sql);
} finally {
  if (connection) await connection.close(); // Return it to the pool
}&lt;/code&gt;&lt;/pre&gt;

&lt;h5&gt;C&lt;/h5&gt;

&lt;pre&gt;&lt;code&gt;/* Create an authInfo handle, allocate key descriptors, add values,
   attach both keys, then request the pooled session.
   Error checks are omitted here. */
OCIAuthInfo *authInfo = NULL;
OCIShardingKey *shardKey = NULL, *superShardKey = NULL;

OCIHandleAlloc(envhp, (dvoid **)&amp;amp;authInfo,
               OCI_HTYPE_AUTHINFO, 0, NULL);

OCIDescriptorAlloc(envhp, (dvoid **)&amp;amp;shardKey,
                   OCI_DTYPE_SHARDING_KEY, 0, NULL);
OCIDescriptorAlloc(envhp, (dvoid **)&amp;amp;superShardKey,
                   OCI_DTYPE_SHARDING_KEY, 0, NULL);

OCIShardingKeyColumnAdd(shardKey, errhp, &amp;amp;customerId,
                         sizeof(customerId), SQLT_INT, OCI_DEFAULT);
OCIShardingKeyColumnAdd(superShardKey, errhp, region,
                         strlen((char *)region), SQLT_CHR, OCI_DEFAULT);

OCIAttrSet(authInfo, OCI_HTYPE_AUTHINFO, shardKey, sizeof(shardKey),
           OCI_ATTR_SHARDING_KEY, errhp);
OCIAttrSet(authInfo, OCI_HTYPE_AUTHINFO, superShardKey, sizeof(superShardKey),
           OCI_ATTR_SUPER_SHARDING_KEY, errhp);

OCISessionGet(envhp, errhp, &amp;amp;svcHandle, authInfo,
              poolName, poolNameLen, NULL, 0, NULL, NULL, NULL,
              OCI_SESSGET_SPOOL);

/* Execute work on the routed shard, then release the session. */
OCISessionRelease(svcHandle, errhp, NULL, 0, OCI_DEFAULT);
OCIDescriptorFree(shardKey, OCI_DTYPE_SHARDING_KEY);
OCIDescriptorFree(superShardKey, OCI_DTYPE_SHARDING_KEY);
OCIHandleFree(authInfo, OCI_HTYPE_AUTHINFO);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Although the APIs differ, each performs the same sequence:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Construct the sharding key.&lt;/li&gt;



&lt;li&gt;Request a pooled connection.&lt;/li&gt;



&lt;li&gt;Allow OCI to route the request to the correct shard.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;Comparing the Drivers&lt;/h3&gt;

&lt;p&gt;The table helps an architect choose the API that matches the application language while preserving the same operating model. &lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Capability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Python (Thick)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Node.js (Thick)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;C&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Client / driver&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Oracle Client&lt;/td&gt;
&lt;td&gt;Oracle Client&lt;/td&gt;
&lt;td&gt;Direct OCI calls&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Session pool&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;create_pool()&lt;/td&gt;
&lt;td&gt;createPool()&lt;/td&gt;
&lt;td&gt;OCISessionPoolCreate()&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Shard-aware checkout&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;pool.acquire()&lt;/td&gt;
&lt;td&gt;pool.getConnection()&lt;/td&gt;
&lt;td&gt;OCISessionGet()&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Sharding / super sharding keys&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Language-native parameters&lt;/td&gt;
&lt;td&gt;Language-native parameters&lt;/td&gt;
&lt;td&gt;OCIShardingKey + OCIAttrSet()&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Connection release&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Context manager or close()&lt;/td&gt;
&lt;td&gt;connection.close()&lt;/td&gt;
&lt;td&gt;OCISessionRelease()&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The APIs are intentionally language-specific, while the core pooling and shard-aware routing workflow is consistent. Individual feature availability varies by driver mode, client version, database service, and configuration.&lt;/p&gt;





&lt;h2&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Oracle's OCI-based drivers provide a unified foundation for building scalable applications across multiple programming languages. Whether applications are written in Python, Node.js or C, developers have access to the same core capabilities—session pooling, shard-aware routing, high availability, and efficient native connectivity—through familiar language APIs.&lt;/p&gt;

&lt;p&gt;The result is a more consistent deployment and operating model across the application stack, with work directed to the data-owning shard and with less duplicated connection-management knowledge between language teams.&lt;/p&gt;





&lt;h2&gt;Frequently Asked Questions&lt;/h2&gt;

&lt;h4&gt;Why should I use Thick mode instead of Thin mode?&lt;/h4&gt;

&lt;p&gt;Thin mode is suitable for many Oracle Database applications, but Thick mode uses Oracle Client libraries and the Oracle Call Interface (OCI). For Oracle Globally Distributed Database, it gives Python and Node.js applications access to OCI-based shard-aware connections and advanced connection management. Features such as Fast Application Notification (FAN), Application Continuity, Transparent Application Continuity (TAC), and Transparent Application Failover (TAF) require support from the relevant driver mode and version, database services, and client or pool configuration. Initializing Oracle Client enables Thick mode for Python and Node.js; it does not by itself enable high-availability behavior.&lt;/p&gt;

&lt;h4&gt;Why do I need sharding keys?&lt;/h4&gt;

&lt;p&gt;Sharding keys allow Oracle Globally Distributed Database to determine which shard contains the requested data. By supplying sharding keys when acquiring connections, applications are automatically routed to the appropriate shard, minimizing cross-shard communication and improving performance.&lt;/p&gt;

&lt;h4&gt;Do I need to know which shard my data resides on?&lt;/h4&gt;

&lt;p&gt;No. Applications simply provide the appropriate sharding key. Oracle Globally Distributed Database handles the routing transparently, allowing developers to focus on application logic rather than shard location.&lt;/p&gt;

&lt;h4&gt;Do all drivers have the same programming model and capabilities?&lt;/h4&gt;

&lt;p&gt;They share a consistent core workflow: configure a client and pool, request a connection with routing information when needed, perform work, and release the connection. The APIs are intentionally language-native, and availability of individual features can vary by driver mode, client version, database service, and configuration.&lt;/p&gt;





&lt;h2&gt;References&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.oracle.com/en/database/oracle/oracle-database/26/lnoci/oci-interface-for-using-shards.html" rel="noopener noreferrer"&gt;Oracle Call Interface documentation&lt;/a&gt;&lt;/li&gt;



&lt;li&gt;&lt;a href="https://python-oracledb.readthedocs.io/en/latest/user_guide/connection_handling.html" rel="noopener noreferrer"&gt;Python Oracle DB&lt;/a&gt;&lt;/li&gt;



&lt;li&gt;&lt;a href="https://node-oracledb.readthedocs.io/en/latest/user_guide/connection_handling.html#examples-to-connect-to-a-globally-distributed-database-based-on-the-sharding-key-type" rel="noopener noreferrer"&gt;Node.js Oracle DB&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>oracle</category>
      <category>multilanguage</category>
      <category>applications</category>
    </item>
    <item>
      <title>End-to-End Agentic AI Observability: Tracing from Agents INTO the Oracle AI Database</title>
      <dc:creator>Anya Summers</dc:creator>
      <pubDate>Thu, 30 Jul 2026 14:42:59 +0000</pubDate>
      <link>https://dev.to/oracledevs/end-to-end-agentic-ai-observability-tracing-from-agents-into-the-oracle-ai-database-1m7i</link>
      <guid>https://dev.to/oracledevs/end-to-end-agentic-ai-observability-tracing-from-agents-into-the-oracle-ai-database-1m7i</guid>
      <description>&lt;p&gt;&lt;strong&gt;Build one continuous trace across a Spring Boot agent workload, Oracle JDBC, and Oracle AI Database server-side execution&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;All source code, configuration, scripts, and supporting docs for the demo are available in the&amp;nbsp;&lt;a href="https://github.com/oracle-devrel/oracle-ai-for-sustainable-dev/tree/main/observability" rel="noopener noreferrer"&gt;observability folder on GitHub&lt;/a&gt;.&lt;/p&gt;





&lt;h2&gt;Key Takeaways&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Oracle AI Database lets the trace continue into database server-side execution instead of stopping at the database edge.&lt;/li&gt;



&lt;li&gt;Spring Boot and Micrometer create the application trace context, while Oracle JDBC adds database client spans.&lt;/li&gt;



&lt;li&gt;
&lt;code&gt;DBMS_OBSERVABILITY&lt;/code&gt;&amp;nbsp;exports Oracle AI Database server-side spans to OpenTelemetry-compatible observability tools; this demo uses Jaeger.&lt;/li&gt;



&lt;li&gt;Agent metadata,&amp;nbsp;&lt;code&gt;MODULE&lt;/code&gt;,&amp;nbsp;&lt;code&gt;ACTION&lt;/code&gt;, and&amp;nbsp;&lt;code&gt;CLIENT_IDENTIFIER&lt;/code&gt;&amp;nbsp;create a foundation for security and audit correlation.&lt;/li&gt;
&lt;/ul&gt;





&lt;p&gt;This walkthrough answers a practical question for agentic applications: can you see one trace that starts with an AI agent request, crosses the Spring Boot and JDBC layers, and continues inside Oracle AI Database? Yes. With Spring Boot, Micrometer, Oracle JDBC observability providers, and&amp;nbsp;&lt;code&gt;DBMS_OBSERVABILITY&lt;/code&gt;, the same request can appear in any OpenTelemetry-compatible observability backend as application spans, JDBC spans, and Oracle AI Database server-side spans. This walkthrough uses Jaeger because it is simple to run and easy to inspect, but the same signal can be viewed in any OpenTelemetry-compliant observability tool.&lt;/p&gt;

&lt;p&gt;That database-internal span is the important part. Typical database observability stops at the database edge: the application trace shows that JDBC waited on the database, then the user must switch tools and manually investigate inside the database. Oracle AI Database keeps the trace continuous. Developers, DevOps teams, and security reviewers can see how long the database actually took, correlate it to database-side evidence, and use the same trace for performance profiling, troubleshooting, and security context.&lt;/p&gt;

&lt;p&gt;Video walkthrough and summary:&amp;nbsp;&lt;a href="https://www.youtube.com/watch?v=Frus_Bw8Qqw" rel="noopener noreferrer"&gt;Tracing from Agents into the Oracle AI Database&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvm3ymnztuswndvax704h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvm3ymnztuswndvax704h.png" alt=" " width="800" height="415"&gt;&lt;/a&gt;&lt;/p&gt;
Featured cover diagram: one OpenTelemetry trace from an AI agent request into Oracle AI Database server-side execution.








&lt;h2&gt;What We Are Building&lt;/h2&gt;

&lt;p&gt;The demo app lives in&amp;nbsp;&lt;code&gt;observability/springboot-oracle-db-otel-demo&lt;/code&gt;. It is a Spring Boot app with two main endpoints:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;Endpoint&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GET /trace/roundtrip&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Simple app-to-database trace showing Spring Boot, Micrometer, JDBC, and database server-side spans.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GET /trace/agent-task/view&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Browser-friendly agentic demo that returns the trace id, agent id, database session metadata, database security context, SQL ID, SQL text, bind values, SQL Monitor preview,&amp;nbsp;&lt;code&gt;DBMS_XPLAN&lt;/code&gt;, and an embedded Jaeger trace on one page. Jaeger is the demo viewer; the telemetry is OpenTelemetry-compatible.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The intended trace shape is:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Browser or curl
  -&amp;gt; Spring Boot HTTP span
  -&amp;gt; Micrometer observation
  -&amp;gt; Oracle JDBC provider spans
  -&amp;gt; Oracle AI Database DB Server span
  -&amp;gt; HTTPS OTLP endpoint
  -&amp;gt; OpenTelemetry-compatible observability backend (Jaeger in this demo)&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuf7rvjiiehrg64ok1sqs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuf7rvjiiehrg64ok1sqs.png" alt=" " width="800" height="417"&gt;&lt;/a&gt;&lt;/p&gt;
Application and JDBC spans flow from the Java process, while database server-side spans flow from Oracle AI Database; both meet in the same OpenTelemetry collector.




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fivvegtwi4izb4tp55bbm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fivvegtwi4izb4tp55bbm.png" alt=" " width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;
The selected database server-side span shows database timing and database-side identity tags in the same trace as the application request.




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkpwd16bf4mchw94hvgr1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkpwd16bf4mchw94hvgr1.png" alt=" " width="800" height="747"&gt;&lt;/a&gt;&lt;/p&gt;
The demo page keeps the agent task, database session context, database security context, SQL diagnostics, and trace bridge together.








&lt;h2&gt;How Do You Set Up and Run the Example?&lt;/h2&gt;

&lt;p&gt;The demo can run on any Linux environment or VM with enough memory for Oracle Database Free, Jaeger, an HTTPS proxy, and the Spring Boot app. You can also use an Autonomous Database in Oracle Cloud instead of a local database; the important requirements are JDBC connectivity and a database server-side exporter that can reach an HTTPS OTLP endpoint.&lt;/p&gt;

&lt;p&gt;A clean OCI Linux VM verification with Oracle Database Free 23.26.2.0 produced the expected Spring Boot, Oracle JDBC, and&amp;nbsp;&lt;code&gt;oracle-db&lt;/code&gt;&amp;nbsp;server-side spans without setting hidden KSTRC instance parameters. The normal setup below uses documented&amp;nbsp;&lt;code&gt;DBMS_OBSERVABILITY&lt;/code&gt;&amp;nbsp;configuration, a reachable HTTPS OTLP endpoint, network ACLs, and the database&amp;nbsp;&lt;code&gt;disttrc&lt;/code&gt;&amp;nbsp;trust wallet.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install Java 25, Maven, Git, Podman, curl, jq, and OpenSSL.&lt;/li&gt;



&lt;li&gt;Run Oracle Database Free on Linux, or provision an Autonomous Database in Oracle Cloud.&lt;/li&gt;



&lt;li&gt;Clone the source from&amp;nbsp;&lt;code&gt;github.com/oracle-devrel/oracle-ai-for-sustainable-dev&lt;/code&gt;&amp;nbsp;and use the&amp;nbsp;&lt;code&gt;observability&lt;/code&gt;&amp;nbsp;folder.&lt;/li&gt;



&lt;li&gt;Start Jaeger with OTLP HTTP enabled. In the sample setup, Jaeger receives app and JDBC spans on&amp;nbsp;&lt;code&gt;http://127.0.0.1:4318/v1/traces&lt;/code&gt;.&lt;/li&gt;



&lt;li&gt;Put a small HTTPS proxy in front of Jaeger's OTLP endpoint for database server-side export. The sample uses NGINX as&amp;nbsp;&lt;code&gt;otel-tls-proxy&lt;/code&gt;, forwarding&amp;nbsp;&lt;code&gt;https://otel-tls-proxy:4318/v1/traces&lt;/code&gt;&amp;nbsp;to Jaeger's&amp;nbsp;&lt;code&gt;http://oracle-db-otel-jaeger:4318/v1/traces&lt;/code&gt;.&lt;/li&gt;



&lt;li&gt;Configure&amp;nbsp;&lt;code&gt;DBMS_OBSERVABILITY&lt;/code&gt;, network ACLs, and the database wallet/trust path so Oracle AI Database can push server-side spans to the HTTPS OTLP endpoint.&lt;/li&gt;



&lt;li&gt;Configure, build, and run the Spring Boot app, then open the agent task view or call the JSON endpoint to generate a trace.&lt;/li&gt;
&lt;/ol&gt;

&lt;pre&gt;&lt;code&gt;sudo dnf install -y podman git java-25-openjdk java-25-openjdk-devel maven jq curl openssl
export JAVA_HOME=/usr/lib/jvm/java-25-openjdk
export PATH="$JAVA_HOME/bin:$PATH"

git clone https://github.com/oracle-devrel/oracle-ai-for-sustainable-dev.git
cd oracle-ai-for-sustainable-dev/observability/springboot-oracle-db-otel-demo
mvn -DskipTests package&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Keep database credentials and runtime endpoints outside the repository:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;export DB_URL='jdbc:oracle:thin:@//127.0.0.1:1521/FREEPDB1'
export DB_USERNAME='FINANCIAL'
export DB_PASSWORD='&amp;lt;app-user-password&amp;gt;'
export OTLP_TRACES_ENDPOINT='http://127.0.0.1:4318/v1/traces'
export TRACE_SAMPLE_PROBABILITY=1.0
export ORACLE_JDBC_SERVER_TELEMETRY_TRACES_ENABLED=true
export ORACLE_JDBC_SERVER_TELEMETRY_LOGGING_ENABLED=false
export ORACLE_JDBC_TRACEPARENT_CLIENT_INFO_ENABLED=true
export ORACLE_JDBC_TRACELEVEL_CLIENT_INFO_ENABLED=true&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code&gt;java -jar target/springboot-oracle-db-otel-demo-0.0.1-SNAPSHOT.jar

curl -sS http://127.0.0.1:8080/trace/roundtrip | jq .

curl -sS \
  'http://127.0.0.1:8080/trace/agent-task?agentId=claims-investigator-agent&amp;amp;task=investigate_payment_anomalies' \
  | jq .&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;For the cohesive browser view, open:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;http://127.0.0.1:8080/trace/agent-task/view?agentId=claims-investigator-agent&amp;amp;task=investigate_payment_anomalies&lt;/code&gt;&lt;/pre&gt;





&lt;h2&gt;Maven Dependencies and the Oracle JDBC Observability Provider&lt;/h2&gt;

&lt;p&gt;The app uses Java 25, the Oracle JDBC 23.26 line from Maven Central, the published Maven Central&amp;nbsp;&lt;code&gt;ojdbc-provider-observability&lt;/code&gt;&amp;nbsp;artifact, and the Oracle JDBC 17 production dependency.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;properties&amp;gt;
  &amp;lt;java.version&amp;gt;25&amp;lt;/java.version&amp;gt;
  &amp;lt;oracle.jdbc.version&amp;gt;23.26.2.0.0&amp;lt;/oracle.jdbc.version&amp;gt;
  &amp;lt;oracle-database.version&amp;gt;${oracle.jdbc.version}&amp;lt;/oracle-database.version&amp;gt;
  &amp;lt;ojdbc.provider.observability.version&amp;gt;1.1.0&amp;lt;/ojdbc.provider.observability.version&amp;gt;
&amp;lt;/properties&amp;gt;

&amp;lt;dependency&amp;gt;
  &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
  &amp;lt;artifactId&amp;gt;spring-boot-starter-actuator&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;

&amp;lt;dependency&amp;gt;
  &amp;lt;groupId&amp;gt;io.micrometer&amp;lt;/groupId&amp;gt;
  &amp;lt;artifactId&amp;gt;micrometer-tracing-bridge-otel&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;

&amp;lt;dependency&amp;gt;
  &amp;lt;groupId&amp;gt;io.opentelemetry&amp;lt;/groupId&amp;gt;
  &amp;lt;artifactId&amp;gt;opentelemetry-exporter-otlp&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;

&amp;lt;dependency&amp;gt;
  &amp;lt;groupId&amp;gt;com.oracle.database.jdbc&amp;lt;/groupId&amp;gt;
  &amp;lt;artifactId&amp;gt;ojdbc17-production&amp;lt;/artifactId&amp;gt;
  &amp;lt;version&amp;gt;${oracle.jdbc.version}&amp;lt;/version&amp;gt;
  &amp;lt;type&amp;gt;pom&amp;lt;/type&amp;gt;
&amp;lt;/dependency&amp;gt;

&amp;lt;dependency&amp;gt;
  &amp;lt;groupId&amp;gt;com.oracle.database.jdbc&amp;lt;/groupId&amp;gt;
  &amp;lt;artifactId&amp;gt;ojdbc-provider-observability&amp;lt;/artifactId&amp;gt;
  &amp;lt;version&amp;gt;${ojdbc.provider.observability.version}&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Spring Boot Actuator creates the observation infrastructure, Micrometer bridges those observations to OpenTelemetry, and the OTLP exporter sends spans to the collector. The&amp;nbsp;&lt;code&gt;oracle-database.version&lt;/code&gt;&amp;nbsp;property keeps Oracle JDBC, UCP, wallet/security, and related transitive artifacts aligned on&amp;nbsp;&lt;code&gt;23.26.2.0.0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The Oracle JDBC observability provider implements the JDBC driver&amp;nbsp;&lt;code&gt;TraceEventListener&lt;/code&gt;&amp;nbsp;interface and publishes JDBC events into OpenTelemetry. Those events include database round trips and connection behavior, with attributes such as connection id, database operation, database user, tenant, and SQL ID. SQL text and connection details are treated as sensitive and are disabled by default unless explicitly enabled.&lt;/p&gt;





&lt;h2&gt;How Do the Spring Boot and JDBC Spans Get Created?&lt;/h2&gt;

&lt;p&gt;The Oracle JDBC OpenTelemetry extensions expect the application to already have an active OpenTelemetry context. They do not create the root application trace by themselves. In this demo, Spring Boot Actuator and Micrometer provide the HTTP and application observations, and the Oracle JDBC provider adds child spans for JDBC driver events such as database round trips. The provider also propagates the trace context to Oracle AI Database so the database can export its server-side span into the same trace.&lt;/p&gt;

&lt;p&gt;If you do not want to add application instrumentation directly, use the&amp;nbsp;&lt;a href="https://opentelemetry.io/docs/zero-code/java/agent/" rel="noopener noreferrer"&gt;OpenTelemetry Java agent&lt;/a&gt;&amp;nbsp;for zero-code Java instrumentation. The Oracle JDBC extensions can then add database round-trip spans as children of the current application span and propagate that context to the server.&lt;/p&gt;

&lt;p&gt;Spring Boot sends application and JDBC spans through OTLP HTTP. The demo sends them to Jaeger, but the same OpenTelemetry signal can be collected by any OpenTelemetry-compatible observability platform. Oracle JDBC provider properties enable the JDBC trace listener and identify the Java service in the collector.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;spring:
  application:
    name: springboot-oracle-db-otel-demo
  datasource:
    url: ${DB_URL}
    username: ${DB_USERNAME}
    password: ${DB_PASSWORD}
    oracleucp:
      connection-factory-properties:
        "[oracle.jdbc.provider.traceEventListener]": observability-trace-event-listener-provider
        "[oracle.jdbc.provider.traceEventListener.unique_identifier]": springboot-oracle-db-otel-demo

management:
  otlp:
    tracing:
      endpoint: ${OTLP_TRACES_ENDPOINT:http://localhost:4318/v1/traces}
  tracing:
    sampling:
      probability: 1.0&lt;/code&gt;&lt;/pre&gt;





&lt;h2&gt;How Do We Export Database Server-Side Spans?&lt;/h2&gt;

&lt;p&gt;The database server-side exporter needs a reachable HTTPS OTLP endpoint. The verified Linux demo uses Jaeger, which accepts OTLP HTTP on port&amp;nbsp;&lt;code&gt;4318&lt;/code&gt;, and puts a small HTTPS proxy in front of it for database server-side export. In the sample, that proxy is NGINX with a trusted local certificate:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Oracle AI Database -&amp;gt; https://otel-tls-proxy:4318/v1/traces
otel-tls-proxy -&amp;gt; http://oracle-db-otel-jaeger:4318/v1/traces&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Replace Jaeger with another OpenTelemetry-compatible collector or backend by changing the OTLP endpoint. The important requirement is that Oracle AI Database can reach the endpoint over HTTPS for server-side trace export.&lt;/p&gt;

&lt;p&gt;Configure the database with&amp;nbsp;&lt;code&gt;DBMS_OBSERVABILITY&lt;/code&gt;, grant network ACL access to the user executing the SQL session, and place the trusted wallet where the distributed trace exporter expects it:&amp;nbsp;&lt;code&gt;WALLET_ROOT/&amp;lt;PDB_GUID&amp;gt;/disttrc&lt;/code&gt;.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;begin
  dbms_observability.add_endpoint(
    endpoint_type =&amp;gt; dbms_observability.otel_traces,
    endpoint =&amp;gt; 'https://otel-tls-proxy:4318/v1/traces',
    credential_name =&amp;gt; null);

  dbms_observability.enable_endpoint('https://otel-tls-proxy:4318/v1/traces');
  dbms_observability.enable_service_option(dbms_observability.capture_traces);
  dbms_observability.enable_service_option(dbms_observability.show_extra_metadata);
  dbms_observability.enable_service(dbms_observability.all_services);
end;
/&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;A clean verification run on Oracle Database Free 23.26.2.0 showed that the documented&amp;nbsp;&lt;code&gt;DBMS_OBSERVABILITY&lt;/code&gt;&amp;nbsp;endpoint setup, the HTTPS OTLP proxy, the network ACL, and the&amp;nbsp;&lt;code&gt;WALLET_ROOT/&amp;lt;PDB_GUID&amp;gt;/disttrc&lt;/code&gt;&amp;nbsp;trust wallet were sufficient for database server-side spans. Older local diagnostic runs used hidden KSTRC parameters while debugging export failures, but those settings are not part of the normal demo setup.&lt;/p&gt;





&lt;h2&gt;How Does JDBC Propagate the Trace Context?&lt;/h2&gt;

&lt;p&gt;The app uses the published&amp;nbsp;&lt;code&gt;ojdbc-provider-observability&lt;/code&gt;&amp;nbsp;dependency. It is currently necessary to force a server telemetry state change before enabling traces, so the driver piggybacks the telemetry state to the database.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;EnumSet&amp;lt;OracleConnection.ServerTelemetry&amp;gt; requestedTelemetry =
    EnumSet.of(OracleConnection.ServerTelemetry.Traces);

oracleConnection.setServerTelemetry(
    EnumSet.noneOf(OracleConnection.ServerTelemetry.class));
oracleConnection.setServerTelemetry(requestedTelemetry);&lt;/code&gt;&lt;/pre&gt;





&lt;h2&gt;How Do We Make the Database Span Useful?&lt;/h2&gt;

&lt;p&gt;A generic&amp;nbsp;&lt;code&gt;DB Server&lt;/code&gt;&amp;nbsp;span proves that the trace crossed into the database, but the demo makes it more useful by setting database session fields before the SQL runs. The app uses the Oracle JDBC end-to-end metrics API for this metadata, so it does not need a separate PL/SQL round trip just to set&amp;nbsp;&lt;code&gt;MODULE&lt;/code&gt;,&amp;nbsp;&lt;code&gt;ACTION&lt;/code&gt;, and&amp;nbsp;&lt;code&gt;CLIENT_IDENTIFIER&lt;/code&gt;.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;String[] metrics = new String[OracleConnection.END_TO_END_STATE_INDEX_MAX];
metrics[OracleConnection.END_TO_END_MODULE_INDEX] = "agent:claims-investigator-agent";
metrics[OracleConnection.END_TO_END_ACTION_INDEX] = "agent-workload-query";
metrics[OracleConnection.END_TO_END_CLIENTID_INDEX] = "traceId=&amp;lt;trace-id&amp;gt;";
metrics[OracleConnection.END_TO_END_ECID_INDEX] = "&amp;lt;trace-id&amp;gt;";
oracleConnection.setEndToEndMetrics(metrics, (short) 0);&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In Jaeger, the orange&amp;nbsp;&lt;code&gt;oracle-db / DB Server&lt;/code&gt;&amp;nbsp;span then carries database-side evidence such as&amp;nbsp;&lt;code&gt;oracle.db.module&lt;/code&gt;,&amp;nbsp;&lt;code&gt;oracle.db.action&lt;/code&gt;,&amp;nbsp;&lt;code&gt;oracle.db.session.id&lt;/code&gt;,&amp;nbsp;&lt;code&gt;oracle.db.pdb&lt;/code&gt;, and&amp;nbsp;&lt;code&gt;oracle.db.query.sql.id&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Some of these names can also appear in client-side JDBC spans because the driver can receive database metadata and the application deliberately sets&amp;nbsp;&lt;code&gt;MODULE&lt;/code&gt;,&amp;nbsp;&lt;code&gt;ACTION&lt;/code&gt;, and&amp;nbsp;&lt;code&gt;CLIENT_IDENTIFIER&lt;/code&gt;. The important difference is provenance and timing: the&amp;nbsp;&lt;code&gt;oracle-db / DB Server&lt;/code&gt;&amp;nbsp;span is emitted by Oracle AI Database itself, with database-side duration, response status, session context, and SQL identity attached to the server's view of the work. A client span can time the Java round trip, but it cannot by itself prove the database server's own span timing in the same trace.&lt;/p&gt;





&lt;h2&gt;How Do We Link the Trace to SQL Diagnostics?&lt;/h2&gt;

&lt;p&gt;The demo returns a canonical SQL ID from the cursor used for its&amp;nbsp;&lt;code&gt;DBMS_XPLAN&lt;/code&gt;&amp;nbsp;and SQL Monitor lookups. Oracle JDBC spans and database-exported spans can also expose&amp;nbsp;&lt;code&gt;oracle.db.query.sql.id&lt;/code&gt;, but do not assume that every layer will report the same cursor. A verified run for the screenshots in this article returned one canonical SQL ID on the demo page and a different SQL ID on the selected&amp;nbsp;&lt;code&gt;oracle-db / DB Server&lt;/code&gt;&amp;nbsp;span.&lt;/p&gt;

&lt;p&gt;Keep the correlation intact with the trace id, database ECID,&amp;nbsp;&lt;code&gt;MODULE&lt;/code&gt;,&amp;nbsp;&lt;code&gt;ACTION&lt;/code&gt;, and session id. Then use the canonical SQL ID returned by the page for its&amp;nbsp;&lt;code&gt;V$SQL&lt;/code&gt;, bind capture, SQL Monitor, and&amp;nbsp;&lt;code&gt;DBMS_XPLAN&lt;/code&gt;&amp;nbsp;drill-down. A SQL ID on a particular span remains useful evidence, but it identifies that span's database cursor rather than a guaranteed cross-layer identifier.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;Evidence&lt;/th&gt;
&lt;th&gt;Where it comes from&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Visual trace and database server evidence&lt;/td&gt;
&lt;td&gt;Trace id, ECID, module, action, session id, and any layer-specific&amp;nbsp;&lt;code&gt;oracle.db.query.sql.id&lt;/code&gt;&amp;nbsp;attribute&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Full SQL text&lt;/td&gt;
&lt;td&gt;Canonical&amp;nbsp;&lt;code&gt;SQL_ID -&amp;gt; V$SQL.SQL_FULLTEXT&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Captured bind samples&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SQL_ID -&amp;gt; V$SQL_BIND_CAPTURE&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runtime plan&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;DBMS_XPLAN.DISPLAY_CURSOR&lt;/code&gt;, the SQL execution plan with optimizer steps, estimated and actual rows, I/O, memory, and timing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SQL Monitor report&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DBMS_SQL_MONITOR.REPORT_SQL_MONITOR&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The demo also calls&amp;nbsp;&lt;code&gt;DBMS_SQL_MONITOR.BEGIN_OPERATION&lt;/code&gt;&amp;nbsp;around the agent task. That call is different from setting JDBC end-to-end metrics: it creates a named database operation for SQL Monitor so the agent task has a database-side operation id and attributes. The trace and SQL-ID bridge still work without it, because the workload SQL uses the&amp;nbsp;&lt;code&gt;MONITOR&lt;/code&gt;&amp;nbsp;hint and the app queries&amp;nbsp;&lt;code&gt;V$SQL&lt;/code&gt;,&amp;nbsp;&lt;code&gt;V$SQL_BIND_CAPTURE&lt;/code&gt;, SQL Monitor, and&amp;nbsp;&lt;code&gt;DBMS_XPLAN&lt;/code&gt;&amp;nbsp;by SQL ID. Keeping&amp;nbsp;&lt;code&gt;BEGIN_OPERATION&lt;/code&gt;&amp;nbsp;makes the demo stronger by giving SQL Monitor a named server-side operation for the agent task; removing it would simplify privileges and code, but would lose that named database-operation evidence.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpaulparkinson.github.io%2Foracle-ai-for-sustainable-dev%2Fobservability%2Fimages%2Fsql-text-bind-samples-monitor.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpaulparkinson.github.io%2Foracle-ai-for-sustainable-dev%2Fobservability%2Fimages%2Fsql-text-bind-samples-monitor.png" alt="Demo page showing full SQL text from SQL_ID to V$SQL.SQL_FULLTEXT, captured bind sample explanation, and SQL Monitor report preview." width="800" height="775"&gt;&lt;/a&gt;SQL_ID lets the demo drill from the trace into full SQL text, bind-sample notes, and SQL Monitor output.&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpaulparkinson.github.io%2Foracle-ai-for-sustainable-dev%2Fobservability%2Fimages%2Fsql-monitor-dbms-xplan-preview.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpaulparkinson.github.io%2Foracle-ai-for-sustainable-dev%2Fobservability%2Fimages%2Fsql-monitor-dbms-xplan-preview.png" alt="Demo page showing SQL Monitor report preview and DBMS_XPLAN execution plan output for the traced SQL statement." width="800" height="765"&gt;&lt;/a&gt;SQL Monitor and DBMS_XPLAN make the traced database span useful for performance profiling and SQL diagnostics.&lt;p&gt;&lt;/p&gt;





&lt;h2&gt;What Does the Agentic AI Demo Page Show?&lt;/h2&gt;

&lt;p&gt;The browser endpoint keeps the demo cohesive by returning the agent story, database diagnostics, and Jaeger trace on one page:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;http://localhost:8080/trace/agent-task/view?agentId=claims-investigator-agent&amp;amp;task=investigate_payment_anomalies&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The page includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Agent id and task name.&lt;/li&gt;



&lt;li&gt;Trace id and SQL ID bridge.&lt;/li&gt;



&lt;li&gt;Database session context, including module/action/client identifier.&lt;/li&gt;



&lt;li&gt;Application bind values used by this exact request.&lt;/li&gt;



&lt;li&gt;Full SQL Text heading labeled&amp;nbsp;&lt;code&gt;from SQL_ID -&amp;gt; V$SQL.SQL_FULLTEXT&lt;/code&gt;.&lt;/li&gt;



&lt;li&gt;Captured Bind Samples heading labeled&amp;nbsp;&lt;code&gt;from SQL_ID -&amp;gt; V$SQL_BIND_CAPTURE&lt;/code&gt;.&lt;/li&gt;



&lt;li&gt;SQL Monitor and&amp;nbsp;&lt;code&gt;DBMS_XPLAN&lt;/code&gt;&amp;nbsp;output.&lt;/li&gt;



&lt;li&gt;An embedded Jaeger trace loaded after a short delay so spans have time to flush.&lt;/li&gt;
&lt;/ul&gt;





&lt;h2&gt;What Does Success Look Like?&lt;/h2&gt;

&lt;p&gt;In the verified Linux environment, Jaeger showed a single trace containing both services:&amp;nbsp;&lt;code&gt;springboot-oracle-db-otel-demo&lt;/code&gt;&amp;nbsp;from the Java process and&amp;nbsp;&lt;code&gt;oracle-db&lt;/code&gt;&amp;nbsp;from Oracle AI Database server-side export.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;{
  "services": [
    "oracle-db",
    "springboot-oracle-db-otel-demo"
  ],
  "ops": [
    "DB Server",
    "Execute query",
    "Fetch a row",
    "http get /trace/agent-task",
    "oracle.demo.agent-database-investigation"
  ]
}&lt;/code&gt;&lt;/pre&gt;





&lt;h2&gt;Oracle Database Metrics Exporter (External)&lt;/h2&gt;

&lt;p&gt;Server-side OpenTelemetry spans answer request-scoped questions: which agent request reached the database, how long that database work took, which SQL ID was involved, and how the database span fits into the same trace as the Java service. For fleet-level and time-series questions, pair that with the external Oracle AI Database Metrics Exporter from&amp;nbsp;&lt;a href="https://github.com/oracle/oracle-db-appdev-monitoring" rel="noopener noreferrer"&gt;oracle/oracle-db-appdev-monitoring&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The exporter is a separate process or container that connects to one or more Oracle AI Database instances and exposes database metrics in standard Prometheus/OpenTelemetry formats. Oracle's exporter project includes default metrics, custom metrics defined in YAML or TOML, support for single instance, clustered, Autonomous, cloud, on-premises, Kubernetes, and container-based databases, plus sample Grafana dashboards.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;th&gt;Best question&lt;/th&gt;
&lt;th&gt;How it complements this demo&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Server-side OpenTelemetry trace export&lt;/td&gt;
&lt;td&gt;What happened during this specific agent request?&lt;/td&gt;
&lt;td&gt;Shows the database span in the same trace as Spring Boot, JDBC, SQL ID, module/action, and agent metadata.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Oracle AI Database Metrics Exporter&lt;/td&gt;
&lt;td&gt;What is happening across the database over time?&lt;/td&gt;
&lt;td&gt;Adds dashboards and alerts for resource use, sessions, waits, workload health, and custom business or security metrics.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A practical setup is to run both signals into the same observability platform. Keep this demo's OTLP trace path for request-level causality, and deploy the exporter beside the database environment for database metrics:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Spring Boot + Oracle JDBC provider -&amp;gt; OTLP traces -&amp;gt; collector/backend
Oracle AI Database server-side exporter -&amp;gt; HTTPS OTLP traces -&amp;gt; collector/backend
Oracle AI Database Metrics Exporter -&amp;gt; Prometheus/OpenTelemetry metrics -&amp;gt; collector/backend&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Used together, traces and metrics give a stronger agentic AI operations story: the trace explains one request all the way into Oracle AI Database, while the metrics exporter shows whether that request happened during broader database pressure, session growth, wait spikes, alert-log events, or workload-specific metric changes.&lt;/p&gt;





&lt;h2&gt;How Does the Trace Connect to Security and Auditing?&lt;/h2&gt;

&lt;p&gt;The demo correlates its trace with Oracle AI Database Deep Data Security. Observability shows what the agent did; database security explains why Oracle AI Database allowed or denied it. The trace already carries the join keys needed for that correlation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;trace.traceId&lt;/code&gt;&amp;nbsp;identifies the distributed request.&lt;/li&gt;



&lt;li&gt;
&lt;code&gt;agent.id&lt;/code&gt;&amp;nbsp;identifies the AI actor in the application span.&lt;/li&gt;



&lt;li&gt;
&lt;code&gt;oracle.db.module=agent:&amp;lt;agent-id&amp;gt;&lt;/code&gt;&amp;nbsp;identifies the same actor inside Oracle AI Database.&lt;/li&gt;



&lt;li&gt;
&lt;code&gt;oracle.db.action&lt;/code&gt;&amp;nbsp;identifies the database phase.&lt;/li&gt;



&lt;li&gt;
&lt;code&gt;CLIENT_IDENTIFIER=traceId=&amp;lt;trace-id&amp;gt;&lt;/code&gt;&amp;nbsp;gives database security and audit policy a compact correlation value.&lt;/li&gt;



&lt;li&gt;
&lt;code&gt;oracle.db.query.sql.id&lt;/code&gt;&amp;nbsp;bridges the trace to SQL text, SQL Monitor,&amp;nbsp;&lt;code&gt;DBMS_XPLAN&lt;/code&gt;, and later audit records.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The sample app now includes a&amp;nbsp;&lt;strong&gt;Database Security Context&lt;/strong&gt;&amp;nbsp;panel backed by a real password-authenticated local Deep Data Security end user. It works without Entra ID, OCI IAM, OAuth tokens, or a JDBC&amp;nbsp;&lt;code&gt;EndUserSecurityContext&lt;/code&gt;&amp;nbsp;call. The page correlates the trace with&amp;nbsp;&lt;code&gt;ORA_END_USER_CONTEXT.username&lt;/code&gt;, the associated schema,&amp;nbsp;&lt;code&gt;MODULE&lt;/code&gt;,&amp;nbsp;&lt;code&gt;ACTION&lt;/code&gt;,&amp;nbsp;&lt;code&gt;CLIENT_IDENTIFIER&lt;/code&gt;, enabled database roles, effective privileges, the granted data role, the applicable data grant, and&amp;nbsp;&lt;code&gt;V$END_USER_DATA_ROLE&lt;/code&gt;&amp;nbsp;for this request. A row-filtering proof runs in that same traced session and shows both the number of rows allowed by the data grant and zero visible rows for other agents.&lt;/p&gt;

&lt;p&gt;The panel deliberately separates regular Oracle Database roles from Oracle AI Database Deep Data Security data roles. A role such as&amp;nbsp;&lt;code&gt;SELECT_CATALOG_ROLE&lt;/code&gt;&amp;nbsp;is a normal session role and appears in&amp;nbsp;&lt;code&gt;SESSION_ROLES&lt;/code&gt;. A DDS data role such as&amp;nbsp;&lt;code&gt;AGENT_CLAIMS_INVESTIGATOR&lt;/code&gt;&amp;nbsp;appears in&amp;nbsp;&lt;code&gt;DBA_DATA_ROLES&lt;/code&gt;&amp;nbsp;and receives data grants in&amp;nbsp;&lt;code&gt;DBA_DATA_GRANTS&lt;/code&gt;. The local setup script&amp;nbsp;&lt;code&gt;observability/sql/setup_local_deep_data_security.sql&lt;/code&gt;&amp;nbsp;creates the local end user&amp;nbsp;&lt;code&gt;claims-investigator-agent&lt;/code&gt;, assigns&amp;nbsp;&lt;code&gt;AGENT_CLAIMS_INVESTIGATOR&lt;/code&gt;&amp;nbsp;with&amp;nbsp;&lt;code&gt;GRANT DATA ROLE&lt;/code&gt;, and grants that role SELECT access to only the agent's event-log rows. The&amp;nbsp;&lt;code&gt;FINANCIAL&lt;/code&gt;&amp;nbsp;database user remains the owning schema rather than the runtime principal.&lt;/p&gt;

&lt;p&gt;A Deep Sec local end user is distinct from a conventional database user. Data roles cannot be granted directly to the&amp;nbsp;&lt;code&gt;FINANCIAL&lt;/code&gt;&amp;nbsp;schema account. Direct password login as the local end user establishes the end-user security context in Oracle AI Database and activates the assigned data role without application-side token propagation. The Entra ID and OCI IAM provider/API examples remain the right pattern when an application must propagate externally managed end-user identities through a shared pool user.&lt;/p&gt;

&lt;p&gt;A security-aware version can run the same agent workload against protected tables, then show effective database user, enabled roles, policy decisions, rows allowed or filtered, and a denied-operation example. After that, a scoped audit policy can record the same actor, module, action, SQL ID, and trace id so the page shows one story: this agent made this request, Oracle AI Database enforced these policies, and the audit trail recorded it.&lt;/p&gt;

&lt;p&gt;The trace itself is not auditing. It is observability: useful for debugging, performance, causality, and visual correlation. Auditing requires durable database records, such as Unified Auditing or Fine-Grained Auditing entries, retained under a security-controlled policy. The best pattern is to use traces to explain the execution path and audit records to prove the security-relevant facts.&lt;/p&gt;

&lt;p&gt;A companion design note expands this direction:&amp;nbsp;&lt;a href="https://paulparkinson.github.io/oracle-ai-for-sustainable-dev/observability/agent-security-observability.md" rel="noopener noreferrer"&gt;Agent Security Observability With Oracle AI Database&lt;/a&gt;.&lt;/p&gt;





&lt;h2&gt;How Can This Connect to Model Observability?&lt;/h2&gt;

&lt;p&gt;Agent observability should not stop at infrastructure traces. The same task can also be correlated with model-level telemetry: prompt, model, tool calls, retrieval steps, latency, token usage, evaluator scores, and final answer quality. Tools such as LangSmith, OpenTelemetry-based GenAI semantic conventions, or enterprise AI observability platforms can capture those model and agent workflow details.&lt;/p&gt;

&lt;p&gt;The useful pattern is to carry the same task or trace identifier through the agent workflow, the Java service, Oracle JDBC, and Oracle AI Database. Then one investigation can answer: which agent ran, which prompt or plan it used, which tool call reached the database, how long the database took, which SQL ran, and which database security policies applied. That ties model observability, application observability, database observability, and security review into one storyline.&lt;/p&gt;





&lt;h2&gt;References&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/oracle-devrel/oracle-ai-for-sustainable-dev/tree/main/observability" rel="noopener noreferrer"&gt;Source code for this demo&lt;/a&gt;&lt;/li&gt;



&lt;li&gt;&lt;a href="https://github.com/oracle/ojdbc-extensions/tree/main/ojdbc-provider-observability" rel="noopener noreferrer"&gt;Oracle JDBC Observability provider&lt;/a&gt;&lt;/li&gt;



&lt;li&gt;&lt;a href="https://github.com/oracle/ojdbc-extensions/tree/main/ojdbc-provider-opentelemetry" rel="noopener noreferrer"&gt;Oracle JDBC OpenTelemetry provider&lt;/a&gt;&lt;/li&gt;



&lt;li&gt;&lt;a href="https://docs.oracle.com/en/database/oracle/oracle-database/26/arpls/dbms_observability.html" rel="noopener noreferrer"&gt;Oracle&amp;nbsp;&lt;code&gt;DBMS_OBSERVABILITY&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;



&lt;li&gt;&lt;a href="https://docs.oracle.com/en/database/oracle/oracle-database/26/tgsql/monitoring-and-tracing-sql.html" rel="noopener noreferrer"&gt;Oracle SQL monitoring and tracing&lt;/a&gt;&lt;/li&gt;



&lt;li&gt;&lt;a href="https://docs.oracle.com/en/database/oracle/oracle-database/26/tgsql/performing-application-tracing.html" rel="noopener noreferrer"&gt;Oracle AI Database application tracing&lt;/a&gt;&lt;/li&gt;



&lt;li&gt;&lt;a href="https://github.com/oracle/oracle-db-appdev-monitoring" rel="noopener noreferrer"&gt;Oracle AI Database Metrics Exporter project&lt;/a&gt;&lt;/li&gt;



&lt;li&gt;&lt;a href="https://www.oracle.com/database/database-exporter/" rel="noopener noreferrer"&gt;Oracle Database Metrics Exporter&lt;/a&gt;&lt;/li&gt;



&lt;li&gt;Video &lt;a href="https://www.youtube.com/watch?v=7vUaDdNzFmQ" rel="noopener noreferrer"&gt;Day One and Beyond - MultiCloud Observability &amp;amp; Management for Oracle Database@X&lt;/a&gt;, Sonali Malik&lt;/li&gt;



&lt;li&gt;&lt;a href="https://www.ateam-oracle.com/ai-agent-observability-understand-how-ai-agents-behave-in-gen-ai-applications-and-ecosystems" rel="noopener noreferrer"&gt;AI Agent Observability: Understand How AI Agents Behave in Gen AI Applications and Ecosystems, Royce Fu and Alex Birzu&lt;/a&gt;&lt;/li&gt;



&lt;li&gt;&lt;a href="https://opentelemetry.io/docs/zero-code/java/agent/" rel="noopener noreferrer"&gt;OpenTelemetry Java agent&lt;/a&gt;&lt;/li&gt;



&lt;li&gt;&lt;a href="https://opentelemetry.io/docs/specs/otel/protocol/exporter/" rel="noopener noreferrer"&gt;OpenTelemetry protocol exporters&lt;/a&gt;&lt;/li&gt;



&lt;li&gt;&lt;a href="https://docs.spring.io/spring-boot/reference/actuator/tracing.html" rel="noopener noreferrer"&gt;Spring Boot tracing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;





&lt;h2&gt;FAQ&lt;/h2&gt;

&lt;h3&gt;Does this replace database auditing?&lt;/h3&gt;

&lt;p&gt;No. The trace is observability, not an audit trail. Use Oracle AI Database Unified Auditing or Fine-Grained Auditing when you need durable security evidence about users, roles, objects, SQL, and policy decisions.&lt;/p&gt;

&lt;h3&gt;Do I have to use Jaeger?&lt;/h3&gt;

&lt;p&gt;No. Jaeger is used here because it is easy to run and inspect, but the demo exports OpenTelemetry traces. The same approach can feed any OpenTelemetry-compatible observability tool that can receive the app/JDBC spans and the Oracle AI Database server-side spans.&lt;/p&gt;

&lt;h3&gt;What makes this different from ordinary database tracing?&lt;/h3&gt;

&lt;p&gt;Ordinary application traces often stop at the database edge. Oracle AI Database can export database server-side spans into the same trace, so the database portion is visible without breaking continuity or switching immediately to a separate diagnostic workflow.&lt;/p&gt;

&lt;h3&gt;Why frame this as an AI agent workload?&lt;/h3&gt;

&lt;p&gt;Agentic applications need explainability across application and database boundaries. Adding agent id, task, module, action, client identifier, and SQL_ID makes the trace useful for operations, security review, and future audit correlation.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>java</category>
      <category>oracle</category>
      <category>database</category>
    </item>
    <item>
      <title>Develop Database-Enforced End-User Auth with Oracle AI Database Deep Data Security and Java</title>
      <dc:creator>Anya Summers</dc:creator>
      <pubDate>Thu, 30 Jul 2026 14:34:15 +0000</pubDate>
      <link>https://dev.to/oracledevs/develop-database-enforced-end-user-auth-with-oracle-ai-database-deep-data-security-and-java-56cp</link>
      <guid>https://dev.to/oracledevs/develop-database-enforced-end-user-auth-with-oracle-ai-database-deep-data-security-and-java-56cp</guid>
      <description>&lt;p&gt;&lt;strong&gt;A practical walkthrough for propagating Microsoft Entra identity through a Spring Boot application via a JDBC connection to Oracle AI Database, which enforces row, column, and cell-level access rules.&lt;/strong&gt;&lt;/p&gt;





&lt;h2&gt;Source code and video&lt;/h2&gt;

&lt;p&gt;All source code for the app is available in the&amp;nbsp;&lt;a href="https://github.com/oracle-devrel/oracle-ai-for-sustainable-dev/tree/main/security" rel="noopener noreferrer"&gt;GitHub repo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Video walkthrough (8 minutes):&amp;nbsp;&lt;a href="https://www.youtube.com/watch?v=WfsqMX9QwPo" rel="noopener noreferrer"&gt;watch directly on YouTube&lt;/a&gt;&amp;nbsp;if the embedded player is unavailable.&lt;/p&gt;





&lt;h2&gt;Key Takeaways&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Pooled JDBC connections can carry end-user identity when application code or&amp;nbsp;&lt;code&gt;ojdbc-provider-spring&lt;/code&gt;&amp;nbsp;attaches an Oracle JDBC end-user security context before SQL runs.&lt;/li&gt;



&lt;li&gt;The OAuth on-behalf-of flow produces a separate database-access token, so the user token proves the browser user while the database token proves access to Oracle AI Database.&lt;/li&gt;



&lt;li&gt;Microsoft Entra app roles such as EMPLOYEES and MANAGERS map to Oracle Deep Data Security data roles, letting the same SQL return different rows based on the signed-in user.&lt;/li&gt;



&lt;li&gt;The Java app stays role-agnostic: Oracle AI Database becomes the enforcement point for row, column, and cell-level authorization.&lt;/li&gt;
&lt;/ul&gt;





&lt;p&gt;Yes—an enterprise Spring Boot application can keep pooled JDBC connections while Oracle AI Database enforces authorization for each signed-in Microsoft Entra user. The application propagates user and database tokens; Deep Data Security applies declarative row, column, and cell-level rules when SQL executes.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpaulparkinson.github.io%2Foracle-ai-for-sustainable-dev%2Fsecurity%2Fimages%2Foracle-ai-database-deep-data-security-cover.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpaulparkinson.github.io%2Foracle-ai-for-sustainable-dev%2Fsecurity%2Fimages%2Foracle-ai-database-deep-data-security-cover.png" alt="Diagram showing a browser user, user token, database token, Spring Boot application, pooled JDBC connection, and Oracle AI Database Deep Data Security enforcing row, column, and cell access." width="800" height="420"&gt;&lt;/a&gt;Microsoft Entra identity flows through Spring Boot into Oracle AI Database, where Deep Data Security enforces fine-grained access.&lt;p&gt;&lt;/p&gt;





&lt;h2&gt;Why This Matters&lt;/h2&gt;

&lt;p&gt;Most enterprise Java services use a connection pool. That is good engineering: fast, stable, observable, and easy to operate. It also means every request may reach the database through the same application database account.&lt;/p&gt;

&lt;p&gt;That is where authorization gets tricky. If Emma and Marvin both use the same pooled database user, the database cannot naturally tell which rows belong to Emma, which columns Marvin may see, or which values a manager may update.&lt;/p&gt;

&lt;p&gt;One option is to push that logic into every application query. Another is to create separate data sources or connection pools per user. Neither is a good fit for real enterprise systems.&lt;/p&gt;

&lt;p&gt;Oracle Deep Data Security changes the shape of the problem. The application still uses a pooled connection, but it attaches an end-user security context to the JDBC connection before SQL runs. Oracle AI Database evaluates declarative data grants at execution time.&lt;/p&gt;

&lt;p&gt;This matters even more for agentic applications. Agents, copilots, and natural-language SQL tools can fan out into many possible data paths. With Deep Data Security, the database remains the final policy enforcement point for fine-grained row, column, and cell-level access.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The important idea:&amp;nbsp;&lt;/strong&gt;the Spring Boot app does not rewrite SQL or implement authorization logic. It propagates identity and tokens to Oracle AI Database, which enforces authorization policies.&lt;/p&gt;

&lt;p&gt;This walkthrough includes the Microsoft Entra setup explicitly instead of only linking to the Microsoft documentation. That part is easy to get subtly wrong, and writing it down forced me to understand it better too.&lt;/p&gt;

&lt;p&gt;A good starting point, and the one I extended to create this Java app, is Richard C. Evans's excellent&amp;nbsp;&lt;a href="https://livelabs.oracle.com/ords/r/dbpm/livelabs/run-workshop?p210_wid=4396" rel="noopener noreferrer"&gt;LiveLabs FastLab: Identity-Aware Database Access with Microsoft Entra ID and Oracle Deep Data Security&lt;/a&gt;. While I am giving credit due, I will mention that Michael McMahon is the fantastic and friendly developer who wrote the Java support for this Deep Data Security feature.&lt;/p&gt;





&lt;h2&gt;What We Are Building&lt;/h2&gt;

&lt;p&gt;The demo is a Spring Boot application that performs four main jobs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Authenticates a browser user with Microsoft Entra ID.&lt;/li&gt;



&lt;li&gt;Requests a database-access token using the OAuth 2.0 on-behalf-of flow.&lt;/li&gt;



&lt;li&gt;Attaches both tokens to an Oracle JDBC connection with EndUserSecurityContext.&lt;/li&gt;



&lt;li&gt;Runs a normal SQL query against an HR table while Oracle AI Database enforces data grants.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The repo includes two aligned demos, both using Oracle UCP for pooled JDBC connections:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Explicit JDBC API version:&lt;/strong&gt;&amp;nbsp;application code obtains both tokens and calls&amp;nbsp;&lt;code&gt;OracleConnection.setEndUserSecurityContext(...)&lt;/code&gt;&amp;nbsp;and&amp;nbsp;&lt;code&gt;clearEndUserSecurityContext()&lt;/code&gt;.&lt;/li&gt;



&lt;li&gt;
&lt;strong&gt;ojdbc-provider-spring provider/SPI version:&lt;/strong&gt;&amp;nbsp;Oracle JDBC reads the current Spring Security context and supplies the end-user security context through the provider SPI.&lt;/li&gt;
&lt;/ul&gt;





&lt;h2&gt;Runtime Flow&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;The browser opens GET /deepsec/query.&lt;/li&gt;



&lt;li&gt;Spring Security redirects the browser to Microsoft Entra ID.&lt;/li&gt;



&lt;li&gt;The user signs in.&lt;/li&gt;



&lt;li&gt;Spring Security receives an authorization code and exchanges it for the user's Entra access token.&lt;/li&gt;



&lt;li&gt;The backend exchanges that user token for a separate database-access token using OAuth 2.0 on-behalf-of.&lt;/li&gt;



&lt;li&gt;Oracle JDBC receives an EndUserSecurityContext containing both tokens, either from explicit application code or through the Spring provider SPI.&lt;/li&gt;



&lt;li&gt;Oracle AI Database validates the context, activates mapped data roles from the Entra roles claim, and enforces data grants while SQL runs.&lt;/li&gt;



&lt;li&gt;The explicit API app clears the context before returning the connection to the pool; the provider manages that context lifecycle through the SPI.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Outcome:&amp;nbsp;&lt;/strong&gt;the application remains role-agnostic. Authorization logic is removed from Java code. Oracle AI Database becomes the enforcement point.&lt;/p&gt;





&lt;h2&gt;Identity and Token Model&lt;/h2&gt;

&lt;h3&gt;Understand the Token Model Before OBO&lt;/h3&gt;

&lt;p&gt;The on-behalf-of flow is easier to follow if you separate the two tokens early:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;Token&lt;/th&gt;
&lt;th&gt;What it proves&lt;/th&gt;
&lt;th&gt;Audience / important claims&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;User token&lt;/td&gt;
&lt;td&gt;Proves the signed-in browser user to the Spring Boot application. The backend uses it as the assertion for OBO.&lt;/td&gt;
&lt;td&gt;Audience is the Spring Boot app. It is requested during browser login.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database-access token&lt;/td&gt;
&lt;td&gt;Proves that the request is addressed to the Oracle AI Database protected resource.&lt;/td&gt;
&lt;td&gt;Audience is the database/API app. For this demo, it should be v2 and include the roles claim used for DDS role mapping.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Both tokens matter. The explicit API demo creates the context with&amp;nbsp;&lt;code&gt;EndUserSecurityContext.createWithToken(databaseAccessToken, endUserToken)&lt;/code&gt;; the provider demo supplies the equivalent context through the JDBC provider SPI.&lt;/p&gt;

&lt;h3&gt;Real Users and Sample HR Records&lt;/h3&gt;

&lt;h4&gt;Real Entra users&lt;/h4&gt;

&lt;p&gt;The browser login uses a real Microsoft Entra user. That user receives app-role assignments on the database/API Enterprise Application, such as EMPLOYEES or MANAGERS.&lt;/p&gt;

&lt;h4&gt;Sample HR records&lt;/h4&gt;

&lt;p&gt;Emma, Marvin, Charlie, and Dana are sample HR rows in the database, not Entra users. For the employee demo, one HR row is mapped to the value Oracle sees in ORA_END_USER_CONTEXT.username.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Setup order:&amp;nbsp;&lt;/strong&gt;the remaining sections configure the database, Microsoft Entra, and then the Spring Boot implementations before running the demo.&lt;/p&gt;





&lt;h2&gt;Database Setup&lt;/h2&gt;

&lt;h3 id="create-an-oracle-ai-database-if-you-do-not-have-one"&gt;1. Create an Oracle AI Database if You Do Not Have One&lt;/h3&gt;

&lt;p&gt;You need an Oracle AI Database with Deep Data Security support. Any 23.26.2 or later database is fine for this walkthrough, including Autonomous Database, Oracle AI Database Free in a container, or another local or cloud database you can reach from the Spring Boot app.&lt;/p&gt;

&lt;h3 id="database-configuration"&gt;2. Configure Microsoft Entra Trust&lt;/h3&gt;

&lt;p&gt;Oracle AI Database needs to trust the Entra tenant and the database/API app registration before it can validate the database-access token. Choose the database-admin setup script for your environment:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;sql-entraid/01_enable_adb_entra_external_authentication.sql&lt;/code&gt;&amp;nbsp;uses&amp;nbsp;&lt;code&gt;DBMS_CLOUD_ADMIN.ENABLE_EXTERNAL_AUTHENTICATION&lt;/code&gt;&amp;nbsp;for Autonomous Database. Edit its&amp;nbsp;&lt;code&gt;DEFINE&lt;/code&gt;&amp;nbsp;values and run it once as&amp;nbsp;&lt;code&gt;ADMIN&lt;/code&gt;.&lt;/li&gt;



&lt;li&gt;
&lt;code&gt;sql-entraid/01_enable_database_free_entra_external_authentication.sql&lt;/code&gt;&amp;nbsp;uses&amp;nbsp;&lt;code&gt;IDENTITY_PROVIDER_TYPE&lt;/code&gt;&amp;nbsp;and&amp;nbsp;&lt;code&gt;IDENTITY_PROVIDER_CONFIG&lt;/code&gt;&amp;nbsp;for Oracle AI Database Free or another non-ADB installation. Run it once as&amp;nbsp;&lt;code&gt;SYSDBA&lt;/code&gt;&amp;nbsp;or equivalent.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For non-ADB databases, keep the domain-qualified Application ID URI aligned across the database/API app registration,&amp;nbsp;&lt;code&gt;DEEPSEC_ENTRA_DATABASE_SCOPE&lt;/code&gt;, and the optional&amp;nbsp;&lt;code&gt;tnsnames.ora&lt;/code&gt;&amp;nbsp;&lt;code&gt;azure_db_app_id_uri&lt;/code&gt;&amp;nbsp;value.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;-- Autonomous Database, run once as ADMIN after editing DEFINE values:
@security/deepdatasecurity-api-version/sql-entraid/01_enable_adb_entra_external_authentication.sql

-- Oracle AI Database Free container, run once as SYSDBA or equivalent:
@security/deepdatasecurity-api-version/sql-entraid/01_enable_database_free_entra_external_authentication.sql&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Verify that the database picked up the identity provider:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;SELECT name, value
  FROM v$parameter
 WHERE name IN ('identity_provider_type', 'identity_provider_config');&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Grant the application connection-pool user only the privileges it needs to connect and attach an end-user security context:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;GRANT CREATE SESSION TO hr_app_user;
GRANT CREATE END USER SECURITY CONTEXT TO hr_app_user;&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;3. Map Entra App Roles to Deep Data Security Roles&lt;/h3&gt;

&lt;p&gt;In the database, Microsoft Entra app roles map to Oracle Deep Data Security data roles:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;CREATE OR REPLACE DATA ROLE hrapp_employees
  MAPPED TO 'AZURE_ROLE=EMPLOYEES';

CREATE OR REPLACE DATA ROLE hrapp_managers
  MAPPED TO 'AZURE_ROLE=MANAGERS';&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If the signed-in user has the Entra app role EMPLOYEES, Oracle activates HRAPP_EMPLOYEES. If that same user later has the Entra app role MANAGERS, or a different signed-in user has MANAGERS, Oracle activates the manager data role. Users can also have multiple app roles.&lt;/p&gt;

&lt;h3 id="the-data-grants"&gt;4. Create the Data Grants&lt;/h3&gt;

&lt;p&gt;The policy itself lives in SQL. For an employee, the row predicate says a user can see rows where the table username matches ORA_END_USER_CONTEXT.username:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;CREATE OR REPLACE DATA GRANT hr.HRAPP_EMPLOYEES_ACCESS
  AS SELECT, UPDATE(phone_number, first_name)
  ON hr.employees
  WHERE upper(user_name) = upper(ORA_END_USER_CONTEXT.username)
  TO hrapp_employees;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;For managers, the policy permits access to direct reports while excluding sensitive columns such as ssn:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;CREATE OR REPLACE DATA GRANT hr.HRAPP_MANAGER_ACCESS
  AS SELECT (ALL COLUMNS EXCEPT ssn),
     UPDATE (salary, department_id, first_name)
  ON hr.employees
  WHERE manager_id = ORA_END_USER_CONTEXT.HR.EMP_CTX.ID
  TO hrapp_managers;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Notice what is not happening here: the Java service or AI agent is not building custom SQL for employees and managers. The app can issue the same business query, and Oracle filters or restricts the result according to the active end-user security context.&lt;/p&gt;

&lt;h3 id="optional-connection-metadata-in-tnsnames-ora"&gt;Optional Connection Metadata in tnsnames.ora&lt;/h3&gt;

&lt;p&gt;This step is optional. The provider demo and its Deep Data Security flow do not require Entra metadata in&amp;nbsp;&lt;code&gt;tnsnames.ora&lt;/code&gt;; use it when you want direct JDBC Thin interactive Entra sign-in or centrally managed TNS connection metadata.&lt;/p&gt;

&lt;p&gt;A wallet's&amp;nbsp;&lt;code&gt;tnsnames.ora&lt;/code&gt;&amp;nbsp;entry can carry that Entra-specific connection metadata. The key additions are&amp;nbsp;&lt;code&gt;token_auth=azure_interactive&lt;/code&gt;&amp;nbsp;and&amp;nbsp;&lt;code&gt;azure_db_app_id_uri=api://&amp;lt;DB_APP_CLIENT_ID&amp;gt;&lt;/code&gt;&amp;nbsp;inside the security block. A&amp;nbsp;&lt;code&gt;mydb_high&lt;/code&gt;&amp;nbsp;alias would look like this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;mydb_high =
  (description=
    (retry_count=20)(retry_delay=3)
    (address=(protocol=tcps)(port=1522)(host=adb.us-ashburn-1.oraclecloud.com))
    (connect_data=(service_name=asdf_mydb_high.adb.oraclecloud.com))
    (security=
      (ssl_server_dn_match=yes)
      (token_auth=azure_interactive)
      (azure_db_app_id_uri=api://4ad8a6b8-asdf-asdf-asdf-5d14bcc22c8a)))&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Use your own service name and database/API app ID URI.&amp;nbsp;&lt;code&gt;token_auth=azure_interactive&lt;/code&gt;&amp;nbsp;is useful for direct JDBC Thin testing because the driver can open the browser-based Entra sign-in flow.&amp;nbsp;&lt;code&gt;azure_db_app_id_uri&lt;/code&gt;&amp;nbsp;tells the driver which Entra resource represents the database.&lt;/p&gt;

&lt;p&gt;The Spring Boot demo itself uses a normal pooled application database user and attaches the Deep Data Security context programmatically, so I keep a separate application alias without&amp;nbsp;&lt;code&gt;token_auth=azure_interactive&lt;/code&gt;&amp;nbsp;for the UCP pool.&lt;/p&gt;





&lt;h2&gt;Entra Setup&lt;/h2&gt;

&lt;p&gt;All of the identity setup below happens in the Azure Portal under Microsoft Entra ID. You will move between App registrations and Enterprise applications.&lt;/p&gt;

&lt;h3&gt;App registration responsibilities&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;Registration / app&lt;/th&gt;
&lt;th&gt;Responsibility&lt;/th&gt;
&lt;th&gt;Where assignments happen&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spring Boot app registration&lt;/td&gt;
&lt;td&gt;Represents the Java web application and OAuth client. It owns the redirect URI, client ID, client secret, and application scope.&lt;/td&gt;
&lt;td&gt;It requests permission to call the database/API app on behalf of the user.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database/API app registration&lt;/td&gt;
&lt;td&gt;Represents Oracle AI Database as the protected resource. It defines the database scope and app roles such as EMPLOYEES and MANAGERS.&lt;/td&gt;
&lt;td&gt;User or group role assignments happen on the database/API Enterprise Application because that is the resource Oracle validates.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;1. Create the Spring Boot app registration&lt;/h3&gt;

&lt;p&gt;Create a separate app registration for the Spring Boot application. Add a Web redirect URI:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;http://localhost:8080/login/oauth2/code/entra&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;For the provider-version browser UI, also add a SPA redirect URI:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;http://localhost:18080&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Expose an API for the Spring Boot app too, using:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;api://&amp;lt;SPRING_BOOT_APP_CLIENT_ID&amp;gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Add a delegated user_impersonation scope. This scope is requested during browser login so the resulting token has the Spring Boot app as its audience. That is required for the on-behalf-of exchange.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpaulparkinson.github.io%2Foracle-ai-for-sustainable-dev%2Fsecurity%2Fdeepdatasecurity-api-version%2Fimages%2Fblog-azure-spring-app-overview.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpaulparkinson.github.io%2Foracle-ai-for-sustainable-dev%2Fsecurity%2Fdeepdatasecurity-api-version%2Fimages%2Fblog-azure-spring-app-overview.png" alt="Azure Portal overview page for the DeepSec Sample Spring Boot App registration, showing redacted identifiers and client credential links." width="800" height="450"&gt;&lt;/a&gt;Figure 1. Microsoft Entra app registration overview for the DeepSec Sample Spring Boot App with identifiers redacted.&lt;p&gt;&lt;/p&gt;

&lt;h3&gt;2. Create the database/API app registration&lt;/h3&gt;

&lt;p&gt;Create an app registration named something like Oracle AI Database DeepSec API. Set its Application ID URI to the default form:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;api://&amp;lt;DB_APP_CLIENT_ID&amp;gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;That default api://... URI is the Autonomous Database path I use in this walkthrough. If you are targeting Oracle AI Database Free or another non-ADB database, use a domain-qualified https://... Application ID URI instead and keep the later database configuration, scope, and tnsnames.ora values aligned.&lt;/p&gt;

&lt;p&gt;Add a delegated scope named user_impersonation. Add app roles named EMPLOYEES and MANAGERS. These are the role values Oracle later sees in the database-access token and maps to DDS data roles.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpaulparkinson.github.io%2Foracle-ai-for-sustainable-dev%2Fsecurity%2Fdeepdatasecurity-api-version%2Fimages%2Fblog-azure-database-enterprise-app-id.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpaulparkinson.github.io%2Foracle-ai-for-sustainable-dev%2Fsecurity%2Fdeepdatasecurity-api-version%2Fimages%2Fblog-azure-database-enterprise-app-id.png" alt="Azure Portal enterprise application overview for the Oracle AI Database DeepSec API, highlighting the application ID used for database scope and token audience." width="800" height="450"&gt;&lt;/a&gt;Figure 2. Microsoft Entra enterprise application overview for the Oracle AI Database DeepSec API with identifiers redacted.&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpaulparkinson.github.io%2Foracle-ai-for-sustainable-dev%2Fsecurity%2Fdeepdatasecurity-api-version%2Fimages%2Fblog-azure-create-app-role.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpaulparkinson.github.io%2Foracle-ai-for-sustainable-dev%2Fsecurity%2Fdeepdatasecurity-api-version%2Fimages%2Fblog-azure-create-app-role.png" alt="Azure Portal Create app role panel with EMPLOYEES entered as the display name and role value, allowed for users and groups." width="800" height="453"&gt;&lt;/a&gt;Figure 3. Azure Portal Create app role panel for an EMPLOYEES role.&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpaulparkinson.github.io%2Foracle-ai-for-sustainable-dev%2Fsecurity%2Fdeepdatasecurity-api-version%2Fimages%2Fblog-azure-app-roles-created.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpaulparkinson.github.io%2Foracle-ai-for-sustainable-dev%2Fsecurity%2Fdeepdatasecurity-api-version%2Fimages%2Fblog-azure-app-roles-created.png" alt="Azure Portal app roles list for the DeepSec Sample Spring Boot App showing EMPLOYEES and MANAGERS roles enabled for users and groups." width="800" height="450"&gt;&lt;/a&gt;Figure 4. Azure Portal app roles page showing EMPLOYEES and MANAGERS roles.&lt;p&gt;&lt;/p&gt;

&lt;p&gt;In the database/API app manifest, set requestedAccessTokenVersion to 2. In older Azure AD Graph and portal references this is often called accessTokenAcceptedVersion. It tells Microsoft Entra which access-token format the resource app supports. For this demo, the database-access token should decode with ver=2.0 and an aud value matching the database/API app client ID.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;"requestedAccessTokenVersion": 2&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I also added the upn optional claim for access tokens so the end-user context has a stable username value to compare against the sample hr.employees.user_name column.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpaulparkinson.github.io%2Foracle-ai-for-sustainable-dev%2Fsecurity%2Fdeepdatasecurity-api-version%2Fimages%2Fblog-azure-optional-upn-claim.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpaulparkinson.github.io%2Foracle-ai-for-sustainable-dev%2Fsecurity%2Fdeepdatasecurity-api-version%2Fimages%2Fblog-azure-optional-upn-claim.png" alt="Azure Portal token configuration page with the Add optional claim panel open and the upn access-token claim selected." width="800" height="465"&gt;&lt;/a&gt;Figure 5. Azure Portal token configuration blade adding the upn optional claim to access tokens.&lt;p&gt;&lt;/p&gt;

&lt;h3&gt;3. Connect the two registrations&lt;/h3&gt;

&lt;p&gt;In the Spring Boot app registration, open API permissions, add a permission from My APIs, choose the database/API registration, and grant its delegated user_impersonation scope.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpaulparkinson.github.io%2Foracle-ai-for-sustainable-dev%2Fsecurity%2Fdeepdatasecurity-api-version%2Fimages%2Fblog-azure-api-permissions.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpaulparkinson.github.io%2Foracle-ai-for-sustainable-dev%2Fsecurity%2Fdeepdatasecurity-api-version%2Fimages%2Fblog-azure-api-permissions.png" alt="Azure Portal API permissions page with Microsoft Graph User.Read and Oracle AI Database DeepSec API user_impersonation delegated permission listed." width="800" height="450"&gt;&lt;/a&gt;Figure 6. Azure Portal API permissions page showing Microsoft Graph User.Read and the Oracle AI Database DeepSec API user_impersonation scope.&lt;p&gt;&lt;/p&gt;

&lt;p&gt;Then go to Enterprise applications, open the database/API enterprise application, and assign users or groups to EMPLOYEES or MANAGERS. For database enforcement, the important assignment is on the database/API enterprise application, because that is the resource for the database-access token Oracle validates.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpaulparkinson.github.io%2Foracle-ai-for-sustainable-dev%2Fsecurity%2Fdeepdatasecurity-api-version%2Fimages%2Fblog-azure-enterprise-users-groups.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpaulparkinson.github.io%2Foracle-ai-for-sustainable-dev%2Fsecurity%2Fdeepdatasecurity-api-version%2Fimages%2Fblog-azure-enterprise-users-groups.png" alt="Azure Portal Enterprise application Users and groups page for the DeepSec Sample Spring Boot App with a user assigned to the EMPLOYEES role." width="800" height="450"&gt;&lt;/a&gt;Figure 7. Azure Portal Enterprise application Users and groups page showing a user assigned to the EMPLOYEES role.&lt;p&gt;&lt;/p&gt;

&lt;p&gt;The Spring Boot YAML in the next section binds these tenant, client, secret, database-scope, application-scope, and browser-client values from environment variables. The provider-version browser UI can use the same client ID or a separate public-client app registration.&lt;/p&gt;





&lt;h2&gt;The Spring Boot Code&lt;/h2&gt;

&lt;h3 id="oracle-ucp-connection-pooling"&gt;Oracle UCP Connection Pooling&lt;/h3&gt;

&lt;p&gt;Both demos let Spring Boot create Oracle UCP from&amp;nbsp;&lt;code&gt;spring.datasource&lt;/code&gt;&amp;nbsp;properties. The explicit API demo sets and clears&amp;nbsp;&lt;code&gt;EndUserSecurityContext&lt;/code&gt;&amp;nbsp;in service code; the provider demo adds its provider settings through&amp;nbsp;&lt;code&gt;spring.datasource.oracleucp.connection-properties&lt;/code&gt;. The Java side uses the 23.26.2.0.0 JDBC/UCP release, so keep the database and driver at that level or later when troubleshooting identity behavior.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;
  &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
  &amp;lt;artifactId&amp;gt;spring-boot-starter-jdbc&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;
&amp;lt;dependency&amp;gt;
  &amp;lt;groupId&amp;gt;com.oracle.database.jdbc&amp;lt;/groupId&amp;gt;
  &amp;lt;artifactId&amp;gt;ojdbc17-production&amp;lt;/artifactId&amp;gt;
  &amp;lt;version&amp;gt;${oracle.jdbc.version}&amp;lt;/version&amp;gt;
  &amp;lt;type&amp;gt;pom&amp;lt;/type&amp;gt;
&amp;lt;/dependency&amp;gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The Oracle production dependency POM keeps the JDK 17 JDBC driver, UCP, wallet support, and related companion artifacts on the same release line.&lt;/p&gt;

&lt;h3&gt;Explicit API Version&lt;/h3&gt;

&lt;h4&gt;OAuth2 client and database-token configuration&lt;/h4&gt;

&lt;p&gt;The explicit API application uses Spring Security OAuth2 login for the browser user and a separate&amp;nbsp;&lt;code&gt;deepsec.entra-id&lt;/code&gt;&amp;nbsp;block for the on-behalf-of database-token exchange:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;spring:
  security:
    oauth2:
      client:
        registration:
          entra:
            client-id: ${DEEPSEC_ENTRA_CLIENT_ID:}
            client-secret: ${DEEPSEC_ENTRA_CLIENT_SECRET:}
            client-authentication-method: client_secret_post
            authorization-grant-type: authorization_code
            redirect-uri: ${DEEPSEC_ENTRA_REDIRECT_URI:{baseUrl}/login/oauth2/code/{registrationId}}
            scope: ${DEEPSEC_ENTRA_APPLICATION_SCOPE:openid,profile,email}
        provider:
          entra:
            authorization-uri: https://login.microsoftonline.com/${DEEPSEC_ENTRA_TENANT_ID:}/oauth2/v2.0/authorize
            token-uri: https://login.microsoftonline.com/${DEEPSEC_ENTRA_TENANT_ID:}/oauth2/v2.0/token
            jwk-set-uri: https://login.microsoftonline.com/${DEEPSEC_ENTRA_TENANT_ID:}/discovery/v2.0/keys
            user-name-attribute: sub

deepsec:
  entra-id:
    tenant-id: ${DEEPSEC_ENTRA_TENANT_ID:}
    client-id: ${DEEPSEC_ENTRA_CLIENT_ID:}
    client-secret: ${DEEPSEC_ENTRA_CLIENT_SECRET:}
    database-scope: ${DEEPSEC_ENTRA_DATABASE_SCOPE:}&lt;/code&gt;&lt;/pre&gt;

&lt;h4&gt;Explicit security-context lifecycle&lt;/h4&gt;

&lt;p&gt;The service first receives the browser user's Entra token from Spring Security. Then it requests a database-access token with the OAuth 2.0 on-behalf-of flow:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;MultiValueMap&amp;lt;String, String&amp;gt; form = new LinkedMultiValueMap&amp;lt;&amp;gt;();
form.add("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer");
form.add("client_id", entraId.getClientId());
form.add("client_secret", entraId.getClientSecret());
form.add("scope", entraId.getDatabaseScope());
form.add("assertion", endUserToken);
form.add("requested_token_use", "on_behalf_of");&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Then the app attaches the Deep Data Security context to the Oracle JDBC connection. The method names are on&amp;nbsp;&lt;a href="https://docs.oracle.com/en/database/oracle/oracle-database/26/jajdb/oracle/jdbc/OracleConnection.html" rel="noopener noreferrer"&gt;oracle.jdbc.OracleConnection&lt;/a&gt;. The important calls are setEndUserSecurityContext(...) before SQL and clearEndUserSecurityContext() before returning the connection to the pool.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;OracleConnection oracleConnection =
  connection instanceof OracleConnection
    ? (OracleConnection) connection
    : connection.unwrap(OracleConnection.class);

EndUserSecurityContext context =
    EndUserSecurityContext.createWithToken(databaseAccessToken, endUserToken);

oracleConnection.setEndUserSecurityContext(context);
try {
    return runQuery(connection, sql);
}
finally {
    oracleConnection.clearEndUserSecurityContext();
}&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;Connection-pool hygiene:&amp;nbsp;&lt;/strong&gt;the finally block is not cosmetic. In a pooled application, clearing the context before returning a connection to the pool is mandatory.&lt;/p&gt;

&lt;h3 id="provider-version-ojdbc-provider-spring"&gt;Provider Version: ojdbc-provider-spring&lt;/h3&gt;

&lt;p&gt;The explicit API version is useful because it shows the moving parts: Spring obtains the signed-in user token, the app exchanges it for a database-access token, and application code calls OracleConnection.setEndUserSecurityContext(...). The provider version keeps the same database policy model but changes where that context is assembled.&lt;/p&gt;

&lt;p&gt;In the provider version, the protected HTTP endpoint is a Spring Security OAuth2 resource-server endpoint. A request reaches /deepsec/query with an OAuth2 bearer token. Spring validates the token and stores the authentication in the request security context. When the service borrows a JDBC connection, ojdbc-provider-spring reads that Spring Security context, obtains the database-access token through the configured OAuth2 client registration, and supplies the EndUserSecurityContext through the Oracle JDBC provider SPI.&lt;/p&gt;

&lt;p&gt;Compared with the API version, the service no longer calls OracleConnection.setEndUserSecurityContext(...) or clearEndUserSecurityContext(). The Deep Data Security context setup moves from application service code into JDBC/Spring provider configuration.&lt;/p&gt;

&lt;h4&gt;Provider dependency&lt;/h4&gt;

&lt;p&gt;The provider app adds the Oracle JDBC Spring provider dependency alongside the Oracle JDBC driver and wallet/security jars:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;
  &amp;lt;groupId&amp;gt;com.oracle.database.jdbc&amp;lt;/groupId&amp;gt;
  &amp;lt;artifactId&amp;gt;ojdbc-provider-spring&amp;lt;/artifactId&amp;gt;
  &amp;lt;version&amp;gt;${ojdbc.provider.version}&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;&lt;/code&gt;&lt;/pre&gt;

&lt;h4&gt;Provider configuration&lt;/h4&gt;

&lt;p&gt;Spring Boot 3.3 creates the Oracle UCP PoolDataSource directly from configuration, so the provider app no longer needs a dedicated&amp;nbsp;&lt;code&gt;UcpDataSourceConfiguration&lt;/code&gt;&amp;nbsp;class. Set the datasource type to UCP and put the JDBC provider settings under&amp;nbsp;&lt;code&gt;spring.datasource.oracleucp.connection-properties&lt;/code&gt;. The&amp;nbsp;&lt;code&gt;registrationId&lt;/code&gt;&amp;nbsp;value must match the Spring OAuth2 client registration that can obtain a database-access token for Oracle AI Database:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;spring:
  datasource:
    type: oracle.ucp.jdbc.PoolDataSource
    url: ${DEEPSEC_JDBC_URL:}
    username: ${DEEPSEC_USERNAME:}
    password: ${DEEPSEC_PASSWORD:}
    driver-class-name: oracle.jdbc.OracleDriver
    oracleucp:
      connection-factory-class-name: oracle.jdbc.datasource.impl.OracleDataSource
      connection-pool-name: ${DEEPSEC_UCP_POOL_NAME:DeepDataSecurityProviderUcpPool}
      initial-pool-size: ${DEEPSEC_UCP_INITIAL_POOL_SIZE:1}
      min-pool-size: ${DEEPSEC_UCP_MIN_POOL_SIZE:1}
      max-pool-size: ${DEEPSEC_POOL_SIZE:4}
      connection-properties:
        "oracle.jdbc.provider.endUserSecurityContext": ojdbc-provider-spring-end-user-security-context
        "oracle.jdbc.provider.endUserSecurityContext.registrationId": entra
  security:
    oauth2:
      client:
        registration:
          entra:
            client-name: entra
            client-id: ${DEEPSEC_ENTRA_CLIENT_ID:}
            client-secret: ${DEEPSEC_ENTRA_CLIENT_SECRET:}
            client-authentication-method: client_secret_post
            authorization-grant-type: ${DEEPSEC_ENTRA_DATABASE_GRANT_TYPE:client_credentials}
            scope: ${DEEPSEC_ENTRA_DATABASE_SCOPE:}
        provider:
          entra:
            authorization-uri: https://login.microsoftonline.com/${DEEPSEC_ENTRA_TENANT_ID:}/oauth2/v2.0/authorize
            token-uri: https://login.microsoftonline.com/${DEEPSEC_ENTRA_TENANT_ID:}/oauth2/v2.0/token

deepsec:
  jwt:
    trusted-issuers: ${DEEPSEC_ENTRA_JWT_TRUSTED_ISSUERS:https://sts.windows.net/${DEEPSEC_ENTRA_TENANT_ID:}/,https://login.microsoftonline.com/${DEEPSEC_ENTRA_TENANT_ID:}/v2.0}
  browser:
    enabled: ${DEEPSEC_BROWSER_ENABLED:true}
    auth-provider: entraid
    tenant-id: ${DEEPSEC_ENTRA_TENANT_ID:}
    client-id: ${DEEPSEC_ENTRA_BROWSER_CLIENT_ID:${DEEPSEC_ENTRA_CLIENT_ID:}}
    scope: ${DEEPSEC_ENTRA_APPLICATION_SCOPE:}&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Spring Boot binds the datasource portion to UCP and the OAuth2 portion to Spring Security. Oracle JDBC reads the provider properties when a pooled connection is borrowed, and the provider uses the matching OAuth2 client registration to request the database-access token. The&amp;nbsp;&lt;code&gt;deepsec.jwt&lt;/code&gt;&amp;nbsp;values configure trusted bearer-token issuers, while&amp;nbsp;&lt;code&gt;deepsec.browser&lt;/code&gt;&amp;nbsp;exposes only the non-secret values needed by the sample UI. Optional&amp;nbsp;&lt;code&gt;dataRoles&lt;/code&gt;&amp;nbsp;and&amp;nbsp;&lt;code&gt;endUserContextAttributes&lt;/code&gt;&amp;nbsp;properties can be added to the same&amp;nbsp;&lt;code&gt;connection-properties&lt;/code&gt;&amp;nbsp;map.&lt;/p&gt;

&lt;h4&gt;Resource-server security configuration&lt;/h4&gt;

&lt;p&gt;The provider app keeps /deepsec/query and /deepsec/whoami protected while allowing the browser page and setup endpoints to load. It also trusts both common Entra issuer forms for the configured tenant, which is helpful when one app registration issues v1 access tokens while another flow emits v2 tokens:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;http
  .authorizeHttpRequests(authorize -&amp;gt; authorize
      .requestMatchers(
          "/", "/index.html", "/app.js", "/styles.css",
          "/deepsec/browser-config", "/deepsec/health", "/deepsec/policies")
      .permitAll()
      .anyRequest().authenticated())
  .oauth2Client(Customizer.withDefaults())
  .oauth2ResourceServer(oauth2 -&amp;gt; oauth2
      .authenticationManagerResolver(jwtAuthenticationManagerResolver));

return JwtIssuerAuthenticationManagerResolver.fromTrustedIssuers(
    "https://sts.windows.net/" + tenantId + "/",
    "https://login.microsoftonline.com/" + tenantId + "/v2.0");&lt;/code&gt;&lt;/pre&gt;

&lt;h4&gt;Service code becomes ordinary JDBC&lt;/h4&gt;

&lt;p&gt;With the provider enabled, the service code is intentionally boring. It checks out a connection and executes SQL. The provider supplies the end-user security context when JDBC needs it:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;try (Connection connection = dataSource.getConnection();
     Statement statement = connection.createStatement()) {
    runSessionInit(statement);
    try (ResultSet resultSet = statement.executeQuery(sql)) {
        // read rows
    }
}&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In my demo environment I disable parallel query to avoid a database-side context handler issue with parallel execution:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;DEEPSEC_SESSION_INIT_SQL="alter session disable parallel query"&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If your database policy package works under parallel query, set that value to empty.&lt;/p&gt;

&lt;h4&gt;Browser-friendly provider flow&lt;/h4&gt;

&lt;p&gt;A pure resource-server endpoint still needs an Authorization: Bearer header. To make the demo natural in a browser without adding a separate frontend build, the provider version includes a tiny same-origin page. It uses MSAL to sign in with Entra, obtains an access token for the Spring Boot API scope, and calls the protected endpoint with that bearer token:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;const token = await getAccessToken();
const response = await fetch("/deepsec/query", {
  headers: { Authorization: `Bearer ${token}` }
});&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The backend exposes only non-secret browser configuration at /deepsec/browser-config: tenant ID, browser client ID, and scope. Client secrets and database passwords stay server-side in .env or a secret manager.&lt;/p&gt;

&lt;h4&gt;Provider runtime flow&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;The browser opens http://localhost:18080/.&lt;/li&gt;



&lt;li&gt;The page signs the user in with Entra and obtains an access token for api://&amp;lt;SPRING_BOOT_APP_CLIENT_ID&amp;gt;/user_impersonation.&lt;/li&gt;



&lt;li&gt;The page calls /deepsec/query with Authorization: Bearer &amp;lt;access-token&amp;gt;.&lt;/li&gt;



&lt;li&gt;Spring Security validates the token and creates the authenticated request context.&lt;/li&gt;



&lt;li&gt;ojdbc-provider-spring reads that context, obtains the database-access token with the entra OAuth2 client registration, and supplies the EndUserSecurityContext to Oracle JDBC.&lt;/li&gt;



&lt;li&gt;Oracle AI Database validates the context, activates mapped data roles, and enforces the same data grants shown earlier.&lt;/li&gt;
&lt;/ol&gt;





&lt;h2&gt;Running the Demo&lt;/h2&gt;

&lt;p&gt;The repository now has two runnable implementations. Use the API version when you want to teach the explicit OBO and OracleConnection API calls. Use the provider version when you want to show how a Spring resource-server endpoint can let ojdbc-provider-spring supply the Deep Data Security context.&lt;/p&gt;

&lt;h3&gt;Run the explicit API version&lt;/h3&gt;

&lt;p&gt;The API version uses Spring oauth2Login. Opening /deepsec/query in the browser redirects to Entra, then the controller receives an OAuth2AuthorizedClient and the service calls the Oracle JDBC Deep Data Security API directly.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;cd security/deepdatasecurity-api-version
cp .env_example .env
vi .env
./build.sh
./run.sh

# Then open:
http://localhost:8080/deepsec/query

# The employee and manager results are shown in the role-change demonstration below.&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;Run the provider/SPI version&lt;/h3&gt;

&lt;p&gt;The provider version is browser-first in the sample repo, but the backend remains a bearer-token resource server. Add http://localhost:18080 as a SPA redirect URI on the Entra app registration used by DEEPSEC_ENTRA_BROWSER_CLIENT_ID, then run:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;cd security/deepdatasecurity-provider-version
cp .env_example .env
vi .env
./build_and_run.sh

# Then open:
http://localhost:18080/

# Click Sign in, then Run query.
# Expected employee result in the page:
{"query":{"rows":["3:Emma Baker"]}}&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;For curl or Postman, request an Entra access token for DEEPSEC_ENTRA_APPLICATION_SCOPE and send it in the Authorization header:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;curl -H "Authorization: Bearer ${ACCESS_TOKEN}" \
  http://localhost:18080/deepsec/query&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;There is still no app-side data-role switch in either flow. Oracle AI Database reads the Entra roles claim and activates the mapped DDS roles automatically.&lt;/p&gt;





&lt;h2&gt;The Nice Part: Changing Roles Without Changing Code&lt;/h2&gt;

&lt;p&gt;I run the same endpoint twice to make the behavior obvious. There are two ways to demonstrate the change:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Two-user path:&lt;/strong&gt;&amp;nbsp;sign in as two different Entra users with different app-role assignments and matching employee or manager rows.&lt;/li&gt;



&lt;li&gt;
&lt;strong&gt;One-user path:&lt;/strong&gt;&amp;nbsp;keep one real demo user, change that user's database/API Enterprise Application role assignment, obtain a fresh token, and map the same Entra username to different sample HR rows.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the video I use my Paul Parkinson Entra user for the one-user path. In both paths, the user identity and role claims travel together in the security context.&lt;/p&gt;

&lt;h3&gt;Employee result&lt;/h3&gt;

&lt;p&gt;First, the signed-in Entra user has the EMPLOYEES app role, and one sample employee row is mapped to that user's&amp;nbsp;&lt;code&gt;ORA_END_USER_CONTEXT.username&lt;/code&gt;. The app executes the business SQL and Oracle returns only that employee row:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;{"rows":["3:Emma Baker"]}&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;Manager result&lt;/h3&gt;

&lt;p&gt;For the one-user path, use this sequence:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Assign that same real Entra user to the MANAGERS app role in the database/API enterprise application.&lt;/li&gt;



&lt;li&gt;Restart or otherwise force a fresh login so the token contains the updated role set.&lt;/li&gt;



&lt;li&gt;Map that user's username to a sample manager row for the one-user demo.&lt;/li&gt;



&lt;li&gt;Run the same GET /deepsec/query endpoint again.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The fresh token now carries MANAGERS instead of, or in addition to, EMPLOYEES. Oracle AI Database sees that role set during context creation, activates the matching DDS data role, and enforces the manager policy. For the one-user demo, mapping the same real Entra username to the sample manager row lets the manager end-user context resolve to the manager employee ID.&lt;/p&gt;

&lt;p&gt;The request is still&amp;nbsp;&lt;code&gt;GET /deepsec/query&lt;/code&gt;. The endpoint, Java code, connection pool, and SQL statement stay the same. The result changes because Oracle applies the manager data grant, while restricted columns remain protected by the database.&lt;/p&gt;

&lt;p&gt;In the sample HR data used in the walkthrough, the manager row has direct reports Emma, Charlie, and Dana. So the same /deepsec/query call moves from a single employee row to the manager's direct-report set:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;{"rows":["3:Emma Baker","4:Charlie Davis","5:Dana Lee"]}&lt;/code&gt;&lt;/pre&gt;





&lt;h2&gt;Helpful Tips&lt;/h2&gt;

&lt;p&gt;/deepsec/whoami is a setup/debug aid, not part of the authorization flow. It shows the exact username value Oracle sees in the end-user context. If your sample rows do not already match that value, use it to update one employee row for a quick demo:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;http://localhost:8080/deepsec/whoami

UPDATE hr.employees
   SET user_name = '&amp;lt;value returned by /deepsec/whoami&amp;gt;'
 WHERE employee_id = 101;
COMMIT;&lt;/code&gt;&lt;/pre&gt;

&lt;ul&gt;
&lt;li&gt;Empty rows can be success. If /deepsec/query returns {"rows":[]} with no exception, the DDS policy probably filtered everything. Make sure hr.employees.user_name matches the end-user context username.&lt;/li&gt;



&lt;li&gt;Use schema-qualified table names. With an active end-user context, unqualified employees can resolve unexpectedly. Use hr.employees.&lt;/li&gt;



&lt;li&gt;Make the database/API token v2. If Oracle reports an invalid database-access token, decode it and check ver=2.0 and an aud value matching the database/API app ID.&lt;/li&gt;



&lt;li&gt;Restart and use a fresh browser session after scope changes. The app reads .env at startup, and browsers cache login sessions aggressively.&lt;/li&gt;
&lt;/ul&gt;





&lt;h2&gt;Productionization Checklist&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Store the Entra client secret in a secret manager, not in a local file.&lt;/li&gt;



&lt;li&gt;Validate expected issuer, audience, scopes, tenant, and role claims explicitly.&lt;/li&gt;



&lt;li&gt;Manage data roles and data grants with versioned database migrations.&lt;/li&gt;



&lt;li&gt;Add integration tests that run the same query as employee and manager identities.&lt;/li&gt;



&lt;li&gt;Audit end-user contexts and data grant behavior in database monitoring.&lt;/li&gt;



&lt;li&gt;Always clear the end-user security context before returning a pooled connection.&lt;/li&gt;
&lt;/ol&gt;





&lt;h2&gt;References&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/paulparkinson/oracle-ai-for-sustainable-dev/tree/main/security" rel="noopener noreferrer"&gt;Demo source code&lt;/a&gt;&lt;/li&gt;



&lt;li&gt;&lt;a href="https://youtu.be/WfsqMX9QwPo" rel="noopener noreferrer"&gt;Video walkthrough&lt;/a&gt;&lt;/li&gt;



&lt;li&gt;&lt;a href="https://www.oracle.com/database/technologies/appdev/jdbc-downloads.html" rel="noopener noreferrer"&gt;Oracle Database JDBC driver and UCP downloads&lt;/a&gt;&lt;/li&gt;



&lt;li&gt;&lt;a href="https://blogs.oracle.com/developers/easy-configuration-of-ucp-with-spring-boot" rel="noopener noreferrer"&gt;Easy configuration of Oracle UCP with Spring Boot&lt;/a&gt;&lt;/li&gt;



&lt;li&gt;&lt;a href="https://blogs.oracle.com/developers/ucp-best-practices-for-oracle-database-19c-and-spring-boot" rel="noopener noreferrer"&gt;Oracle UCP best practices with Spring Boot&lt;/a&gt;&lt;/li&gt;



&lt;li&gt;&lt;a href="https://docs.oracle.com/en/database/oracle/oracle-database/26/jajdb/oracle/jdbc/OracleConnection.html" rel="noopener noreferrer"&gt;OracleConnection API reference, 26ai&lt;/a&gt;&lt;/li&gt;



&lt;li&gt;&lt;a href="https://github.com/oracle/ojdbc-extensions/tree/main/ojdbc-provider-spring" rel="noopener noreferrer"&gt;Oracle JDBC Providers for Spring, ojdbc-provider-spring&lt;/a&gt;&lt;/li&gt;



&lt;li&gt;&lt;a href="https://blogs.oracle.com/database/oracle-deep-data-security-is-now-available-in-oracle-ai-database-26ai" rel="noopener noreferrer"&gt;Oracle Deep Data Security announcement&lt;/a&gt;&lt;/li&gt;



&lt;li&gt;&lt;a href="https://livelabs.oracle.com/ords/r/dbpm/livelabs/run-workshop?p210_wid=4396" rel="noopener noreferrer"&gt;Richard C. Evans, LiveLabs FastLab: Identity-Aware Database Access with Microsoft Entra ID and Oracle Deep Data Security&lt;/a&gt;&lt;/li&gt;



&lt;li&gt;&lt;a href="https://docs.oracle.com/en/database/oracle/oracle-database/26/ddscg/understand-oracle-deep-data-security.html" rel="noopener noreferrer"&gt;Oracle Deep Data Security concepts&lt;/a&gt;&lt;/li&gt;



&lt;li&gt;&lt;a href="https://docs.oracle.com/en/database/oracle/oracle-database/26/ddscg/configure-data-grants.html" rel="noopener noreferrer"&gt;Configure Deep Data Security data grants&lt;/a&gt;&lt;/li&gt;



&lt;li&gt;&lt;a href="https://docs.oracle.com/en/database/oracle/oracle-database/26/sqlrf/create-data-grant.html" rel="noopener noreferrer"&gt;SQL Reference: CREATE DATA GRANT&lt;/a&gt;&lt;/li&gt;



&lt;li&gt;&lt;a href="https://docs.oracle.com/en/database/oracle/oracle-database/26/dbseg/authenticating-and-authorizing-microsoft-entra-id-ms-ei-users-oracle-databases-oracle-exadata-datab.html" rel="noopener noreferrer"&gt;Microsoft Entra ID with Oracle AI Database&lt;/a&gt;&lt;/li&gt;



&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/identity-platform/reference-app-manifest" rel="noopener noreferrer"&gt;Microsoft Entra app manifest reference&lt;/a&gt;&lt;/li&gt;



&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/identity-platform/access-tokens" rel="noopener noreferrer"&gt;Microsoft identity platform access tokens&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;





&lt;h2&gt;FAQ&lt;/h2&gt;

&lt;h3&gt;What problem does this pattern solve?&lt;/h3&gt;

&lt;p&gt;It lets a Java service keep the operational benefits of a pooled application database user while still allowing Oracle AI Database to enforce access for the signed-in end user.&lt;/p&gt;

&lt;h3&gt;Why are there two tokens?&lt;/h3&gt;

&lt;p&gt;The user token proves who signed in to the Spring Boot app. The database-access token is addressed to the Oracle AI Database resource and carries the claims Oracle validates for role mapping and data grants.&lt;/p&gt;

&lt;h3&gt;Do employees and managers need different Java code?&lt;/h3&gt;

&lt;p&gt;No. The same endpoint, SQL statement, and connection pool are used. Different results come from Entra role claims mapped to Oracle Deep Data Security data roles.&lt;/p&gt;

&lt;h3&gt;Where should Entra role assignments be made?&lt;/h3&gt;

&lt;p&gt;Assign users or groups on the database/API Enterprise Application, because that application represents the Oracle AI Database resource for the database-access token.&lt;/p&gt;

&lt;h3&gt;Does the provider version still require a bearer token?&lt;/h3&gt;

&lt;p&gt;Yes. The backend endpoint is a resource-server endpoint. The browser sample obtains the token with MSAL and sends it automatically, while curl or Postman must send the Authorization header explicitly.&lt;/p&gt;

&lt;h3&gt;Which version should I show first?&lt;/h3&gt;

&lt;p&gt;Show the API version first when the audience needs to see the token exchange and EndUserSecurityContext calls. Show the provider version when the audience is focused on Spring configuration and reducing database-security code in services.&lt;/p&gt;

</description>
      <category>java</category>
      <category>oracle</category>
      <category>ai</category>
      <category>database</category>
    </item>
  </channel>
</rss>
