Skip to content
Francesco Castaldi
← Blog

Tech

Building Deterministic AI Agents: Beyond Fragile Prompt Chains

Architectural patterns for production-grade coding agents: closed-loop control, context compaction, Model Context Protocol, and fail-closed sandboxing.

Francesco Castaldi6 min read

Building a prototype that asks an LLM to generate code takes an afternoon. Building an autonomous agent that reliably diagnoses production incidents, navigates multi-gigabyte codebases, and writes verified bug fixes without human babysitting requires a fundamentally different mindset.

In demo applications, failures are humorous. In production engineering environments, non-deterministic agents create corrupted Git states, introduce subtle security vulnerabilities, exhaust token budgets, and hallucinate passing tests.

Moving from toy prompt chains to production-grade autonomous systems is not a matter of writing longer prompts. It is an exercise in distributed systems engineering: implementing closed-loop control, managing state invariants, bounding context windows, and establishing strict verification gates.

Here is an architectural breakdown of the principles required to build reliable, deterministic AI agents for software engineering.

The Closed-Loop Control Architecture

Most naive agent implementations rely on open-loop generation: the system receives a user request, executes a sequence of prompt completions, runs a bash script, and assumes the task is complete.

Production systems must operate as closed-loop controllers. The agent does not simply generate actions; it continuously measures state transitions against explicit invariants.

┌──────────────┐     ┌──────────────┐     ┌──────────────┐
│  Goal State  │────►│  Track/Plan  │────►│  Tool Calls  │
└──────────────┘     └──────────────┘     └──────┬───────┘
       ▲                                         │
       │          ┌──────────────┐               ▼
       └──────────│ Verification │◄─────── Worktree Env
                  │ (TDD/Checks) │         (Sandboxed)
                  └──────────────┘

The control cycle consists of four distinct phases:

  1. Observation: Read disk state, environment variables, compiler diagnostics, and test runner outputs directly, never relying on prior assumptions.
  2. Intent Formulation: Break complex requirements into atomic, verifiable sub-tasks with discrete exit conditions.
  3. Deterministic Action: Execute typed tools with strict schema validation rather than unstructured shell commands.
  4. Verification: Run objective test harnesses to validate state before committing or declaring success.

[!IMPORTANT] An agent must never rely on its own internal reasoning to verify code changes. Verification must always come from external, deterministic runtimes: unit test suites, typecheckers, linters, and compiler exit codes.

Context Window Engineering: The Token Budget

Context windows are often treated as infinite scratchpads. In practice, long-running agentic tasks quickly suffer from context dilution, attention drift, and quadratic latency overhead.

When an agent's context exceeds tens of thousands of tokens of raw build logs, stack traces, and repeated tool outputs, reasoning quality drops significantly.

Eager vs Lazy Tool Schema Loading

A common mistake is injecting every available tool schema into the initial system prompt. In enterprise environments with dozens of integrations, schemas alone can consume half of the context window before work begins.

Modern protocols like the Model Context Protocol (MCP) solve this through tiered discovery:

  • Eager Tools: Core filesystem and command primitives required for orientation and basic navigation.
  • Lazy Tools: Domain-specific tools (database query inspectors, PR reviewers, cloud deployments) described only by lightweight declarations. The agent inspects full JSON schemas on demand only when invoked.
// Lightweight tool declaration: minimal context overhead
interface LazyToolDescriptor {
  name: string;
  description: string;
  server: string;
}
 
// Full schema is fetched on demand only when the agent routes to it
async function resolveToolDefinition(descriptor: LazyToolDescriptor): Promise<ToolSchema> {
  const schemaPath = path.join(MCP_REGISTRY, descriptor.server, `${descriptor.name}.json`);
  const rawSchema = await fs.promises.readFile(schemaPath, "utf-8");
  return JSON.parse(rawSchema);
}

Context Compaction and Milestone Checkpoints

Long-running workflows (such as multi-file refactoring or cross-repo dependency upgrades) must compact state proactively.

A resilient agent maintains an append-only transaction ledger on disk (such as a session state manifest). When token consumption crosses predefined thresholds, the orchestrator triggers compaction:

  • Milestone Collapsing: Intermediate trial-and-error logs and exploratory file reads are summarized into completed milestones.
  • Invariant Preservation: High-priority constraints (target repository branch, signed-off identity, formatting limits) remain pinned in context.
  • Ephemeral Pruning: Terminal outputs from passing commands are discarded once the exit code (0) is recorded.

Deterministic Tool Execution via MCP

Relying on raw shell execution (bash -c "..." or PowerShell) introduces catastrophic failure modes: character escaping bugs, character encoding corruption (Mojibake), hanging child processes, and platform disparities.

Production agents must interact through typed interfaces governed by JSON Schema:

{
  "name": "create_pull_request_review",
  "description": "Submit a formal code review on a pull request",
  "parameters": {
    "type": "object",
    "required": ["owner", "repo", "pull_number", "event", "body"],
    "properties": {
      "owner": { "type": "string" },
      "repo": { "type": "string" },
      "pull_number": { "type": "integer" },
      "event": { "type": "string", "enum": ["APPROVE", "REQUEST_CHANGES", "COMMENT"] },
      "body": { "type": "string" }
    }
  }
}

By decoupling tool execution from raw shell strings:

  1. Arguments are validated before execution, preventing malformed payload injection.
  2. Background tasks are tracked via explicit process IDs with non-blocking reactive wakeups, eliminating wasteful busy-waiting loops.
  3. Encoding is strictly enforced as UTF-8 without Byte Order Marks (BOM), preserving cross-platform compatibility.

Fail-Closed Security and Sandboxing Boundaries

Autonomous agents operating on production systems need hard containment boundaries. Giving an agent unconstrained write access to the host environment invites unintended side effects.

Isolated Workspace Lanes (Git Worktrees)

Never allow an autonomous agent to execute risky changes directly on the primary working directory. Use isolated Git worktrees:

# Create an isolated coding lane branched from upstream master
git worktree add -b fix/issue-45877-cache-keys ./worktrees/wt-pr-45877 upstream/master

If the agent takes a wrong path or breaks local dependencies, the worktree can be pruned instantly without leaving untracked residue in the main workspace.

Fail-Closed Fallbacks

When an agent encounters an unhandled exception or ambiguous state during security-critical operations, it must fail closed.

A concrete example from analytical engines: when per-user query caching is enabled, if context resolution fails, the system must never silently omit the user discriminator. Doing so would cause user results to be cached under a shared key, leaking sensitive data to subsequent callers.

Instead, the system logs a warning and appends an uncacheable, single-execution token:

# Fail-safe cache key partitioning
if is_feature_enabled("CACHE_QUERY_BY_USER"):
    try:
        exec_context = current_host_execution_context()
        if not exec_context.is_guest and exec_context.identity_digest:
            keys.append(("semantic-user", exec_context.identity_digest))
    except Exception as ex:
        logger.warning("Could not resolve semantic execution context for cache: %s", ex)
        # Unique nonce ensures zero cross-user cache collisions
        keys.append(("semantic-user-unresolved", uuid.uuid4().hex))

Agents must enforce the same principle: when a safety check, test harness, or credential verification cannot be validated, execution must stop immediately rather than guessing.

The Verification Gate: Evidence Before Claims

The defining difference between a demo agent and an engineering partner is the verification protocol.

A production agent follows the Zero Red Checks Policy:

  1. TDD Red/Green Cycle: Before touching production code, write a reproduction test that asserts the defect. Watch it fail. Apply the minimal architectural fix. Watch the test pass.
  2. Delta and Module Coverage: Enforce strict coverage floors. If a project requires 100% test coverage on touched modules (--cov-fail-under=100), any unexercised fallback branch is treated as a build failure.
  3. Static Analysis Audit: Run linters and typecheckers before pushing. Check line lengths (e.g. Ruff/Black 88-column limit) and typing invariants.
  4. Independent Evidence: The agent must output reproducible command invocations and verified exit codes before reporting completion.
       ┌────────────────────────┐
       │   1. Failing Test      │ ──► Confirms bug existence
       └──────────┬─────────────┘
                  │
                  ▼
       ┌────────────────────────┐
       │   2. Surgical Fix      │ ──► Minimal diff footprint
       └──────────┬─────────────┘
                  │
                  ▼
       ┌────────────────────────┐
       │   3. Full Test Suite   │ ──► Zero regressions
       └──────────┬─────────────┘
                  │
                  ▼
       ┌────────────────────────┐
       │   4. Static Analysis   │ ──► Types & formatting pass
       └────────────────────────┘

Summary Checklist for Production AI Agents

When designing autonomous systems for mission-critical software engineering, measure your architecture against these core tenets:

  • State Persistence: Maintain an append-only transaction state outside the model's transient memory.
  • Bounded Context: Decouple tool schemas, compact finished milestones, and discard raw stdout noise.
  • Typed Protocol: Standardize on Model Context Protocol (MCP) tool schemas with JSON Schema validation.
  • Isolated Sandboxing: Use Git worktrees and ephemeral virtual environments for all mutating operations.
  • Fail-Closed Guarantees: Never swallow exceptions or omit security discriminators when handling state.
  • Evidence-Driven Completion: No task is done until external linters, typecheckers, and unit test suites exit with code 0.
  • #ai
  • #agents
  • #llm
  • #software-engineering
  • #mcp
  • #architecture

Share article

Zero tracking, no cookies, privacy-respecting.

Share article

Article

Building Deterministic AI Agents: Beyond Fragile Prompt Chains

https://francescocastaldi.it/blog/building-deterministic-ai-agents-beyond-fragile-prompt-chains