openlatch-client 0.2.2

OpenLatch runtime enforcement node — the capture-and-enforce adapter that evaluates every covered action against a coding agent's Autonomy Zone before it runs
docs.rs failed to build openlatch-client-0.2.2
Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
Visit the last successful build: openlatch-client-0.0.1

OpenLatch is the agent-agnostic execution control layer for enterprise AI agents. This repository is its open-source capture-and-enforce adapter: the node that runs inside a coding agent's own execution path, evaluates every covered action against that agent's Autonomy Zone before it runs, and is what gives an agent the control level Enforced.

It is Mode 1 — the native hook — the first of three adapters onto one policy brain. It plugs into agents through their native lifecycle hooks, decides locally, and forwards what it sees to the control plane.

This README is for people working on the client. If you want to use OpenLatch, start at docs.openlatch.ainpx @openlatch/client init and you are done.


Where this node sits

OpenLatch runs a five-step control loop around each agent's Agent Autonomy Zone — the operating envelope of what an agent may do, authored on the platform as intent policies across five dimensions (Quality, Economics, Security, Safety, Compliance) and compiled to deterministic rules. This client touches four of the five steps. None of the authoring happens here.

Step What this node does What it does not do
1 · Identify Stamps every event with agent type, session, host and identity, so the platform's record of a sanctioned agent stays live rather than quarterly Discovery. An agent arrives here by being installed onto, never by being found
2 · Define Nothing. It receives the compiled zone as a signed policy bundle and holds it resident Author policy. Intent policies are written and compiled on the platform
3 · Detect Captures the actions the execution graph is built from — tool calls, commands, file changes, model calls, cost — and matches them against the resident bundle Detection of its own. No models, no scanning, no cross-host correlation
4 · Act Returns the verdict at the hook boundary, before the action runs, from an in-process evaluation Ask the network for it
5 · Improve Writes the local audit log and forwards every event, verdict and mode asynchronously Report, alert, or render a dashboard

Four properties shape the code more than any feature does:

Property What it means here
Inside the agent, not a proxy in front of it The decision point is the agent's own hook lifecycle, so it sees the action rather than the traffic. It is not a sandbox — the threat model is a misbehaving agent, not an operator working to defeat it
Local-authoritative The resident bundle is the authority, not a cache of one. Evaluation reads an in-memory handle and returns, so the node keeps deciding with the network unplugged
Sanctioned agents only Depth over dragnet. This node makes the agents an enterprise has already blessed enforceable; it does not go looking for the ones it has not
Coverage is a deployment outcome An adapter in the execution path is what makes an agent Enforced. Without one an agent can still be Monitored (visible through an integration) or Known (identified and classified). The record always states which level applies

The three adapters

Mode Surface This repo
Mode 1 — native hook Coding agents, including hosted ones Yes. Ships first
Mode 2 — framework middleware LangChain / LangGraph No. Ships second
Mode 3 — SDK + tool / MCP gate Bedrock, Azure AI Foundry, custom agents No. Ships third, or on demand

Verdicts, and which ones this node enforces today

The verdict vocabulary is Allow, Ask, Block or Optimize. What this binary can express is narrower than the vocabulary, and this is the honest map of it. A verdict is deterministic because an explicit policy produced it.

Verdict On the wire / in the bundle What this node does today
Allow allow, approve The action proceeds. approve is a user-confirmed allow
Ask No wire variant — an allow / approve carrying context Rendered to Claude Code as permissionDecision: "ask", the alert as its reason. It allows and flags; it does not hold the agent waiting on a human. It prevents recurrence, not the first occurrence
Block deny — the only action a kind=command rule may take Reaches the agent as a native deny at pre_tool_use, naming the rule that produced it
Optimize kind=request rules: prefix_reorder, history_trim, prompt_edit Measurement only. history_trim and prompt_edit are rewritten to observe at bundle load whatever the config says; prefix_reorder may act only behind boundary.transforms_act, which ships false. Out of the box the boundary observes, reports what a transform would have saved, and forwards the original bytes

Staging is per rule: observe records the match and allows the action, enforce applies the verdict — Monitor and Enforce in the product. Most restrictive wins across matching rules, and every rule is coerced to observe when the bundle's enforcement_enabled is false.

Deny enforcement is event-scoped. Only pre-action events have somewhere to put a deny; on a post-action event it degrades to {} and survives in the record alone.


Develop

git clone https://github.com/OpenLatch/openlatch-client && cd openlatch-client
cargo build
cargo test

Rust 1.88+, no other prerequisites. cargo build produces both binaries in target/debug/.

Work in a sandbox, not on your own machine

openlatch init installs into ~/.openlatch, writes ~/.claude/settings.json, and points every live Claude Code session on the host at its boundary listener. You do not want your work-in-progress build doing that.

olbox gives you a complete install that cannot touch your own — its own state directory, its own agent config, its own ports:

cargo install --path tools/sandbox --locked    # once

olbox enter dev         # a shell inside the sandbox
openlatch init          # installs into the sandbox and nothing else
claude                  # this session — and only this session — is routed
exit                    # and you are back on your own machine

It lives in tools/sandbox rather than in the client, because sandboxing is what we need and the client is what customers install. olbox list, olbox exec dev -- <cmd>, olbox destroy dev, and eval "$(olbox env dev)" if you would rather not use a subshell — the README covers all of it.

Under the hood it is five environment variables derived from the name, which are supported client seams you can also set by hand. Getting one wrong is not a failed test — it writes your real ~/.claude/settings.json — which is why the tool exists.

Variable Role
OPENLATCH_DIR State: config, token, logs, outbox
CLAUDE_CONFIG_DIR Agent config. Claude Code's own variable, so the daemon and the session it captures cannot end up pointed at different directories
OPENLATCH_PORT Daemon port (sandboxes: 17400–17499)
OPENLATCH_BOUNDARY_PORT Model-boundary port (sandboxes: 17600–17699)
OPENLATCH_BOUNDARY_OWN_WIRING Lets a non-default-port instance write its own agent config. Never inferred — see .claude/rules/isolated-instances.md

openlatch uninstall --purge works inside a sandbox and removes the install; olbox destroy removes the sandbox itself.

A sandbox is never supervised: the OS unit is machine-global and carries no environment, so one installed from a sandbox shell would supervise your real install from your dev binary. supervision enable refuses there, init skips it, and doctor reports Persistence as not applicable. Use openlatch start — a sandbox is a session of work, not something that should survive a reboot.

Verify before you push

cargo fmt --all -- --check
cargo clippy --all-targets --all-features -- -D warnings
cargo test --all-features
cargo test --lib --no-default-features   # proves openlatch-hook links no `core`

tools/e2e holds the end-to-end harness; it drives a real daemon, so run it under olbox exec <name> -- or in its Docker mode — never straight against your own install. It enforces that itself and will refuse to start on a machine whose isolation seams are unset, printing the commands to fix it.


The output contract

Every command that reports health renders from one model (src/cli/report.rs) over the same eleven sections. Three rules decide what you see, and they are enforced by tests rather than by review:

State Mark Means
Ok Enabled and proven working. The only green.
Off Switched off deliberately. Always carries a code and a remedy.
Degraded / Pending Working, but not at full capability, or still verifying
Unknown Blocked by another section, which it names. One outage, one cross.
Failed Enabled and not working
NotApplicable · Absent from the build, or an exercised privacy opt-out

Eleven sections, always all reported, under three groups — the questions a reader asks in the order they ask them:

Group Sections Question
Runtime Environment · Daemon · Persistence · Update is it running, and will it keep running?
Coverage Hooks · Boundary · Policy · Inventory · Integrity is it seeing everything, and enforcing?
Platform Cloud · Telemetry is any of it leaving this machine?

A green section is one line. Anything else lists what needs attention, with its cause and its remedy. -v unfolds every check.

Runtime
  Environment  ✓    3 passed, 1 not applicable
  Daemon       ✓    2 checks passed
  Persistence  ⚠    Disabled at your request (user_opt_out)   [OL-1513]
                    The daemon will not restart after a logout, a reboot, or a crash.
                    source : [supervision] disabled_reason = "user_opt_out"
                    remedy : Run `openlatch supervision enable` to re-arm it.
  Update       ✓    Daemon is serving the installed version (0.1.18)

Platform
  Cloud        ✓    Connected: http://localhost:5173 (0 event(s) forwarded)
  Telemetry    ·    Disabled by user (opt-out honoured)

Policy sits under Coverage, not Platform: the bundle arrives from the cloud but the evaluation is local and authoritative — it is about what this host enforces, not about what leaves it.

Exit codes

Code Meaning
0 Healthy
1 Something enabled is not working
2 Usage error (clap)
3 Resource not found
4 Permission denied
5 Conflict — OL-1501 only, because systemd's RestartPreventExitStatus=5 keys off it
7 Degraded — everything runs, but something is switched off or not at full capability
130 Interrupted

7 matters: openlatch doctor && deploy must not succeed on a host where nothing is captured or enforced.


Commands

Command Notes
openlatch init Complete, idempotent reinstall. Reclaims the ports first — identifying the holder by the port, not by daemon.pid — then proves the daemon it started is the one serving (PID, version, /health).
openlatch status Dashboard plus the eleven-section verdict
openlatch start [--foreground] [--port N] [--boundary-port N] Refuses when a daemon already runs (OL-1501, exit 5)
openlatch stop / restart stop also removes the agent's boundary wiring, and says so
openlatch doctor Full diagnosis with causes and remedies
openlatch doctor --fix Heals what it can, backs up to .bak siblings + ~/.openlatch/fix-journal.json. Never re-enables what you switched off.
openlatch doctor --restore Reverses the last --fix. Surgical merge preserves your non-OpenLatch hooks.
openlatch doctor --rescue [--since 24h] [--yes] Redacted diagnostic ZIP for support
openlatch boundary status Full classification, not a port probe
openlatch boundary {enable,disable} [-y] [--no-restart] Writes [boundary] enabled, then offers the restart that applies it
openlatch hooks status The Hooks section in detail
openlatch supervision {install,uninstall,enable,disable,status} OS-native supervisor (launchd / systemd --user / Task Scheduler), user-scope only
openlatch inventory {list,log,rescan,status,inspect,projects,ack} Configuration-plane monitoring
openlatch auth {login,logout,status} · openlatch telemetry {…} · openlatch update · openlatch logs · openlatch uninstall [--purge] · openlatch docs

Noun-verb aliases: hooks install = init, daemon start = start, and so on — identical arguments, pinned by test.

Global flags: --json, --verbose/-v, --debug, --quiet/-q, --no-color. Every diagnostic's JSON carries the same sections[] the human rendering shows.


Architecture

Hooks live inside the agent, the client decides locally, the platform authors rules and runs analysis.

┌─────────────────────────────────────────────────────────────────┐
│  Your Machine                                                   │
│                                                                 │
│  ┌──────────┐  hook event   ┌──────────────────────────────┐    │
│  │  Claude  │──────────────▶│   OpenLatch Client           │    │
│  │  Code    │               │   (localhost:7443)           │    │
│  │          │               │                              │    │
│  │          │               │   1. Wrap in envelope        │    │
│  │          │               │   2. Evaluate resident       │    │
│  │          │               │      policy in-process       │    │
│  │          │  ◀─ verdict ──│   3. Redact credentials      │    │
│  └──────────┘               │   4. Write local audit log   │    │
│                             │   5. Batch → forward         │────┼──▶  OpenLatch platform
│                             │      ◀── policy bundle       │◀───┼──   (the policy brain:
│                             └──────────────────────────────┘    │      zone, rules, analysis)
└─────────────────────────────────────────────────────────────────┘
  • Hook-native, not a proxy — the decision point runs inside the agent's own runtime, so it evaluates the action rather than the traffic. Model-call economics additionally use a loopback listener on 127.0.0.1.
  • Local-authoritative verdicts — the daemon evaluates a resident policy bundle in-process; the verdict path never touches the network. The cloud serves rules, it does not decide.
  • The daemon owns ANTHROPIC_BASE_URL — written after its bind succeeds and a synthetic round trip proves the listener can reach the provider, removed on teardown. Nothing else writes it, so the agent config can never name a listener that does not exist.
  • Fail-static, not fail-open — a resident bundle keeps enforcing offline forever. Genuine fail-open is limited to "no bundle was ever fetched" and "the hook binary cannot reach the daemon".
  • Privacy-first — credentials are redacted locally before anything leaves the machine.

Two binaries ship: openlatch (<30 MB, the CLI and daemon) and openlatch-hook (<20 MB, spawned per hook event, fails open to ~/.openlatch/logs/fallback.jsonl).

The product perimeter — what the client does, and what it deliberately does not — is AGENTS.md. Subsystem detail (envelope format, policy engine, subsystem supervision, the boundary's wiring invariants, releasing) lives in .claude/rules/, one file per subsystem, scoped to the paths it governs.

Supported agents

Claude Code, on macOS, Windows and Linux, across 12 hook events. It is the reference implementation, and the only agent this node can currently make Enforced. Other coding agents are recognised as envelope agent_type values — cursor, windsurf, github-copilot, codex-cli, gemini-cli, cline, openclaw — so their events store and attribute correctly, but init cannot install their hooks yet.

Adding one means an AgentBinding in src/hooks/bindings/ and a verdict translator in src/hook_output/ — without the translator a deny is discarded before the developer ever sees it, which is worse than not enforcing. The translator comes first.


Configuration

Precedence: CLI flags → environment (OPENLATCH_*) → ~/.openlatch/config.toml → defaults.

Variable Description Default
OPENLATCH_DIR State directory ~/.openlatch
OPENLATCH_PORT Daemon port. init binds this exact port; unset, it probes 7443–7543. 7443
OPENLATCH_API_URL Cloud API base URL https://app.openlatch.ai
OPENLATCH_API_KEY API key (credential chain: environment → keychain → encrypted file)
OPENLATCH_SKIP_KEYRING Treat the OS keychain as unavailable, so the env var wins unset
OPENLATCH_LOG error / warn / info / debug / trace info
OPENLATCH_BOUNDARY_ENABLED Model-boundary listener. While on, the daemon points the agent at it — but only once a synthetic request proves the listener can reach the provider. Being wired disables Claude Code Remote Control. true
OPENLATCH_BOUNDARY_PORT Boundary port. A non-default port makes the instance isolated. 7600
OPENLATCH_BOUNDARY_OWN_WIRING Let an isolated instance write its own agent config derived from the port
OPENLATCH_BOUNDARY_UPSTREAM Where the boundary forwards. Every model call and provider credential goes here — change it only for a local harness. https://api.anthropic.com
OPENLATCH_BOUNDARY_TRANSFORMS_ACT Whether an acting prefix_reorder rule may rewrite the forwarded request body. Off, the boundary measures and forwards the original bytes. See Optimize for what it does and does not gate. false
OPENLATCH_POLICY_ENABLED Local policy evaluation. false is a complete off switch, not observe mode. true
OPENLATCH_INVENTORY_ENABLED Configuration-plane monitoring true
OPENLATCH_CLOUD_BATCH_MAX_EVENTS Events per ingest batch, clamped to 1..=100 50
OPENLATCH_CLOUD_BATCH_MAX_WAIT_MS Flush deadline, anchored to the first buffered event 5000
OPENLATCH_CLOUD_OUTBOX_MAX_BYTES Outbox cap before drop-oldest (0 disables) 104857600
OPENLATCH_CLOUD_FALLBACK_MAX_BYTES Fallback-log cap before drop-oldest (0 disables) 52428800
OPENLATCH_AUTO_UPDATE Background auto-update worker true
OPENLATCH_UPDATE_CHECK Startup version check true
OPENLATCH_NPM_REGISTRY Registry origin for openlatch update https://registry.npmjs.org
OPENLATCH_TELEMETRY_DISABLED 1 opts out of anonymous usage telemetry unset

Cloud forwarding is not optional. [cloud] enabled and OPENLATCH_CLOUD_ENABLED are still parsed so existing configs keep loading, but they are ignored — a client that captures events and sends them nowhere is a log rotator. Point [cloud] api_url elsewhere to change where events go.

Auto-update, its quiet-window deferral and its rollback sentinel are documented in the module rustdoc of src/core/update.rs; openlatch update --check --json is the manual probe.


Releases

Two artifacts on independent version lines, both cut by release-please from Conventional Commits:

Artifact Tag Where
The client v1.2.3 GitHub Releases, npm, crates.io
The wire-format JSON Schemas schemas-v1.2.3 npm, PyPI

The schema packages are versioned only by changes under schemas/, so a major bump means a breaking change to the events exchanged with the platform and nothing else. Pin them independently. Details in .claude/rules/release-lines.md.

Why it is open source

The platform is closed; this node is Apache-2.0, for three enterprise reasons.

Reason Why it matters
Transparency A node that sits in the execution path of every agent action cannot be a black box. Source access is a hard procurement gate at regulated enterprises.
Auditability Security reviews, internal AppSec and third-party testers all need to read the code that enforces policy on production agents.
Distribution The developer inside the account is who runs npx @openlatch/client init first. Open source removes the friction from that.

Security

  • Report vulnerabilities to security@openlatch.ai; private vulnerability reporting is enabled on this repository
  • Releases are built with SLSA provenance via GitHub Actions
  • Full policy: SECURITY.md

Contributing

Read the Contributing Guide, pick a good first issue, and join us on Slack.

Licensed under Apache-2.0.

Resource URL
Website openlatch.ai
Documentation docs.openlatch.ai
Slack openlatch.slack.com
Twitter / X @openlatch