feat(run): zero-setup governance bridge for launched agents (Epic A) - #81
Merged
Conversation
Python client for the Go kernelcapture daemon's Unix-socket control plane (register_session / end_session / health), speaking the JSON-line kernelcapture.daemon.v1 protocol, plus cgroup v2 helpers that create a per-run cgroup and report its inode-based cgroup id (the value bpf_get_current_cgroup_id returns). Everything degrades gracefully: no daemon, no cgroup v2, or an unprivileged launcher all return unavailable/None instead of raising, so the launcher can still govern via the env/hook path. Refs Epic A (#63), task #66.
Extend 'ardur run' so that, with --mission/--allowed-tools/--max-tool-calls, it issues a Mission Passport, starts a governance session, and launches the agent governed end to end — no prior 'ardur protect', no permanent edit to ~/.claude/settings.json. - run_bridge.run_governed(): ephemeral Ardur home (keys + state + active passport) -> embedded loopback GovernanceProxy + session -> launch agent via an AgentAdapter -> best-effort cgroup + eBPF daemon correlation -> finalize into a behavioral attestation + signed receipt chain -> print a governance summary. - AgentAdapters: EnvProxyAdapter (generic, routes tool calls to the session via ARDUR_PROXY_URL/ARDUR_API_TOKEN/ARDUR_SESSION_ID), ClaudeCodeAdapter (points the hook at the run via VIBAP_HOME + a scoped --plugin-dir, no settings.json edit), and TransparentInterceptAdapter (SCAFFOLD only for non-hook CLIs — iptables/LD_PRELOAD egress shim, issue #69). - cli: governance flags on the 'run' subparser; legacy hub-streaming path is preserved and selected when no governance flag is present. - Integration test launches a stand-in agent making a PERMIT-able and a DENY-able call and asserts a started session, evaluated calls, a cryptographically verifiable signed receipt chain, and a verifiable behavioral attestation — with zero 'ardur protect' setup. Refs Epic A (#63), tasks #66 and #69.
| server_thread = threading.Thread(target=server.serve_forever, name="ardur-run-proxy", daemon=True) | ||
| server_thread.start() | ||
|
|
||
| correlation = kc.CorrelationResult(available=False, reason="not attempted") |
| """ | ||
| try: | ||
| self.path.rmdir() | ||
| except OSError: |
| passport_path.write_text(token + "\n", encoding="utf-8") | ||
| try: | ||
| passport_path.chmod(0o600) | ||
| except OSError: |
| kc.KernelCaptureClient(kc.daemon_socket_path()).end_session( | ||
| session_id=session_id, trace_id=trace_id | ||
| ) | ||
| except (kc.DaemonUnavailable, kc.DaemonProtocolError): |
ClaudeCodeAdapter's docstring omitted a key architectural fact: the Claude Code hook evaluates tool calls locally via the plugin mechanism and never POSTs to ARDUR_PROXY_URL/evaluate. The embedded proxy therefore observes 0 events on the claude-code path, making result.total_events misleadingly empty while governance is still active through the hook. Document the gap explicitly in the class docstring and add a test that pins result.total_events == 0 on the claude-code path. The test will need updating once the hook is wired to also report to the embedded proxy (Epic A #63).
This was referenced Jul 1, 2026
gnanirahulnutakki
marked this pull request as ready for review
July 1, 2026 05:56
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bridge layer: eBPF detection → real auto-governance
This wires Ardur's proven detection and governance halves together so a single
command governs an agent with zero manual per-project setup. Today the eBPF
detector (
go/pkg/kernelcapture/) sees an agent launch but nothing routes thatprocess's tool calls through the governance proxy (
python/vibap/proxy.py), andgovernance is opt-in via
ardur protectwriting per-project hooks. This PRturns
ardur runinto the launcher that closes that gap.Refs Epic A (#63), #66 (daemon/launcher), #69 (wire governance to
auto-detected agents).
What works now (Slice 1)
ardur run --mission "..." --allowed-tools Read,Glob --max-tool-calls N -- <agent-cmd...>:ephemeral Ardur home (keys + state +
active_mission.jwt). No priorardur protect.governance to this session, via an
AgentAdapter:EnvProxyAdapter(generic / default): exportsARDUR_PROXY_URL/ARDUR_API_TOKEN/ARDUR_SESSION_IDso a cooperatingagent POSTs each tool call to the session's
/evaluate.ClaudeCodeAdapter: points Claude Code's hook at this run viaVIBAP_HOME--plugin-dir— run-scoped, temporary, no permanent editto
~/.claude/settings.json.the agent and registers
(session_id, root_pid, cgroup_id)with the eBPFdaemon over its Unix-socket control plane (
kernelcapture.daemon.v1). Thecgroup id is the cgroup directory inode — exactly what
bpf_get_current_cgroup_id()returns — so it correlates 1:1 with kernelevents. When cgroup v2 or the daemon is unavailable (e.g. macOS, unprivileged
host), it degrades gracefully and still governs via the env/hook path.
signed receipt chain and prints a short governance summary
(permits/denials, attestation digest, receipt count, kernel-link status).
Tests
python/tests/test_run_bridge.py— integration test:ardur runs a stand-inagent that makes a PERMIT-able (
Read) and a DENY-able (Bash) tool call andasserts, with zero
ardur protectsetup:(
verify_chain);verify_attestation),with
permits/denialsmatching;~/.claude/settings.json;Plus adapter unit tests and
python/tests/test_kernel_correlation.py(cgrouphelpers against a temp-dir fake root; the daemon client against a fake AF_UNIX
server; input validation; graceful-degradation paths).
Results: full Python suite 1036 passed / 32 skipped; Go
kernelcaptureandgo build ./...green. New files are ruff-clean.Design notes (decisive, pragmatic)
real
GovernanceProxy(evaluate / result / end / attest). It reuses the realevaluation, receipt-signing, and attestation logic; it is cleanly stoppable so
it does not outlive a run (important under tests). It does not fork the
big
serve_proxy.ardur run's legacy Ardur-Personal-Hub streaming path is preserved andselected when no governance flag is present; governance flags switch it to the
bridge. Existing
run_under_hubtests stay green.its documented JSON-line protocol directly. A native Go launcher remains a
possible future.
Scaffold only / remaining gap (the honest part)
is
TransparentInterceptAdapter— a fixed interface + clearTODO, notimplemented. The intended mechanism is an iptables REDIRECT (Linux) or an
LD_PRELOAD/ proxy-env egress shim routing the agent's API traffic throughthe governance proxy. Follow-up: [A] Wire the full governance cycle to auto-detected agents #69.
paths here are the protocol client, the cgroup helpers (temp-root), and the
graceful-degradation branches (the bridge ran end-to-end on macOS).
chain (under the run's
VIBAP_HOME); unifying the hook to also drive theembedded proxy session's
/evaluateis a natural follow-up.Opening as draft — not for merge.