Skip to main content

ai-agent-gateway-cli

Python client for the ai-agent-gateway HTTP/SSE protocol, shipped as the hank entrypoint.

Usage

python -m agent_gateway_cli login
python -m agent_gateway_cli chat "hello"
python -m agent_gateway_cli --config-namespace cashnerd login
python -m agent_gateway_cli --config-namespace cashnerd chat --session default "hello"
hank login
hank chat
hank chat "hello"
hank chat --model-key anthropic.claude-sonnet-5 --effort high \
  --model-key-for plan.author=anthropic.claude-opus-5-5 \
  --effort-for plan.author=high "research this company"
hank chat --model-key anthropic.claude-opus-5-5 --save-preference "remember this choice"
hank preset
hank chat --model-preset <published-name> "run this under a published preset"
hank chat --show-thinking "walk me through the margin bridge"
hank replay ~/.local/state/hank/evidence/isolated-gateways/<key>/ --session sess_<id>
hank control health

The default config namespace is agent-gateway for python -m agent_gateway_cli and hank for the hank entrypoint, which writes:

  • ~/.cache/hank/cli_config.json
  • ~/.cache/hank/sessions/<name>.json

--config-namespace cashnerd writes under ~/.cache/cashnerd/ instead. The namespace directory and its sessions/ directory are created mode 0700; the files inside are 0600.

Each named session file holds a local transcript, which is display and persistence only, and the gateway session identity: the live session id and token, and the conversation's durable id (log_session_id, the first session's id), which is set when the first chat POST is accepted — init alone does not establish a durable conversation, so a session that never sent one re-inits plainly. Each turn sends only the new user message; the gateway replays the conversation from its session log. When the stored token is no longer usable (the session's roughly one-hour TTL has passed, or the gateway restarted), the CLI opens a new gateway session with resume_session_id set to the durable id, and the conversation continues for a session the caller owns. If the gateway refuses the resume, the CLI prints a notice and starts a new conversation. --new clears both the transcript and the identity.

GatewayTransport and AsyncGatewayTransport (agent_gateway_cli/transport.py, with session_map.SessionMap for per-conversation identity) are the one Python owner of the gateway chat client contract — init and resume, token reuse, the 401 rule, SSE frame parsing, re-attaching a dropped stream via /chat/subscribe from the last delivered seq, cancel, tool approvals and results — consumed by hank chat, api/dev/chat_cli.py and, for frame parsing only, the control client; the contract they mirror is the consolidation design record. Every StreamEnvelope carries received_at, and stream_turn yields the gateway's :keepalive as a seq-less transport_keepalive envelope that never moves the reconnect cursor. A stream_error is yielded and changes neither when the turn ends nor how it re-attaches, but a turn that then fails carries the last stream_error's detail in its error message. A chat stream that sends no byte for STREAM_IDLE_SECONDS (60, four missed keepalives; the transports' stream_idle_seconds option) is a dead transport: the read times out and the turn re-attaches through /chat/subscribe like a dropped connection; the server turn is never cancelled.

No client wait is a minted cap. /chat/init and the non-stream posts have no request timeout, and a turn re-attaches for as long as the server still holds it, repeating the last reconnect delay. The turn-busy 409 loop keeps retrying. The cancel post is bounded only by a caller's own timeout. The returned session carries elapsed_s, conflict_retries and reconnects, the cancel returns cancel_outcome and cancel_elapsed_s, and a transport failure names its cause and the seconds it waited.

Async callers implement AsyncSessionHolder; MemorySessionHolder and SessionMap.holder(key) supply it. Same-key holders share live identity and coordinate init with POST acceptance, releasing the lock before SSE reading. The streaming holder still pins its own session for approvals. cancel_chat, submit_approval and submit_tool_result take the holder and post on the in-flight session the caller passes; a dead-bearer 401 refreshes the holder for the next turn and the request is never replayed on the new token. SessionMap.invalidate(key) retires that conversation for every old holder; delayed init or POST completion cannot save it over the key's new identity.

hank chat with no message on an interactive terminal replaces the process with the agent-gateway-tui binary (installed with npm install -g @henrychien/agent-gateway-tui, whose engines field declares Node >= 24), passing the resolved namespace, session and any --research-file-id as arguments and the resolved connection as GATEWAY_BASE_URL, GATEWAY_API_KEY, GATEWAY_USER_ID, and AGENT_GATEWAY_ROUTE_PREFIX. The terminal reads no per-turn selector, so --skill, --mode, --model-key, --effort, --model-key-for, --effort-for, --model-preset, --save-preference, --clear-preference, --raw, --drain-trailing and --new given there are named in one stderr note and the terminal launches; they apply to the message form. hank chat "<message>" and any non-interactive stdout run the Python client in this package instead.

hank replay <path> opens a retained chat transcript in the same terminal binary without connecting to a gateway: <path> is a sess_*.jsonl file, a directory holding them, an isolated instance's records root (state/logs/chat/) or a checkout (api/chat_logs/), searched in that order. --session <sess_id> picks one transcript, otherwise the newest by mtime; the chosen file is printed on stderr and passed as agent-gateway-tui --config-namespace <ns> --replay <file>.

The Python client prints streamed thinking only with --show-thinking: the model's summarized thinking appears dimmed on a [thinking] line ahead of the [text] reply. Without the flag, thinking deltas go to the debug log only. The TUI shows thinking by default and needs no flag.

When the provider refuses a turn (a refusal event), the Python client keeps the text already delivered, prints [refusal] <category>: <explanation> and [refusal] <guidance> after it, and exits 0; nothing is reset, and the guidance is to start a fresh conversation before continuing.

When the gateway reports a frame it could not deliver (a stream_error event), both the Python message CLI and api/dev/chat_cli.py print [stream_error] <error> where it arrived and keep reading; the turn ends on its own terminal, and the exit code follows that terminal.

Both the Python message CLI and api/dev/chat_cli.py stop cleanly (exit 0) on stream_complete with terminal_disposition: "awaiting_approval". They show a neutral parked notice with approval_id, job_id, and root_turn_id when present, plus usage, without re-prompting or continuing automatically. The attempt remains open in the gateway's durable log; like an interrupted turn, the packaged CLI does not append it to local history. Completed turns alone commit local history; unknown terminal dispositions remain protocol errors.

Contract

The CLI sends context.channel = "cli" on every chat request. When user_id is configured, it is sent as a top-level field on both /api/chat/init and /api/chat. Tool approvals post tool_call_id, nonce, approved, and allow_tool_type.

The CLI consumes authenticated capability_choices from /api/chat/init. --model-key and --effort are sent as given; the gateway judges them against those choices and refuses an ineligible key or unsupported effort. When both are omitted, the chat request omits all selection fields and the server resolves the capability default. Repeatable --model-key-for CAPABILITY=MODEL_KEY and optional --effort-for CAPABILITY=LEVEL flags populate init-time run overrides; the capability id is sent as given and the gateway owns which capabilities are selectable. Provider-qualified selectors, upstream model names, display-label aliases, and local fallback catalogs are not accepted.

--save-preference and --clear-preference use the authenticated server preference contract for account-wide session.driver state; the server refuses a save with no --model-key. Local transcript files do not store or resolve model preferences.

hank preset lists the names the model-selection policy publishes and your selection, hank preset <name> selects one of those names account-wide, and hank preset --clear clears it, through the gateway's /model-presets/self door, which owns which names exist. The shipped policy publishes none today, so the list is empty and any name is refused. Concrete --model-key/--effort selection is unchanged.

hank chat --model-preset NAME sends model_preset on /api/chat/init (only when given): the session runs under that published preset, above your saved preset and your firm's, for every turn on it; roles the preset omits come from policy. The gateway refuses a name its policy does not publish before any turn runs. A stored session token is not reused when the flag is given; the CLI re-inits, resuming the stored conversation.

--research-file-id <n> attaches context.research_file_id to every chat turn (interactive and message form) and binds a control dispatch run; the gateway's research preflight judges the id.

Every gateway request is composed as base_url + route_prefix + path, so the hosted deployment is reached as https://hank.investments/gateway/api/chat/init. The default BaseUrlPolicy accepts any hostname over https, allows http only for a loopback host, and rejects a query or fragment. A base URL may carry the deployment path: it is hoisted out of the base URL into the route prefix, so --base-url https://hank.investments/gateway resolves to base URL https://hank.investments with route prefix /gateway/api, identical to --base-url https://hank.investments --route-prefix /gateway/api. An explicit --route-prefix (or AGENT_GATEWAY_ROUTE_PREFIX) that neither equals nor extends the base URL path is refused, naming both values. TLS verification is on by default; --insecure and HANK_INSECURE=1 are honored only for a loopback gateway, and HANK_CA_BUNDLE selects a custom CA bundle. Products that need a narrower policy can inject BaseUrlPolicy(allowed_schemes=..., allowed_hostnames=...).

Control plane

Chat and control use separate credentials and separate channels. hank login --control-api-key <key> [--control-channel <channel>] stores the control key in the same namespace config file. Chat requests always send context.channel = "cli"; control sessions default to --control-channel tui (cron/scripts convention sets AGENT_CONTROL_CHANNEL=cli instead). Whichever channel is used MUST match the channel of the GATEWAY_USER_KEYS entry the control key belongs to, or session mint fails with ChannelMismatch.

hank control <subcommand> drives /api/control/*: dispatch starts an autonomous run (--profile analyst --mode once|skill … [--follow]); runs, run, logs, watch, cancel observe and steer it; approvals/approve/ deny, batches, artifacts, and schedules cover the rest. Run listings are owner-and-channel scoped: hank control runs shows only runs started on the control key's channel (documented in docs/reference/agent-run-evaluation.md).

--follow follows a run until it settles; nothing cancels it on a clock. --timeout-seconds is an optional caller bound: reaching it stops following and prints elapsed_s with the hank control watch <run> next call, and the run keeps going. Ctrl-C cancels that exact run (or batch) and waits for its terminal receipt; a second Ctrl-C exits at once with cancel_outcome: unknown. Receipts and batch results carry elapsed_s, reconnects, cancel_elapsed_s and cancel_outcome, and control object results carry elapsed_s.

hank control approvals lists pending approvals and approve/deny resolve them; there is no standing approval policy to read or set from the CLI.

Metadata

Release files for ai-agent-gateway-cli 0.8.7

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for ai-agent-gateway-cli 0.8.7
File Size Uploaded
ai_agent_gateway_cli-0.8.7.tar.gz 171.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for ai-agent-gateway-cli 0.8.7
File Interpreter ABI Platform
ai_agent_gateway_cli-0.8.7-py3-none-any.whl Python 3 none any Details

Total release size: 264.5 kB

Release files / ai_agent_gateway_cli-0.8.7.tar.gz

Download URL ai_agent_gateway_cli-0.8.7.tar.gz
Size 171.2 kB
Tags Source
SHA-256 checksum
How to use checksums
06d6580378dda22ae6cbf2bd3b4c840cf32957af787475801a9ef1171f6cc6ec
BLAKE2b-256 checksum
How to use checksums
58aea9d496278aa21b9ba9f5f687446e50174c0c3edca53b552f21e02eb3b0f4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.2

Release files / ai_agent_gateway_cli-0.8.7-py3-none-any.whl

Download URL ai_agent_gateway_cli-0.8.7-py3-none-any.whl
Size 93.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
abbf042d975a12e39e1085243345eb2d031d1b587118a41fc27d83d7e3a5a409
BLAKE2b-256 checksum
How to use checksums
781a7fd083e187e3d0071426f3a43ef3251bddc9a8205514e4ee0dd6c065b610
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.2

Release history Release notifications | RSS feed

This release

0.8.7 This release

2 release files

0.8.6

2 release files

0.8.5

2 release files

0.8.1

2 release files

0.8.0

2 release files

0.7.1

2 release files

0.7.0

2 release files

0.6.0

2 release files

0.5.4

2 release files

0.5.3

2 release files

0.5.2

2 release files

0.5.1

2 release files

0.5.0

2 release files

0.4.4

2 release files

0.4.3

2 release files

0.4.2

2 release files

0.4.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page