Pitot
Guides / 03 — Configuration & SDKs

Guide 03 / Configuration & SDKs

Tenant config & typed SDKs

Several tools sharing one blocking hook is where agent tooling usually turns hostile: last writer wins, and nobody knows who broke it. Pitot makes configuration tenant-partitioned — one fragment per tool, deterministic merge, loud attributable conflicts — and delivers its typed SDKs from your own registry, pinned to the CLI version.

01One fragment per tenant

Every tool or person that registers processes with Pitot owns exactly one file in .pitot/conf.d/, and no tenant ever edits another tenant's file:

.pitot/
  conf.d/
    boatstack.yaml    # a tool's controller, written by its installer
    interlock.yaml    # another tool's controller, different request kind
    my-policy.yaml    # your own, scaffolded by `pitot init`

Installing a second Pitot-based tool is additive by construction: it drops its own fragment next to yours, pitot run and pitot dev merge them, and uninstalling it is deleting its fragment.

02Merge rules: deterministic, and loud on conflict

The effective configuration is the deterministic merge of all fragments in filename order. Each fragment is a complete, strictly parsed mini-config declaring controllers: and/or consumers:.

Consumers compose

Any number of tenants can observe action.requested events.

One owner per kind

Two fragments claiming the same request kind fail discovery with an error naming both files — instead of two tools silently fighting over one blocking hook.

Unique ids

Controller and consumer ids must be unique across fragments; collisions are discovery errors, not overwrites.

03Fragment anatomy

# .pitot/conf.d/my-policy.yaml
controllers:
  shell:
    id: local-shell-policy
    command: ["go", "run", "main.go"]
    dir: "kimi-policy"       # per-process cwd, repo-relative
    deadline_ms: 2000
    on_timeout: deny
    on_unavailable: deny
  • dir: sets a process's working directory relative to the repository root, so each tenant's command stays project-relative.
  • requires_protocol: "1" optionally pins the protocol version a fragment was written against; a fragment the running binary cannot honor fails discovery — and pitot upgrade re-checks this for every fragment before moving the pin.
  • Deadlines and defaults are declared, not implied. A Controller registration states its deadline_ms and what happens on timeout or unavailability.

04TypeScript SDK

TypeScriptverified against release v0.1.2
01 pitot install typescript

writes a scoped .npmrc and installs @operatorstack/pitot@<pinned version>

The package resolves through the distribution front door to our registry — it is never published to public npm. The installed version is locked to the repository's CLI pin, so the SDK and the binary can never drift apart.

package.json

npm honors a project registry config only inside a project. When the directory has no package.json, pitot install typescript creates a minimal private one first — without it, npm would silently ignore the scoped .npmrc and fail against public npm with a confusing 404.

05Python SDK

Pythonverified against release v0.1.2
01 pitot install python

writes .pitot/registry and installs operatorstack-pitot==<pinned version>

The distribution name is operatorstack-pitot (the bare PyPI name was taken), but the import is what you would expect:

import pitot

06No SDK required — the protocol is the contract

The SDKs are optional conveniences, not the source of protocol truth. Compatibility is defined by versioned JSON Schemas, newline-delimited JSON framing, and explicit request/response state machines. If a program can read JSON Lines from standard input, it can be a Consumer:

# ordinary Python — no Pitot SDK
import json, sys

for line in sys.stdin:
    event = json.loads(line)
    print(json.dumps({
        "host": event["host"]["name"],
        "action_id": event["action"]["id"],
        "kind": event["action"]["kind"],
    }), file=sys.stderr)

Content projection (full, sha256, or omit) is applied before bytes enter your process, and a Consumer failure can never allow or deny the waiting host action.