Skip to content

Tools and runners

Everything an agent can do to the world outside its prompt lives in tools/. There are two kinds of tool, and they are built very differently:

  • Sandbox tools read and edit a private checkout and run registered checks. They run next to the runtime, offline, and need no approval.
  • Operations read or change a connected system. They run on a customer runner or a provider API, and every write follows the operation contract.

One package per domain. The package docstrings in each __init__.py are the authoritative one-line summary.

Package Runner entry point Other modules
tools/terraform/ runner.py policy.py: source and plan inspection
tools/postgres/ runner.py backups.py, queries.py, migrations.py, archive.py, models.py
tools/kubernetes/ runner.py manifest.py (manifest policy), diagnostics.py, cluster.py, browse.py, map.py, session.py, usage.py, change.py
tools/containers/ runner.py build.py, scan.py
tools/aws/ runner.py, secrets.py, account.py ec2.py, cost.py, finops.py, power.py, resize.py, discover.py, contracts.py
tools/azure/ runner.py built on the shared cloud runner kit
tools/gcp/ runner.py, account.py machine.py
tools/cloudformation/ runner.py changes.py, contracts.py
tools/workspace/ none (not a runner) sandbox.py, skills.py, source_archive.py, harness.py

Adapters shared by the runtime and the runners. A runner package imports from here; it never imports from runtime/.

Module Holds
terraform_ops.py, gitops_ops.py, database_ops.py, registry_ops.py Pydantic request contracts for each runner, and signature(...), the HMAC every runner verifies
runner_support.py signed_body (signature and timestamp check), private file helpers, protected-branch checkout
runner_process.py Bounded subprocess execution, RunnerError, endpoint validation
cloud_runner.py The cloud runner kit: CloudScope, Gateway, ReceiptLedger, CloudRunner, create_app
cloud_change.py The change half of the kit: ChangeGateway, ChangeLedger, CloudChanges, mount_changes
git_actions.py, git_checkout.py, repository_archive.py Pinned checkouts of registered repositories
outbound.py Outbound endpoint policy and an address-pinned HTTP transport
link.py Connects runners out to Polo so they need no public address

A runner is a small FastAPI service that holds the customer’s credentials and trusts nothing but a correctly signed request within its configured scope. The Terraform runner (tools/terraform/runner.py) shows the whole shape:

RunnerConfig validated settings: signing key, stacks, state targets
Runner the engine: plan, accept, perform, inspect, receipt
Dispatch the request body: capability, arguments, task_id, run_id, stack, approved
create_runner() the factory uvicorn loads; returns the FastAPI app
policy.py inspect_source() and inspect_plan(): what the runner refuses
  • Configuration is encrypted at rest. Runner.from_env reads a Fernet-encrypted configuration file named by environment variables. It lists the stacks the runner may touch. A request for anything else is refused.
  • One signed endpoint. PUT /api/terraform/v1/operations/{identifier} plus GET /health. The handler calls signed_body, validates the body as a Dispatch, and returns runner.receipt(...).
  • A local receipt ledger. Plans and receipts are kept in the runner’s own storage, keyed by the request identifier, so a repeated request returns the recorded receipt instead of running again.
  • A policy module. inspect_source checks the checked-out stack before Terraform may load modules or providers; inspect_plan checks the plan (for example, destroys when allow_destroy is off).
  • Bounded reason codes. A failure is reported as a code from a fixed list (commit_not_head, source_policy, plan_failed, …). Raw tool output stays in the runner’s log and is not sent to Polo.

The Azure and Google Cloud runners are thinner because they use the kit in tools/common/cloud_runner.py. Each supplies a scope class (AzureScope, GcpScope), a gateway that talks to the cloud (AzureGateway, GcpGateway) and a RunnerConfig; the kit supplies the signed endpoint, the receipt ledger and the neutral capabilities cloud.inventory and cloud.cost. The AWS runner predates the kit and carries its own copy of the ledger.

Listening. uvicorn loads the factory, and the control plane sends signed requests to the runner’s HTTPS address:

Terminal window
uv run --locked uvicorn tools.terraform.runner:create_runner --factory --host 127.0.0.1 --port 8091

Linked. The runner opens no port. python -m tools.common.link builds the same runner applications, polls Polo for jobs over an outgoing HTTPS connection, hands each signed request to the right runner, and posts the answer back. The runner still verifies Polo’s signature itself. A connection on a linked runner has an endpoint of the form runner://… (api/src/modules/operations/domain/relay.ts).

The tools image (tools.Dockerfile, entrypoint deploy/docker/tools-entrypoint.sh) selects a runner with the RUNNER variable, or links out when POLO_RUNNER_TOKEN is set. Operator instructions are on Runners.

For every kind of connection there is an OperationProvider (api/src/modules/operations/application/ports.ts) in the control plane. It declares capabilities, lists a connection’s targets, and implements normalize, permits, read, review, dispatch and verify.

Four providers are built into the operations module (TerraformProvider, GitOpsProvider, OciProvider, DatabaseProvider), plus SlackProvider. Other modules contribute theirs through OperationsModule.register([...]) in api/src/app.ts: MCP, GitHub, AWS, AWS Secrets Manager, CloudFormation, Azure, Google Cloud, network diagnostics, Prometheus, Loki, Tempo, Jira, ClickUp, Slack, Gmail, Google Chat, Google files and calendar.

The model never sees a provider. It sees tools the run was granted, described by typed arguments.

  • NATIVE_TOOLS in runtime/operation_tools.py maps each capability ID to an OperationTool(mode, arguments, target_field), where mode is "read" or "propose" and arguments is a Pydantic Contract.
  • SandboxTools in runtime/tools.py handles the three workspace capabilities: workspace.read, workspace.write and workspace.run.

The comment above NATIVE_TOOLS states the rule: registration is required on both sides of the boundary. A capability the control plane knows but the runtime does not is invisible to the model; one the runtime knows but the control plane does not is refused.

Say you want a new read on the Terraform runner.

  1. Runner. Add the capability to the runner’s request model (Dispatch.capability) and implement it in Runner. Share any new request contract through tools/common/.
  2. Provider. Add a Capability (id, mode, input_schema) to the provider’s capabilities, and handle it in normalize and in read (for a read) or review, dispatch and verify (for a write).
  3. Runtime. Add the capability to NATIVE_TOOLS with its argument contract.
  4. If it is a write, add its ID to MUTATION_CAPABILITIES in api/src/modules/work/domain/work.ts. That set is what stops a read-only agent from holding it.
  5. UI. Give it a label in ui/shared/capabilities.js, and add it to the right family in ui/features/product-capabilities.js and in docs/CAPABILITIES.md. Those two are kept in step by hand.
  6. Tests. A runner test in tests/, a provider test in api/test/, and where possible a runtime test that drives the whole path. See Testing.
  1. Create tools/<domain>/ with a runner.py that exposes create_runner(). For a cloud, start from the kit in tools/common/cloud_runner.py.
  2. Implement OperationProvider in the module that owns the domain and pass it to OperationsModule.register in api/src/app.ts.
  3. To let the runner be linked or run from the tools image, add it to RUNNERS in tools/common/link.py, to RELAY_PROVIDERS in api/src/modules/operations/domain/relay.ts, and to the case in deploy/docker/tools-entrypoint.sh.
  4. Add the connection form to the UI and continue with steps 3 to 6 above for each capability.

Sandbox checks are fixed, typed commands. An agent cannot run an arbitrary shell command in the sandbox.

  1. Add a Skill to _CATALOG in tools/workspace/skills.py, with a Pydantic Arguments model and an execute function that runs inside EngineeringSandbox.
  2. Map a check ID to it in CHECKS in runtime/tools.py.
  3. Add the check ID to workspace.run in WORKSPACE_CAPABILITIES (api/src/modules/work/domain/work.ts) so a profile can grant it.

“Skill” means three related things in the code. Keep them apart.

Meaning Where What it is
Executable skill tools/workspace/skills.py A typed command that runs in the sandbox. The catalog holds terraform.check, helm.check, kubernetes.check, github_actions.check, dockerfile.check, shell.check, docker.build, repository.test and four scaffold.* skills. skill_catalog() lists them; execute_skill() runs one.
Published skill api/src/modules/knowledge/ A knowledge document of kind skill. An agent granted skills.read on one exact document version can read it as guidance. It is text, not code.
Terraform skill ui/features/product-terraform-skill.js A product-level setup: a repository, a folder and a cloud account. It registers a Terraform connection on that account’s runner, so the user never deals with runners. See The Terraform skill.
  • The operation contract for what review, dispatch and verify must guarantee.
  • MCP for tools that come from an external server instead of a runner.
  • Runners for deploying a runner.