cli: axctl bootstrap-agent — one-shot scoped agent setup#67
Merged
Conversation
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>
128a1d0 to
35cfdf0
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Collapses the 15-step manual sequence we hit bootstrapping
@axolotlyesterday into a single command. Closes friction items §0, §2, §3, and §4 fromshared/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-axolotlWhat's hidden inside
axp_a_) are refused with a clear message; the command explains you needaxp_u_to create agents.base_url= user_env= source=as the first line — kills the.activesilent-override footgun for good.POST /api/v1/agentswithX-Space-Id, not the legacy/agentsin the caller's default space. This is the only path that actually survives the prod ALB.--allow-existingis set.--bio,--specialization,--system-promptvia the proxiedPUT /api/v1/agents/manage/{name}./credentials/agent-patfirst (canonical per the ax-operator skill). On HTML / 404 / 405 it transparently falls back toPOST /api/v1/keyswithbound_agent_id,allowed_agent_ids,audience, and the correct prod scope vocabulary (api:read,api:write). User seesMinted via mgmtorMinted via keys_fallbackso operators know which path worked.{save_to}/.ax/config.toml+.ax/tokenat 0600. Optional named profile compatible withaxctl profile verify.GET /auth/mewith the fresh PAT and printsallowed_spacesso 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→ cleanaxctl bootstrap-agent smoke-$(date +%s) --space-id <dev-space> --save-to /tmp/smoke→ full flow, verify.ax/token+ whoami shows space-lock.ax-cli-devspace — should usekeys_fallbackpath since/credentials/*isn't proxied.--dry-runprints plan without calling the API (covered by unit test, should also be visually verified).--allow-existingreuses an existing agent and mints a fresh PAT (covered by unit test).What's intentionally NOT in this PR
axctl agents delete <name>works.--from-file <yaml>. The one-shot works for one-shot. Bulk provisioning is future scope.--avatar-fileflag onagents updatein 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