Define a CLI Tool
This guide shows how to define a CLI tool that invokes a local binary (e.g., kubectl, gh, aws) under Orloj's governance and isolation model.
Quick start
apiVersion: orloj.dev/v1
kind: Tool
metadata:
name: kubectl-get-pods
spec:
type: cli
description: "List Kubernetes pods in a namespace"
input_schema:
type: object
properties:
namespace:
type: string
format:
type: string
enum: [json, yaml, wide]
cli:
command: kubectl
args:
- get
- pods
- -n
- "{{ .namespace }}"
- -o
- "{{ .format }}"
image: bitnami/kubectl:1.30
env_from:
- name: KUBECONFIG
secretRef: k8s-kubeconfig
risk_level: medium
operation_classes: [read]
runtime:
timeout: 15sApply:
orlojctl apply -f kubectl-get-pods.yamlHow it works
- The agent selects the tool and provides JSON input matching
input_schema. - Orloj evaluates
cli.argstemplates against the parsed JSON input to build an argv array. - Secrets referenced in
cli.env_fromare resolved from the secret store. - The command runs inside a container (the default) or directly on the worker host (
isolation_mode: none). - Stdout is returned to the agent as the tool result.
Argument templates
Each entry in cli.args is evaluated as a Go text/template with the model's JSON input as the data context. Static entries (without {{ }}) pass through unchanged.
args:
- get
- pods
- -n
- "{{ .namespace }}"If the model sends {"namespace": "production", "format": "json"}, the resulting argv is ["get", "pods", "-n", "production"]. Each template produces exactly one argv entry -- there is no shell splitting.
Templates that evaluate to an empty string are dropped from the final argv.
Passing input via stdin
For tools that read structured input from stdin (e.g., jq, custom CLIs), set stdin_from_input: true:
cli:
command: jq
args: [".items[].metadata.name"]
image: ghcr.io/jqlang/jq:1.7
network: none
stdin_from_input: trueBoth templated args and stdin can be combined.
Credentials
CLI tools do not use spec.auth (it is rejected at validation time). Instead, map Orloj secrets to the environment variables your binary expects using env_from:
cli:
command: gh
args: ["pr", "list", "--repo", "{{ .repo }}"]
image: ghcr.io/cli/cli:2.50
env_from:
- name: GITHUB_TOKEN
secretRef: gh-api-tokenFor multiple credentials (e.g., AWS):
env_from:
- name: AWS_ACCESS_KEY_ID
secretRef: aws-creds
key: access_key
- name: AWS_SECRET_ACCESS_KEY
secretRef: aws-creds
key: secret_keyUse env for non-secret literals:
env:
AWS_DEFAULT_REGION: us-east-1Container isolation (default)
CLI tools default to container isolation. The operator provides a container image containing the binary via cli.image. The container runs with:
--read-onlyfilesystem--cap-drop=ALL--security-opt no-new-privileges--network bridge(configurable viacli.network)- Resource limits from
cli.resources(per-tool) or the global worker config (--tool-container-memory,--tool-container-cpus,--tool-container-pids-limit)
Set cli.network: none for tools that do not need outbound network access:
cli:
command: jq
image: ghcr.io/jqlang/jq:1.7
network: nonePer-tool container resources
Tools that need more resources than the global defaults (e.g. Chromium-based tools) can declare per-tool overrides via cli.resources. When set, these take precedence over the global --tool-container-* flags:
cli:
command: screenshot
image: my-chromium:latest
network: bridge
resources:
memory: 1g
cpus: "1.0"
pids_limit: 256| Field | Format | Description |
|---|---|---|
memory | Docker memory string (128m, 1g) | Container memory limit. |
cpus | Decimal string (0.50, 1.0) | Container CPU limit. |
pids_limit | Integer | Container PID limit. |
Operators can set a ceiling with --tool-container-max-memory, --tool-container-max-cpus, and --tool-container-max-pids-limit on orlojd. Manifests exceeding the ceiling are rejected at apply time.
Direct execution (no container)
For trusted tools on the worker host, set isolation_mode: none. The binary must exist on the worker's filesystem. cli.image is not required in this mode.
spec:
type: cli
cli:
command: /usr/local/bin/my-tool
args: ["--flag", "{{ .value }}"]
runtime:
isolation_mode: noneOutput capture
cli.output controls what is returned to the agent:
stdout(default) -- return stdout onlystderr-- return stderr onlyboth-- return{"stdout": "...", "stderr": "..."}as JSON
Non-zero exit codes produce a tool error with the exit code and stderr tail in the error details.
Worker flags
| Flag | Env | Default | Description |
|---|---|---|---|
--cli-tool-allowed-commands | ORLOJ_CLI_TOOL_ALLOWED_COMMANDS | (empty) | Comma-separated command allowlist. Empty allows all. |
--cli-tool-max-argv-length | ORLOJ_CLI_TOOL_MAX_ARGV_LENGTH | 4096 | Max total argv byte length. |