Build from what happened.
Receive projected events for memory, audit, usage, reports, telemetry, or your own tools. Content projection — full, sha256, or omit — is applied before bytes enter your process.
Field note 00 / The host boundary
Your coding agent runs shell commands, edits files, and calls tools. Pitot puts your own code in the loop at that boundary — to allow, deny, or record each action — without forking the agent or rewriting a host integration for every tool.
normalize
project event
correlate request
validate response
shell.execute → allow
Their agent → Pitot → your code. Pitot transports. Your code decides.
Pitot separates two capabilities that are easy to blur: observing what the agent did, and deciding what happens next. A passive Consumer cannot reach the response channel. A Controller is statically registered for one request kind and returns at most one response for the pending action.
Receive projected events for memory, audit, usage, reports, telemetry, or your own tools. Content projection — full, sha256, or omit — is applied before bytes enter your process.
Receive a registered request while the host waits, check your own source of truth, and return one correlated allow or deny. Late, stale, duplicate, and malformed responses are rejected.
Pitot reports. Your controller decides.
Different tools intervene at different points. Pitot sits at the one boundary every coding agent already exposes — the host hook — and keeps the agents your team already chose.
replace the product
advise the model
expose tools and data
connect your code to observable events and waiting actions
pitot init writes two substrate files: a one-line committed version pin, and a committed shim that hydrates exactly that release — sha256-verified — into a per-user cache and execs it. Fresh clones, CI, and cloud agents run .pitot/bin/pitot with zero setup.
.pitot/ ├── version # one semver line — the only version authority ├── bin/ │ ├── pitot # committed shim (sh) │ └── pitot.ps1 # committed shim (PowerShell) └── conf.d/ # tenant fragments — see plate 05
.pitot/version.~/.cache/pitot/<version>/, verified against the published checksums.txt.PITOT_NO_HYDRATE=1 makes hydration cache-only.curl -fsSL https://get.operatorstack.systems/pitot | sh
one-time convenience CLI, from the distribution front door
pitot init --template shell-policy --language go --dir agent-policy
writes the pin, the shims, a runnable Controller, and one tenant fragment
pitot doctor --host kimi
reports pin, cache, shim, and host-hook wiring
pitot dev --host kimi -- kimi -p "Run: echo hello"
runs your agent behind your Controller and prints each decision
Full walkthrough, CI story, and the fail-closed rules → Install & the repo-pinned shim.
pitot upgrade verifies the new release, re-checks every tenant fragment against it, and rewrites the one pin line — nothing else. Commit that diff and every clone hydrates the new version on its next invocation. Roll back by reverting it.
--- a/.pitot/version +++ b/.pitot/version -0.1.1 +0.1.2
pitot upgrade --check
report pinned vs latest
pitot upgrade
hydrate, validate tenants, rewrite .pitot/version
pitot doctor
inspect the effective boundary, pin, and cache state
What gets re-validated and how drift is surfaced → Upgrades as a reviewed diff.
Every tool or person that registers processes with Pitot owns exactly one fragment in .pitot/conf.d/, and no tenant ever edits another tenant's file. The effective configuration is the deterministic merge of all fragments in filename order.
conf.d/boatstack.yamla tool's controller, written by its installer
conf.d/interlock.yamlanother tool's controller, different request kind
conf.d/my-policy.yamlyour own, scaffolded by pitot init
Any number of tenants can observe the same events.
Two fragments claiming the same request kind fail discovery with an error naming both files — loud and attributable.
Installing a second tool drops its own fragment next to yours. Uninstalling it is deleting its fragment.
Fragment anatomy, requires_protocol, and dir: → Tenant config & typed SDKs.
Typed SDKs install from our own registry through the distribution front door, pinned to the CLI's version. They are optional conveniences — the protocol truth is versioned JSON Schemas and newline-delimited JSON. If a program can read JSON Lines from standard input, it can be a Consumer.
pitot install typescript
→ scoped .npmrc + @operatorstack/pitot@<pinned version>
pitot install python
→ .pitot/registry + operatorstack-pitot==<pinned version> · import pitot
# ordinary Python — reads projected events from stdin 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)
Once the boundary is open, the missing feature becomes ordinary code.
Build selective project memory from projected events, without preserving entire chats.
Check tests, CI, repository state, or another source of truth before the agent proceeds.
Read the build →Route a release or protected action through your own local approval logic.
Read the build →Track actions, model usage, latency, failures, and outcomes across coding-agent hosts.
Read the build →Use the same boundary to create workflows and tools that coding-agent vendors have not built yet.
Every supervised adapter must pass a binary-observed loop through the real coding-agent host on Ubuntu, macOS, and Windows:
prompt → real hook → projected Consumer → Controller allow / deny → tool result
Not just generated payloads.
Not just protocol unit tests.
The actual integration boundary.
Ubuntu · macOS · Windows — Claude, Codex, Copilot CLI, Cursor, Gemini, Kimi, OpenCode, Pi, Qwen
Pitot transports the event and the decision. Your code decides what approved, verified, safe, complete, or useful means.