Skip to content

cli: axctl bootstrap-agent — one-shot scoped agent setup#67

Merged
madtank merged 2 commits intomainfrom
orion/bootstrap-agent
Apr 18, 2026
Merged

cli: axctl bootstrap-agent — one-shot scoped agent setup#67
madtank merged 2 commits intomainfrom
orion/bootstrap-agent

Conversation

@madtank
Copy link
Copy Markdown
Member

@madtank madtank commented Apr 18, 2026

Summary

Collapses the 15-step manual sequence we hit bootstrapping @axolotl yesterday into a single command. Closes friction items §0, §2, §3, and §4 from shared/state/axctl-friction-2026-04-17.md.

axctl bootstrap-agent axolotl \
  --space-id ed81ae98-50cb-4268-b986-1b9fe76df742 \
  --description "Playful ax-cli helper" \
  --model codex:gpt-5.4 \
  --audience both \
  --save-to /home/ax-agent/agents/axolotl \
  --profile next-axolotl

What's hidden inside

  1. User-PAT gate. Agent PATs (axp_a_) are refused with a clear message; the command explains you need axp_u_ to create agents.
  2. Effective-config line. Prints base_url= user_env= source= as the first line — kills the .active silent-override footgun for good.
  3. Space-aware creation. POST /api/v1/agents with X-Space-Id, not the legacy /agents in the caller's default space. This is the only path that actually survives the prod ALB.
  4. Idempotent. If the agent already exists in the target space, exits 2 unless --allow-existing is set.
  5. Metadata polish. Optional --bio, --specialization, --system-prompt via the proxied PUT /api/v1/agents/manage/{name}.
  6. PAT minting with automatic fallback. Tries /credentials/agent-pat first (canonical per the ax-operator skill). On HTML / 404 / 405 it transparently falls back to POST /api/v1/keys with bound_agent_id, allowed_agent_ids, audience, and the correct prod scope vocabulary (api:read, api:write). User sees Minted via mgmt or Minted via keys_fallback so operators know which path worked.
  7. Workspace scaffold. {save_to}/.ax/config.toml + .ax/token at 0600. Optional named profile compatible with axctl profile verify.
  8. Post-mint verification. GET /auth/me with the fresh PAT and prints allowed_spaces so the caller immediately sees that containment landed (agent-lock + space-lock).

Test plan

  • uv run pytest tests/test_bootstrap_agent.py -v → 9 passed (all new)
  • uv run pytest tests/ → 230 passed (no regressions)
  • uv run ruff check + ruff format --check → clean
  • Smoke test on dev: axctl bootstrap-agent smoke-$(date +%s) --space-id <dev-space> --save-to /tmp/smoke → full flow, verify .ax/token + whoami shows space-lock.
  • Smoke test on prod: same, in ax-cli-dev space — should use keys_fallback path since /credentials/* isn't proxied.
  • --dry-run prints plan without calling the API (covered by unit test, should also be visually verified).
  • --allow-existing reuses an existing agent and mints a fresh PAT (covered by unit test).

What's intentionally NOT in this PR

  • Delete-on-failure / rollback. If PAT minting fails after agent creation, the agent remains. Keeping it manual for now — axctl agents delete <name> works.
  • Bulk --from-file <yaml>. The one-shot works for one-shot. Bulk provisioning is future scope.
  • Avatar at create time. We ship the separate --avatar-file flag on agents update in PR agents: avatar-day bundle — set via PUT, --avatar-url/-file, config line #65; users can follow-up with that command for now.

🤖 Generated with Claude Code

anvil and others added 2 commits April 18, 2026 01:01
Collapses the 15-step manual sequence documented in
shared/state/axctl-friction-2026-04-17.md §0 into a single command:

    axctl bootstrap-agent axolotl \
      --space-id <uuid> \
      --description "..." \
      --model codex:gpt-5.4 \
      --audience both \
      --save-to /home/ax-agent/agents/axolotl \
      --profile next-axolotl

What it does, in order:

1. Requires a user PAT (axp_u_). Agent PATs are refused with an
   actionable message — they can't create agents or mint credentials.
2. Prints the effective-config line (base_url + user_env + source path)
   so operators don't silently target the wrong environment.
3. POST /api/v1/agents with X-Space-Id — the proven-prod creation path.
4. If the agent already exists in the target space and --allow-existing
   is set, reuses it; otherwise exits 2 with a clear error.
5. Optional metadata polish via PUT /api/v1/agents/manage/{name}.
6. Mints an agent-bound PAT. Tries /credentials/agent-pat first
   (canonical per the ax-operator skill); on HTML/404/405 falls back
   to POST /api/v1/keys with bound_agent_id + allowed_agent_ids +
   audience + prod-compatible scopes (api:read, api:write). The
   fallback exists because /credentials/* isn't routed to the backend
   on prod today.
7. Writes workspace: {save_to}/.ax/config.toml and .ax/token at 0600.
8. Optional named profile compatible with `axctl profile verify`.
9. Verifies with GET /auth/me + the fresh PAT and prints resolved
   allowed_spaces so the caller sees containment (agent-lock + space-lock).

client.py: extended create_key() with bound_agent_id, audience, scopes,
and space_id parameters. Backward-compatible (all kwargs default None).

Tests: 9 new covering happy-path with mgmt route, HTML-fallback,
404-fallback, already-exists abort, already-exists reuse, user-PAT-required
gate, agent-PAT rejection, --dry-run (zero side effects), and the
effective-config line. 230 existing tests still pass.

Tracked friction items closed: §0 (one-shot), §2 (effective-config line
on mutating commands), §3 (CLI-side fallback when /credentials/* isn't
routed), §4 (prod scope vocabulary hidden behind the command).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…review fix)

Addresses axolotl's PR #67 review finding. The prior implementation
caught all httpx.HTTPStatusError and returned None, which silently
treated 401/403/5xx as "agent not found" and proceeded to creation.
Axolotl reproduced with a fake 401: the helper returned None.

Fix:
- Narrow the exception handling in _find_agent_in_space: only a 404
  is treated as "not found" (space gone / non-member → downstream POST
  gives a cleaner error). Everything else (401, 403, 5xx, network
  errors) propagates so users see the real failure instead of a
  confusing duplicate-create downstream.

Regression tests added (parametrized):
- test_bootstrap_does_not_swallow_existence_check_errors[401/403/500/503]
  asserts the command exits non-zero AND never reaches POST /api/v1/agents
  or any PAT-mint path when the existence check returns an auth/server
  error.
- test_bootstrap_handles_404_on_existence_as_not_found asserts a 404
  is still treated as "agent absent, continue" — the one benign case.

All 14 bootstrap tests + 235 suite total pass. Ruff clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@madtank madtank force-pushed the orion/bootstrap-agent branch from 128a1d0 to 35cfdf0 Compare April 18, 2026 01:01
@madtank madtank merged commit 67e8547 into main Apr 18, 2026
6 checks passed
@madtank madtank deleted the orion/bootstrap-agent branch April 18, 2026 01:29
@madtank madtank restored the orion/bootstrap-agent branch April 19, 2026 06:29
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