keystone-agent-client
Consumption client for deployed Keystone pro-code agents (US-19 / FDP-3442): discover agents in your workspace and invoke them from any Python app — sync, streaming (SSE), or background runs — without learning the gateway's transport quirks.
Dependency-thin by design (D-M4-G): httpx only. Embedding an agent must not pull the
authoring SDK's LangGraph tree into your app. (Authoring agents? That's
keystone-agent-sdk + the keystone CLI — not this package.)
Install
Published with the keystone-cli bundle (FDP-4561). Pick the channel that matches the
environment you call — the package name carries the channel, the import path does not:
| Environment | Package | Index |
|---|---|---|
| dev | keystone-agent-client-dev |
GAR onx-py-public |
| qc | keystone-agent-client-qc |
GAR onx-py-public |
| staging | keystone-agent-client-staging |
GAR onx-py-public |
| prod | keystone-agent-client |
Public PyPI |
# dev / qc / staging — GAR needs Google credentials to READ (401 without them), so uv asks
# `keyring`, whose GAR plugin hands over your gcloud login. One-time setup:
uv tool install keyring --with keyrings.google-artifactregistry-auth
gcloud auth application-default login # an account with read access to vinid-devops
# then:
uv add keystone-agent-client-dev --keyring-provider subprocess \
--index https://oauth2accesstoken@asia-east1-python.pkg.dev/vinid-devops/onx-py-public/simple/
# prod — Public PyPI, no credentials
uv add keystone-agent-client
GAR is therefore only reachable by people and runners with a Google identity on
vinid-devops; a consumer outside the organisation uses the PyPI (prod) package.
Every channel installs the same module: from keystone.agent_client import ….
Quickstart
from keystone.agent_client import AgentPlatformClient
# Config from args or env: KEYSTONE_API_BASE / KEYSTONE_TOKEN / KEYSTONE_WORKSPACE_ID
# (the CI convention, FDP-3441). No stage: that tier is retired (FDP-2071).
with AgentPlatformClient() as client:
# Every page of the workspace's registry; deployed_only keeps the pro-code agents with a
# Live version — the only kind the gateway routes (Visual Builder agents are listed too).
for a in client.list_agents(deployed_only=True):
print(a.name, a.id, a.status)
# By registry name or registry UUID (connect() is an alias of agent()):
agent = client.agent("docs-qa")
# Sync — blocks until the agent answers (its manifest budget):
run = agent.invoke({"question": "What is Keystone?"})
print(run.status, run.result)
# Multi-turn — pass the previous run's session_id to continue the thread. session_id names
# the CONVERSATION (same on every turn); run_id names this turn's own run (get_run key):
follow_up = agent.invoke({"question": "And in Vietnamese?"}, session_id=run.session_id)
assert follow_up.session_id == run.session_id and follow_up.run_id != run.run_id
# Streaming (token-level):
for event in agent.stream({"question": "..."}, stream_mode="messages"):
if event["type"] == "token":
print(event["content"], end="")
# Background run + poll:
handle = agent.invoke_async({"question": "long job..."})
done = agent.get_run(handle.run_id, wait=True) # returns on terminal OR awaiting_human
print(done.status, done.trace_id) # trace_id = observability link (FDP-3435)
What the client owns for you
- Kong path + tenant headers (
Authorization,X-Workspace-ID) and a fresh 32-hexX-Correlation-IDper call — the exact literal the platform stamps as the Langfusetrace_id, soRun.trace_idis a working trace link. - Cold-start (warming) retries: a scaled-to-zero version wakes on first call; the
gateway answers
202 warmingwithout forwarding — the client honoursRetry-Afterand re-POSTs safely. - SSE parsing for
stream(); polling forget_run(wait=True)— which returns anawaiting_humanrun immediately (approvals can take days; your app decides what to do). - Typed errors decoded from the platform envelope (
RT_*/GW_*):AgentNotFoundError,AgentConflictError(e.g. cancel on a terminal run),AgentAuthError, baseAgentClientErrorwith.code. - Version pinning:
invoke(..., version="v3")sendsX-Agent-Version. - Name or UUID: the gateway routes on the agent name, so
agent(<uuid>)looks the name up in agent-hub first (one GET). A not-found there falls back to using the string as the name — agent-hub's naming rule admits a UUID that starts witha-f. "Not-found" is a 404 or a 403 whoseerror.details.reasonisresource_registry_miss/resource_inactive(agent-hub's authz gate refuses an unknown or deleted UUID before it can 404); any other 403 still raisesAgentAuthError, whose.reasoncarries that deny reason.
Versioning and compatibility
SemVer, on the client's own pyproject.toml version — independent of
keystone-cli's, even though both ship in one pipeline.
- patch — a fix; no public signature changes.
- minor — additive only: a new method, a new keyword-only argument, a new
AgentInfo/Runfield. Existing calls keep working. - major — anything that breaks a caller: a removed or renamed method, a changed return shape,
a new required argument. While on
0.x, a breaking change bumps the minor instead. - ⚠️ Bump on every content change. GAR is immutable and the pipeline tolerates a duplicate
upload, so an unbumped change is silently not published —
-devkeeps serving the old code under the same version.
The client speaks two public surfaces, both v1: agent-gateway /api/agent-service/v1/agents/…
(invoke, runs, threads) and agent-hub /api/agent-hub/v1/agents (discovery). What else a call
needs lives in the agent's own image, not in the environment: the runtime is baked in when
the agent is built, so an agent built on an older runtime base lacks the newer routes even on an
up-to-date environment.
| Client | Gateway / hub API | Needs the agent built on | For |
|---|---|---|---|
0.1.x |
v1 |
any agent-runtime | invoke, invoke_async, stream, get_run, cancel, list_agents, agent/connect |
0.1.x |
v1 |
agent-runtime ≥ v2026.09.03.1 |
threads, thread_state, thread_history, delete_thread, resume, multitask_strategy |
≥ 0.1.3 |
v1 |
agent-runtime > v2026.09.27.1 (FDP-5539) |
Run.trace_id / Run.agent_uuid on sync invoke (older agents: None; invoke_async / get_run always had them) |
≥ 0.1.4 |
v1 |
agent-runtime > v2026.09.27.1 (FDP-5538) |
a distinct Run.run_id per turn on sync invoke (older agents: the conversation root, i.e. turn 1's id) |
An agent built before that runtime answers those calls 404 (AgentNotFoundError); rebuild it
to pick them up (see api.md → io_schema for
why a republish alone does not rebuild).
Not in scope (v1): HITL approve/reject (approver tooling lives in the keystone CLI and
the portal), authoring/deploying agents, async (await) client — the surface is small on
purpose; an async twin can be added when demand shows up.
Example
examples/consume_docs_qa.py is a runnable end-to-end script. Export the four
KEYSTONE_* variables above (API base = the Kong edge; token = your login token or a
service token), then:
python examples/consume_docs_qa.py [agent-name]
Metadata
Release files for keystone-agent-client 0.1.4
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| keystone_agent_client-0.1.4.tar.gz | 20.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| keystone_agent_client-0.1.4-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 34.3 kB
Release files / keystone_agent_client-0.1.4.tar.gz
| Download URL | keystone_agent_client-0.1.4.tar.gz |
|---|---|
| Size | 20.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
76f24b96ac1656b130ba79f86f5ad9179be0030c08ba3615fb3370efa854f46d
|
|
BLAKE2b-256 checksum How to use checksums |
c66fb567387448a47ab17f593bd644476ed91453e218ad58de84070c0174dfaf
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.12
|
Release files / keystone_agent_client-0.1.4-py3-none-any.whl
| Download URL | keystone_agent_client-0.1.4-py3-none-any.whl |
|---|---|
| Size | 14.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0b9be624a2c76fd76d0bbc7b016e3c3719695d5cc4ffae9b75138d0feaf9e826
|
|
BLAKE2b-256 checksum How to use checksums |
3713e9ada3695769bbe6c3b109570d9438e05c0e6e7123b2e83f1b8d1b70e6d8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.12
|