Skip to content

AgentID auth, external runner, activity protocol, approval flow #240

Draft
cyruszhang wants to merge 20 commits intomainfrom
cyrusz/agent-refactor
Draft

AgentID auth, external runner, activity protocol, approval flow #240
cyruszhang wants to merge 20 commits intomainfrom
cyrusz/agent-refactor

Conversation

@cyruszhang
Copy link
Copy Markdown
Collaborator

@cyruszhang cyruszhang commented Apr 29, 2026

Summary

Five related but separable threads in one PR — they share fate (the
runner uses Bearer auth, the activity protocol assumes Bearer auth)
and the conversion was done as one continuous arc.

1. AgentID Bearer authentication

Replaces the legacy X-Agent-ID header with Authorization: Bearer <JWT>, verified at the gateway against the configured AgentID
provider's JWKS. Cross-hub identity instead of per-trial API keys.

  • Gateway: _agentid.py builds a Verifier from DOJOZERO_AGENTID_*
    env config; every authenticated request resolves agent_id from
    the JWT sub. 503 on misconfiguration (no silent fallback).
  • Client: dojozero-client.GatewayTransport accepts an
    agentid_client and an agentid_audience; tokens are minted per
    request and sent as Authorization: Bearer ....
  • Compat: X-Agent-ID is no longer accepted by the gateway or sent
    by the client. The legacy dojozero-agent CLI and the demo
    robust_agent.py will get 401 against an AgentID-enabled gateway
    and need a follow-up to migrate (out of scope here).

2. Phase 1 canary runner

New dojozero-agent-runner package — a self-contained external agent
runner that connects to a DojoZero gateway as an agent over Bearer
auth. Canary configuration: degen × qwen-max.

  • _runner.py: opens DojoClient.connect_trial with an
    agent_id_client_sdk.Client for AgentID identity, builds an
    agentscope ReActAgent for the configured persona × LLM, polls
    events, reacts until trial_ended.
  • _llm.py: factory for ChatModelBase + matching FormatterBase
    across openai / dashscope / anthropic / gemini / grok.
  • _tools.py: registers get_balance, get_current_odds,
    place_bet, get_bets as ReAct tools backed by the SDK.
  • _emitting_model.py: wraps the ChatModelBase so each LLM call
    reports model.call back to the gateway (best-effort; observability
    never blocks the agent loop).

Deliberately lighter than the in-process BettingAgent — no event
throttling, retry queues, or memory compression. The canary's job is
to prove the externalization contract works end-to-end; performance
work lands only when a success-criteria gate fails.

3. AgentID activity protocol

Makes DojoZero a first-adopter hub of the activity-discovery design
(agent-identity/design/2026-05-04-activity-discovery.en.md).

Hub discovery surface — four public .well-known/* endpoints on
the gateway:

  • GET /.well-known/agent-id-jwks — Ed25519 JWKS (RFC 7517/8037)
  • GET /.well-known/agent-id-activity-manifest — JWS-signed manifest
  • GET /.well-known/agent-id-activity-categories — Tier-2 catalog
  • GET /.well-known/agent-id-activity-schemas/{verb}/{version} — JSON Schema

aip-activity verifies these via HubManifestFetcher; namespace
ownership is enforced by eTLD+1 of service_id (no central registry).

Activity emitters (gateway-side, all best-effort):

  • Tier-1: auth.deny, session.start, session.end,
    transfer.value, tool.use (on POST /bets)
  • Tier-1 (runner-forwarded): model.call via POST /activity/model-call
  • Tier-2: dojozero.bet_decision, joined to transfer.value via
    transaction_id. Carries decision_kind, selection, stake/confidence
    buckets, sha256(rationale), model, sport, game_id. The hub-flavored
    richness the canonical Tier-1 schema can't carry.

Per-request auth.verify auto-emit is off — at full DojoZero
load it would produce ~2.7M near-identical events/day. Tier-1
categories with real signal are hand-emitted instead.

4. Agent runner: --agent-* flag set, identity vs brain decoupled

Reworked the runner CLI around the "identity is durable, brain is
iterated often" split. Identity (profile dir or zip) and brain
(sys_prompt + model config YAML) are now orthogonal inputs.

Identity (mutually exclusive):
--agent-profile profile dir under $AGENTID_HOME/agents/
--agent-zip zip with agent.json + private_key
(none) AGENTID_* env (k8s pod default)

Brain:
--agent-brain single YAML, sys_prompt + model:
--agent-prompt + --agent-model piecewise / per-field override

Removes the legacy --persona / --llm matrix lookup (DojoZero-repo-
bound) plus --portal-zip, --identity, --private-key,
--sys-prompt-file, --model-config and DOJOZERO_* env equivalents.

New dojo0 agents build-brains expands agents/personas × agents/llms
into per-combination YAMLs in ./agent-brains/. The runner consumes
these via --agent-brain; matrix authoring stays where it is,
expansion is a build step.

Requires agent-id-client-sdk>=0.2.1 (adds AgentConfig — the brain
loader). Now on PyPI.

New section 5

5. Approval flow (spec §7.6.7 grant tokens)

Agents registering with --request-approval route every bet through
an IdP-mediated approval. Hub submits to /agentid/approvals, polls,
verifies the decision JWT vs IdP JWKS, mints + consumes a single-use
hub-internal grant, then places the bet atomically.

Gateway:

  • _approvals.py (new): ApprovalCoordinator + grant store + JWT
    verification.
  • /bets branches on adapter.is_approval_required → 202 with
    PendingApprovalResponse.
  • New GET /bets/pending/{approval_id} polls the IdP and on approved
    verifies + mints + consumes + places in one shot, returning
    BetResponse.
  • Tier-1 emissions: approval.requested / .granted / .denied
    (per design §9.5).

Client (dojozero-client):

  • place_bet now returns BetResult | PendingApproval.
  • New TrialConnection.check_pending_bet() returns BetResult on
    approve-and-placed, PendingBetStatus otherwise.

Runner:

  • --request-approval flag (env DOJOZERO_REQUEST_APPROVAL).
  • In approval mode, registers a check_pending_bet tool and rewrites
    place_bet's docstring for the pending contract.

Bet content sourced from the local stash, never from the JWT's ctx —
a buggy IdP can't change what gets placed. Grant tokens stay
hub-internal; agent only ever sees approval_id.

aip-idp: zero changes (existing /agentid/approvals + portal endpoints
already do everything). DojoZero is the first hub adopter of this
spec section.

Configuration

Env Purpose
DOJOZERO_AGENTID_TRUSTED_PROVIDERS Comma-separated trusted IdP domains
DOJOZERO_AGENTID_AUDIENCE Gateway's public origin (= JWT aud)
DOJOZERO_AGENTID_ACTIVITY_API_KEY Hub auth to aip-activity (optional)
DOJOZERO_AGENTID_AGENT_TOKEN Gateway's own management token (optional)
DOJOZERO_HUB_SERVICE_ID Public origin for the hub-publisher routes
DOJOZERO_HUB_SIGNING_KEY_PEM Ed25519 PKCS#8; mint with python -m agent_id_service_sdk.keygen --out hub.pem
DOJOZERO_HUB_NAMESPACE Tier-2 prefix (default dojozero)
DOJOZERO_HUB_SIGNING_KID Key id (default hub-key-1)
DOJOZERO_AGENT_PROFILE Identity profile dir name (also via --agent-profile)
DOJOZERO_AGENT_ZIP Identity zip path (also via --agent-zip)
DOJOZERO_AGENT_BRAIN Brain YAML path (also via --agent-brain)
DOJOZERO_AGENT_PROMPT Prompt-only file path (piecewise)
DOJOZERO_AGENT_MODEL Model-only YAML path (piecewise)
DOJOZERO_REQUEST_APPROVAL Run in approval mode (also --request-approval)
DOJOZERO_AGENTID_APPROVAL_ENDPOINT IdP approvals URL; auto-derived from PROVIDER_URLS if single entry

See docs/external_agent_migration.md for the full env-var table.

Tests

  • 1,106 unit tests across gateway, runner, activity emitters, hub
    publisher, hub-keypair handling.
  • 10 E2E discovery tests in tests/test_e2e_hub_discovery.py
    stands up the gateway with a real HubPublisher and runs
    HubManifestFetcher against it via httpx.ASGITransport. Catches
    drift between signer and verifier across the full chain (manifest
    JWS, JWKS encoding, eTLD+1 ownership, schema URL resolution,
    fetcher caching). Gated behind --run-integration; opt-in via
    uv pip install -e ../aip-activity. Not in GitHub CI — Aone is
    the planned home (DojoZero on github.com but aip-activity on
    Alibaba GitLab; cross-repo checkout works in Aone, not GitHub).

Dependencies

  • agent-id-service-sdk>=0.3.0 (PyPI) — Verifier,
    HubManifestFetcher, build_manifest/sign_manifest,
    generate_signing_keypair, public_key_to_jwk. Symmetric sign +
    verify means gateway and aip-activity agree on wire format by
    construction.
  • agent-id-client-sdk>=0.2.0 — runner-side identity / token minting.

Test plan

  • uv run pytest packages/ — full unit suite green
  • uv pip install -e ../aip-activity && pytest packages/dojozero/tests/test_e2e_hub_discovery.py --run-integration — E2E green
  • Mint pre-env hub key, set DOJOZERO_HUB_* + DOJOZERO_AGENTID_* secrets, deploy gateway
  • curl https://<pre-gateway>/.well-known/agent-id-jwks returns JWKS with the expected kid
  • curl https://<pre-gateway>/.well-known/agent-id-activity-manifest returns a compact JWS with Content-Type: application/jose
  • Register the canary (degen × qwen-max) external agent against pre, verify Bearer auth round-trips
  • Place a bet from the canary; verify aip-activity receives tool.use, transfer.value, and dojozero.bet_decision joined by transaction_id
  • Runner reports model.call after each LLM invocation; rows show in aip-activity
  • dojo0 agents build-brains --personas degen --llms-filter Qwen writes ./agent-brains/dojozero-degen-qwen.yaml
  • Runner with --agent-profile + --agent-brain registers, polls, places bets end-to-end
  • Runner with --request-approval submits an approval; principal sees the card on the IdP portal
  • Approve on portal → bet places; deny → bet rejected with note; no decision → expires cleanly
  • aip-activity rows show approval.requested / .granted / .denied joined by approval_id

Out of scope (follow-up)

  • Migrate the legacy dojozero-agent CLI / demo robust_agent.py
    off X-Agent-ID (will get 401 against AgentID-enabled gateway).
  • Hub key rotation flow — spec doesn't address yet.
  • E2E pass 2 (full ingest path with mocked IdP) — drafted, deferred.
  • Aone wiring for the integration test.
  • Additional Tier-2 categories (bet settlement, prediction trade, etc.).
  • Runner perf hardening (event throttling, retry queues, memory
    compression) — only when a success-criteria gate fails without it.

Phase 0 of external-agent migration. Gateway accepts `Authorization: AIP <token>`
and verifies via aip-identity-verify against a configured IdP. Legacy X-Agent-ID
path is unchanged; AIP stays disabled when env isn't set. Wired in at the
dashboard server and standalone gateway CLI. Tests + design doc included.
Constructed with aip_client (from aip-identity-sdk), transport sends
`Authorization: AIP <token>` per request; AIPClient.get_token(audience)
handles caching + refresh. Audience defaults to gateway base URL.
Legacy X-Agent-ID path unchanged. New `[aip]` extra; lazy import.
Renames AIP → AgentID across gateway, SDK transport, and runner config.
Upstream packages aip-identity-{verify,sdk} 0.1.x → agent-id-{service,
client}-sdk 0.2.0; classes are `Verifier`/`AIPClient` and errors are
`TokenExpiredError` / `TokenInvalidError` / `ProviderUntrustedError` /
`SignatureInvalidError`. Optional extras renamed `[aip]` → `[agentid]`.

X-Agent-ID is removed end-to-end. Gateway accepts only `Authorization:
Bearer <token>` verified by AgentID; missing/malformed → 401, no
verifier → 503. Transport never emits X-Agent-ID. Legacy dojozero-agent
CLI + robust_agent.py demo will 401 — follow-up.

Verifier wires activity reporting: when DOJOZERO_AGENTID_ACTIVITY_API_-
KEY + AGENT_TOKEN are set, auth.verify is auto-emitted per request.

Adds dojozero-agent-runner package skeleton + RunnerConfig (env-driven,
loads persona/LLM YAMLs, AgentID identity required); core loop is a
stub. Env vars `DOJOZERO_AIP_*` → `DOJOZERO_AGENTID_*`, documented in
.env.example and deploy/.env.template. "Protocol" wording dropped.
Adds the runner core (_runner.py + _llm.py + _tools.py): loads
Identity.from_env(), builds Client for Bearer auth, opens
DojoClient.connect_trial with agentid_client, constructs an agentscope
ReActAgent with the persona prompt + matched LLM, registers SDK-backed
tools (get_balance, get_current_odds, place_bet), polls events and
reacts until trial_ended. Phase 1 canary: degen × Qwen (qwen3-max).

DojoClient.connect_trial gains agentid_client / agentid_audience
params, threaded through GatewayTransport. Daemon resolves auth via
AGENTID_AGENT_ID env first (Identity + Client), falls back to legacy
api_key when unset; the daemon's "no auth configured" startup error
points at AgentID env vars first, legacy second. agent-id-client-sdk
is lazily imported so dojozero-client still installs without it.

dojozero-client 0.3.0 → 0.4.0 (breaking; matches the gateway-side
Bearer-only switch). 7 new runner tests cover event formatting and
SDK-backed tool behavior.
Wires four hand-emitted Tier-1 emitters: auth.deny on Bearer verify
failure, session.start on register, session.end on unregister + a
lifespan sweep for stranded sessions, transfer.value on accepted bets.
Per-request auth.verify auto-emit OFF (report_auto_verify=False) —
projected ~2.7M/day flood replaced by hand-emitted high-signal
categories. All best-effort; failures log WARN, never block auth.
14 new tests. Known issue: payload fields don't match aip-activity's
server schemas (would 422); reconciliation tracked in
agent-identity/design/2026-05-04-activity-discovery.en.md §9.
Reconciles gateway/_activity.py with aip-activity's server schemas.
Before, every emitted event would 422 — fields were structurally
wired but didn't match server expectations. emit_auth_deny: rename
reason→error_class. emit_session_start: persona/model/sport_type
into attributes dict, scenario="trial". emit_session_end: roll-ups
into summary dict, outcome="completed". emit_transfer_value: canonical
{amount_bucket, currency, direction, amount?, purpose, transaction_id,
linked_tier2="dojozero.bet_decision"}; hub-specific richness deferred
to the linked Tier-2 event. New _amount_bucket() helper mirrors
aip-activity's AMOUNT_BUCKETS whitelist. 28 tests (was 14).
Make DojoZero a first-adopter publisher of the AgentID activity protocol.
The gateway now serves all four discovery artefacts so aip-activity can
verify Tier-2 events from this hub without any out-of-band registration.

- New HubPublisher module: loads an Ed25519 keypair from
  DOJOZERO_HUB_SIGNING_KEY_PEM, signs the manifest via
  agent_id_service_sdk.sign_manifest, and serves JWKS / categories /
  schemas. Hard-coded v1 catalog: dojozero.bet_decision (transaction_id,
  decision_kind, selection, stake/confidence bucket, rationale_hash,
  model, game_id, sport).
- Four .well-known routes on the gateway:
    GET /.well-known/agent-id-jwks
    GET /.well-known/agent-id-activity-manifest        (application/jose)
    GET /.well-known/agent-id-activity-categories
    GET /.well-known/agent-id-activity-schemas/{verb}/{version}
  All return 503 when DOJOZERO_HUB_* env is unset (publisher disabled).
- Wired HubPublisher.from_env() into both gateway-construction sites
  (CLI and dashboard_server/_trial_manager).
- 16 tests including a full HubManifestFetcher round-trip that proves
  wire compatibility with aip-activity's discovery client.
- Bump agent-id-service-sdk>=0.3.0 with local path source for dev.
The agent's invocation of place_bet is a tool call; emit canonical Tier-1
tool.use alongside the existing transfer.value so consumers can join the
"what tool did the agent run" stream to the "what value moved" stream.

- New emit_tool_use() in gateway/_activity.py: payload is
  {tool_name, args_hash, tool_invocation_id, duration_ms?, success,
   linked_transfer_id?}. args_hash is sha256 over canonical-JSON form so
  raw args never leave the gateway. Returns the invocation id so callers
  still link forward when the verifier is None.
- _hash_args + new_tool_invocation_id helpers; sorted-key JSON form
  makes the same args hash the same regardless of dict order.
- POST /bets emits tool.use on both success and failure paths, with
  duration_ms from time.monotonic() around the broker call. Success
  case sets linked_transfer_id = bet_id (= transfer.value.transaction_id);
  failure case omits it (no transfer happened).
- 8 new tests covering hash stability, payload shape, success/failure
  outcomes, caller-supplied invocation ids, duration clamping, and
  swallowed emission failures.
LLM token usage / latency happens entirely on the runner side and is
invisible to the gateway today. Wire it through: runner wraps the chat
model, posts usage to the gateway after each call, gateway forwards as
a canonical Tier-1 model.call. The hub stays the single emission source.

- emit_model_call() in gateway/_activity.py: payload is
  {model, tokens_in, tokens_out, latency_ms?, cost_usd?, purpose?,
   outcome?, linked_tool_invocation_id?}; negatives clamped fail-safe.
- POST /activity/model-call: agent-Bearer-authed, requires registration,
  forwards via emit_model_call.
- ModelCallReport pydantic model with camelCase aliases.
- TrialConnection.report_model_call() in dojozero-client; failures
  swallowed (observability never blocks the agent).
- EmittingChatModel wraps any agentscope ChatModelBase: time.monotonic
  latency, extracts usage from ChatResponse, reports success/error
  outcome. Streaming pass-through with one-time INFO log (canary uses
  stream=False).
- Runner wraps create_model(...) in EmittingChatModel(_, connection)
  before constructing the ReActAgent.
- 10 new tests (5 emit_model_call, 5 EmittingChatModel); 1091 passing
  repo-wide.
Every accepted bet now emits both Tier-1 transfer.value and Tier-2
dojozero.bet_decision joined by transaction_id. Closes Phase 4.

- emit_bet_decision() validates against the JSON Schema HubPublisher
  serves; sha256 of rationale (raw text never leaves), confidence
  bucketed low/medium/high. Unknown markets log + skip.
- BetRequest + client place_bet gain optional model/confidence/rationale,
  forwarded only to the Tier-2 event.
- POST /bets emits transfer.value then bet_decision; sport from
  state.metadata, game_id from broker._event when available.
- 14 tests including a round-trip that validates payloads against the
  published schema.
The gateway was reinventing Ed25519 → JWK encoding and shipping a
dojo0 hub-keygen subcommand that did literally nothing the SDK CLI
doesn't. Pull both into the SDK so every adopter shares one
implementation; document the mint command in the deployment doc.

- _hub_publisher.py: drop private _public_key_to_jwk; use
  agent_id_service_sdk.public_key_to_jwk instead.
- Delete dojo0 hub-keygen subcommand + tests. Operators run
  `python -m agent_id_service_sdk.keygen --out hub.pem`.
- docs/external_agent_migration.md: new "Hub publisher" section
  with the four DOJOZERO_HUB_* env vars and a pointer to the SDK
  keygen as the canonical mint flow.
- pyproject: SDK 0.3.0 resolves directly from PyPI now that it's
  published; lockfile re-resolved.
- New tests/test_e2e_hub_discovery.py (10 integration tests, gated
  behind --run-integration). Stands up the gateway with a real
  HubPublisher and runs HubManifestFetcher against it via httpx
  ASGITransport — no network, no subprocesses. Catches drift
  between the signer side and the verifier side.
- Test imports app.main from aip-activity via pytest.importorskip
  so it skips cleanly when aip-activity isn't installed; opt-in
  via `uv pip install -e ../aip-activity`.
- Not in GitHub CI — aip-activity is on Alibaba GitLab, not
  reachable from GitHub Actions. Aone is the planned CI home.
@cyruszhang cyruszhang changed the title agent runner substrate AgentID auth + Phase 1 external runner + activity-protocol hub publisher May 5, 2026
cyruszhang added 7 commits May 5, 2026 13:16
Reuse the hub publisher's Ed25519 key (the one already signing the
manifest) for outer-envelope auth on each activity POST. Sunsets the
static DOJOZERO_AGENTID_ACTIVITY_API_KEY path with no migration
required — DojoZero is the only adopter so far.

- _agentid.py: drop DOJOZERO_AGENTID_ACTIVITY_API_KEY env read.
  Construct HubPublisher.from_env() and thread its
  (private_key, kid, service_id) into the SDK Verifier as
  hub_signing_key / _kid / _service_id. The activity_endpoint and
  agent_token_for_emit pass-through is unchanged.
- Activity reporting now requires the hub publisher to be configured
  (DOJOZERO_HUB_SERVICE_ID + DOJOZERO_HUB_SIGNING_KEY_PEM). Without
  those, emission stays disabled — same behavior as before just
  triggered by a different env var.
- test_gateway_agentid: replace the bearer-key wiring test with a
  hub-publisher wiring test (mints a fresh keypair, asserts the
  Verifier received the right private_key/kid/service_id).
- Bump agent-id-service-sdk pin to >=0.4.0; local-path source
  override during 0.4.0 development. 1106 unit + 10 E2E green.
…SDK)

Before: the E2E discovery test imported app.schemas.namespace_ownership
from aip-activity, forcing every contributor (and any CI environment) to
have aip-activity installed in DojoZero's venv. With the helper lifted
into agent-id-service-sdk 0.4.1, the cross-repo coupling disappears.

- test_e2e_hub_discovery.py: import verify_namespace_ownership +
  NamespaceOwnershipError from agent_id_service_sdk. Drop the
  pytest.importorskip("app.main") and the type-ignore'd lazy imports
  inside TestNamespaceOwnership.
- Update the test docstring: GitHub CI can now run this freely; the
  Aone-only constraint applied to the (deferred) Pass 2 ingest test,
  not Pass 1 discovery.
- Bump SDK pin to >=0.4.1. 1106 unit + 10 E2E green.
The gateway no longer needs an IdP-issued JWT to declare its privacy
posture — that was a leftover from the pre-§5.0 era when X-AgentID-Token
was the only way to carry the claim. Read the posture from env, build
the privacy dict, hand it to the SDK Verifier; SDK signs it into every
HubJWS envelope.

- _agentid.py reads DOJOZERO_HUB_PRIVACY_DEFAULT_LEVEL (default 'summary')
  and optional DOJOZERO_HUB_PRIVACY_OVERRIDES (JSON map). Builds
  hub_privacy_claim and passes to Verifier.
- Drop DOJOZERO_AGENTID_AGENT_TOKEN env read; agent_token_for_emit kwarg
  no longer set on the Verifier.
- Update test_gateway_agentid wiring test for the new privacy env vars
  and the absence of the token. Add monkeypatch.delenv on DOJOZERO_HUB_*
  in the minimum-config test so a populated dev .env doesn't leak.
- Bump SDK pin to >=0.4.2. 1106 unit + 10 E2E green.
Hub identity is process-wide and origin-wide, not trial-scoped. The
four .well-known/* routes were attached to per-trial gateway apps,
which meant the hub's public surface only existed while a trial was
running — and got duplicated across every concurrent trial. Move
them to the dashboard server's persistent app where they belong.

- New gateway/_hub_routes.py: register_hub_routes(app) helper. Reads
  app.state.hub_publisher at request time so callers populate it
  during their own lifespan setup.
- dashboard_server/_server.py: builds HubPublisher.from_env() in
  lifespan, calls register_hub_routes(app). Routes available before
  any trial is scheduled, persist after they finish.
- gateway/_server.py: drop the four route handlers, the
  hub_publisher field on GatewayState, and the hub_publisher kwarg
  on create_gateway_app. Per-trial gateways no longer carry the hub
  surface.
- cli.py + dashboard_server/_trial_manager.py: drop hub_publisher
  threading; trial gateways are created without it.
- Tests: hub-publisher and E2E discovery tests pivot to mounting
  register_hub_routes on a minimal FastAPI app — same code path the
  dashboard uses, no trial-app construction needed just to exercise
  the four routes. 1106 unit + 10 E2E green.
Run the runner against a live trial without the DojoZero shell setup.

- _config.build_config(): programmatic entry point that both env and
  CLI loaders reduce to. RunnerConfig.identity carries a pre-loaded
  AgentID Identity (portal-zip or CLI profile); falls back to
  Identity.from_env() only when None.
- __main__.py: argparse CLI with --portal-zip / --agent-profile /
  --dashboard-url and friends; flags override env.
- --dashboard-url derives the trial-prefixed gateway URL and defaults
  the AgentID audience to the dashboard origin — keeps the JWT `aud`
  matching what the gateway expects.

gateway register_agent: accept AgentID Bearer JWT as preferred auth,
fall back to legacy apiKey for pre-AgentID clients. Mismatched
apiKey/Bearer identity → 403.
Identity (mutually exclusive):
--agent-profile <name>   profile dir
--agent-zip <path>       zip
(none)                   AGENTID_* env

Brain:
--agent-brain <path>             single YAML (sys_prompt + model:)
--agent-prompt + --agent-model   piecewise / per-field override

Removes --persona/--llm matrix lookup and the older flag set
(--portal-zip, --identity, --sys-prompt-file, --model-config, ...).

Adds `dojo0 agents build-brains`: expands agents/personas × agents/llms
into per-combination YAMLs in ./agent-brains/. Authoring matrix stays;
expansion is a build step.

Requires agent-id-client-sdk 0.2.1. 35 tests pass.
Agents registering with request_approval=true route bets through
an IdP-mediated approval. Hub submits to /agentid/approvals,
polls, verifies the decision JWT vs IdP JWKS, mints + consumes a
single-use grant, then places the bet atomically.

Gateway: new _approvals.py + /bets/pending/{id} route. Bet
content sourced from local stash, never from JWT ctx. Adds
approval.requested/granted/denied Tier-1 emissions.

Client: place_bet returns BetResult | PendingApproval; new
check_pending_bet returns BetResult on approved-and-placed in one
round-trip. connect_trial gains request_approval.

Runner: --request-approval flag (env DOJOZERO_REQUEST_APPROVAL).
In approval mode, registers check_pending_bet tool. Grants stay
hub-internal; agent only sees approval_id.
@cyruszhang cyruszhang changed the title AgentID auth + Phase 1 external runner + activity-protocol hub publisher AgentID auth, external runner, activity protocol, approval flow May 7, 2026
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