@openlatch/client 0.1.18 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +207 -237
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<img src="https://raw.githubusercontent.com/OpenLatch/openlatch-client/main/assets/banner.jpg" alt="OpenLatch —
|
|
2
|
+
<img src="https://raw.githubusercontent.com/OpenLatch/openlatch-client/main/assets/banner.jpg" alt="OpenLatch — the execution control layer for enterprise AI agents" width="600" />
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
|
-
<h3 align="center">
|
|
5
|
+
<h3 align="center">Greater control should enable greater autonomy.<br /><em>Dare to Let Go.</em></h3>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
8
|
<a href="https://github.com/OpenLatch/openlatch-client/actions/workflows/pr-checks.yml"><img src="https://github.com/OpenLatch/openlatch-client/actions/workflows/pr-checks.yml/badge.svg" alt="PR Checks" /></a>
|
|
@@ -15,332 +15,303 @@
|
|
|
15
15
|
|
|
16
16
|
---
|
|
17
17
|
|
|
18
|
-
**OpenLatch
|
|
18
|
+
**[OpenLatch](https://openlatch.ai) 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**.
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
npx @openlatch/client init
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
That's it. `init` auto-detects your installed agents, writes the hook configs, and starts the daemon that captures agent activity and forwards it to the OpenLatch control plane.
|
|
20
|
+
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.
|
|
25
21
|
|
|
26
|
-
|
|
27
|
-
<!-- <p align="center">
|
|
28
|
-
<img src="https://raw.githubusercontent.com/OpenLatch/openlatch-client/main/assets/demo.gif" alt="OpenLatch demo" width="700" />
|
|
29
|
-
</p> -->
|
|
22
|
+
**This README is for people working on the client.** If you want to *use* OpenLatch, start at [docs.openlatch.ai](https://docs.openlatch.ai) — `npx @openlatch/client init` and you are done.
|
|
30
23
|
|
|
31
|
-
|
|
24
|
+
---
|
|
32
25
|
|
|
33
|
-
|
|
26
|
+
## Where this node sits
|
|
34
27
|
|
|
35
|
-
OpenLatch
|
|
28
|
+
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.
|
|
36
29
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
30
|
+
| Step | What this node does | What it does not do |
|
|
31
|
+
| ---- | ------------------- | ------------------- |
|
|
32
|
+
| **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 |
|
|
33
|
+
| **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 |
|
|
34
|
+
| **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 |
|
|
35
|
+
| **4 · Act** | Returns the verdict at the hook boundary, before the action runs, from an in-process evaluation | Ask the network for it |
|
|
36
|
+
| **5 · Improve** | Writes the local audit log and forwards every event, verdict and mode asynchronously | Report, alert, or render a dashboard |
|
|
40
37
|
|
|
41
|
-
|
|
38
|
+
Four properties shape the code more than any feature does:
|
|
42
39
|
|
|
43
|
-
|
|
40
|
+
| Property | What it means here |
|
|
41
|
+
| -------- | ------------------ |
|
|
42
|
+
| **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 |
|
|
43
|
+
| **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 |
|
|
44
|
+
| **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 |
|
|
45
|
+
| **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 |
|
|
44
46
|
|
|
45
|
-
|
|
46
|
-
| ------ | ------ | --------------- | ------ |
|
|
47
|
-
| Coding agents | Claude Code | Native lifecycle hooks | **Shipping** |
|
|
48
|
-
| Coding agents | Cursor, Windsurf, GitHub Copilot, Codex CLI, Gemini CLI, Cline, OpenClaw | Native lifecycle hooks | Planned |
|
|
49
|
-
| Cloud agent platforms | Azure AI Foundry, AWS Bedrock | SDK + tool/MCP gate | Planned |
|
|
50
|
-
| Agentic Python frameworks | LangChain / LangGraph-class | Framework middleware | Planned |
|
|
47
|
+
### The three adapters
|
|
51
48
|
|
|
52
|
-
|
|
49
|
+
| Mode | Surface | This repo |
|
|
50
|
+
| ---- | ------- | --------- |
|
|
51
|
+
| **Mode 1 — native hook** | Coding agents, including hosted ones | **Yes.** Ships first |
|
|
52
|
+
| **Mode 2 — framework middleware** | LangChain / LangGraph | No. Ships second |
|
|
53
|
+
| **Mode 3 — SDK + tool / MCP gate** | Bedrock, Azure AI Foundry, custom agents | No. Ships third, or on demand |
|
|
53
54
|
|
|
54
|
-
|
|
55
|
-
| ------------ | :---: | :-----: | :---: | ----------: |
|
|
56
|
-
| Claude Code | :white_check_mark: | :white_check_mark: | :white_check_mark: | 12 |
|
|
55
|
+
### Verdicts, and which ones this node enforces today
|
|
57
56
|
|
|
58
|
-
|
|
57
|
+
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.
|
|
59
58
|
|
|
60
|
-
|
|
59
|
+
| Verdict | On the wire / in the bundle | What this node does today |
|
|
60
|
+
| ------- | --------------------------- | ------------------------- |
|
|
61
|
+
| **Allow** | `allow`, `approve` | The action proceeds. `approve` is a user-confirmed allow |
|
|
62
|
+
| **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 |
|
|
63
|
+
| **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 |
|
|
64
|
+
| **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 |
|
|
61
65
|
|
|
62
|
-
|
|
66
|
+
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.
|
|
63
67
|
|
|
64
|
-
|
|
68
|
+
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.
|
|
65
69
|
|
|
66
|
-
|
|
67
|
-
| -------- | ------------------ |
|
|
68
|
-
| **Tool Poisoning** | Malicious MCP servers, tampered tool definitions, supply chain attacks on agent tooling |
|
|
69
|
-
| **PII & Credential Leakage** | API keys, tokens, passwords, and personal data leaving your machine via agent actions |
|
|
70
|
-
| **Prompt Injection** | Injected instructions hidden in files, tool outputs, or web content that hijack agent behavior |
|
|
71
|
-
| **Shell Command Guardrails** | Destructive commands (`rm -rf /`), privilege escalation, crypto miners, reverse shells |
|
|
72
|
-
| **Supply Chain** | Compromised dependencies, typosquatted packages, unsigned binaries |
|
|
73
|
-
| **Attack Path Analysis** | Multi-step attack chains that combine individually benign actions into a malicious sequence |
|
|
74
|
-
|
|
75
|
-
## Quick Start
|
|
70
|
+
---
|
|
76
71
|
|
|
77
|
-
|
|
72
|
+
## Develop
|
|
78
73
|
|
|
79
74
|
```bash
|
|
80
|
-
|
|
75
|
+
git clone https://github.com/OpenLatch/openlatch-client && cd openlatch-client
|
|
76
|
+
cargo build
|
|
77
|
+
cargo test
|
|
81
78
|
```
|
|
82
79
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
```bash
|
|
86
|
-
cargo install openlatch-client
|
|
87
|
-
openlatch init
|
|
88
|
-
```
|
|
80
|
+
Rust 1.88+, no other prerequisites. `cargo build` produces both binaries in `target/debug/`.
|
|
89
81
|
|
|
90
|
-
###
|
|
82
|
+
### Work in a sandbox, not on your own machine
|
|
91
83
|
|
|
92
|
-
|
|
84
|
+
`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.
|
|
93
85
|
|
|
94
|
-
|
|
86
|
+
`olbox` gives you a complete install that cannot touch your own — its own state directory, its own agent config, its own ports:
|
|
95
87
|
|
|
96
88
|
```bash
|
|
97
|
-
|
|
98
|
-
openlatch init
|
|
89
|
+
cargo install --path tools/sandbox --locked # once
|
|
99
90
|
|
|
100
|
-
|
|
101
|
-
#
|
|
102
|
-
#
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
# Check daemon status, uptime, and event counts
|
|
106
|
-
openlatch status
|
|
107
|
-
|
|
108
|
-
# View the 20 most recent security events
|
|
109
|
-
openlatch logs
|
|
110
|
-
|
|
111
|
-
# Tail events in real time
|
|
112
|
-
openlatch logs -f
|
|
91
|
+
olbox enter dev # a shell inside the sandbox
|
|
92
|
+
openlatch init # installs into the sandbox and nothing else
|
|
93
|
+
claude # this session — and only this session — is routed
|
|
94
|
+
exit # and you are back on your own machine
|
|
95
|
+
```
|
|
113
96
|
|
|
114
|
-
|
|
115
|
-
openlatch logs --since 1h
|
|
97
|
+
It lives in [`tools/sandbox`](tools/sandbox/README.md) 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.
|
|
116
98
|
|
|
117
|
-
|
|
118
|
-
openlatch start
|
|
119
|
-
openlatch stop
|
|
120
|
-
openlatch restart
|
|
99
|
+
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.
|
|
121
100
|
|
|
122
|
-
|
|
123
|
-
|
|
101
|
+
| Variable | Role |
|
|
102
|
+
| -------- | ---- |
|
|
103
|
+
| `OPENLATCH_DIR` | State: config, token, logs, outbox |
|
|
104
|
+
| `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 |
|
|
105
|
+
| `OPENLATCH_PORT` | Daemon port (sandboxes: 17400–17499) |
|
|
106
|
+
| `OPENLATCH_BOUNDARY_PORT` | Model-boundary port (sandboxes: 17600–17699) |
|
|
107
|
+
| `OPENLATCH_BOUNDARY_OWN_WIRING` | Lets a non-default-port instance write *its own* agent config. Never inferred — see [`.claude/rules/isolated-instances.md`](.claude/rules/isolated-instances.md) |
|
|
124
108
|
|
|
125
|
-
|
|
126
|
-
openlatch doctor
|
|
109
|
+
`openlatch uninstall --purge` works inside a sandbox and removes the install; `olbox destroy` removes the sandbox itself.
|
|
127
110
|
|
|
128
|
-
|
|
129
|
-
openlatch uninstall
|
|
111
|
+
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.
|
|
130
112
|
|
|
131
|
-
|
|
132
|
-
openlatch uninstall --purge
|
|
113
|
+
### Verify before you push
|
|
133
114
|
|
|
134
|
-
|
|
135
|
-
|
|
115
|
+
```bash
|
|
116
|
+
cargo fmt --all -- --check
|
|
117
|
+
cargo clippy --all-targets --all-features -- -D warnings
|
|
118
|
+
cargo test --all-features
|
|
119
|
+
cargo test --lib --no-default-features # proves openlatch-hook links no `core`
|
|
136
120
|
```
|
|
137
121
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
| Command | Description |
|
|
141
|
-
| ------- | ----------- |
|
|
142
|
-
| `openlatch init` | Auto-detect agents, install hooks, start daemon. Safe to re-run. |
|
|
143
|
-
| `openlatch status` | Show daemon status, uptime, and event counts |
|
|
144
|
-
| `openlatch start [--foreground]` | Start the daemon (or run in foreground) |
|
|
145
|
-
| `openlatch stop` | Stop the daemon |
|
|
146
|
-
| `openlatch restart` | Restart the daemon |
|
|
147
|
-
| `openlatch logs [-f] [-n N] [--since T]` | View event logs. `-f` follows, `-n` limits count, `--since` filters by time |
|
|
148
|
-
| `openlatch doctor` | Diagnose configuration and connectivity issues |
|
|
149
|
-
| `openlatch doctor --fix` | Auto-heal common issues (config, hooks, daemon, binary). Creates `.bak` siblings and a journal so changes can be reverted with `--restore`. |
|
|
150
|
-
| `openlatch doctor --restore` | Reverse the most recent `--fix` run. Surgical merge for `~/.claude/settings.json` preserves user edits to non-OpenLatch hooks. |
|
|
151
|
-
| `openlatch doctor --rescue [--since 24h] [--output PATH] [--yes]` | Bundle a redacted diagnostic ZIP for sharing with support. Privacy filter runs on every text file; tokens are hard-redacted. |
|
|
152
|
-
| `openlatch uninstall [--purge] [--yes]` | Remove hooks and stop daemon. `--purge` deletes all data |
|
|
153
|
-
| `openlatch supervision {install,uninstall,enable,disable,status}` | Manage the OS-native supervisor that keeps the daemon alive after reboot (launchd / systemd --user / Task Scheduler). Installed by default during `openlatch init`; opt out with `openlatch init --no-persistence`. |
|
|
154
|
-
| `openlatch inventory {list,log,rescan,status,inspect,projects,ack}` | Inspect configuration monitoring: list observed config sources (MCP servers, skills, commands, hooks, rules), tail `ai.openlatch.config.*` events from the audit log, force a rescan, report monitor health, drill down on a single source (`inspect`), enumerate observed project roots (`projects`), and acknowledge pending alerts (`ack`). |
|
|
155
|
-
| `openlatch docs` | Open documentation in browser |
|
|
122
|
+
`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.
|
|
156
123
|
|
|
157
|
-
|
|
124
|
+
---
|
|
158
125
|
|
|
159
|
-
|
|
126
|
+
## The output contract
|
|
160
127
|
|
|
161
|
-
|
|
128
|
+
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:
|
|
162
129
|
|
|
163
|
-
|
|
130
|
+
| State | Mark | Means |
|
|
131
|
+
| ----- | :--: | ----- |
|
|
132
|
+
| `Ok` | `✓` | Enabled **and proven working**. The only green. |
|
|
133
|
+
| `Off` | `⚠` | Switched off deliberately. Always carries a code and a remedy. |
|
|
134
|
+
| `Degraded` / `Pending` | `⚠` | Working, but not at full capability, or still verifying |
|
|
135
|
+
| `Unknown` | `⚠` | Blocked by another section, which it names. One outage, one cross. |
|
|
136
|
+
| `Failed` | `✗` | Enabled and not working |
|
|
137
|
+
| `NotApplicable` | `·` | Absent from the build, or an exercised privacy opt-out |
|
|
164
138
|
|
|
165
|
-
|
|
139
|
+
Eleven sections, always all reported, under three groups — the questions a reader asks in the order they ask them:
|
|
166
140
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
141
|
+
| Group | Sections | Question |
|
|
142
|
+
| ----- | -------- | -------- |
|
|
143
|
+
| **Runtime** | Environment · Daemon · Persistence · Update | is it running, and will it keep running? |
|
|
144
|
+
| **Coverage** | Hooks · Boundary · Policy · Inventory · Integrity | is it seeing everything, and enforcing? |
|
|
145
|
+
| **Platform** | Cloud · Telemetry | is any of it leaving this machine? |
|
|
171
146
|
|
|
172
|
-
|
|
147
|
+
A green section is one line. Anything else lists what needs attention, with its cause and its remedy. `-v` unfolds every check.
|
|
173
148
|
|
|
174
|
-
|
|
149
|
+
```
|
|
150
|
+
Runtime
|
|
151
|
+
Environment ✓ 3 passed, 1 not applicable
|
|
152
|
+
Daemon ✓ 2 checks passed
|
|
153
|
+
Persistence ⚠ Disabled at your request (user_opt_out) [OL-1513]
|
|
154
|
+
The daemon will not restart after a logout, a reboot, or a crash.
|
|
155
|
+
source : [supervision] disabled_reason = "user_opt_out"
|
|
156
|
+
remedy : Run `openlatch supervision enable` to re-arm it.
|
|
157
|
+
Update ✓ Daemon is serving the installed version (0.1.18)
|
|
158
|
+
|
|
159
|
+
Platform
|
|
160
|
+
Cloud ✓ Connected: http://localhost:5173 (0 event(s) forwarded)
|
|
161
|
+
Telemetry · Disabled by user (opt-out honoured)
|
|
162
|
+
```
|
|
175
163
|
|
|
176
|
-
|
|
177
|
-
| ---- | ----------- |
|
|
178
|
-
| `--json` | Machine-readable JSON output (parseable by `jq`) |
|
|
179
|
-
| `--verbose` / `-v` | Show extra operational detail |
|
|
180
|
-
| `--debug` | Show internal state and timings (implies `--verbose`) |
|
|
181
|
-
| `--quiet` / `-q` | Suppress all output except errors |
|
|
182
|
-
| `--no-color` | Disable colored output |
|
|
164
|
+
`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.
|
|
183
165
|
|
|
184
|
-
### Exit
|
|
166
|
+
### Exit codes
|
|
185
167
|
|
|
186
168
|
| Code | Meaning |
|
|
187
169
|
| ---- | ------- |
|
|
188
|
-
| 0 |
|
|
189
|
-
| 1 |
|
|
190
|
-
| 2 | Usage error (
|
|
170
|
+
| 0 | Healthy |
|
|
171
|
+
| 1 | Something enabled is not working |
|
|
172
|
+
| 2 | Usage error (clap) |
|
|
191
173
|
| 3 | Resource not found |
|
|
192
174
|
| 4 | Permission denied |
|
|
193
|
-
| 5 | Conflict |
|
|
194
|
-
|
|
|
175
|
+
| 5 | Conflict — `OL-1501` only, because systemd's `RestartPreventExitStatus=5` keys off it |
|
|
176
|
+
| **7** | **Degraded** — everything runs, but something is switched off or not at full capability |
|
|
177
|
+
| 130 | Interrupted |
|
|
195
178
|
|
|
196
|
-
|
|
179
|
+
`7` matters: `openlatch doctor && deploy` must not succeed on a host where nothing is captured or enforced.
|
|
180
|
+
|
|
181
|
+
---
|
|
197
182
|
|
|
198
|
-
|
|
183
|
+
## Commands
|
|
184
|
+
|
|
185
|
+
| Command | Notes |
|
|
186
|
+
| ------- | ----- |
|
|
187
|
+
| `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`). |
|
|
188
|
+
| `openlatch status` | Dashboard plus the eleven-section verdict |
|
|
189
|
+
| `openlatch start [--foreground] [--port N] [--boundary-port N]` | Refuses when a daemon already runs (`OL-1501`, exit 5) |
|
|
190
|
+
| `openlatch stop` / `restart` | `stop` also removes the agent's boundary wiring, and says so |
|
|
191
|
+
| `openlatch doctor` | Full diagnosis with causes and remedies |
|
|
192
|
+
| `openlatch doctor --fix` | Heals what it can, backs up to `.bak` siblings + `~/.openlatch/fix-journal.json`. Never re-enables what you switched off. |
|
|
193
|
+
| `openlatch doctor --restore` | Reverses the last `--fix`. Surgical merge preserves your non-OpenLatch hooks. |
|
|
194
|
+
| `openlatch doctor --rescue [--since 24h] [--yes]` | Redacted diagnostic ZIP for support |
|
|
195
|
+
| `openlatch boundary status` | Full classification, not a port probe |
|
|
196
|
+
| `openlatch boundary {enable,disable} [-y] [--no-restart]` | Writes `[boundary] enabled`, then offers the restart that applies it |
|
|
197
|
+
| `openlatch hooks status` | The Hooks section in detail |
|
|
198
|
+
| `openlatch supervision {install,uninstall,enable,disable,status}` | OS-native supervisor (launchd / systemd --user / Task Scheduler), user-scope only |
|
|
199
|
+
| `openlatch inventory {list,log,rescan,status,inspect,projects,ack}` | Configuration-plane monitoring |
|
|
200
|
+
| `openlatch auth {login,logout,status}` · `openlatch telemetry {…}` · `openlatch update` · `openlatch logs` · `openlatch uninstall [--purge]` · `openlatch docs` | |
|
|
201
|
+
|
|
202
|
+
Noun-verb aliases: `hooks install` = `init`, `daemon start` = `start`, and so on — identical arguments, pinned by test.
|
|
203
|
+
|
|
204
|
+
Global flags: `--json`, `--verbose`/`-v`, `--debug`, `--quiet`/`-q`, `--no-color`. Every diagnostic's JSON carries the same `sections[]` the human rendering shows.
|
|
199
205
|
|
|
200
|
-
|
|
201
|
-
- **`openlatch-hook`** (<20MB) — minimal hook handler spawned by agent hooks. Reads event JSON from stdin, forwards to the daemon on `localhost:7443`, and writes the verdict to stdout. If the daemon is unreachable, it fails open (returns allow) and logs to `~/.openlatch/logs/fallback.jsonl`. Not invoked directly by users.
|
|
206
|
+
---
|
|
202
207
|
|
|
203
|
-
##
|
|
208
|
+
## Architecture
|
|
204
209
|
|
|
205
|
-
|
|
210
|
+
Hooks live inside the agent, the client decides locally, the platform authors rules and runs analysis.
|
|
206
211
|
|
|
207
212
|
```
|
|
208
213
|
┌─────────────────────────────────────────────────────────────────┐
|
|
209
214
|
│ Your Machine │
|
|
210
215
|
│ │
|
|
211
216
|
│ ┌──────────┐ hook event ┌──────────────────────────────┐ │
|
|
212
|
-
│ │ Claude
|
|
213
|
-
│ │ Code
|
|
214
|
-
│ │ │
|
|
215
|
-
│ │ │
|
|
216
|
-
│ │ │
|
|
217
|
-
│ │ │
|
|
218
|
-
│ │ │ ◀─ verdict
|
|
219
|
-
│ └──────────┘
|
|
220
|
-
│ │ 5. Batch → forward
|
|
221
|
-
│ │ ◀── policy bundle
|
|
222
|
-
│ └──────────────────────────────┘ │
|
|
217
|
+
│ │ Claude │──────────────▶│ OpenLatch Client │ │
|
|
218
|
+
│ │ Code │ │ (localhost:7443) │ │
|
|
219
|
+
│ │ │ │ │ │
|
|
220
|
+
│ │ │ │ 1. Wrap in envelope │ │
|
|
221
|
+
│ │ │ │ 2. Evaluate resident │ │
|
|
222
|
+
│ │ │ │ policy in-process │ │
|
|
223
|
+
│ │ │ ◀─ verdict ──│ 3. Redact credentials │ │
|
|
224
|
+
│ └──────────┘ │ 4. Write local audit log │ │
|
|
225
|
+
│ │ 5. Batch → forward │────┼──▶ OpenLatch platform
|
|
226
|
+
│ │ ◀── policy bundle │◀───┼── (the policy brain:
|
|
227
|
+
│ └──────────────────────────────┘ │ zone, rules, analysis)
|
|
223
228
|
└─────────────────────────────────────────────────────────────────┘
|
|
224
229
|
```
|
|
225
230
|
|
|
226
|
-
**
|
|
227
|
-
|
|
228
|
-
- **
|
|
229
|
-
- **
|
|
230
|
-
- **Privacy-first** — credentials
|
|
231
|
-
- **Fail-static, not fail-open** — a resident bundle keeps enforcing offline forever, and a failed refresh keeps the last-known-good bundle. Genuine fail-open is limited to two cases: no bundle was ever fetched, and the hook binary being unable to reach the daemon. Event forwarding is separately durable — events spool to `outbox.jsonl` and sync later.
|
|
232
|
-
|
|
233
|
-
## Updating openlatch
|
|
231
|
+
- **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`.
|
|
232
|
+
- **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.
|
|
233
|
+
- **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.
|
|
234
|
+
- **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".
|
|
235
|
+
- **Privacy-first** — credentials are redacted locally before anything leaves the machine.
|
|
234
236
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
```bash
|
|
238
|
-
# Disable the worker (env or config):
|
|
239
|
-
export OPENLATCH_AUTO_UPDATE=false
|
|
240
|
-
# or, persistently:
|
|
241
|
-
echo -e "[update]\nauto_update = false" >> ~/.openlatch/config.toml
|
|
242
|
-
```
|
|
237
|
+
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`).
|
|
243
238
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
`openlatch update` is the manual escape hatch and works whether or not the worker is enabled:
|
|
247
|
-
|
|
248
|
-
```bash
|
|
249
|
-
# Probe the registry; never modifies anything.
|
|
250
|
-
openlatch update --check --json
|
|
251
|
-
|
|
252
|
-
# Apply the update if one is available.
|
|
253
|
-
openlatch update --apply --yes
|
|
254
|
-
```
|
|
239
|
+
The product perimeter — what the client does, and what it deliberately does not — is **[AGENTS.md](AGENTS.md)**. Subsystem detail (envelope format, policy engine, subsystem supervision, the boundary's wiring invariants, releasing) lives in [`.claude/rules/`](.claude/rules/), one file per subsystem, scoped to the paths it governs.
|
|
255
240
|
|
|
256
|
-
|
|
241
|
+
### Supported agents
|
|
257
242
|
|
|
258
|
-
|
|
243
|
+
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.
|
|
259
244
|
|
|
260
|
-
|
|
261
|
-
- The new release's `min_supported_client` is greater than the running version (run `npm install -g @openlatch/client@latest` to resync).
|
|
262
|
-
- A signature does not verify against the trusted key bundled into the running binary (exit 1, OL-1504).
|
|
245
|
+
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.
|
|
263
246
|
|
|
264
|
-
|
|
247
|
+
---
|
|
265
248
|
|
|
266
249
|
## Configuration
|
|
267
250
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
|
279
|
-
|
|
|
280
|
-
| `
|
|
281
|
-
| `
|
|
282
|
-
| `
|
|
283
|
-
| `
|
|
284
|
-
| `
|
|
285
|
-
| `
|
|
286
|
-
| `OPENLATCH_SKIP_KEYRING` | Treat the OS keychain as unavailable, so `OPENLATCH_API_KEY` is used instead. The credential chain is keychain → env var → encrypted file, so a stale keychain entry otherwise wins over the env var. | unset |
|
|
287
|
-
| `OPENLATCH_CLOUD_ENABLED` | Set to `false` to stop forwarding events to the cloud | `true` |
|
|
288
|
-
| `OPENLATCH_CLOUD_BATCH_MAX_EVENTS` | Events per ingest batch. Clamped to `1..=100`; `1` restores one POST per event. | `50` |
|
|
251
|
+
Precedence: CLI flags → environment (`OPENLATCH_*`) → `~/.openlatch/config.toml` → defaults.
|
|
252
|
+
|
|
253
|
+
| Variable | Description | Default |
|
|
254
|
+
| -------- | ----------- | ------- |
|
|
255
|
+
| `OPENLATCH_DIR` | State directory | `~/.openlatch` |
|
|
256
|
+
| `OPENLATCH_PORT` | Daemon port. `init` binds this exact port; unset, it probes 7443–7543. | `7443` |
|
|
257
|
+
| `OPENLATCH_API_URL` | Cloud API base URL | `https://app.openlatch.ai` |
|
|
258
|
+
| `OPENLATCH_API_KEY` | API key (credential chain: environment → keychain → encrypted file) | — |
|
|
259
|
+
| `OPENLATCH_SKIP_KEYRING` | Treat the OS keychain as unavailable, so the env var wins | unset |
|
|
260
|
+
| `OPENLATCH_LOG` | `error` / `warn` / `info` / `debug` / `trace` | `info` |
|
|
261
|
+
| `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` |
|
|
262
|
+
| `OPENLATCH_BOUNDARY_PORT` | Boundary port. A non-default port makes the instance isolated. | `7600` |
|
|
263
|
+
| `OPENLATCH_BOUNDARY_OWN_WIRING` | Let an isolated instance write its own agent config | derived from the port |
|
|
264
|
+
| `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` |
|
|
265
|
+
| `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](#verdicts-and-which-ones-this-node-enforces-today) for what it does and does not gate. | `false` |
|
|
266
|
+
| `OPENLATCH_POLICY_ENABLED` | Local policy evaluation. `false` is a complete off switch, not observe mode. | `true` |
|
|
267
|
+
| `OPENLATCH_INVENTORY_ENABLED` | Configuration-plane monitoring | `true` |
|
|
268
|
+
| `OPENLATCH_CLOUD_BATCH_MAX_EVENTS` | Events per ingest batch, clamped to `1..=100` | `50` |
|
|
289
269
|
| `OPENLATCH_CLOUD_BATCH_MAX_WAIT_MS` | Flush deadline, anchored to the first buffered event | `5000` |
|
|
290
|
-
| `
|
|
291
|
-
| `
|
|
292
|
-
| `
|
|
293
|
-
| `
|
|
294
|
-
| `
|
|
295
|
-
| `
|
|
296
|
-
| `OPENLATCH_UPDATE_CHECK` | Set to `false` to disable the daemon's startup version check | `true` |
|
|
297
|
-
| `OPENLATCH_AUTO_UPDATE` | Set to `false` to disable the background auto-update worker | `true` |
|
|
298
|
-
| `OPENLATCH_UPDATE_CHECK_INTERVAL_SECS` | Worker poll cadence (E2E override; production keeps the default) | `21600` (6h) |
|
|
299
|
-
| `OPENLATCH_UPDATE_QUIET_WINDOW_SECS` | Seconds of hook silence required before a non-critical apply | `60` |
|
|
300
|
-
| `OPENLATCH_UPDATE_MAX_DEFER_SECS` | Hard cap on cumulative deferral before applying anyway | `86400` (24h) |
|
|
301
|
-
| `OPENLATCH_CLOUD_OUTBOX_MAX_BYTES` | Cap on `~/.openlatch/outbox.jsonl` size before drop-oldest eviction (0 disables) | `104857600` (100 MB) |
|
|
302
|
-
| `OPENLATCH_CLOUD_FALLBACK_MAX_BYTES` | Cap on `~/.openlatch/logs/fallback.jsonl` size before drop-oldest via offset advance (0 disables) | `52428800` (50 MB) |
|
|
270
|
+
| `OPENLATCH_CLOUD_OUTBOX_MAX_BYTES` | Outbox cap before drop-oldest (0 disables) | `104857600` |
|
|
271
|
+
| `OPENLATCH_CLOUD_FALLBACK_MAX_BYTES` | Fallback-log cap before drop-oldest (0 disables) | `52428800` |
|
|
272
|
+
| `OPENLATCH_AUTO_UPDATE` | Background auto-update worker | `true` |
|
|
273
|
+
| `OPENLATCH_UPDATE_CHECK` | Startup version check | `true` |
|
|
274
|
+
| `OPENLATCH_NPM_REGISTRY` | Registry origin for `openlatch update` | `https://registry.npmjs.org` |
|
|
275
|
+
| `OPENLATCH_TELEMETRY_DISABLED` | `1` opts out of anonymous usage telemetry | unset |
|
|
303
276
|
|
|
304
|
-
|
|
277
|
+
**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.
|
|
305
278
|
|
|
306
|
-
|
|
279
|
+
Auto-update, its quiet-window deferral and its rollback sentinel are documented in the module rustdoc of [`src/core/update.rs`](src/core/update.rs); `openlatch update --check --json` is the manual probe.
|
|
307
280
|
|
|
308
|
-
|
|
309
|
-
- **Private vulnerability reporting** enabled on this repository
|
|
310
|
-
- All releases are built with **SLSA provenance** via GitHub Actions
|
|
311
|
-
- See [SECURITY.md](SECURITY.md) for our full security policy
|
|
281
|
+
---
|
|
312
282
|
|
|
313
283
|
## Releases
|
|
314
284
|
|
|
315
|
-
|
|
285
|
+
Two artifacts on independent version lines, both cut by [release-please](https://github.com/googleapis/release-please) from Conventional Commits:
|
|
316
286
|
|
|
317
287
|
| Artifact | Tag | Where |
|
|
318
288
|
| -------- | --- | ----- |
|
|
319
|
-
| The client
|
|
320
|
-
| The wire-format JSON Schemas | `schemas-v1.2.3` |
|
|
289
|
+
| The client | `v1.2.3` | GitHub Releases, npm, crates.io |
|
|
290
|
+
| The wire-format JSON Schemas | `schemas-v1.2.3` | npm, PyPI |
|
|
321
291
|
|
|
322
|
-
The schema packages are versioned **only** by changes under `schemas/`, so a
|
|
323
|
-
major bump means a breaking change to the events exchanged between the client
|
|
324
|
-
and the platform — and nothing else. Pin them independently of the client
|
|
325
|
-
version; they carry no dependency on it.
|
|
292
|
+
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`](.claude/rules/release-lines.md).
|
|
326
293
|
|
|
327
|
-
|
|
328
|
-
from Conventional Commits. Contributor-facing details are in
|
|
329
|
-
[AGENT.md](AGENT.md#releasing--two-independent-version-lines).
|
|
294
|
+
## Why it is open source
|
|
330
295
|
|
|
331
|
-
|
|
296
|
+
The platform is closed; this node is Apache-2.0, for three enterprise reasons.
|
|
332
297
|
|
|
333
|
-
|
|
298
|
+
| Reason | Why it matters |
|
|
299
|
+
| ------ | -------------- |
|
|
300
|
+
| **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. |
|
|
301
|
+
| **Auditability** | Security reviews, internal AppSec and third-party testers all need to read the code that enforces policy on production agents. |
|
|
302
|
+
| **Distribution** | The developer inside the account is who runs `npx @openlatch/client init` first. Open source removes the friction from that. |
|
|
334
303
|
|
|
335
|
-
|
|
336
|
-
- Check [good first issues](https://github.com/OpenLatch/openlatch-client/labels/good%20first%20issue) for entry points
|
|
337
|
-
- Join our [Slack](https://openlatch.slack.com) to connect with the team
|
|
304
|
+
## Security
|
|
338
305
|
|
|
339
|
-
|
|
306
|
+
- Report vulnerabilities to **security@openlatch.ai**; private vulnerability reporting is enabled on this repository
|
|
307
|
+
- Releases are built with SLSA provenance via GitHub Actions
|
|
308
|
+
- Full policy: [SECURITY.md](SECURITY.md)
|
|
309
|
+
|
|
310
|
+
## Contributing
|
|
340
311
|
|
|
341
|
-
|
|
312
|
+
Read the [Contributing Guide](.github/CONTRIBUTING.md), pick a [good first issue](https://github.com/OpenLatch/openlatch-client/labels/good%20first%20issue), and join us on [Slack](https://openlatch.slack.com).
|
|
342
313
|
|
|
343
|
-
|
|
314
|
+
Licensed under [Apache-2.0](LICENSE).
|
|
344
315
|
|
|
345
316
|
| Resource | URL |
|
|
346
317
|
| -------- | --- |
|
|
@@ -348,4 +319,3 @@ Licensed under the [Apache License 2.0](LICENSE).
|
|
|
348
319
|
| Documentation | [docs.openlatch.ai](https://docs.openlatch.ai) |
|
|
349
320
|
| Slack | [openlatch.slack.com](https://openlatch.slack.com) |
|
|
350
321
|
| Twitter / X | [@openlatch](https://x.com/openlatch) |
|
|
351
|
-
| GitHub | [github.com/OpenLatch](https://github.com/OpenLatch) |
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openlatch/client",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "OpenLatch runtime enforcement node — the capture-and-enforce
|
|
3
|
+
"version": "0.2.2",
|
|
4
|
+
"description": "OpenLatch runtime enforcement node — the capture-and-enforce adapter that evaluates every covered action against a coding agent's Autonomy Zone before it runs",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -20,10 +20,10 @@
|
|
|
20
20
|
"postinstall": "node lib/postinstall.js"
|
|
21
21
|
},
|
|
22
22
|
"optionalDependencies": {
|
|
23
|
-
"@openlatch/client-darwin-arm64": "0.
|
|
24
|
-
"@openlatch/client-darwin-x64": "0.
|
|
25
|
-
"@openlatch/client-linux-x64": "0.
|
|
26
|
-
"@openlatch/client-linux-arm64": "0.
|
|
27
|
-
"@openlatch/client-win32-x64": "0.
|
|
23
|
+
"@openlatch/client-darwin-arm64": "0.2.2",
|
|
24
|
+
"@openlatch/client-darwin-x64": "0.2.2",
|
|
25
|
+
"@openlatch/client-linux-x64": "0.2.2",
|
|
26
|
+
"@openlatch/client-linux-arm64": "0.2.2",
|
|
27
|
+
"@openlatch/client-win32-x64": "0.2.2"
|
|
28
28
|
}
|
|
29
29
|
}
|