<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Damian Rybicki's blog]]></title><description><![CDATA[Damian Rybicki's blog]]></description><link>https://drybicki.hashnode.dev</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1593680282896/kNC7E8IR4.png</url><title>Damian Rybicki&apos;s blog</title><link>https://drybicki.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Mon, 28 Sep 2026 12:02:51 GMT</lastBuildDate><atom:link href="https://drybicki.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[AI Agents in Oracle APEX 26.1 - Build a Secure Agent Architecture That Won't Destroy Your Database]]></title><description><![CDATA[Integrating AI into enterprise applications is the number one topic right now. In earlier APEX versions like 24.1, we were amazed by the "AI Assistants" component, which were essentially classic chatb]]></description><link>https://drybicki.hashnode.dev/ai-agents-in-oracle-apex-26-1-build-a-secure-agent-architecture-that-won-t-destroy-your-database</link><guid isPermaLink="true">https://drybicki.hashnode.dev/ai-agents-in-oracle-apex-26-1-build-a-secure-agent-architecture-that-won-t-destroy-your-database</guid><category><![CDATA[Oracle]]></category><category><![CDATA[#oracle-apex]]></category><category><![CDATA[PL/SQL]]></category><category><![CDATA[orclapex]]></category><category><![CDATA[APEX-26.1]]></category><category><![CDATA[ai agents]]></category><dc:creator><![CDATA[Damian Rybicki]]></dc:creator><pubDate>Fri, 22 May 2026 10:06:43 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6a0f841bd8e265f60d6511be/f2d82a64-dd2d-43f1-a3ae-91557078876c.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Integrating AI into enterprise applications is the number one topic right now. In earlier APEX versions like 24.1, we were amazed by the "AI Assistants" component, which were essentially classic chatbots. We could ask the bot to summarize a piece of text or generate a query. But let's be honest: merely answering questions is simply not enough in a process-driven business.</p>
<p>The problem is that giving LLMs direct database access is asking for trouble. That’s why APEX 26.1 takes a different approach, isolating the model using the AI Agents and AI Tools components. The creators establish clear security boundaries here:</p>
<blockquote>
<p>The model does not directly query your tables or run arbitrary code. Instead, it works from a curated list of capabilities you expose declaratively.</p>
</blockquote>
<p>In today’s post, we will put this theory into practice and build a working application. We will create a bank call center assistant that not only checks and blocks a customer's card, but then sends the appropriate notification to the client, creates an entry in the audit logs, and finally updates the report on the screen. To keep the focus on the architecture rather than complex UI, our demo will use a straightforward setup: a standard Interactive Report paired with an overlaying modal AI Assistant. The entire solution will be built on solid engineering foundations: the business logic will remain securely in PL/SQL packages, and a declarative Human-in-the-loop mechanism will ensure the model never makes a critical decision on its own.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a0f841bd8e265f60d6511be/e0cd149a-8cd6-4638-b11f-5bde85a72f57.png" alt="" style="display:block;margin:0 auto" />

<h3>APEX 26.1 Tool Types - Understanding the "Agent Loop"</h3>
<p>To keep AI in check, APEX 26.1 provides us with AI Tools. Under the hood, these tools can operate in one of two modes:</p>
<ul>
<li><p><strong>Augment System Prompt</strong> – these run in the background with every new message. They are used to discreetly inject necessary variables into the context, such as the employee's username or their local time zone.</p>
</li>
<li><p><strong>On Demand</strong> – this is a set of specific skills. The agent utilizes them only when it determines they are needed to solve a problem. This is how it initiates the so-called decision loop (Agent Loop).</p>
</li>
</ul>
<p>When it comes to the actions themselves, APEX natively offers three main types of tools:</p>
<ul>
<li><p><strong>Retrieve Data (SQL)</strong> – secure database reads (e.g., verifying customer data or order status).</p>
</li>
<li><p><strong>Execute Server-side Code (PL/SQL)</strong> – for triggering backend business logic and modifying data.</p>
</li>
<li><p><strong>Execute Client-side Code (JavaScript)</strong> – it allows the agent to directly manipulate the interface in the user's browser (e.g., displaying notifications or opening dialog windows).</p>
</li>
</ul>
<h3>Demo in Action: Tool Chaining Step-by-Step</h3>
<p>Enough theory - let's see this in a live system. I have logged into a support agent account. In front of me is the standard view: a report showing credit cards and a button to open the AI chat window.</p>
<p>Let's see the Agent Loop mechanism in a real-world scenario. We inform the assistant that a customer, Arthur Johnson, has lost his wallet containing his documents, and we want to block his card. Let's watch the cascade of events that unfolds in the background.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a0f841bd8e265f60d6511be/3103170d-63a1-4036-b50e-b7e36cb87f03.png" alt="" style="display:block;margin:0 auto" />

<p>As seen in the screenshot above, the model correctly mapped the first and last name, and then queried the database using the Retrieve Data tool. But what is most interesting here? The model didn't act blindly. Instead of blocking the first available card, it recognized from the query results that the client has two cards. It paused and asked for clarification.</p>
<p>I then provide the specifics: "Block card ID 201. Reason: Lost wallet."</p>
<p>At this moment, the agent parses my intent, extracts the ID, and attempts to execute the PL/SQL procedure. This is where APEX steps in to halt the action. In line with the principle of zero trust, the system prevents any autonomous data modifications and enforces the Human-in-the-loop mechanism. Instead of a silent database update, a native modal pops up in the center of the screen:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a0f841bd8e265f60d6511be/d39d01ac-f84a-4cb5-b412-206bee56d91c.png" alt="" style="display:block;margin:0 auto" />

<p>This is exactly how our strict safeguard works in practice - the built-in Requires Confirmation option. Only after explicitly clicking <strong>[Confirm]</strong> does APEX allow the request to proceed, letting the database execute the PL/SQL package. But the flow doesn't end there. Once the agent gets the green light from the database (execution confirmed), it automatically triggers the next two blocks in its chain. First, it calls a PL/SQL procedure to send an email to the client, and immediately after, it uses Execute Client-side Code to run a specific JS script directly in our browser.</p>
<p>Actually, just look at the background of the screenshot below — you can instantly see the result of this final action. The report has refreshed on the fly; the card now has an updated status and an assigned reason for the block. To neatly wrap up the whole process, the agent outputs a brief summary of all completed steps in the chat window. And here is a little cherry on top proving that the model retains context: at the very end, it asks whether, given the lost wallet, we want to go ahead and block the customer's remaining card as well.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a0f841bd8e265f60d6511be/e5b50236-67fd-4a45-8a9b-dd9ab684e2af.png" alt="" style="display:block;margin:0 auto" />

<p><em>We've just seen the full chain: Read -&gt; Intent -&gt; Human Authorization -&gt; Database Change -&gt; Communication -&gt; UI Refresh. How do we build this? Let's dive under the hood.</em></p>
<h3>Step 1: Defining and Securing the Agent</h3>
<p>I have configured Generetive AI Service that use Claude Models, you can check how to configure it <a href="https://docs.oracle.com/en/database/oracle/apex/26.1/htmdb/creating-generative-ai-service-objects.html#GUID-DB03722F-56E7-4B5A-91E6-3A3D504D97ED">here</a>.</p>
<p>We navigate to Shared Components -&gt; AI Agents. This is where we configure the core engine of our assistant. We provide it with the appropriate operational context and define strict business rules. This is exactly where we clearly instruct the model on which boundaries it must not cross, and immediately secure it against attempts to extract these instructions (protection against Prompt Leakage).</p>
<p>Our base configuration looks like this:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a0f841bd8e265f60d6511be/195a803e-b77c-496b-bc6f-e44999ef4ef6.png" alt="" style="display:block;margin:0 auto" />

<h3>Step 2: Retrieving Database Data (Retrieve Data)</h3>
<p>It's time to give the agent access to customer information. We create a new tool named GET_CLIENT_CARDS (selecting Retrieve Data as the type) and attach the following configuration:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a0f841bd8e265f60d6511be/a310f416-2d7d-44e5-8aa3-bd1e16a893a8.png" alt="" style="display:block;margin:0 auto" />

<p>As a side note, this query is obviously tailored strictly for our demo. In a production environment, searching by first and last name alone is asking for trouble due to potential duplicates. For the sake of this post, however, we don't need to write massive queries. We intentionally took a shortcut here because potential collisions (like having three people named Arthur Johnson in the database) are handled elegantly by the agent's reasoning capabilities. The model is safeguarded by specific directives in the System Prompt and the Tool Description—it knows that if the database spits out more than one record, it must halt the entire flow and ask us to clarify.</p>
<h3>Step 3: Server-side Code, Strict Validation, and "Human-in-the-loop"</h3>
<p>In the Oracle documentation, we can find a very promising excerpt:</p>
<blockquote>
<p>In the tool configuration, developers can enable Requires Confirmation (...) This allows an AI Agent tool to pause for explicit user consent before continuing, without requiring custom client-side confirmation code (...)</p>
</blockquote>
<p>We take them at their word. We create the BLOCK_CARD tool (type: <strong>Execute Server-side Code</strong>) and check this crucial flag: <strong>Requires Confirmation</strong>. This acts as our primary safeguard—it pauses the process and displays a native authorization modal to the user, all without having to write any custom JS code from scratch.</p>
<p>From the APEX perspective, the tool's code is just a simple wrapper calling our <code>secure_card_pkg</code> database package:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a0f841bd8e265f60d6511be/883b06eb-cbd1-4406-8826-e6855e98d46f.png" alt="" style="display:block;margin:0 auto" />

<p>The actual business logic lives in the database. Examine the code below, paying particular attention to the <code>apex_ai.set_tool_result</code> API. This package makes all the difference—it allows our PL/SQL code to report the operation status back to the LLM agent, completely overriding the tool's default response.</p>
<pre><code class="language-sql">CREATE OR REPLACE PACKAGE BODY secure_card_pkg IS

PROCEDURE block_card(p_card_id IN NUMBER, p_reason IN VARCHAR2) IS
    v_status VARCHAR2(20);
BEGIN
    -- 1. Hard Validation: Does the card exist?
    BEGIN
        SELECT status INTO v_status FROM bank_cards WHERE card_id = p_card_id;
    EXCEPTION
        WHEN NO_DATA_FOUND THEN
            apex_ai.set_tool_result(
                p_result =&gt; 'ERROR: The specified card ID does not exist.'
            );
            RETURN;
    END;

    -- 2. Business Validation: Is the card already blocked?
    IF v_status = 'BLOCKED' THEN
        apex_ai.set_tool_result(
            p_result =&gt; 'INFO: This card is already blocked. No action taken.'
        );
        RETURN;
    END IF;

    -- 3. Core Business Action
    UPDATE bank_cards 
    SET status = 'BLOCKED', 
        blocked_reason = p_reason 
    WHERE card_id = p_card_id;

    -- 4. Secure Audit Trail
    INSERT INTO ai_audit_logs (username, action_name, card_id, reason)
    VALUES (NVL(v('APP_USER'), 'AI_AGENT'), 'BLOCK_CARD', p_card_id, p_reason);

    COMMIT;

    -- 5. CRITICAL: Reporting success back to the Agent Loop
    apex_ai.set_tool_result(
        p_result =&gt; 'SUCCESS: The card has been securely blocked.'
    );
        
    EXCEPTION
        WHEN OTHERS THEN
            ROLLBACK;
            apex_ai.set_tool_result(
                p_result =&gt; 'ERROR: Unexpected database error.'
            );
    END block_card;

END secure_card_pkg;
</code></pre>
<p>Why is this double validation so important? Because when designing an AI integration, we must assume that the model can make mistakes. Additionally, we have to handle concurrency issues.</p>
<p>Imagine the agent hallucinates a card ID out of thin air. Or a real-life scenario: a customer calls to block a card, but it has already been blocked by someone else. Our PL/SQL package prevents hard database errors from bubbling up to the frontend. Instead, it catches the exception and returns simple feedback to the model via <code>apex_ai.set_tool_result</code>.</p>
<p>The model reads this technical status, understands it, and smoothly translates it into human language, replying to the employee: "It looks like the card is already blocked in the system, so I haven't taken any further action." This is what we call graceful error handling. The user understands the situation, and the application flow remains uninterrupted.</p>
<p>For sending notifications, we create a twin tool, NOTIFY_CLIENT, hooked up to our mailing procedure.</p>
<h3>Step 4: Client-side Code - Driving the Browser</h3>
<p>The card is blocked, the email is sent, so for the final step, we need to update the employee's interface so they can see the changes firsthand. To achieve this, we create the REFRESH_DASHBOARD tool (type: Execute Client-side Code).</p>
<p>The catch here is that frontend operations are inherently asynchronous. To prevent the model from marking the task as completed prematurely, the APEX documentation suggests a simple trick. We need to wrap the entire action in a Promise object. This forces the agent to pause - the model will only move forward and display the summary in the chat once the report has physically reloaded on the screen.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a0f841bd8e265f60d6511be/5d333d9f-b342-4d5e-84b7-c273fb3e5056.png" alt="" style="display:block;margin:0 auto" />

<h3>Red Team: Attempting to Hack Your Own Agent</h3>
<p>The APEX architecture cleverly takes the burden of securing the LLM communication off our shoulders. But a good developer never takes anything on blind faith. Let's see what happens when a malicious employee (a classic Insider Threat) tries to weaponize our decision loop for an attack.</p>
<p>We fire up a timeless classic: Prompt Injection mixed with SQL Injection. I paste the following payload into the chat:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a0f841bd8e265f60d6511be/046f0e57-4c7b-4f65-8f58-b1ccd66362bb.png" alt="" style="display:block;margin:0 auto" />

<p>The result? We hit a brick wall. And a much smarter one than you might expect. Instead of mindlessly invoking the tool, the agent parses the payload and then "politely" replies:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a0f841bd8e265f60d6511be/025e3545-83bd-4e17-90d3-fbba0962924e.png" alt="" style="display:block;margin:0 auto" />

<p>What just happened here? We are looking at a textbook example of Defense in Depth. We have two completely independent lines of defense:</p>
<p><strong>Line 1: The Model's Guardrails (Soft Protection).</strong> Modern LLMs are no longer just dumb next-word prediction engines. Thanks to a tight System Prompt (our ZERO TRUST rule from Step 1), the model instantly caught the jailbreak attempt and recognized the SQL Injection syntax. The outcome? A hard refusal to execute the action. This is our first barrier.</p>
<p><strong>Line 2: The Ironclad APEX Architecture (In Case the Model Goes Rogue).</strong> But let's consider this: what if tomorrow someone discovers a new, exceptionally sneaky jailbreak that manages to trick the model? What if the manipulated Agent actually sends this malicious payload to the BLOCK_CARD tool?</p>
<p>This is where the attacker would crash into the APEX architecture, which simply does not trust LLMs:</p>
<ul>
<li><p><strong>Defused SQLi:</strong> APEX natively validates parameters from the agent against a JSON schema. Even if this DROP statement somehow slipped through, it ultimately lands in bind variables (:P_REASON). In the worst-case scenario, our dangerous attack would simply be saved as a plain text string in the blocked_reason column.</p>
</li>
<li><p><strong>No Free Rein:</strong> The model isn't running wild in our database; it only calls an API with specific tools. Any attempt to execute a data modification would still be halted by the Requires Confirmation option. The attacker would have to manually authorize their own attack via a native popup window, leaving an undeniable, named trail in the audit logs (ai_audit_logs).</p>
</li>
</ul>
<h3>Summary</h3>
<p>AI Agents in APEX 26.1 bring a completely fresh approach to the table. Instead of building fancy chatbots, we demote this massive, unpredictable LLM from being the main decision-maker to functioning as a highly intelligent user intent parser. The hard decision-making logic stays exactly where it belongs - in auditable PL/SQL packages - while critical actions still require a human click. And this is the kind of AI integration you can confidently take to production without giving your Security department a heart attack.</p>
]]></content:encoded></item></channel></rss>