Decision: abx deploy is the fixed publish step; descriptions belong to template authoring
Status: implemented
Implemented: 2026-09-30
Supersedes (partial): 2026-09-09-template-deploy-command.md — its "coexisting abx deploy" section (project deployment into a sandbox via an in-sandbox agent) is replaced.
Related: 2026-09-30-nl-template-server-entrypoint.md, 2026-09-30-nl-template-init-and-create-scope-prompt.md.
The file name keeps
agent-flagfor history: adeploy --agentflag was first implemented and then withdrawn the same day (see "Reversed decision").
Problem
Reported while using the CLI:
abx deployfailed with[E7001] No LLM API key found for qwen-code agent— asking forBAILIAN_CODING_PLAN_API_KEY/DASHSCOPE_API_KEY/OPENAI_API_KEY, unrelated to the LLM keyabx config/abx createmanage.abx deploy --verbosefailed withNo such option: --verbose.- The intent was unclear:
deployis naturally "build + push + create template", a fixed procedure, yet it started aqwen-codecloud sandbox with its own agent and its own LLM route.
Root cause: deploy_shortcut was a separate "NL project deployment" product (Sandbox.deploy) that shared only the name with the template pipeline: its own credential chain (api/deploy.py), a private option set (no -v, no --region, no ACR options), unusable without an LLM.
Decision
The template lifecycle has three steps with one job each:
| Step | Command | Input | AI |
|---|---|---|---|
| 1. Author | abx template init / abx template init "DESCRIPTION" |
scaffold case or description → Dockerfile, commands.py, template.yaml |
optional |
| 2. Publish | abx deploy [PATH] |
the template directory | never |
| 3. Launch | abx create --template ID |
template ID | never |
abx create "DESCRIPTION" = 1 + 2 + 3 with a confirmation before anything is pushed.
abx deploy [PATH]=abx template deploywithPATHdefaulting to.:docker build→ push to ACR →CreateTemplate→ poll. No LLM, no key, no cloud sandbox, no description argument. The option set is derived fromtemplate build.paramsat import time, so-v/--verbose,--acr-namespace,--region,--yes, ... are inherited and cannot drift.- What a template is lives in
template.yaml(plusDockerfile/commands.py), written at authoring time.deployonly readsname,resources.cpu,resources.memory,generation; CLI options override them. - A missing
Dockerfilestopsdeploywith a message that points atabx template init. --traditionalbecomes a hidden, deprecated no-op with a warning. The oldINSTRUCTIONargument is removed (click reports an unexpected extra argument).Sandbox.deploy()(in-sandbox agent deploy) is kept as an SDK API only;DeployLLMKeyMissingError(E7001) remains for it, with an updated suggestion.
Reversed decision: deploy --agent
First implemented: abx deploy [PATH] [INSTRUCTION] --agent ran the local Qwen Code on the project to write Dockerfile + template.yaml before the pipeline (flag name chosen over --auto / --ai because it names the actor). It was withdrawn after review, for these reasons:
- Two sources of truth. A description given at deploy time ("FastAPI, listens on 8080") is exactly the information that belongs in
template.yaml(ports,capabilities,resources,env,custom_commands). Typed into a deploy command it is consumed once and never recorded, so the next deploy cannot reproduce it. - Wrong verb.
deployis a fixed publish step; generating or editing source files inside it makes a cloud-cost command also a code-rewriting command. - Drift from the real template contract. Since the generator now writes
commands.py(theSandboxServerwithout which a deployed sandbox cannot be used), a second, separate prompt for "existing project" files would have to be kept in sync withagent/template_guide.py, and it was not. - Same LLM route and credentials as
abx createwas right, but that is achieved by authoring throughtemplate init, which already reuses it.
The code written for it (deploy --agent, CodingAgentBackend.generate_for_project, codegen.prepare_project_template, .abx-bak backups, tree fingerprinting) was removed rather than left as dead code.
Open follow-up (not implemented): adopting an existing project
An existing project without a Dockerfile / template.yaml has no AI path today. If needed, it belongs under authoring, not deploy: e.g. abx template init reading an existing directory and writing the template files next to the code, through the same _generate_template_workspace and TEMPLATE_GUIDE, with the backup / "files touched" guard rails that were prototyped here. It needs its own design because template init DIRECTORY currently means "scaffold into this directory" and natural-language init takes a single positional.
Alternatives rejected
- Keep the NL default and only add
--verbose. Fixes the symptom but keeps a hidden LLM dependency and second credential route for a command whose name promises a fixed procedure. deployauto-detects a missing Dockerfile and runs the AI silently. Surprising: edits the user's tree and needs an LLM without being asked.- Drop
Sandbox.deploy. Public SDK surface with tests; only the CLI route was wrong.
Consequences
- Breaking for CLI users who relied on
abx deploy ./p "instruction": author withabx template init "DESCRIPTION", thenabx deploy ./<name>. Documented in CHANGELOG (Breaking Changes #2). - Fewer credential concepts: CLI AI steps use
abx config/ABX_LLM_*; only the SDKSandbox.deploykeeps its own env-var chain. abx deploy --helplists ~30 options because it mirrorstemplate deploy; acceptable, and prevents drift.
Verification
tests/test_cli/test_deploy_commands.py: delegation,PATHdefault, never touches agent / LLM keys /Sandbox.deploy,-vworks, option parity withtemplate build, help has no AI options, description argument and--agent/-iare rejected, missing Dockerfile points attemplate init, ACR fail-fast, deprecated--traditional.- Golden
help-deploy.
Related changes shipped with this report
abx create --dir DIRECTORYand the interactive "Save the generated template under which directory?" prompt (default~/.abx/generated).- Live activity block (
OutputManager.activity: header plus scrolling grey lines, see2026-09-30-rolling-agent-activity-lines.md) fed by--output-format stream-json, with process-tree termination on timeout / Ctrl-C. - Bounded research round (20 turns, 240 s,
ABX_QWEN_RESEARCH_TIMEOUT), separate "Researching public facts" phase, and a fresh session for the assessment after a failed research round (qwen rejects re-pinning an existing--session-id, the cause of the bogus "assessment unavailable" warning).