Context
Pitot launches Controllers as local subprocesses and mediates events over JSON-Lines stdio (control.requested → control.response), with deadlines and fail-closed on_timeout: deny / on_unavailable: deny defaults. This works well on a developer laptop. As Interlock/Pitot get used from cloud coding agents (Claude Code remote, Cursor cloud, Devin-style, Codespaces) and ephemeral sandboxes, we need to confirm the transport still reaches a controller — and fails closed when it can't.
Scope: transport & ergonomics only. The enforcement authority stays the broker (hash-bound evidence + effect) and the CI merge gate. This issue must not move the guarantee into the transport.
Problem
In remote/sandboxed environments, the assumptions Pitot relies on locally may not hold:
- the controller binary may be absent (ephemeral, fresh clone)
- egress may be blocked (a remote controller becomes unreachable → denies all)
- the agent may not run an interactive shell / the shell-hook path may not fire
- runtime state (
rt.json) is per-session and must be re-initialized
Scenario matrix (what we need to verify)
| Environment |
Controller present? |
Hooks fire? |
Notes |
| Local laptop |
yes |
shell + git hooks |
works today |
| Local agent (Claude Code / Cursor) |
yes |
yes — incl. Claude Code PreToolUse hooks |
needs a thin host adapter |
| Remote/cloud agent (sandbox) |
needs hydration |
maybe |
the gap this issue targets |
| CI |
yes |
n/a |
the authority (merge gate) |
Proposed approaches (to evaluate)
- Local sidecar controller (recommended default). Hydrate + run the controller inside the sandbox; keep stdio IPC local so no egress is required and
on_unavailable: deny stays meaningful. Claude Code's hooks execute shell in the sandbox, so if the controller is hydrated and hooks are wired, enforcement fires locally — even remotely.
- Host-hook adapters. Thin per-host shims mapping the agent's native hook system to
control.requested emission (Claude Code PreToolUse/PostToolUse, git hooks via core.hooksPath, devcontainer postCreate bootstrap). Idempotent on a fresh clone.
- Optional network transport. A gRPC/HTTP variant of the Controller contract for a centralized policy service, with auth + latency budget + fail-closed on unreachable. Only where a local controller isn't viable.
Bias toward explicit control.requested (structured EffectRequest) over shell-hook inference — consistent with Interlock refusing to infer effects from command strings.
Non-goals
- Changing the enforcement authority (broker + CI gate remain authoritative).
- Inferring file effects from shell command strings.
- Requiring network egress for the common path.
Open questions
- Bootstrap/hydration story for ephemeral sandboxes (who runs it, when)?
- Per-host adapters vs one common interception point?
- How do we test each host in CI (matrix of agent environments)?
Acceptance criteria
References
integrations/pitot Controller contract (interlock-pitot)
- Interlock broker (authority):
labs/21-interlock/interlock/broker
Context
Pitot launches Controllers as local subprocesses and mediates events over JSON-Lines stdio (
control.requested→control.response), with deadlines and fail-closedon_timeout: deny/on_unavailable: denydefaults. This works well on a developer laptop. As Interlock/Pitot get used from cloud coding agents (Claude Code remote, Cursor cloud, Devin-style, Codespaces) and ephemeral sandboxes, we need to confirm the transport still reaches a controller — and fails closed when it can't.Scope: transport & ergonomics only. The enforcement authority stays the broker (hash-bound evidence + effect) and the CI merge gate. This issue must not move the guarantee into the transport.
Problem
In remote/sandboxed environments, the assumptions Pitot relies on locally may not hold:
rt.json) is per-session and must be re-initializedScenario matrix (what we need to verify)
Proposed approaches (to evaluate)
on_unavailable: denystays meaningful. Claude Code's hooks execute shell in the sandbox, so if the controller is hydrated and hooks are wired, enforcement fires locally — even remotely.control.requestedemission (Claude Code PreToolUse/PostToolUse, git hooks viacore.hooksPath, devcontainerpostCreatebootstrap). Idempotent on a fresh clone.Bias toward explicit
control.requested(structuredEffectRequest) over shell-hook inference — consistent with Interlock refusing to infer effects from command strings.Non-goals
Open questions
Acceptance criteria
control.requestedreaches a hydrated local controller.References
integrations/pitotController contract (interlock-pitot)labs/21-interlock/interlock/broker