Skip to content

Usage by prompt

Planisphere can collect token counters already present in a local Codex or Claude Code transcript. Enable the companion for a selected project, then open Activity → Usage by prompt in the console. Each prompt shows visible prompt and response text sizes alongside available input, output, cache and reasoning token counts. Token observations are grouped by agent system, provider and model, with source and coverage limits.

This is optional numeric telemetry. The companion reads the transcript locally and sends an allowlist of identifiers, timestamps, counters and source metadata. It does not upload raw prompts, replies, tool arguments or transcript paths. It makes no model requests and does not calculate a provider bill or add Planisphere billable meter events. Your existing activity hooks keep their own Codex or Claude Code capture behavior.

Read the numbers

Label Meaning
Observed A number reported by the source telemetry. It is not an independently verified provider invoice.
Estimated An explicitly labeled estimate, kept separate from observed counts. The bundled adapters do not invent estimates for missing values.
Unknown or incomplete A counter, attribution or part of the prompt is unavailable. Unknown is never converted to zero; an available subtotal may cover only part of the work.

Cache and reasoning figures can overlap input and output. If a source reports 100 input tokens including 40 cache-read tokens, normalized input remains 100. If its input excludes cache and separately reports 40 cache-read and 10 cache-write tokens, normalized input is 150. Likewise, reasoning already included in 25 output tokens is not added again. Unknown inclusion rules leave the affected normalized figure unknown. The current Claude adapter does not establish reasoning/output overlap, so its normalized output and total can be unknown even when raw output is present.

The source details distinguish a model reported for a response from a model configured for a session or turn. A configured model does not prove which model served a request; unreported routing cannot be verified. Provider attribution also remains unknown unless the source or explicit connection configuration establishes it. Do not infer the provider from a model name. Only configure token_usage.providers when you have evidence for that mapping.

Native response identifiers and streaming updates are deduplicated. Older Codex cumulative-counter intervals are labeled as such; their observation count is not a verified count of model requests. Interrupted turns, missing counter baselines and unsupported transcript formats remain incomplete. Child-agent usage is combined only when explicit source identifiers establish the parent prompt. Unlinked or ambiguous child activity is not silently added.

Prompt and response bytes

The collector counts supported visible user-prompt and assistant-text records in UTF-8 locally. A byte contains eight storage bits. response_bytes / prompt_bytes is an expansion ratio, not a measure of intelligence or useful information. The console shows exact numeric counts for the declared source coverage; a partial transcript supplies a partial count, never a complete exchange by guess. Unknown sizes remain unknown. A zero-byte prompt has no defined expansion ratio.

Text counting excludes hidden reasoning, tool arguments/results, system and retrieved context, and non-text attachments. Provider input tokens can include large amounts of this additional material, so exchange bytes do not estimate total model-input cost. Commentary and final answer text are part of the visible assistant response. Native message identities distinguish legitimate repeated messages from replayed or streaming copies. Unsupported attribution remains explicitly incomplete. No prompt or response text or content hash is uploaded by the usage companion; its fingerprints cover numeric metadata and provenance. They do not detect an unmarked replacement of text with other text of the same byte length.

The private prompt intake accepts an optional exchange object alongside token observations. Older reports remain valid and display missing sizes. Counts and message counts cannot be erased or decreased by a newer snapshot. The derived exchange_accounting includes storage bits, combined bytes and an expansion ratio when complete coverage and a nonzero prompt size permit it. Summaries keep missing and partial prompt counts visible and group exchanges by system/agent.

Explicit task-information estimates

The bundled collector does not infer semantic bits from token counts, text length, compression or language-model surprise. An authenticated reporter can supply an optional task_information object through the same private POST /tenant/token-usage/prompts intake. This is a declared model, not a built-in quality score or an independently verified correctness result.

The estimate must name the task, recipient's prior-knowledge baseline, estimator and version, and assumptions describing the uncertainty model. Entropy before and after is expressed in bits. Define the baseline from the recipient's knowledge; counting information already fully known to that recipient as new would overstate the result. Reusing one fact across several cases does not turn it into several independent bits.

A synthetic example of the additional fields, not measured live usage:

{
  "exchange": {
    "encoding": "utf-8",
    "prompt_bytes": 200,
    "response_bytes": 800,
    "prompt_messages": 1,
    "response_messages": 1,
    "coverage": {"status": "observed", "limitations": []},
    "source": {
      "format": "synthetic-example",
      "adapter_version": "1",
      "fingerprint": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
      "basis": "Synthetic text-size example"
    }
  },
  "task_information": {
    "target_ref": "example:which-of-16",
    "recipient_baseline_ref": "example:recipient-before-answer",
    "estimator": "declared-finite-state-model",
    "estimator_version": "1",
    "assumptions_ref": "example:16-equiprobable-alternatives-reliably-resolved",
    "entropy_before_bits": 4,
    "entropy_after_bits": 0,
    "correctness_status": "passed",
    "correctness_evidence_ref": "example:checked-answer-key",
    "exchange_basis": {
      "prompt_bytes": 200,
      "response_bytes": 800,
      "prompt_messages": 1,
      "response_messages": 1,
      "source_fingerprint": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
    },
    "source": {
      "format": "synthetic-information-estimate",
      "adapter_version": "1",
      "fingerprint": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
      "basis": "Illustrative model; not an empirical benefit claim"
    }
  }
}

These fields supplement the existing prompt identity, revision, status, coverage and calls; they are not a complete intake request. The example yields 4 modeled task bits over 1,000 exchanged bytes, or 0.004 estimated task bits/byte. The server checks the contract and reported basis, not whether the assumptions or referenced correctness evidence are true. A failed or unverified correctness status, incomplete exchange, zero combined bytes or changed exchange basis leaves density unavailable. Negative uncertainty changes remain negative.

A later report can provide a revised estimate as a new immutable revision. Omitting the estimate retains the prior declaration; if text-size measurements change, the old estimate remains visible with unavailable density until a new matching estimate is supplied. This binds the estimate to measured numeric metadata, not to raw content. Task-information estimates are not added across prompts or subagents because their targets and supporting information may overlap. Token cost, human effort and correctness remain separate observations.

Prepare one project

Use macOS or Linux with Python 3.10+ and an existing Planisphere connection. Start with the Codex or Claude Code guide if the project is not connected. For Codex, update the base capture helper using that guide before enabling usage; its prompt acknowledgement supplies the native receipt link. Keep the same intended system, environment and surface.

Run the following from the project you want to enable. Choose a distinct private directory for each project and machine; replace my-project below.

PROJECT="$PWD"
USAGE_DIR="$HOME/.local/state/planisphere/token-usage/my-project"
umask 077
mkdir -p "$USAGE_DIR"
chmod 700 "$USAGE_DIR"
curl --fail --output "$USAGE_DIR/planisphere_usage.py" \
  https://api.planisphere.ooo/docs/examples/python/planisphere_usage.py
curl --fail --output "$USAGE_DIR/planisphere_codex.py" \
  https://api.planisphere.ooo/docs/examples/python/planisphere_codex.py
curl --fail --output "$USAGE_DIR/planisphere_claude_prompt.py" \
  https://api.planisphere.ooo/docs/examples/python/planisphere_claude_prompt.py

Review the downloaded helpers before installation. The two capture helpers must remain siblings when installing the optional Claude prompt bridge. The usage installer copies a versioned helper into the private destination and saves backups there. Backups of old hook settings may contain keys; keep them private.

Codex connection configuration

Use the existing private connection.json for this project's Codex connection. Replace <project-path-hash> with its actual directory name, then enable usage without changing the other connection fields:

USAGE_CONFIG="$HOME/.local/state/planisphere/codex/<project-path-hash>/connection.json"
python3 - "$USAGE_CONFIG" <<'PY'
import json, os, sys
from pathlib import Path
path = Path(sys.argv[1])
config = json.loads(path.read_text())
config.setdefault("token_usage", {})["enabled"] = True
with path.open("w") as stream:
    os.fchmod(stream.fileno(), 0o600)
    json.dump(config, stream, indent=2)
    stream.write("\n")
PY

This changes a private credential file without displaying it. Do not paste its contents into a chat or commit it. Install the companion:

python3 "$USAGE_DIR/planisphere_usage.py" install \
  --config "$USAGE_CONFIG" --project "$PROJECT" \
  --agent-system codex --destination "$USAGE_DIR/installed"

Review the changed definitions in Codex's /hooks UI and use its normal trust flow. The installer does not grant trust or edit global Codex configuration. Start a new session in the selected project after review.

Claude Code connection configuration

Create a private connection file for the same workspace and system as the existing Claude activity hooks. This example uses the original guide's system name, claude-code-primary; adjust the non-secret labels to match your setup. It asks for the tenant key privately and refuses to overwrite an existing file.

USAGE_CONFIG="$USAGE_DIR/claude-connection.json"
python3 - "$USAGE_CONFIG" <<'PY'
import getpass, json, os, sys
config = {
    "base_url": "https://api.planisphere.ooo",
    "system_id": "claude-code-primary",
    "environment": "development",
    "surface": "claude-code",
    "api_key": getpass.getpass("Planisphere tenant API key: ").strip(),
    "token_usage": {"enabled": True},
}
if not config["api_key"]:
    raise SystemExit("A tenant key is required")
fd = os.open(sys.argv[1], os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
with os.fdopen(fd, "w") as stream:
    json.dump(config, stream, indent=2)
    stream.write("\n")
PY
python3 "$USAGE_DIR/planisphere_usage.py" install \
  --config "$USAGE_CONFIG" --project "$PROJECT" \
  --agent-system claude_code --destination "$USAGE_DIR/installed"

This adds usage handlers to this project's .claude/settings.local.json. It does not replace existing prompt capture. Usage can appear in Activity without an original receipt link. To retain that exact link for future prompts, install the optional bridge below.

The bridge preserves Planisphere's prompt capture and retains the acknowledged receipt identity alongside the native prompt ID. It replaces exactly one existing Planisphere UserPromptSubmit command; it cannot install a missing prompt capture handler. Session, tool and response handlers remain in place.

If the reviewed command uses Planisphere's legacy lane-aware shell capture, add this top-level setting to the private connection file before installing the bridge:

"claude_prompt_capture": {"profile": "legacy-shell-lanes-v1"}

Use this profile only for a reviewed matching legacy hook, not arbitrary shell scripts. It preserves lane selection from PLANISPHERE_LANE, then the session entry in ~/.claude/planisphere-lanes.json, then the whitespace-stripped ~/.claude/planisphere-lane file. A selected lane uses system lane:<lane> and its own prompt counter; without a lane, the system is claude-code-primary. App and Git metadata are retained. The plain generated hook remains the default without this profile.

With the profile enabled, numeric usage waits locally for the bridge's exact native prompt-to-system mapping, which the bridge retains before attempting receipt delivery. Enabling it before the bridge is installed can therefore leave reports queued. Install the matching bridge and verify a new prompt; do not treat that pending state as delivered usage.

First review the existing command privately in the selected settings file. Do not display or copy a command containing a key into chat, logs or shell history. The following prints only handler positions and SHA-256 digests of command strings, so you can identify the exact command you reviewed:

CAPTURE_SETTINGS="$PROJECT/.claude/settings.local.json"
python3 - "$CAPTURE_SETTINGS" <<'PY'
import hashlib, json, sys
from pathlib import Path
settings = json.loads(Path(sys.argv[1]).read_text())
for group_index, group in enumerate(settings.get("hooks", {}).get("UserPromptSubmit", [])):
    for hook_index, hook in enumerate(group.get("hooks", [])):
        command = hook.get("command")
        if isinstance(command, str):
            print(group_index, hook_index, hashlib.sha256(command.encode()).hexdigest())
PY

Set the full 64-character digest of that one reviewed command, then install:

REVIEWED_COMMAND_SHA="replace-with-the-full-reviewed-command-digest"
python3 "$USAGE_DIR/planisphere_usage.py" install \
  --config "$USAGE_CONFIG" --project "$PROJECT" \
  --agent-system claude_code --destination "$USAGE_DIR/installed" \
  --capture-helper "$USAGE_DIR/planisphere_claude_prompt.py" \
  --capture-settings-path "$CAPTURE_SETTINGS" \
  --replace-command-sha256 "$REVIEWED_COMMAND_SHA"

If your existing prompt handler is in ~/.claude/settings.json, explicitly set CAPTURE_SETTINGS="$HOME/.claude/settings.json" before reviewing it and calculating its digest. That replacement changes the existing global prompt handler and retains its original global scope; the usage companion still runs only under the selected project. Use this option only when that global capture change is intended. The installer refuses zero or multiple matching commands, preserves unrelated hooks, and stores a private backup. It does not move a global handler into project settings or grant any hook trust.

Delivery and recovery

The installed companion handles Stop, SubagentStop and SessionEnd. It checks that the hook's working directory is under the selected project. Stop and child-stop hooks run in the background; session-end capture only queues a bounded local snapshot. A later hook or manual flush can deliver it. Hook failures do not interrupt the agent's work.

The companion keeps a private, metadata-only invocation journal at usage-outbox/hook-outcomes.jsonl beside the connection configuration. It records invocation, validated input, collection, queueing and the final outcome, with native session/turn identifiers, collector hash and timestamps. It never records input bodies, transcript paths, credentials or exception messages, and is not uploaded. At 512 KiB it rotates once to .previous; older diagnostics are bounded. A journal write failure is reported separately and does not stop usage capture.

Outcomes distinguish disabled capture, project filtering, malformed hook input, configuration/capture failure, queued reports and exact acknowledged revisions. A flush delivering another prompt does not establish this prompt's delivery. A last collecting stage without queueing narrows an interrupted invocation; it does not identify why it stopped. No matching invocation record means only no recorded invocation: interpreter startup failures, diagnostic write failures, rotation and host dispatch outside the observed window still limit that evidence. A hook's zero exit status alone never establishes capture. Use genuine native completion IDs to compare host dispatch, this journal and the existing outbox; manual hook calls do not validate automatic activation.

The local outbox retains the latest numeric snapshot per prompt and checks the acknowledged prompt and revision before marking delivery. Retries do not add the same usage again. A newer snapshot updates that prompt; streaming snapshots are not summed as separate requests. A pending report is not yet visible in the console.

To retry already queued reports without scanning historical transcripts:

python3 "$USAGE_DIR/planisphere_usage.py" flush --config "$USAGE_CONFIG"

Inspect delivered, pending_reports and errors. Each invocation attempts at most four reports; repeat if reports remain after resolving a delivery error. A successful empty flush establishes only that this local queue has no pending reports, not that every prompt was captured.

For a deliberate recovery from one known transcript, select one prompt by native ID, or explicitly select the latest prompt:

python3 "$USAGE_DIR/planisphere_usage.py" sync \
  --config "$USAGE_CONFIG" --transcript /absolute/path/to/selected-transcript.jsonl \
  --agent-system codex --latest

Use --agent-system claude_code for Claude, or replace --latest with --prompt-id NATIVE_PROMPT_ID for a specific prompt. Do not omit both selectors unless you intentionally want all prompts in that selected transcript. The installation does not scan old sessions or backfill historical activity. Run collect PATH --agent-system codex or claude_code to inspect a selected transcript's numeric projection locally without delivery.

Verify in Activity and receipts

Complete a new prompt in the selected project, allow the background hook to finish, and open Activity → Usage by prompt. Check the system, session, prompt, model source, coverage and counters against the local source. One delivered prompt establishes that observation arrived; it does not prove complete session coverage.

When the native prompt ID and acknowledged receipt identity are available, the usage detail links to the original prompt receipt. The owner's receipt view can show that private usage. It is an unsealed observation: it does not change the original receipt's seal, and usage fields are not added to shared or public recipient receipts. A prompt without an exact receipt match remains unlinked; the helper does not guess from text or timestamps.

To stop capture, set token_usage.enabled to false in the private connection file and remove only the companion's handlers from the project settings. In Codex, use /hooks to review those changes. Restore a replaced Claude prompt handler from its private backup only after checking for newer settings changes. Keep or remove local queued reports according to your needs; disabling hooks does not erase reports already delivered to your workspace.