Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
84cfbcb
RFC 0245: OTEL telemetry replaces proxy attach for Claude Code
philcunliffe Aug 17, 2026
4a339d5
LLP 0251-0256: the decisions RFC 0245 promises on acceptance
philcunliffe Aug 17, 2026
555c2b3
LLP 0257: spec for the claude telemetry listener source
philcunliffe Aug 17, 2026
8d79880
Forward-ref LLP 0245 from the proxy-attach docs it displaces
philcunliffe Aug 17, 2026
e86b667
RFC 0245 names all six decisions it spawns
philcunliffe Aug 17, 2026
76cdfba
Carry the 0245 back-reference in Related, not a new header field
philcunliffe Aug 17, 2026
02250bc
LLP 0251 renumbered to 0258: open PR #817 claimed 0251
philcunliffe Aug 17, 2026
bf8b0c9
The OTLP http/json listener becomes shared core machinery (LLP 0257)
philcunliffe Aug 17, 2026
922ccbd
A live producer that is not the proxy records through the gateway (LL…
philcunliffe Aug 17, 2026
7cbfca8
The claude plugin hosts a listener for Claude Code's own telemetry (L…
philcunliffe Aug 17, 2026
ac54504
A hermetic smoke drives the telemetry listener end to end (LLP 0257)
philcunliffe Aug 17, 2026
2e2686f
The claude settings writer gains an otel mode behind a version floor …
philcunliffe Aug 17, 2026
3282bf5
hyp attach claude writes the telemetry env block in otel mode (LLP 0258)
philcunliffe Aug 17, 2026
2beae92
Spooled body files fill the projection gaps events never carry (LLP 0…
philcunliffe Aug 18, 2026
9b00bb1
The daemon caps the body spool and the listener consumes it (LLP 0253)
philcunliffe Aug 18, 2026
4ea1bd1
The telemetry smoke joins body fixtures and recovers an evicted sessi…
philcunliffe Aug 18, 2026
4139218
Behavioral events land in their own claude_telemetry_events dataset (…
philcunliffe Aug 18, 2026
1f7eb09
The telemetry smoke reads behavioral events back from claude_telemetr…
philcunliffe Aug 18, 2026
a812d36
The session-ignore control handler becomes shared core machinery (LLP…
philcunliffe Aug 18, 2026
154e134
The claude listener hosts session ignore and drops the session at ing…
philcunliffe Aug 18, 2026
f4dc24c
hyp session ignore posts to every recorder that offers the route (LLP…
philcunliffe Aug 18, 2026
f574b3f
A privacy smoke drives session ignore through both recorders (LLP 025…
philcunliffe Aug 18, 2026
57a63cb
hyp status renders a capture-health line for otel-attached clients (L…
philcunliffe Aug 18, 2026
70e77f2
A capture-health smoke drives hyp status through lockstep, gap, and d…
philcunliffe Aug 18, 2026
6149abc
hyp attach claude migrates a proxy attach to otel in one command (LLP…
philcunliffe Aug 18, 2026
3fdb53a
hyp status names the attach mode on the text surface (LLP 0245)
philcunliffe Aug 18, 2026
918d0f1
The migration overlap window collapses to one row set (LLP 0245)
philcunliffe Aug 18, 2026
48152ab
The migration's CA offer stops claiming a trust it never verified (LL…
philcunliffe Aug 18, 2026
b13c981
hyp status proves the migrated mode through the real collector (LLP 0…
philcunliffe Aug 18, 2026
83d75c4
hyp purge and detach empty the raw-body spool (LLP 0253)
philcunliffe Aug 18, 2026
854764b
The folder usage policy decides at ingest on the OTEL path (LLP 0254)
philcunliffe Aug 18, 2026
0612735
A privacy smoke drives .hypignore through the telemetry listener (LLP…
philcunliffe Aug 18, 2026
0356d95
The glossary stops saying attach rewrites a base URL (LLP 0258)
philcunliffe Aug 18, 2026
512b176
A release gate checks the OTEL shapes against the installed Claude Code
philcunliffe Aug 18, 2026
721bb7b
The README describes the attach Claude Code actually gets (LLP 0245)
philcunliffe Aug 18, 2026
ba60e70
The shape check waits out an export interval before calling it broken
philcunliffe Aug 18, 2026
2221a69
RFC 0245 renumbered to 0262: open PR #815 claimed 0245
philcunliffe Aug 18, 2026
97be41e
RFC 0262 and the docs it spawns are Accepted
philcunliffe Aug 18, 2026
b8ea981
The 0245 renumber reaches the test tree's @refs
philcunliffe Aug 18, 2026
746ff92
Merge remote-tracking branch 'origin/master' into HEAD
Aug 18, 2026
f65d452
Review round 1: the daemon stops minting the body spool, and status s…
Aug 18, 2026
e200abd
Review round 2: hyp status compares where claude exports against wher…
Aug 18, 2026
42138e7
Review round 2: a restarted daemon stops reporting a month-long captu…
Aug 18, 2026
487bf2d
Review round 2: otel attach names the OTLP keys that outrank the endp…
Aug 18, 2026
9df31a0
The client hook enforces the body spool cap too, so a down daemon is …
philcunliffe Aug 18, 2026
c6ba6ed
LLP 0263 is Accepted
philcunliffe Aug 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,16 @@ Written acceptance procedures:
Proves Desktop traffic reaches `ai_gateway_messages` by both the live
gateway route and the `~/.codex/sessions` backfill route, and is
attributable via `entrypoint`. See `docs/ACCEPTANCE.md`.
- `openclaw_capture`: opt-in/manual, needs OpenClaw with both `anthropic` and
`openai` credentials. Proves both capture lanes (live gateway and the
scheduled transcript sweep) and that a turn both lanes observe settles to
one row. See `docs/ACCEPTANCE.md`.
- `claude_otel_shape_check`: opt-in/manual, needs a real Claude Code 2.1.214
or newer. The release gate against upstream drift on the OTEL attach path:
proves the installed Claude Code still honors the managed `env` block and
still emits the event names, attributes, and raw body fields the telemetry
listener reads, then checks the rows and the `hyp status` capture-health
line agree. See `docs/ACCEPTANCE.md`.

Good acceptance smoke candidates (no written procedure yet):

Expand Down Expand Up @@ -171,6 +181,7 @@ src/
cli/ # dispatch, walkthrough, core_commands
config/ # v2 schema, validator
daemon/ # platform installers (launchd / systemd) + lifecycle
otlp/ # shared OTLP http/json listener machinery
plugin_install/ # resolver, fetch, lock, update_check
sinks/ # cron driver + encoder utility
hypaware-core/
Expand Down Expand Up @@ -210,6 +221,7 @@ hyp smoke walkthrough_picker_to_first_query
hyp smoke client_attach_idempotent
hyp smoke gateway_claude_capture
hyp smoke gateway_codex_capture
hyp smoke claude_telemetry_capture
hyp smoke hypignore_capture_drop
hyp smoke local_only_export_withhold
hyp smoke source_optout_export_withhold
Expand All @@ -236,6 +248,18 @@ If the release touched a client adapter, run the matching procedure in
[`docs/ACCEPTANCE.md`](docs/ACCEPTANCE.md) and record the result in the
release notes.

If the release touched the **claude** adapter (`@hypaware/claude`, the
telemetry listener, the body spool, or the attach settings writer), the
matching procedure is
[`claude_otel_shape_check`](docs/ACCEPTANCE.md#claude_otel_shape_check). It is
not optional for those releases and it is not substitutable by the hermetic
smokes: `claude_telemetry_capture` POSTs a fixture we wrote, so it agrees with
itself no matter what upstream did. Only a real Claude Code can tell you it
renamed an event, dropped a flag, or changed the raw body format, and the
failure mode is silent (null columns, not an error). Record the observed
`claude --version` and the full event-name list in the release notes so the
next release has a baseline to diff against.

<!-- neutral:llp-conventions -->
## LLP conventions

Expand Down
39 changes: 35 additions & 4 deletions CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,9 @@ offers `claude`, `codex`, and `otel`, the two raw proxy rows being **hidden**

- **Client source**: a known tool HypAware configures for you. `claude` and
`codex` are the client sources. Picking one adds its gateway upstream *and*
its adapter plugin (`@hypaware/claude` / `@hypaware/codex`), which attaches
the tool (rewrites its base URL), installs hooks/skills, and can backfill
its local history. Client sources are the only sources that can be
[[autodetect]]ed.
its adapter plugin (`@hypaware/claude` / `@hypaware/codex`), which
[[attach]]es the tool, installs hooks/skills, and can backfill its local
history. Client sources are the only sources that can be [[autodetect]]ed.
- **Raw proxy source**: `raw-anthropic` / `raw-openai`. Picking one opens the
gateway with that provider upstream but configures no client; the user
points their own SDK app or script at the local gateway by hand. Serves the
Expand All @@ -31,6 +30,38 @@ offers `claude`, `codex`, and `otel`, the two raw proxy rows being **hidden**
OpenTelemetry signals. Like a raw proxy source, it is manual and not
autodetectable.

An `otel` picker source is not the same thing as "a source that speaks OTLP".
`@hypaware/claude` runs its own OTLP listener to receive Claude Code's
telemetry ([[attach]] mode `otel`, LLP 0257), on its own port, with its own
payload rules and its own datasets. That listener is claude-owned: a machine
attached that way still has `claude` as a **client source** here, autodetected
and configured for the user, and nothing about it turns on the `otel` source.
Picking `otel` is what a user does for *their own* app's telemetry.

### Attach

Writing a reversible block into a **client source**'s own configuration so that
what the tool does reaches HypAware, and being able to take it back out.
`hyp attach <client>` writes it, `hyp detach <client>` removes exactly those
keys and restores anything they displaced, and the undo record (the `_hypaware`
marker) lives in the file that was edited.

Attach is not one mechanism. Each client adapter picks a **mode**, and
`hyp status` names it (`claude [configured, attached (otel)]`):

- **`base_url`**: point the tool's API base URL at the local gateway. `codex`
attaches this way.
- **`proxy`**: set `HTTPS_PROXY` and trust a machine-local CA, so the gateway
sees the tool's TLS traffic without its base URL being touched (LLP 0232).
- **`otel`**: turn on the tool's own OpenTelemetry export and point it at a
HypAware listener (LLP 0258). No base URL, no proxy, no CA: the tool still
talks straight to its provider and HypAware receives a copy of what it did.
`claude` attaches this way.

The mode is worth naming because it decides what being attached costs: only
`proxy` installs CA trust, and only `base_url` and `proxy` put the HypAware
daemon on the request path.

### Autodetect

The first-run wizard inspecting the system for the presence of a **client
Expand Down
92 changes: 60 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -276,16 +276,46 @@ client's own config file (for example `~/.claude/settings.json` for
Claude, a `hypaware` provider entry in `~/.codex/config.toml` for
Codex); unrelated keys in every file are preserved.

### Proxy mode (keeps Claude Code's Remote Control working)

By default `hyp attach claude` points `ANTHROPIC_BASE_URL` at the local
gateway. Claude Code disables **Remote Control** whenever that variable
points anywhere other than `api.anthropic.com`, so an attached machine
loses it.

Proxy mode avoids that by leaving the base URL alone and routing Claude
Code through the gateway as an HTTPS proxy instead. Turn it on in the
`ai-gateway` section of `~/.hyp/hypaware-config.json` and restart the
### Claude Code attaches by telemetry, not by proxy

`hyp attach claude` writes one reversible `env` block into
`~/.claude/settings.json` that turns on Claude Code's own OpenTelemetry
export and points it at a loopback listener the daemon runs. It leaves
`ANTHROPIC_BASE_URL` alone, sets no proxy, and installs no certificate
authority, so Claude Code still talks straight to `api.anthropic.com`,
**Remote Control keeps working**, and a daemon that is down or wedged costs
you capture rather than your session. Nothing has to be quit and reopened:
Claude Code reads the `env` block at launch, on every launch path.

Two things ride along with the conversation rows:

- **Raw request and response bodies** land in `~/.hyp/spool/claude-bodies`
(owner-only) until the listener projects them and deletes them. They carry
what the events do not: the system prompt, the tool list, and untruncated
tool arguments. The directory is capped (512 MB by default, oldest evicted
first), and both `hyp purge` and `hyp detach claude` empty it.
- **Behavioral signals the wire never showed** land in their own
`claude_telemetry_events` table: tool accept and reject decisions,
permission mode changes, per-request cost, hook and MCP health.

Claude Code **2.1.193 or newer** is required (2.1.214 for the full
tool-decision detail). Below the floor, attach refuses the switch, leaves any
existing attach exactly as it is, and prints `claude update`, rather than
silently capturing less.

`hyp detach claude` removes exactly those keys, restores anything they
displaced, and sweeps the spool.

If this machine was attached by proxy before, `hyp attach claude` is also the
migration: it releases the proxy keys, unwinds the launchd environment, and
tells you how to end the CA trust that it will not end for you
(`hyp detach claude --purge`).

### Proxy mode (TLS interception for the clients that still proxy)

Claude Code no longer uses this path. It remains how the gateway captures a
client that cannot simply be pointed at a different base URL. Turn it on in
the `ai-gateway` section of `~/.hyp/hypaware-config.json` and restart the
daemon:

```json
Expand All @@ -294,37 +324,35 @@ daemon:

```sh
hyp daemon restart
hyp attach claude
```

On the next attach, HypAware sets `HTTPS_PROXY` and `NODE_EXTRA_CA_CERTS`
instead of the base URL. What this changes:
Such a client is then pointed at the gateway with `HTTPS_PROXY` and
`NODE_EXTRA_CA_CERTS` rather than a base URL. What that changes:

- **A machine-local certificate authority is generated** under
`~/.hyp/hypaware/tls`, readable only by you, and name-constrained so it
cannot vouch for any host outside the provider set HypAware intercepts.
On macOS, attach also adds it to your **login keychain** as a user-domain
trusted root, because Claude Code's Remote Control transport trusts only
the keychain: macOS raises its own password dialog, and declining it
leaves capture working with Remote Control's inbound channel off. No admin
rights are needed and the machine-wide system keychain is not touched. On
other platforms trust stays file-scoped to Claude Code's own settings.
`hyp status` shows the fingerprint and whether the keychain still trusts
it. `hyp detach claude` keeps the CA and the trust, so re-attaching does
not ask again; `hyp detach claude --purge` and `hyp daemon uninstall`
remove both.
- **Only `api.anthropic.com` is decrypted**, because that is the only host
a registered upstream names. Every other host Claude Code talks to is
tunnelled through without being decrypted.
- **What gets recorded does not change.** Only `/v1/messages` is recorded,
exactly as before; the other paths Claude Code calls on that host are
passed through without being stored.
On macOS it can also be added to your **login keychain** as a user-domain
trusted root, for a client whose transport trusts only the keychain: macOS
raises its own password dialog, and declining it leaves capture working
with that inbound channel off. No admin rights are needed and the
machine-wide system keychain is not touched. On other platforms trust
stays file-scoped to the client's own settings. `hyp status` shows the
fingerprint and whether the keychain still trusts it.
`hyp detach <client>` keeps the CA and the trust, so re-attaching does not
ask again; `hyp detach <client> --purge` and `hyp daemon uninstall` remove
both.
- **Only the hosts a registered upstream names are decrypted.** Every other
host the client talks to is tunnelled through without being decrypted.
- **What gets recorded does not change.** Only the recorded API paths are
stored; the other paths a client calls on the same host are passed through
without being stored.

Two things to know before turning it on:

- If the daemon is not running, Claude Code's HTTPS all fails, not just its
model calls. Attach refuses to write the settings unless proxy mode is
actually running, and `hyp detach claude` is the escape hatch.
- If the daemon is not running, a proxied client's HTTPS all fails, not just
its model calls. Attach refuses to write the settings unless proxy mode is
actually running, and `hyp detach <client>` is the escape hatch.
- If you already use a corporate proxy, set `upstream_proxy` to it so
traffic still chains through it. Attach warns and backs up your existing
`HTTPS_PROXY` (restored on detach) rather than silently replacing it:
Expand Down
Loading
Loading