Files
Stirling-PDF/engine/AGENTS.md
T
e5767ed58b Change AI engine to execute tools in Java instead of on frontend (#6116)
# Description of Changes
Redesign AI engine so that it autogenerates the `tool_models.py` file
from the OpenAPI spec so the Python has access to the Java API
parameters and the full list of Java tools that it can run. CI ensures
that whenever someone modifies a tool endpoint that the AI enigne tool
models get updated as well (the dev gets told to run `task
engine:tool-models`).

There's loads of advantages to having the Java be the one that actually
executes the tools, rather than the frontend as it was previously set up
to theoretically use:
- The AI gets much better descriptions of the params from the API docs
- It'll be usable headless in the future so a Java daemon could run to
execute ops on files in a folder without the need for the UI to run
- The Java already has all the logic it needs to execute the tools 
- We don't need to parse the TypeScript to find the API (which is hard
because the TS wasn't designed to be computer-read to extract the API)

I've also hooked up the prototype frontend to ensure it's working
properly, and have built it in a way that all the tool names can be
translated properly, which was always an issue with previous prototypes
of this.

---------

Co-authored-by: Anthony Stirling <[email protected]>
Co-authored-by: EthanHealy01 <[email protected]>
2026-04-20 15:57:11 +01:00

4.8 KiB

Stirling AI Engine Guide

This file is for AI agents working in engine/.

The engine is a Python reasoning service for Stirling. It plans and interprets work, but it does not own durable state, and it does not execute Stirling PDF operations directly. Keep the service narrow: typed contracts in, typed contracts out, with AI only where it adds reasoning value.

Commands

All engine commands can be run from the repository root using Task:

  • task engine:check — run all checks (typecheck + lint + format-check + test)
  • task engine:fix — auto-fix lint + formatting
  • task engine:install — install Python dependencies via uv
  • task engine:dev — start FastAPI with hot reload (localhost:5001)
  • task engine:test — run pytest
  • task engine:lint — run ruff linting
  • task engine:typecheck — run pyright
  • task engine:format — format code with ruff
  • task engine:tool-models — generate tool_models.py from Java OpenAPI spec

Code Style

  • Keep task engine:check passing.
  • Use modern Python when it improves clarity.
  • Prefer explicit names to cleverness.
  • Avoid nested functions and nested classes unless the language construct requires them.
  • Prefer composition to inheritance when combining concepts.
  • Avoid speculative abstractions. Add a layer only when it removes real duplication or clarifies lifecycle.
  • Add comments sparingly and only when they explain non-obvious intent.

Typing and Models

  • Deserialize into Pydantic models as early as possible.
  • Serialize from Pydantic models as late as possible.
  • Do not pass raw dict[str, Any] or dict[str, object] across important boundaries when a typed model can exist instead.
  • Avoid Any wherever possible.
  • Avoid cast() wherever possible (reconsider the structure first).
  • All shared models should subclass stirling.models.ApiModel so the service behaves consistently.
  • Do not use string literals for any type annotations, including cast().

Configuration

  • Keep application-owned configuration in stirling.config.
  • Only add STIRLING_* environment variables that the engine itself truly owns.
  • Do not mirror third-party provider environment variables unless the engine is actually interpreting them.
  • Let pydantic-ai own provider authentication configuration when possible.

Architecture

Package Roles

  • stirling.contracts: request/response models and shared typed workflow contracts. If a shape crosses a module or service boundary, it probably belongs here.
  • stirling.models: shared model primitives and generated tool models.
  • stirling.agents: reasoning modules for individual capabilities.
  • stirling.api: HTTP layer, dependency access, and app startup wiring.
  • stirling.services: shared runtime and non-AI infrastructure.
  • stirling.config: application-owned settings.

Source Of Truth

  • stirling.models.tool_models is the source of truth for operation IDs and parameter models.
  • Do not duplicate operation lists if they can be derived from tool_models.OPERATIONS.
  • Do not hand-maintain parallel parameter schemas when the generated tool models already define them.
  • If a tool ID must match a parameter model, validate that relationship explicitly in code.

Boundaries

  • Keep the API layer thin. Route modules should bind requests, resolve dependencies, and call agents or services. They should not contain business logic.
  • Keep agents focused on one reasoning domain. They should not own FastAPI routing, persistence, or execution of Stirling operations.
  • Build long-lived runtime objects centrally at startup when possible rather than reconstructing heavy AI objects per request.
  • If an agent delegates to another agent, the delegated agent should remain the source of truth for its own domain output.

AI Usage

  • The system must work with any AI, including self-hosted models. We require that the models support structured outputs, but should minimise model-specific code beyond that.
  • Use AI for reasoning-heavy outputs, not deterministic glue.
  • Do not ask the model to invent data that Python can derive safely.
  • Do not fabricate fallback user-facing copy in code to hide incomplete model output.
  • AI output schemas should be impossible to instantiate incorrectly.
    • Do not require the model to keep separate structures in sync. For example, instead of generating two lists which must be the same length, generate one list of a model containing the same data.
    • Prefer Python to derive deterministic follow-up structure from a valid AI result.
  • Use NativeOutput(...) for structured model outputs.
  • Use ToolOutput(...) when the model should select and call delegate functions.

Testing

  • Test contracts directly.
  • Test agents directly where behaviour matters.
  • Test API routes as thin integration points.
  • Prefer dependency overrides or startup-state seams to monkeypatching random globals.