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.
The packages
Section titled “The packages”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 |
tools/common
Section titled “tools/common”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 |
How a runner is structured
Section titled “How a runner is structured”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 targetsRunner the engine: plan, accept, perform, inspect, receiptDispatch the request body: capability, arguments, task_id, run_id, stack, approvedcreate_runner() the factory uvicorn loads; returns the FastAPI apppolicy.py inspect_source() and inspect_plan(): what the runner refuses- Configuration is encrypted at rest.
Runner.from_envreads 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}plusGET /health. The handler callssigned_body, validates the body as aDispatch, and returnsrunner.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_sourcechecks the checked-out stack before Terraform may load modules or providers;inspect_planchecks the plan (for example, destroys whenallow_destroyis 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.
Two ways to run one
Section titled “Two ways to run one”Listening. uvicorn loads the factory, and the control plane sends signed requests to the runner’s HTTPS address:
uv run --locked uvicorn tools.terraform.runner:create_runner --factory --host 127.0.0.1 --port 8091Linked. 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.
The control-plane side: providers
Section titled “The control-plane side: providers”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 runtime side: registered tools
Section titled “The runtime side: registered tools”The model never sees a provider. It sees tools the run was granted, described by typed arguments.
NATIVE_TOOLSinruntime/operation_tools.pymaps each capability ID to anOperationTool(mode, arguments, target_field), wheremodeis"read"or"propose"andargumentsis a PydanticContract.SandboxToolsinruntime/tools.pyhandles the three workspace capabilities:workspace.read,workspace.writeandworkspace.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.
Add an operation to an existing provider
Section titled “Add an operation to an existing provider”Say you want a new read on the Terraform runner.
- Runner. Add the capability to the runner’s request model (
Dispatch.capability) and implement it inRunner. Share any new request contract throughtools/common/. - Provider. Add a
Capability(id,mode,input_schema) to the provider’scapabilities, and handle it innormalizeand inread(for a read) orreview,dispatchandverify(for a write). - Runtime. Add the capability to
NATIVE_TOOLSwith its argument contract. - If it is a write, add its ID to
MUTATION_CAPABILITIESinapi/src/modules/work/domain/work.ts. That set is what stops a read-only agent from holding it. - UI. Give it a label in
ui/shared/capabilities.js, and add it to the right family inui/features/product-capabilities.jsand indocs/CAPABILITIES.md. Those two are kept in step by hand. - Tests. A runner test in
tests/, a provider test inapi/test/, and where possible a runtime test that drives the whole path. See Testing.
Add a new provider and runner
Section titled “Add a new provider and runner”- Create
tools/<domain>/with arunner.pythat exposescreate_runner(). For a cloud, start from the kit intools/common/cloud_runner.py. - Implement
OperationProviderin the module that owns the domain and pass it toOperationsModule.registerinapi/src/app.ts. - To let the runner be linked or run from the tools image, add it to
RUNNERSintools/common/link.py, toRELAY_PROVIDERSinapi/src/modules/operations/domain/relay.ts, and to thecaseindeploy/docker/tools-entrypoint.sh. - Add the connection form to the UI and continue with steps 3 to 6 above for each capability.
Add a sandbox check
Section titled “Add a sandbox check”Sandbox checks are fixed, typed commands. An agent cannot run an arbitrary shell command in the sandbox.
- Add a
Skillto_CATALOGintools/workspace/skills.py, with a PydanticArgumentsmodel and anexecutefunction that runs insideEngineeringSandbox. - Map a check ID to it in
CHECKSinruntime/tools.py. - Add the check ID to
workspace.runinWORKSPACE_CAPABILITIES(api/src/modules/work/domain/work.ts) so a profile can grant it.
Skills
Section titled “Skills”“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. |
Where to go next
Section titled “Where to go next”- The operation contract for what
review,dispatchandverifymust guarantee. - MCP for tools that come from an external server instead of a runner.
- Runners for deploying a runner.