Reference#

The complete public surface of @kontourai/plumb: the plumb command, the config file, the files plumb writes, and the programmatic API. For how the pieces behave together, read Concepts.

CLI#

plumb run [--context <key>=<value>]
plumb escalate <context-name> <log-file>

Both commands load the config first (see Configuration). Any error, including a config error, is printed as plumb: <message> on stderr with exit code 1.

plumb run#

Runs CHECKS_CMD, appends its output to LOG_FILE, and escalates under the context name checks if it exits non-zero. See Check runner.

--context takes exactly one key=value as a separate argument; --context=key=value is not accepted. The deploy key records deploy provenance (see Run context and deploy provenance). A malformed context is ignored with a warning.

Exits 0 once the config has loaded and the header line is written to LOG_FILE, whatever the checks, guards, or agent do. Failing to create, open, or write LOG_FILE exits 1.

plumb escalate#

Escalates the failure named <context-name>, using the last 4000 bytes of <log-file> as evidence, through the same guards and agent as plumb run. Use a distinct context name per failure mode so they do not share a lock or cooldown.

Exits 0 when the agent ran successfully and when a guard suppressed the escalation. Exits 1 when the config is invalid, a guard setting is not a plain non-negative integer, or the agent command fails.

Usage and exit codes#

Invocation Output Exit
no arguments, --help, -h, --version usage on stdout 0
any other unrecognized form usage on stderr 1
a command that throws plumb: <message> on stderr 1

--version prints the usage text, not a version number.

Configuration#

The config is a bash file. Plumb loads it with bash -c 'set -a; source "$1"; env -0', so every assignment is exported and bash handles quoting, comments, expansion, and command substitution. Start from plumb.config.example.

Config file location#

The CLI reads the file named by the PLUMB_CONFIG environment variable. If PLUMB_CONFIG is unset, it falls back to plumb.config at the root of the installed package, which normally does not exist; keep your config with the deployment and set PLUMB_CONFIG.

Because bash inherits plumb's environment, a key that is already set in the environment counts as configured even when the file does not set it.

Configuration keys#

Key Required Default Meaning
REPO_URL yes Repository cloned into AGENT_WORKDIR.
CHECKS_CMD yes Shell command that exits non-zero when a hard check fails.
AGENT_WORKDIR yes Dedicated clone for the agent. Reset to origin/main before each escalation; never the deployment checkout.
AGENT_CMD yes Headless agent command. The prompt is appended as its last argument.
LOG_FILE yes Append-only checks log. Also determines the escalations log and default state directory.
CONTEXT_DOCS no the repo docs Text inserted into the prompt as the docs the agent reads first.
PLUMB_STATE_DIR no state beside LOG_FILE Directory for lock, cooldown, counter, and deploy-sha files.
PLUMB_ESCALATE_COOLDOWN_SECS no 21600 Per-context cooldown in seconds. Also the age after which a lock whose process has exited is treated as stale.
PLUMB_ESCALATE_DAILY_CAP no 8 Escalations allowed per local day, across all contexts.

A required key that is unset or empty fails with Missing required config key: <KEY>. The two guard settings must be plain decimal digits, such as 0, 8, or 21600. Anything else, including an empty value, surrounding spaces, 1e3, 0x10, or -1, makes escalation fail with <KEY> must be a non-negative integer, got "<value>". To use a default, leave the key unset rather than empty. A daily cap of 0 suppresses every escalation. A cooldown of 0 disables the cooldown; the per-context lock still holds while its escalation runs.

Files written#

Path Written by Contents
LOG_FILE plumb run === <timestamp> plumb checks[ context=<key>=<value>] headers and checks output.
LOG_FILE with .log replaced by -escalations.log escalations Agent output and <timestamp> SUPPRESS <context> (<reason>) lines.
<state>/lock-<context> escalations Held while an escalation for the context runs.
<state>/last-<context>[_3adeploy_3a<sha>] escalations Unix time of the last escalation, for the cooldown.
<state>/count-YYYYMMDD escalations Number of escalations that day; deleted after three days.
<state>/deploy-sha plumb run --context deploy=<sha> The latest deploy SHA.
AGENT_WORKDIR escalations The agent's clone of REPO_URL.

<state> is PLUMB_STATE_DIR or state beside LOG_FILE. In file names, lowercase letters and digits of the context are kept and every other byte, including _ and uppercase letters, becomes _ plus two lowercase hex digits: db-refresh is db_2drefresh and Checks is _43hecks. An encoded name longer than 100 characters, including any deploy signature, keeps its first 70 characters followed by - and a 24-character SHA-256 prefix of the full name. Distinct context names therefore never share files. Plumb creates the directories it needs.

Programmatic API#

import { escalate, loadConfig, run } from "@kontourai/plumb";

const config = loadConfig(process.env.PLUMB_CONFIG);
run(config, { context: `deploy=${process.env.DEPLOY_SHA}` });
escalate("custom-job", "/var/log/custom-job.log", config);

The package exports three functions and their types. All three are synchronous.

loadConfig#

loadConfig(configPath?: string): PlumbConfig

Sources the file at configPath (default: PLUMB_CONFIG, else the package's plumb.config) and returns a PlumbConfig. It has the required and optional keys from Configuration keys that are set, plus environment: every variable exported while sourcing, including those inherited from plumb's own environment, which plumb passes to the checks command and the agent. Throws on a missing required key.

run#

run(config: PlumbConfig, options?: RunOptions): number

The function behind plumb run. RunOptions has context?: string (the key=value string) and escalateOptions?: EscalateOptions, passed through to the escalation. A valid context derives runContext (key=value); a deploy context also derives deployContext (with the previous recorded SHA) and guardSignature (deploy:<sha>). Fields the context does not derive are passed through unchanged, so without a context a caller's deployContext, guardSignature, and runContext are all honored. Setting a field in escalateOptions that the context also derives throws escalateOptions.<field> conflicts with context "<context>"; pass one or the other before the checks run. Returns 0. It throws if LOG_FILE cannot be created, opened, or written; escalation errors are swallowed.

escalate#

escalate(
  context: string,
  logPath: string,
  config: PlumbConfig,
  options?: EscalateOptions,
): EscalateResult

The function behind plumb escalate. Returns { escalated: true } after the agent runs, or { escalated: false, reason } when a guard suppressed it, with reason one of already running, within <N>s cooldown, or daily cap <N> reached. Throws on an invalid guard setting or a failed agent command.

EscalateOptions:

Option Type Effect
runAgent (input: RunAgentInput) => void Replaces the agent call. When set, plumb also skips cloning, fetching, and the deploy diffstat. RunAgentInput is { config, prompt, escalationLog }.
now () => Date Clock used for guards and timestamps.
deployContext DeployContext { sha, previousSha? }; adds deploy provenance to the prompt.
guardSignature string Keys the cooldown to <context>:<signature> while sharing the context's lock. plumb run uses deploy:<sha>.
runContext string Adds Run context: <value>. to the prompt when there is no deployContext.

Troubleshooting#

Symptom Cause
plumb: Missing required config key: REPO_URL PLUMB_CONFIG is unset or points at the wrong file, or the file leaves a required key empty. A preceding No such file or directory line from bash names the path it tried.
plumb: ignoring malformed context ... The --context value failed validation, for example a deploy value that is not 7 to 64 hex digits. The run continued without it.
Usage text and exit 1 The arguments did not match a form above, such as --context=deploy=... or plumb escalate with one argument.
A check failed but no agent ran Look for a SUPPRESS line in the escalations log: the lock, cooldown, or daily cap stopped it.
plumb: Agent command exited with status <N> From plumb escalate: the agent ran and failed. Its output is in the escalations log.
plumb: PLUMB_ESCALATE_DAILY_CAP must be a non-negative integer, got "..." A guard setting is set but is not plain decimal digits, for example empty, abc, -1, or 1e3. Unset it to use the default. plumb run ignores this error and does not escalate.
Every escalation is suppressed with daily cap 0 reached PLUMB_ESCALATE_DAILY_CAP is set to 0.
The agent did not see the latest commits git fetch origin failed in AGENT_WORKDIR, so plumb skipped the reset. The fetch error is printed on plumb's stderr.

Source: docs/reference.md