Skip to main content

Bring Your Own Agent

Modulo can run an agent image you already own. You keep your agent; Modulo provides the governed workspace, the identity it runs under, the schedule it runs on, and the audit trail around it. Your agent runs the same interface a first-party runner image satisfies, so nothing in your pipelines changes when you point a node at your own image.

The agent interface

Your image must satisfy four things:

  1. It provides a POSIX sh. Modulo’s file operations run through a shell, so a shell must exist in the image.
  2. Modulo launches one process. Modulo writes the prompt and the context files into the workspace, then starts one process from the command the agent declares and runs it to completion.
  3. Modulo observes the exit code. The exit code is the only signal that decides the node’s status.
  4. It may write one JSON document. The process may write a single JSON object to /home/user/output.json. Nothing else is read.

There is no protocol, no callback endpoint, no sidecar, and no configurable output path. The workspace holds no credential back to the Modulo control plane. The output file and the exit code together cover reporting, so no second channel is needed.

The output file

The output file is optional. If you write it, no key is required. Modulo surfaces these conventional keys, and any agent that writes them gets uniform treatment in the run detail:

The output file
Key Type What Modulo does with it
status string Surfaces the agent’s own verdict verbatim as agent_status. It never changes the node’s status.
outcome string Surfaces the agent’s own verdict verbatim as agent_outcome. It never changes the node’s status.
summary string Surfaced as the node’s result summary.
changed_files array of strings Surfaced on the node output for review.
pr_url string Surfaced as a delivered pull request (PR) URL, marked agent-reported and unverified.
Pipeline config
{
  "status": "success",
  "outcome": "opened-pr",
  "summary": "Updated the retry policy and opened a pull request.",
  "changed_files": ["backend/src/modulo/core/retry.py"],
  "pr_url": "https://github.com/your-org/your-repo/pull/123"
}

Anything beyond these keys is yours to model. Declare an output schema to make it machine-readable.

Declaring the command

A sandbox agent node runs the command on its agent. In the default llm mode a node needs a non-empty agent_commands list and a prompt; in script mode it needs a non-empty script_command. The graph validator rejects a node that is missing its command (SANDBOX_MISSING_COMMAND). The items in agent_commands are joined into one shell command at run time.

Output schemas and the strict and lenient modes

An agent or a pipeline node can declare an output schema. The schema is pinned to a version and validated against the payload your agent writes. You can also declare no schema, in which case the envelope is accepted as a free-form JSON object.

Validation runs in one of two modes, chosen by an operator for the instance or for one organization:

Output schemas and the strict and lenient modes
Mode What it enforces
lenient (default) Missing required fields still fail the node. Violations of the other constraints (types, enums, patterns, formats, bounds, and composition) are logged as warnings, and the payload passes.
strict Any violation fails the node. Modulo attempts a repair pass before it gives up.

The mode is resolved in this order: the organization’s setting, then the instance-wide setting, then the MODULO_SCHEMA_VALIDATOR_MODE environment variable as a first-boot seed, then lenient.

On a schema failure the node fails, the raw output is retained for diagnosis, and the run carries a contract schema error (contract.schema).

Where the image runs

Every workspace image resolves through one channel: the environment profile bound to the pipeline. The profile’s image_ref names the image, and the profile’s provider_type decides where it runs. The image is org-owned on every tier, so you control the supply chain wherever the work lands.

Where the image runs
Tier provider_type Where your image comes from
Bundled Runner (Docker) runner_docker The deployment’s own Docker engine, behind the shipped filtered socket proxy.
Remote Docker runner_docker A Docker engine you own, reached through MODULO_DOCKER_HOST.
Kubernetes kubernetes Your cluster and your registry. The image never leaves your boundary.
E2B e2b A template in your own E2B account. The profile’s image_ref is the template ID.

On Kubernetes, the profile must declare image_ref, and the image must provide a POSIX shell because the dispatcher’s file operations run through the exec subresource. Attach image pull secrets to the workspace ServiceAccount if your image is private. The Kubernetes guide covers the profile, the ServiceAccount, and the least-privilege Role.

Pin your images by digest for a reproducible supply chain. Modulo resolves whichever image_ref you set; digest pinning is advice, not something the platform enforces. Execution Environments describes each provider in full.

What Modulo observes and what your agent reports

Modulo separates what it observed from what your agent claimed. Every fact carries one of four provenance values:

What Modulo observes and what your agent reports
Value Meaning
observed Modulo saw it directly through a credential it holds.
derived Modulo computed it from observed facts.
reported It arrived from an external surface, including your agent’s output file.
attested A governance event Modulo recorded itself.

For a bring-your-own agent run:

  • The process exit code and the wall-clock time are observed.
  • The node’s terminal status is derived from the exit code.
  • Everything read from /home/user/output.json is reported. Your agent’s status, outcome, summary, changed_files, and pr_url are the agent’s claims; Modulo did not measure them.
  • Modulo’s own approvals, guardrail verdicts, and human-in-the-loop (HITL) decisions are attested.

A reported datum also carries a witnessed_via stamp naming the channel it arrived through.

The default is optimistic: Modulo records the provenance and does not block a run because a fact was reported. There is no built-in refusal mechanism. If you want to enforce a claim, compose primitives you already have: fetch the truth with a connector query and let a gate compare it against what the agent said. An agent that reports truthfully meets no friction.

How failures and partial results surface

The exit code decides the node’s status, and there is no synthetic completion:

How failures and partial results surface
Situation Outcome
Non-zero exit The node fails. Retries and terminal classification follow the node’s policy, and the raw output streams are retained.
Exit 0, but output.json is missing, empty, or unparseable The node fails with sandbox.no_output_json. Modulo never invents a completion.
Exit 0, but the output is a lost-session marker The node fails.
Exit 0, a valid output.json, no declared schema The node completes and the envelope is surfaced as it is.
Exit 0, a valid output.json, a schema violation The node fails per the strict and lenient modes above, and the content is retained.

A partial result, meaning a failed run that still wrote a readable output.json, is kept for diagnosis and surfaced with its provenance intact. It never turns a failed node into a completed one.

  • Agents: the atomic unit of pipeline work and its configuration fields
  • Execution Environments: every runtime provider and how to choose between them
  • Kubernetes: run workspaces as pods in your own cluster
  • Schema System: versioned input and output definitions that validate agent output
  • Secrets: how connector and model credentials are encrypted at rest
  • Troubleshooting: common setup issues and fixes