jcardena.com Blog Where the Agent Ends: Architecting the Deterministic Interface to LLM Agents
145 posts
EN ES

Where the Agent Ends: Architecting the Deterministic Interface to LLM Agents

Software

Architecting reliable LLM agent systems requires a deterministic interface, treating agents as untrusted services. This post details building a robust harness with strict schemas and supervised contro

The demo is always flawless. An agent receives a vague user request, reasons through a plan, calls a few tools, and produces a perfect result. It feels like magic. Then you ship it. At 3 a.m., an alert fires because the agent hallucinated a string for a crucial `item_id` parameter on an internal inventory endpoint, expecting an integer UUID. Instead of a clear type error, a downstream system silently failed to process an order, corrupting a user’s state. The magic is gone, and you’re left with a non-deterministic mess.

I’ve spent years building systems where different paradigms meet—web services, data pipelines, and now AI. The pattern is always the same: reliability is born at the interfaces. Agentic systems are no different. The most important architectural decision you'll make is defining the boundary where the agent's fuzzy, probabilistic world ends and your system's crisp, deterministic logic begins.

The Agent is an Untrusted Service

The first mistake I see teams make is treating the LLM agent as a trusted component inside their core application logic. It isn’t. An agent is a powerful but unpredictable black box. You wouldn't let a brand-new junior developer merge directly to main without a code review; you shouldn't let an agent execute actions against your systems without a similar boundary.

The most robust pattern is to wrap the agent in a "deterministic harness." Your core system never talks to the LLM directly. Instead, it communicates with this harness, which serves as an adapter or gateway, responsible for managing the agent's entire lifecycle for a given task. The harness translates a structured request from your system into a prompt, constrains the agent's tool access, and validates its response before passing it back. It ensures a stable contract to the rest of the system, even if the service behind it is unpredictable.

The Agent is anYour Only DefenseisThe Control LoopStaysObservability forthe UnpredictableBoring Foundationsfor Radical
How this unfolds

Your Only Defense is the API Contract

At this boundary, the API contract is everything. Vague, string-based interfaces are a recipe for production failures. A robust contract between your deterministic code and the agent harness must be explicit about three things:

First, the **input schema**. Don’t just pass the agent a raw user query. Give it a structured object containing the goal, relevant data, constraints, and any context it needs. Libraries like Pydantic are invaluable here for defining clear, enforceable data models. This reduces ambiguity and gives the model a much better chance of success.

Second, the **tool manifest**. This is critical. The agent should not have access to every tool your system exposes. For each specific task, the harness should generate a manifest listing only the tools (functions, APIs) that are safe and relevant. If a user asks to "summarize my latest report," the agent gets the `get_report` and `summarize_text` tools—and nothing else. This is the principle of least privilege, and it prevents a whole class of disastrous side effects.

Third, the **output schema**. The agent must be constrained to respond in a predictable, structured format. While platforms like OpenAI's Structured Outputs can guarantee syntactic correctness (e.g., valid JSON), they don't guarantee semantic or business-rule validity. Your harness needs to validate the agent's output against a precise schema, often using libraries like Instructor (built on Pydantic) to parse and validate. If the output is malformed, fails type checking, or violates business rules (e.g., an agent trying to set a negative price), you have a clean failure path. Simple regex or naive `json.loads` will inevitably fail at 3 a.m. when the LLM outputs a trailing comma or an unexpected escaped character.

The Control Loop Stays in Your Code

The allure of autonomous agents is watching them create and execute multi-step plans. Tools like AutoGPT or CrewAI, and even the academic formulation in the ReAct paper (Yao et al., 2022), often present a self-correcting loop where the model itself decides the next step and directly executes actions. This "pure autonomous agent" pattern works well for sandbox environments or local research tasks like code generation, where state mutations are contained and easily rolled back.

However, in a production system, especially one involving transactional enterprise databases (ERP, finance, healthcare) where state mutations cannot be rolled back easily, you cannot cede the control loop to the LLM. The agent doesn’t get to decide what to do next on its own. It *proposes*, and your deterministic code *disposes*.

Here’s what that looks like in practice:

  1. Your code gives the agent a goal and its allowed tools.
  2. The agent thinks and returns a single proposed next step (e.g., "call `get_user_data` with `user_id: 123`").
  3. Your harness intercepts this proposal. It checks: Is this tool in the manifest? Are the parameters valid against their Pydantic schema? Does this violate a business rule (e.g., calling `delete_all_users`)?
  4. Only if the proposal is valid does your deterministic code actually execute the tool. It owns the side effect.
  5. The result of the tool execution is passed back to the agent as new context, and the loop repeats.

This "supervised ReAct" loop is the key to building debuggable systems. When something goes wrong, you have a perfect execution trace. You can see the exact sequence of proposals from the agent and the decisions your code made. In a fully autonomous loop, the agent's reasoning is an opaque, internal state. Here, it’s a series of explicit, auditable requests.

Observability for the Unpredictable

You can't debug what you can't see. Traditional logs that only capture the final outcome are useless for agentic systems. At the deterministic interface, you need to log the entire conversation to understand *why* a failure occurred.

For every cycle of the control loop, I make sure we log:

  • The precise prompt and tool manifest sent to the LLM.
  • The raw, complete response from the LLM, including any chain-of-thought reasoning.
  • The agent's parsed proposal (the tool and its parameters).
  • The result of the harness's validation (pass or fail, and why).
  • The final structured data passed back to the core system.

This level of detail is non-negotiable. When an agent starts behaving strangely, these logs are the only way to reconstruct its "thought process." They allow you to find the ambiguity in your prompt or the flaw in your tool design that led it astray. Without this, you're just guessing.

SOURCESAppsEventsFilesAPIsINGESTIONStreamingBatch loadersPROCESSINGDeterministicpipelineLLM agentsValidation gateSTORAGELakehouseVector storeSERVINGAPIsDashboardsAgent tools
Reference data + AI architecture

Boring Foundations for Radical Outcomes

It feels counterintuitive. I'm building these dynamic, intelligent systems, but the secret to making them work in production is to constrain them with "boring" software engineering principles: strong contracts, validation, and clear boundaries. As practitioners like Hamel Husain consistently advocate, robust evaluation and deterministic constraints are often more valuable than pure, unconstrained "magic." The innovation isn't just in the LLM; it's in the architecture that safely connects it to the real world.

Here are the rules that hold up in production:

  • Treat the agent as a third-party API. It’s powerful, external, and not fully under your control. Architect accordingly.
  • Define rigid schemas for inputs and outputs. Your harness validates everything that crosses the boundary, in both directions, using tools like Pydantic.
  • Use task-specific tool manifests. Grant the minimum viable permissions for every single task.
  • Own the control loop. The agent proposes steps; your deterministic code is the final arbiter and executor of those steps.
  • Log the whole conversation at the boundary. When things fail, the "why" is in the prompt, the reasoning, and the validation result.

The most powerful agentic systems won't be the ones that run wild. They will be the ones built on these robust, deterministic foundations. The interface is where you contain the chaos, and in doing so, create systems that are not only capable, but also reliable and maintainable.

JC
Juan Cardena
Enterprise Architect, Data & AI

Enterprise architect with 25 years across web, software, data, and AI. MIT CDAO ’25. Writing on agentic AI in production.