Skip to content

Tell plain-launch sessions their model calls are not priced or policy-checked (0.19.0) - #37

Merged
davidcrowe merged 3 commits into
mainfrom
feat/unpriced-session-notice
Sep 16, 2026
Merged

davidcrowe merged 3 commits into
mainfrom
feat/unpriced-session-notice

Conversation

@davidcrowe

@davidcrowe davidcrowe commented Sep 16, 2026

Copy link
Copy Markdown
Collaborator

Why

Production 2026-09-16: 0 of 69 external tenants have ever used a <harness>-acp launcher. Everyone runs plain claude; nothing tells them what that session does and does not get from ACP.

agenticcontrolplane.com#178 added a notice to the installer's bundled offline engines — which online installs never run, and which the marketplace plugin never touches. This is the port into the govern.mjs that actually executes. Rebased onto #36 (merged): with the transcript collector in, plain claude is priced, so the message leads with what the session has and names only the policy half as missing.

What

One systemMessage, once per session, on the first allowed call of a plain-launch session:

[ACP] Tool calls in this session are checked and logged, and model-call cost is estimated from the session transcript (API-rate equivalent, not a metered charge). Model calls are not policy-checked: plain `claude` sends them straight to the provider, so tool-result redaction and model routing are off. For those, launch with `claude-acp` (~/.acp/bin/claude-acp). Shown once per session.
  • Wording tracks the pricing condition. The clause "cost is estimated from the session transcript" is used when the payload names a readable transcript_path — the same condition under which Send transcript model usage on PostToolUse so plain claude sessions get costed (0.18.0) #36 prices the session. A harness that hands over no transcript (nothing priced) gets the plainer "…neither priced nor policy-checked…" form instead. Never a claim the hook cannot back.
  • Detection: every launcher is generated from one template whose first act is export ACP_KEY; the hook inherits that env. No ACP_KEY, and no ANTHROPIC_BASE_URL/OPENAI_BASE_URL at agenticcontrolplane.com, means the session was not started by a launcher. Bias to silence on doubt.
  • Once per session: marker ~/.acp/session-notices/unpriced-<session_id> (id sanitised to [A-Za-z0-9._-], capped at 120), created with {flag:"wx"} so parallel hook processes for one session cannot both fire. No session_id in the payload → silence, no marker. Markers older than 7 days are pruned; the directory is capped at 200 newest.
  • Per harness: named by ACP_CLIENT (claude-acp; codex-acp for both codex and the Codex plugin's codex-plugin; qwen-acp, opencode-acp, pi-acp, hermes-acp, prime-acp, grok-acp, dsh-acp). Cursor has no launcher and gets nothing.
  • Launcher absent on disk (marketplace-only claude plugin install): the notice ends with install the \claude-acp` launcher: curl -sf https://agenticcontrolplane.com/install.sh | bash` instead of a path that does not exist.
  • Never on: local mode, no-credential, managed-unenrolled, deny, ask. Rides the allow path as ONE JSON object, merged with the tier-divergence notice and the wire warning when they apply (the updatedInput branch now reads the message once, not twice — with an exclusive-create claim a second read would have dropped it). Wrapped in try/catch: any failure is silence.
  • Version → 0.19.0. README section added (and the 0.18.0 collector section carried through the merge).

Tests

node --test220 pass, 0 fail (16 in test/unpriced-session-notice.test.mjs, spawning the real hook against a stub gateway):

  • notice on a plain launch (transcript on the payload → the "estimated from the transcript" form), with no hookSpecificOutput; never says "governance"
  • no transcript on the payload, or a missing one → the "neither priced nor policy-checked" form
  • silent on the 2nd and 3rd call of the same session; fires once for a new session
  • nothing without a session id (and no marker)
  • nothing with ACP_KEY in env; nothing with a provider base URL at the proxy
  • deny and ask byte-identical with no notice; the notice then rides the session's first allowed call
  • wire warning + notice → ONE JSON object carrying both; warning alone on the next call
  • codex (both client spellings) names codex-acp; cursor gets nothing
  • six parallel hook processes for one session → exactly one notice
  • 8-day-old markers pruned, recent ones kept, directory capped at 200
  • hostile session ids sanitised and length-capped (no path escape)
  • launcher absent on disk → installer wording
  • --local mode silent, no marker dir, call still audited locally

wire-warning.test.mjs and transcript-model-usage.test.mjs run as launched sessions (ACP_KEY in env) so their assertions stay about the warning / the PostToolUse body alone.

Not merged, not published to npm, not deployed. Publishing is yours.

…-checked (0.19.0)

Production 2026-09-16: 0 of 69 external tenants have a single llm.proxy.*
row, and every external harness tenant has zero costed runs. Everyone runs
the plain binary; nobody runs <harness>-acp, and nothing tells them — the
only surface that mentions cost is the launcher's own exit line, which
only prints after you already used the launcher.

The hook runs under the plain binary, so the hook says it. Port of the
installer's bundled-engine change (agenticcontrolplane.com PR #178) into
the govern.mjs that online installs fetch and the marketplace plugin runs
— #178 alone reaches nobody in today's data. One systemMessage, once per
session, on the first ALLOWED call:

  [ACP] Tool calls in this session are checked and logged. Model calls are
  not: plain `claude` sends them straight to the provider, so they are
  neither priced nor policy-checked (tool-result redaction, model routing).
  For the cost X-ray and model-call policy, launch with `claude-acp`
  (~/.acp/bin/claude-acp). Shown once per session.

- Detection: every launcher is generated from one template whose first act
  is `export ACP_KEY`; the hook inherits that env. No ACP_KEY, and no
  provider base URL at the ACP proxy, means the session was not started by
  a launcher. Bias to silence on doubt.
- Once per session: marker ~/.acp/session-notices/unpriced-<session_id>
  (id sanitised to [A-Za-z0-9._-], capped at 120), created with an
  exclusive flag so parallel hook processes for the same session cannot
  both fire. No session_id in the payload means silence, no marker.
  Markers older than 7 days are pruned; the directory is capped at 200.
- Every harness with a launcher is named by ACP_CLIENT (claude-acp,
  codex-acp for both "codex" and "codex-plugin", qwen-acp, opencode-acp,
  pi-acp, hermes-acp, prime-acp, grok-acp, dsh-acp). Cursor has no
  launcher and gets nothing.
- Marketplace-only installs (`claude plugin install`) have this hook but no
  launcher on disk; for them the notice names the installer instead of a
  path that does not exist. One deviation from #178, in favour of saying
  something true.
- Cloud mode only: the local path never reaches it. No-credential and
  managed-unenrolled paths never reach it either.
- Purely additive: rides the allow path as one JSON object (merged with the
  tier notice and the wire warning when they apply — the updatedInput
  branch now reads the message once instead of twice), never a deny or an
  ask, and the whole thing is wrapped so any failure is silence.
- Version 0.19.0 so the attestation hash is compared against a fresh
  version. Branched off main independently of the transcript-usage PR
  (0.18.0); the two conflict in bin/govern.mjs and this one rebases.

Tests (test/unpriced-session-notice.test.mjs, real hook against a stub
gateway): notice on a plain launch with nothing that could change the
call; silent on the 2nd/3rd call of the same session; fires once for a new
session; nothing without a session id; nothing with ACP_KEY in env or a
provider base URL at the proxy; deny and ask byte-identical with no
notice, then the notice rides the session's first allowed call; wire
warning + notice as ONE object; codex names codex-acp, cursor gets
nothing; six parallel processes yield exactly one notice; stale markers
pruned and the directory capped; hostile session ids sanitised; launcher
absent names the installer; --local silent. wire-warning.test.mjs now
runs as a launched session so its assertions stay about the warning.
…-notice

# Conflicts:
#	.claude-plugin/marketplace.json
#	README.md
#	bin/govern.mjs
#	plugin.json
…licy half it lacks

#36 made plain `claude` priced — from the transcript, at API-rate
equivalents — so "neither priced nor policy-checked" became false the
moment it merged. Rewritten to lead with what the session has and name
only what still needs the launcher:

  [ACP] Tool calls in this session are checked and logged, and model-call
  cost is estimated from the session transcript (API-rate equivalent, not
  a metered charge). Model calls are not policy-checked: plain `claude`
  sends them straight to the provider, so tool-result redaction and model
  routing are off. For those, launch with `claude-acp`
  (~/.acp/bin/claude-acp). Shown once per session.

The estimate-vs-metered distinction is stated up front so it does not
arrive later as a support question. The wording is chosen by the same
condition that makes the session priced — a readable transcript_path on
the payload — so a harness that hands over no transcript still gets the
plainer "neither priced nor policy-checked" form rather than a claim the
hook cannot back. Every mechanical guarantee is unchanged: allow path
only, once per session via the exclusive marker, silence with no
session_id, never in --local, never with ACP_KEY or a proxy base URL.

Tests: wording assertions updated; new case for a payload with no (or a
missing) transcript; transcript-model-usage.test.mjs runs as a launched
session so its PreToolUse assertion stays about the body. 220/220.
@davidcrowe
davidcrowe merged commit 04b572e into main Sep 16, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant