tools/cognirelay_client.py is a single-file, stdlib-only command-line client for CogniRelay’s continuity endpoints. It exists so that agents and operators can read, write, and prepare continuity data without a third-party HTTP library or a full SDK.
The client is a thin transport tool. It does not validate capsule contents, inject fields, or make decisions about what to persist — that remains the agent’s responsibility. It sends what you give it, returns what the server gives back, and surfaces errors through exit codes and stderr.
urllib.request, argparse, hashlib from stdlib)The client exposes five subcommands:
| Subcommand | Purpose |
|---|---|
read |
Read a continuity capsule |
upsert |
Write or update a continuity capsule |
list |
List and discover continuity capsules |
capabilities |
Show server capabilities (feature map) |
token hash |
Compute the SHA-256 hex digest of a token (for config files) |
Running with no subcommand prints usage to stderr and exits 2.
read, upsert, list, and capabilities share these arguments (not present on token hash):
| Argument | Default | Notes |
|---|---|---|
--base-url |
COGNIRELAY_BASE_URL env, else required |
One trailing slash stripped before use |
--token |
— | Raw bearer token (visible in process listings; prefer --token-file or --token-env in production) |
--token-file |
— | Path to file containing token (stripped of trailing whitespace) |
--token-env |
— | Name of env var containing token (stripped of trailing whitespace) |
--timeout |
30.0 |
HTTP timeout in seconds |
The client resolves the bearer token using the first non-empty source in this order:
--token (explicit value on command line)--token-file (read file, strip trailing whitespace)--token-env (read the named env var)COGNIRELAY_TOKEN env var (implicit fallback)If none resolve, the client exits 3.
python tools/cognirelay_client.py read \
--base-url http://localhost:8080 \
--token-file /run/secrets/agent_token \
--subject-kind user \
--subject-id agent-1
--subject-kind accepts: user, peer, thread, task. This calls POST /v1/continuity/read with allow_fallback: true and prints either the raw JSON response or a client-side text rendering.
--format json (default): pretty-printed JSON response body. Does not send view in the request and preserves the server response body unchanged.--format startup: sends view="startup" to the server and renders the startup_summary block as compact section-based text. This is a CLI presentation convenience layered on top of the canonical continuity.read response, not a different hook contract. If the server predates view="startup" (response lacks startup_summary), the client falls back to a legacy renderer that extracts fields from capsule.continuity directly.The client tolerates both continuity schema 1.0 and 1.1 payloads on read surfaces. For issue #194, newly written continuity capsules and continuity archive/fallback/cold artifacts move to schema 1.1; stabilized-shape legacy 1.0 continuity payloads remain readable and upgrade safely where supported. Truly pre-stabilization payloads missing required modern continuity fields are still outside the automatic migration boundary.
Use --output <path> to write to a file instead of stdout.
Sections are divided into always-shown (rendered with (none) when empty) and conditional (suppressed entirely when empty/null):
Always shown: Source State, Top Priorities, Active Constraints, Open Loops.
Conditional: Recovery Warnings (with health status), Trust Signals (4-line digest), Thread Identity (from capsule.thread_descriptor), Negative Decisions, Rationale Entries, Session Trajectory, Stable Preferences.
Not shown: updated_at, stance_summary, active_concerns — available in --format json.
The startup text format is a presentation convenience. Its exact layout is not a stable contract. Scripts that need stable field access must use --format json.
=== Source State ===
active
=== Top Priorities ===
- Complete authentication refactor
- Update deployment runbook
=== Active Constraints ===
- No breaking API changes before v2
=== Open Loops ===
(none)
=== Trust Signals ===
Recency: current (fresh)
Completeness: orientation adequate
Integrity: healthy, verified
Scope: exact
=== Thread Identity ===
Auth refactor [active]
Keywords: auth, security
=== Negative Decisions ===
- No caching layer: adds complexity without current need
=== Rationale Entries ===
- [constraint] perf: Latency budget is 200ms
=== Session Trajectory ===
- Reviewed PR #42
- Started auth module extraction
=== Stable Preferences ===
- [communication] Prefer concise responses
If there is no capsule and no startup_summary, only Source State (and Recovery Warnings if present) is printed, followed by (no capsule available).
python tools/cognirelay_client.py upsert \
--base-url http://localhost:8080 \
--token-file /run/secrets/agent_token \
--input capsule.json
The JSON file is the complete POST /v1/continuity/upsert request body (must include subject_kind, subject_id, capsule). The client sends it verbatim — no field injection or validation.
The request body may include an optional session_end_snapshot to merge the fixed startup-critical snapshot field set into the capsule before persistence — see Payload Reference for the merge algorithm and field constraints. It may also include lifecycle_transition and superseded_by to atomically transition a thread capsule’s lifecycle — see Payload Reference.
Use --stdin instead of --input to pipe JSON from another process. Exactly one of the two is required. Payloads over 256 KiB are rejected client-side.
Include session_end_snapshot in the upsert request body to merge the fixed startup-critical snapshot field set into the capsule before persistence — the server applies the merge, the client sends it verbatim.
{
"subject_kind": "user",
"subject_id": "agent-1",
"capsule": { "...": "..." },
"session_end_snapshot": {
"open_loops": ["finish auth refactor"],
"top_priorities": ["ship v2 API"],
"active_constraints": ["no breaking changes before release"],
"stance_summary": "Wrapping up auth work, blocked on review."
}
}
The P0 fields (open_loops, top_priorities, active_constraints, stance_summary) are required when session_end_snapshot is present. P1 fields (negative_decisions, session_trajectory, rationale_entries) are optional — null means preserve the existing capsule value. See Payload Reference for the full merge algorithm.
python tools/cognirelay_client.py list \
--base-url http://localhost:8080 \
--token-file /run/secrets/agent_token \
--subject-kind thread \
--sort salience \
--limit 10
Sends POST /v1/continuity/list. All flags map 1:1 to request body fields — omitted flags produce omitted fields (server defaults apply).
| Flag | Maps to | Type |
|---|---|---|
--subject-kind |
subject_kind |
Choice: user, peer, thread, task |
--limit |
limit |
int |
--include-fallback |
include_fallback |
flag |
--include-archived |
include_archived |
flag |
--include-cold |
include_cold |
flag |
--lifecycle |
lifecycle |
Choice: active, suspended, concluded, superseded |
--scope-anchor |
scope_anchor |
str |
--keyword |
keyword |
str |
--label-exact |
label_exact |
str |
--anchor-kind |
anchor_kind |
str |
--anchor-value |
anchor_value |
str |
--sort |
sort |
Choice: default, salience |
Output is always pretty-printed JSON. No --format flag. No --output flag — pipe to a file if needed.
python tools/cognirelay_client.py capabilities \
--base-url http://localhost:8080 \
--token-file /run/secrets/agent_token
Sends GET /v1/capabilities. Prints the server’s feature map as pretty-printed JSON. No additional arguments beyond connection args. Output is always JSON — no --format or --output flag.
python tools/cognirelay_client.py token hash --value "my-secret-token"
Prints the lowercase hex SHA-256 digest to stdout. Use --file or --env instead of --value to read the token from a file or environment variable. Exactly one source is required.
Use this to generate token hashes for peer_tokens.json and other config files.
| Code | Meaning |
|---|---|
| 0 | Success (including degraded/fallback reads) |
| 1 | HTTP error (4xx/5xx) — stderr shows Error: HTTP {code}: {body} |
| 2 | Usage or argument error |
| 3 | Token resolution failed (no source, empty --token, unreadable or empty --token-file, or --token-env naming an unset/empty variable) |
| 4 | Connection or network error (timeout, DNS, refused) |
| 5 | Response parse error (non-JSON body) |
| 6 | Token source unreadable or empty (token hash only — file not found, empty file, env var unset or empty, empty --value) |
| Variable | Used by | Purpose |
|---|---|---|
COGNIRELAY_BASE_URL |
read, upsert, list, capabilities |
Default for --base-url |
COGNIRELAY_TOKEN |
read, upsert, list, capabilities |
Implicit token fallback (lowest precedence) |
Canonical startup hook — restore orientation after a context reset:
Send POST /v1/continuity/read with view: "startup" and allow_fallback: true, then forward the response unchanged into the runtime.
Canonical request shape:
{
"subject_kind": "user",
"subject_id": "$AGENT_ID",
"view": "startup",
"allow_fallback": true
}
If you need an operator-facing local rendering instead of unchanged forwarding, use the CLI convenience mode:
python tools/cognirelay_client.py read \
--subject-kind user --subject-id "$AGENT_ID" \
--format startup
Canonical pre_compaction_or_handoff hook — persist orientation before context loss:
python tools/cognirelay_client.py upsert --input /tmp/capsule.json
Operator token preparation — generate a hash for config:
python tools/cognirelay_client.py token hash --value "$NEW_TOKEN"
Use the capabilities subcommand to discover what the current CogniRelay instance supports (including features like startup view, trust signals, and salience ranking):
python tools/cognirelay_client.py capabilities --base-url http://localhost:8080 --token-file /run/secrets/agent_token
See API Surface for the endpoint contract.