Keep your coding agent. Add the behavior it is missing.
Every supervised adapter must pass a binary-observed prompt → real action boundary → projected Consumer → Controller allow/deny → tool-result loop on Ubuntu, macOS, and Windows.
Supervised adapters: Claude · Cursor · Codex · Devin · GitHub Copilot CLI · Gemini · Kimi Code · OpenCode · Pi · Qwen Code
Supervised runtime capabilities: action control · Consumer delivery · explicit request
One language-neutral boundary for the coding agents your team already uses.
Your coding agent runs shell commands, edits files, and calls tools. Pitot lets you put 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.
Pitot reports what happened. Your code decides what it means.
The fastest way to understand Pitot is to watch one real command get allowed and another get denied. This walkthrough is exactly what Pitot's automated test suite exercises on every commit, so the behavior below is verified, not aspirational.
1. Scaffold a sample shell policy. This writes a runnable Controller that
allows shell commands by default and denies any command containing the canary
string PITOT_DENY_ME:
pitot init --template shell-policy --language go --dir ./kimi-policy
cd ./kimi-policy2. Check your Kimi host wiring (Pitot does not edit your Kimi config for you):
pitot doctor --host kimiIf the PreToolUse hook is missing, add it to ~/.kimi-code/config.toml:
[[hooks]]
event = "PreToolUse"
matcher = "Bash"
command = "pitot hook kimi"3. Run Kimi behind the Controller. pitot dev starts the runtime, launches
the agent you name after --, and prints each decision:
pitot dev --host kimi -- kimi -p "Run: echo hello"An ordinary command is allowed and runs. Now ask for the canary:
pitot dev --host kimi -- kimi -p "Run: PITOT_DENY_ME=1 echo nope"The Controller denies it. The denied command never executes, and the denial
reason — Pitot sample policy blocked the PITOT_DENY_ME canary. — is returned to
Kimi as the blocked tool result. The shell-policy sample is a demonstration
tripwire, not a general shell-security control; the point is that your code
made the decision.
Devin exposes the same Pitot control semantics through a stateful ACP boundary, so it does not need lifecycle-hook files:
pitot doctor --host devin
pitot dev --host devin -- devin -p "Run: echo hello"To attach to an already-running Pitot runtime, use the explicit single-prompt surface:
pitot acp devin --runtime "$PITOT_RUNTIME" --prompt "Run: echo hello"Pitot selects only ACP's one-shot allow_once or reject_once options.
Interactive, resume, and multi-turn Devin sessions are not supported in this
initial adapter.
Building above coding agents usually forces one of two expensive choices:
- Maintain separate hooks, payload decoders, response formats, version quirks, and diagnostics for every host.
- Own or fork an entire coding-agent runtime just to gain a dependable event boundary.
Pitot provides a third option: keep the coding agents your users already chose, integrate with their host boundary once, and build only the product that differentiates you.
Pitot separates two capabilities that are easy to blur:
| Role | Receives | Can reply? | Typical use |
|---|---|---|---|
| Consumer | Projected events | No | Usage, memory, analytics, reports |
| Controller | Registered requests | Yes | Approval, verification, policy, workflow |
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.
Configure a Consumer with an omit content projection:
consumers:
- id: action-audit
command: ["python3", "./examples/action-audit.py"]
events: ["action.requested"]
projection:
content: omitPitot writes newline-delimited JSON to the program's standard input:
{"pitot_version":"1","type":"action.requested","host":{"name":"claude"},"action":{"id":"act_7f2","kind":"shell"},"content":{"mode":"omit"},"observation":{"source":"host_hook","fidelity":"direct"}}The Consumer is ordinary Python—no Pitot SDK required:
import json
import 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)The command is projected out before bytes enter the Consumer pipe. Consumer failure cannot allow or deny the waiting host action.
A coding-agent skill can make a synchronous request:
pitot request release.approval --data '{"release":"v1.4.0"}' --runtime "$PITOT_RUNTIME"Register one Controller for that request kind:
controllers:
release.approval:
id: local-approval
command: ["./examples/local-approval"]
deadline_ms: 2000
on_timeout: deny
on_unavailable: denyThe Controller receives:
{"pitot_version":"1","type":"control.requested","kind":"release.approval","action_id":"act_7f2","data":{"release":"v1.4.0"}}It checks its own source of truth and returns one correlated response:
{"pitot_version":"1","type":"control.response","controller_id":"local-approval","action_id":"act_7f2","outcome":"allow","message":"v1.4.0 is approved for publication."}Pitot validates the controller identity, action ID, deadline, schema, and single-response rule before carrying the answer back. Pitot does not know what “approved” means; the Controller owns that definition.
Nobody installs Pitot per machine — the repository pins it. pitot init
writes two substrate files alongside your tenant fragments:
.pitot/version— one committed semver line, the only version authority.pitot/bin/pitot(+pitot.ps1) — a committed shim that reads the pin, hydrates exactly that release into a per-user cache (~/.cache/pitot/<version>/, sha256-verified against the publishedchecksums.txt), and execs it
Fresh clones, CI, and cloud agents run .pitot/bin/pitot with zero setup —
the first invocation hydrates, every later one is a cache hit. There is no
fallback to whatever binary happens to be on PATH; a missing release with no
network fails closed with a named error, and PITOT_NO_HYDRATE=1 makes
hydration cache-only.
Upgrades are a reviewed diff. 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.
pitot upgrade --check # report pinned vs latest
pitot upgrade # hydrate, validate tenants, rewrite .pitot/versionFor a global CLI convenience (running pitot init in new repos), grab a
release binary or build from source:
go install github.com/operatorstack/pitot/cmd/pitot@latestTyped SDKs install from our own registry through the distribution front door — never public npm/PyPI, pinned to the CLI's version:
pitot install typescript # scoped .npmrc + @operatorstack/pitot@<version>
pitot install python # .pitot/registry + operatorstack-pitot==<version>(install typescript creates a minimal private package.json when the
directory has none — npm honors a project registry config only inside a
project.)
Inspect the effective local boundary, pin, and cache state at any time:
pitot doctor1. Scaffold a Controller. pitot init writes a runnable project — source
and a package manifest — and registers it as one tenant fragment under
.pitot/conf.d/. It never overwrites existing files unless you pass --force.
Pick a starting template with --template:
pitot init --template shell-policy --language go --dir kimi-policyInitialized go controller (shell-policy) in kimi-policy
Files written: .pitot/conf.d/kimi-policy.yaml, kimi-policy/go.mod, kimi-policy/main.go
Next:
1. Configure a supported host hook (see: pitot doctor --host HOST).
2. Run: pitot dev --host HOST -- AGENT [ARGS...]
example: pitot dev --host kimi -- kimi -p "<prompt>"
Available templates are shell-policy (allow/deny shell commands),
release-approval and blank-controller (request/response controllers), and
blank-consumer (a passive event reader). Without --template, pitot init
detects the language from the current directory or prompts you to choose. The
four first-class languages (python, typescript, go, rust) each generate a
complete project that builds after installing dependencies.
2. Run your agent behind it. pitot dev discovers every fragment under
.pitot/conf.d/, starts the runtime and the declared Controllers, waits until
the runtime is ready, then launches the agent you name after -- with
PITOT_RUNTIME set so its host hook finds the runtime. It prints each decision
as the agent makes it:
pitot dev --host kimi -- kimi -p "Run: PITOT_DENY_ME=1 echo nope"Starting Pitot dev environment for host kimi...
Runtime ready. Starting agent: kimi -p Run: PITOT_DENY_ME=1 echo nope
Decisions:
[DENY] shell (act_1a9) — Pitot sample policy blocked the PITOT_DENY_ME canary.
Agent finished. Runtime stopped.
--host must name a supported agent (claude, codex, copilot, cursor,
devin, gemini, kimi, opencode, pi, qwen). Hook-based hosts must
already be wired to pitot hook HOST (see Connect your agent and
pitot doctor --host HOST); Devin connects over ACP and needs no hook file.
The runtime descriptor lives in a per-invocation
temporary path and is removed on exit, so concurrent pitot dev sessions never
collide.
3. Swap the agent. The same project — the same Controller and the same
fragment — works with any other supported host whose hook is wired. Change only
--host and the agent command:
pitot dev --host cursor -- cursor-agent -p "Run: PITOT_DENY_ME=1 echo nope"The boundary is language- and agent-neutral: one Controller, every agent.
Configuration is tenant-partitioned: 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:
.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`
Each fragment is a complete, strictly parsed mini-config declaring
controllers: and/or consumers:. Merge rules:
- Consumers always compose. Any number of tenants can observe
action.requestedevents. - A request kind has one owner. Two fragments claiming the same kind (for
example
shell) fail discovery with an error naming both files — a loud, attributable conflict instead of two tools silently fighting over one blocking hook. Controller and consumer ids must also be unique across fragments. requires_protocol: "1"optionally pins the protocol version a fragment was written against; a fragment this binary cannot honor fails discovery.dir:sets a process's working directory (relative to the repository root), so each tenant's command stays project-relative:
controllers:
shell:
id: local-shell-policy
command: ["go", "run", "main.go"]
dir: "kimi-policy"
deadline_ms: 2000
on_timeout: deny
on_unavailable: denyInstalling a second Pitot-based tool is therefore additive by construction: it
drops its own fragment next to yours, pitot run/pitot dev merge them, and
uninstalling it is deleting its fragment.
pitot dev is the recommended path. If you need to manage the runtime yourself
(for example, sharing one runtime across several long-lived agent sessions),
start it with repository-owned configuration and an owner-only runtime
descriptor:
export PITOT_RUNTIME="${XDG_RUNTIME_DIR:-$TMPDIR}/pitot/project.json"
pitot run --runtime "$PITOT_RUNTIME"With no --config, pitot run discovers and merges the repository's
.pitot/conf.d/ fragments. Pass --config PATH to override discovery with one
explicit file (useful for tests and ad-hoc runtimes).
Start coding-agent CLIs from the same environment. Their pitot hook HOST
commands discover the authenticated runtime through PITOT_RUNTIME. Without
that variable or --runtime PATH, hooks remain observation-only for backwards
compatibility. Once a runtime is explicitly selected, transport or
authentication failure blocks the controllable action.
On Windows, set the descriptor in the launching PowerShell session:
$env:PITOT_RUNTIME = Join-Path $env:LOCALAPPDATA "Pitot\project.json"
pitot run --runtime $env:PITOT_RUNTIMEEvery host below normalizes its native blocking boundary to a shell action and
passes Pitot's language-neutral decoder conformance suite. The E2E column marks
adapters exercised by the cross-platform agent supervisor (the badge at the top);
the Platforms column names the operating systems each one is verified on. Kimi
additionally has an in-repo, no-model test that asserts the full allow and
deny control path end to end.
| Host | Blocking boundary | Hook wiring | Platforms | Verified in this repo |
|---|---|---|---|---|
| Kimi Code | PreToolUse / Bash |
native config.toml |
Ubuntu · macOS · Windows | decoder + E2E + allow/deny control test |
| Claude | PreToolUse |
native settings hook | Ubuntu · macOS · Windows | decoder + E2E |
| Cursor | beforeShellExecution |
bridge (integrations/cursor) |
Ubuntu · macOS · Windows (WSL) | decoder + E2E |
| Codex | PreToolUse |
bridge (integrations/codex) |
Ubuntu · macOS · Windows | decoder + E2E |
| Devin | session/request_permission (ACP) |
stateful ACP transport | Ubuntu · macOS · Windows | decoder + E2E |
| GitHub Copilot CLI | PreToolUse |
bridge (integrations/copilot) |
Ubuntu · macOS · Windows | decoder + E2E |
| Gemini | BeforeTool |
bridge (integrations/gemini) |
Ubuntu · macOS · Windows | decoder + E2E |
| OpenCode | PreToolUse |
bridge (integrations/opencode) |
Ubuntu · macOS · Windows | decoder + E2E |
| Pi | tool_call |
extension (integrations/pi) |
Ubuntu · macOS · Windows | decoder + E2E |
| Qwen Code | PreToolUse |
bridge (integrations/qwen) |
Ubuntu · macOS · Windows | decoder + E2E |
"Decoder" means Pitot correctly normalizes that host's payload into the stable event envelope. It does not claim Pitot judges whether any command is safe — that decision belongs to your Controller. On Windows, Cursor runs under WSL; every other host runs natively.
A host earns a supervised adapter only when its blocking boundary can complete Pitot's causal loop: a proposed command must reach a Controller, an allow or deny decision must apply before the command runs, and — critically — a denied action must return to the model so the agent can continue from the blocked outcome rather than halting. This last requirement, deny-continuation, is what distinguishes a supervisable boundary from one that can only abort.
Devin is the worked example. Its lifecycle hooks (PreToolUse,
PermissionRequest) apply a denial but end the turn instead of handing the
rejected outcome back to the model, so they do not complete the loop in
non-interactive mode. Its Agent Client Protocol surface does: the client
selects reject_once, the canary never executes, and Devin makes a follow-up
model request carrying the rejection and continues. Pitot therefore ships Devin
over ACP and does not ship the hook wiring. The full investigation, including
the content-safe evidence receipt, is in
docs/devin-adapter-research.md.
The per-host hooks below wire each agent's native blocking boundary to Pitot. Hosts whose hook config lives in the repository are wired by Pitot itself:
pitot init --host claude # also: cursor, codex, geminiThat writes exactly one marker-owned entry into the host's repo config
(.claude/settings.json, .cursor/hooks.json, .codex/hooks.json,
.gemini/settings.json), pointing at the repo shim — foreign entries are
never touched, and the committed fragment .pitot/hooks/<host>.fragment.json
witnesses what was installed. pitot doctor --host HOST reports the entry as
FOUND, MISSING, or DRIFTED against that witness; pitot doctor --host HOST --fix restores a drifted entry (Pitot's own entries only — that is the single
mutation doctor ever performs, and only on request).
Hosts configured at user level (Kimi, Copilot, Qwen) or via plugin
files (OpenCode, Pi) keep the one-time manual edit below — Pitot does not
edit files outside the repository; pitot init --host kimi prints the exact
snippet. Once wired, both pitot dev and the manual runtime flow use the same
hook.
pitot init --host claude
pitot init --host codexBoth wire a PreToolUse hook (matcher Bash) whose command runs the repo
shim: "$CLAUDE_PROJECT_DIR"/.pitot/bin/pitot hook claude (Codex uses the
repo-relative equivalent plus a PowerShell commandWindows). Exit 0 allows;
exit 2 blocks with the Controller's reason.
Install Kimi Code on macOS or Linux using its official installer:
curl -fsSL https://code.kimi.com/kimi-code/install.sh | bash
kimi --versionOn Windows, use the official PowerShell installer:
irm https://code.kimi.com/kimi-code/install.ps1 | iex
kimi --versionConnect Kimi Code's blocking shell boundary to Pitot in
~/.kimi-code/config.toml:
[[hooks]]
event = "PreToolUse"
matcher = "Bash"
command = "pitot hook kimi"Kimi sends the hook payload to Pitot on standard input. Pitot exits 0 when
the request is accepted and 2 when input is malformed or the configured
Controller denies the action. Check
the non-interactive Kimi CLI after configuration with:
kimi -p "Show the repository status"See the Kimi Code documentation for CLI authentication, configuration, and hook behavior.
Copy integrations/copilot/PreToolUse to a stable executable path (or use
PreToolUse.ps1 on Windows), then add the following Claude-compatible hook to
~/.copilot/settings.json:
{
"hooks": {
"PreToolUse": [{
"matcher": "Bash",
"hooks": [{"type": "command", "command": "/path/to/PreToolUse"}]
}]
}
}The bridge keeps the blocking payload on Pitot's standard hook_event_name
and tool_input.command boundary and returns Copilot's structured native deny
reason when a Controller rejects the command. See the official
Copilot CLI hooks reference.
pitot init --host cursorWrites the committed bridge .pitot/bin/hooks/cursor-beforeShellExecution
(which execs the repo shim and returns Cursor's native permission: "deny"
envelope, including the Controller message) and points .cursor/hooks.json at
it with failClosed: true. See Cursor's
hooks documentation.
pitot init --host geminiWrites the committed bridge .pitot/bin/hooks/gemini-BeforeTool and registers
it as a BeforeTool command hook for run_shell_command. The bridge
translates Pitot rejection into Gemini's structured decision: "deny" and
reason response so the model receives the blocked tool result. See the
Gemini CLI hooks reference.
Copy integrations/qwen/PreToolUse to a stable executable path (or use the
Node-based PreToolUse.cjs bridge on Windows), then add this command hook to
~/.qwen/settings.json:
{
"hooks": {
"PreToolUse": [{
"matcher": "^Bash$",
"hooks": [{"type": "command", "command": "/path/to/PreToolUse"}]
}]
}
}Qwen sends the native JSON payload on standard input. The bridge returns its native structured allow or deny decision and preserves the Controller reason. See the official Qwen Code hooks guide.
Copy integrations/pi/pitot.ts into ~/.pi/agent/extensions/pitot.ts (or the
repository-local .pi/extensions/ directory). The extension converts Pi's
blocking tool_call event into Pitot's stable envelope and returns Pi's native
block response when Pitot rejects the request. See the official
Pi extensions documentation.
Devin needs no hook file. Pitot speaks to it over the Agent Client Protocol,
launching devin acp as a stdio JSON-RPC server and correlating each
tool_call command by its tool-call ID:
pitot dev --host devin -- devin -p "Run: echo hello"To attach to an already-running runtime, use the explicit single-prompt surface:
pitot acp devin --runtime "$PITOT_RUNTIME" --prompt "Run: echo hello"Pitot maps an allow decision to ACP's one-shot allow_once and a deny to
reject_once; it never selects a persistent option such as allow_always or a
bypass mode. Attestation comes from the Controller receipts and canary rather
than a lifecycle-hook witness. This initial adapter is single-prompt; resume and
multi-turn Devin sessions are not yet supported. See
docs/devin-adapter-research.md for why ACP,
not hooks, is the supervised boundary.
Pitot uses supervised local processes in v1. It starts declared Consumers and Controllers itself, applies each projection before bytes enter the child pipe, and exposes only a loopback endpoint authenticated by the owner-only runtime descriptor. The capability token is never passed to child processes or logs.
Pitot's reference implementation is Go. Its public contract is not.
Compatibility is defined by:
- versioned JSON Schemas;
- newline-delimited JSON framing;
- explicit request/response state machines;
- capability and projection declarations; and
- language-neutral conformance fixtures.
If a program can read JSON Lines from standard input, it can be a Consumer. If it can return one schema-valid response on its dedicated output channel, it can be a Controller.
Official client libraries are optional conveniences—not a prerequisite and not the source of protocol truth.
Every event identifies its schema, source, session, and observation quality:
{
"pitot_version": "1",
"id": "evt_01J...",
"type": "action.requested",
"time": "2026-07-19T16:05:00Z",
"host": {
"name": "cursor",
"adapter_version": "1.0.0"
},
"session_id": "sess_42",
"action": {
"id": "act_7f2",
"kind": "shell"
},
"content": {
"mode": "sha256",
"sha256": "9f2..."
},
"observation": {
"source": "host_hook",
"fidelity": "direct"
}
}Adapters preserve host capability differences. A normalized field is never presented as directly observed when the host omitted it or Pitot inferred it.
Synchronous hooks are control channels: the host is blocked waiting for an answer. Pitot makes that privilege explicit.
- Exactly one Controller may register for a request kind.
- Registration is static, auditable configuration.
- The registration declares a deadline and unavailable/timeout defaults.
- Every response is bound to its pending action and Controller identity.
- Late, stale, duplicate, mismatched, and malformed responses are rejected.
- A pending action receives exactly one terminal resolution.
- Registration and its configuration fingerprint appear in diagnostics.
The bridge mechanically enforces the declared default. It contains no approval, safety, completion, or shipping policy engine.
Pitot distinguishes a broken measurement boundary from a judgment about the work:
{
"pitot_version": "1",
"type": "boundary.fault",
"host": "cursor",
"action_id": "act_7f2",
"reason": "empty-command"
}Reason codes never contain prompts, commands, tool inputs, or outputs. A Controller may choose to deny, retry, report, or escalate the fault according to its own policy.
Pitot is local and storage-free by default.
- Raw host payloads terminate inside the adapter.
- Content projection is
full,sha256, oromitper Consumer. - Projection happens before delivery, not inside downstream applications.
- No network exporter is enabled implicitly.
- Pitot does not retain an event history unless a configured Consumer does.
- Passive Consumers receive no Controller capability.
- Diagnostics and fault codes are content-safe.
- selective cross-agent memory;
- session and decision reports;
- token and cost attribution;
- reliability and host-compatibility diagnostics;
- OpenTelemetry exporters;
- human approval routers;
- security or compliance Controllers;
- delivery verifiers;
- custom agent interfaces over existing runtimes; and
- new Pitot-compatible coding-agent runtimes.
Pitot mediates the host's wired boundary and nothing else. Understanding where that boundary ends is part of using it correctly.
- Only the wired boundary is mediated. Pitot sees an action when the host
actually routes it through the configured hook or ACP transport. An action
the host takes through a path you did not wire is not observed.
pitot doctor --host HOSTreports whether the boundary is present, and for repo-owned hosts whether the entry has drifted. - User-level hook configs are user-editable. For hosts wired at user level
(Kimi, Copilot, Qwen) the hook lives in a file the user owns and can change
or remove. Pitot does not police edits outside the repository; it reports the
current state through
pitot doctor. - Without a runtime, hooks only observe. A
pitot hookinvocation with noPITOT_RUNTIME(or--runtime) selected records the action and exits allowing it — observation-only, for backwards compatibility. It now prints a one-line notice so this mode is never silent, andpitot doctorflags a host that is wired but has no runtime. Once a runtime is explicitly selected, transport or authentication failure blocks the controllable action rather than falling open. - Hosts retain their own bypass options. Some hosts expose persistent or
bypass permissions (for example Devin's ACP
allow_alwaysand switch-bypass modes). Pitot never selects them — it uses only the one-shotallow_once/reject_onceoptions — but a human operating the host directly still can. - Kimi executes the action if its hook process crashes or times out. This
is Kimi's host semantics, not a Pitot decision, and Pitot cannot override it:
a supervisory-control analysis of the Kimi lifecycle shows a reachable
transition from the pending state straight to an unsupervised execution when
the hook fails open, which no supervisor placed at the boundary can prevent.
Every other supervised host either fails closed or returns the denial to the
model.
pitot doctor --host kimistates this plainly. If your policy cannot tolerate fail-open execution, prefer a fail-closed host (such as Cursor, wired withfailClosed: true) or Devin's ACP transport.
None of these change the core contract: within the boundary Pitot mediates, every pending action receives exactly one terminal resolution. Pitot reports. Your controller decides.
Pitot does not define whether:
- work is correct or complete;
- an action is safe;
- a person granted approval;
- evidence satisfies a requirement;
- a claim is valid; or
- something may be shipped.
Those meanings belong to your Controller. Pitot reports. Your controller decides.
pitot/
├── schema/ public event and response schemas
├── protocol/ framing and state-machine specifications
├── adapters/ built-in coding-agent hook boundaries
├── sensor/ normalization and observation pipeline
├── bridge/ controller routing and response transport
├── projection/ full, sha256, and omit policies
├── config/ strict Consumer and Controller declarations
├── runtime/ authenticated ingress and local process delivery
├── conformance/ language-neutral fixtures and negative controls
├── examples/ local approval and Consumer examples
└── cmd/pitot/ reference Go executable
- Report before interpretation. Preserve what the host actually supplied.
- Make capability structural. Consumers cannot reply; Controllers can.
- Keep host churn together. Decoders and encoders share one compatibility boundary.
- Project before delivery. Privacy is enforced before content crosses the process boundary.
- Correlate every answer. A response can resolve only its pending action.
- Prefer one maintained implementation. Use a language-neutral protocol instead of rewriting the host boundary in every ecosystem.
- Extend standards where they fit. Map to OpenTelemetry GenAI conventions without erasing Pitot-specific provenance or semantic events.
A pitot probe measures pressure difference so another system can determine airspeed. It does not fly the aircraft.
Pitot applies the same separation to coding-agent tooling: measurement belongs at the host boundary; interpretation and control belong downstream.
Start with the protocol and conformance fixtures. A new adapter should declare its host capabilities, normalize supported events, classify boundary faults without exposing content, encode Controller responses, and pass the shared positive and negative fixture suite. It must also meet the host admission criteria above — a boundary that can only abort on denial, rather than return the outcome to the model, is not yet supervisable.
See CONTRIBUTING.md for development setup, the two adapter transport classes (one-shot hook and stateful ACP-style transport), and the cross-platform verification every adapter must pass.
Apache-2.0