Agent Sandbox Registry

agent-sandbox

Decision: Single-question clarification loop for the abx create NL path

无中文版

.agents/notes/implemented/feature/2026-09-29-single-question-clarification.md

Decision: Single-question clarification loop for the abx create NL path

Status: implemented Proposed: 2026-09-29 Implemented: 2026-09-29

Problem

abx create "<description>" feeds one free-form sentence straight into the coding agent. A sentence that never mentions the runtime, its dependencies, the entry command, exposed ports, resource expectations, or the data the sandbox works on produces a mediocre template: the agent has to guess, and the user only discovers the guess after a build + deploy + create cycle has been paid for.

Asking for everything at once is not acceptable either: a battery of questions is poor UX and the task explicitly forbids more than one question per round. Blocking non-interactive callers until the description is "complete" is worse — CI has nobody to answer, so it must fail fast with actionable guidance instead of hanging.

Decision

Assess the description before generation and complete it through a single-question clarification loop:

API Design

New module src/agent_sandbox/agent/clarify.py (the thin adapter around the agent's structured rounds):

CLARITY_THRESHOLD: float = 0.8            # our gate, not the model's
MAX_CLARIFY_ROUNDS: int = 5              # one question per round
ASSESS_SESSION_TURNS: int = 3            # assessment = pure reasoning
DEFAULT_CLARIFY_TIMEOUT: float = 180.0

CLARIFY_SCHEMA: dict[str, Any]           # completeness / question / missing / example

@dataclass(frozen=True)
class ClarifyAssessment:
    completeness: float                  # clamped into 0..1
    question: str | None                 # at most ONE question
    missing: tuple[str, ...] = ()
    example: str | None = None
    @property
    def complete(self) -> bool: ...
    def missing_summary(self) -> str: ...

@dataclass
class ClarifyOutcome:
    session_id: str = ""                 # non-empty -> generation resumes it
    assessment: ClarifyAssessment | None = None
    rounds: int = 0
    degraded: bool = False               # assessment unavailable (never blocks)
    skipped: bool = False                # --yes fast path

def new_session_id() -> str
def assessment_prompt(description: str) -> str
def answer_prompt(answer: str) -> str
def parse_assessment(payload: Any) -> ClarifyAssessment | None
def evaluate(prompt, *, binary, env, cwd, session_id=None, resume=None,
             timeout=DEFAULT_CLARIFY_TIMEOUT) -> ClarifyAssessment | None

CodingAgentBackend (see the pluggable-backend note) grows one method so the CLI orchestrates through the backend seam and never imports the Qwen dialect:

def assess(self, prompt, *, workdir, binary, env,
           session_id=None, resume=None) -> ClarifyAssessment | None: ...

_generate_and_deploy_template (cli/commands/sandbox.py) runs prepare_workdir → _clarify_requirements → generate(..., resume_session=...); a non-empty outcome.session_id is the session the generator resumes.

Alternatives considered

Dependencies

Test Strategy

All tests are offline; the qwen binary and the network are fully mocked.

Acceptance criteria

Implementation

Evidence

Files changed