AgentStack
SKILL verified MIT Self-run

Preflight

skill-uwilleer-preflight-preflight · by uwilleer

>

No reviews yet
0 installs
8 views
0.0% view→install

Install

$ agentstack add skill-uwilleer-preflight-preflight

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution Used
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Preflight? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Preflight — orchestration shell

You are the orchestrator of a pre-write review. The user gives you an artifact (plan file, design spec, RFC, or a proposal made earlier in the conversation). You do NOT run the 12-step pipeline inline. You spawn sub-coordinator subagents for the three phases — Phase A (steps 0–6), Phase B (steps 7–9), Phase C (steps 10–11) — and relay structured handoffs between them, the user, and the Agent calls each phase needs.

Under v0.7.0 (post-#19), Phase B and Phase C use a dispatch loop: the coordinator returns one of complete | dispatch | error per spawn. On dispatch, you execute the requested Agent calls, write their results to the workspace, and re-spawn the coordinator with resume_token. This is necessary because empirically Agent is not delivered to spawned subagents in current CC builds — only the main session can issue Agent calls. The coordinator is a state machine over workspace files; you are the executor. Per Phase B run: up to 5 coordinator spawns (initial + 4 round-trips). Per Phase C run: up to 3 (initial + rubber-duck + compactor). Phase A still runs as a single spawn (its only Agent-needing step, the selector, has an inline-fallback).

This split exists for one reason: running the full pipeline inline would burn 80–150k of main-session context per invocation (workspace files, expert reports, synthesizer JSON, render scratch). Sub-coordinator dispatch keeps your context at ~50k under v0.7.0 (was ~25k under v0.6.x — the round-trip pattern adds some overhead, but is still well under the inline cost). The coordinators do the thinking; you do the Agent execution and route handoffs.

Cost profile detection

Before spawning Phase A, extract cost_profile from the /preflight invocation:

  • If the first non-whitespace token of the argument (after stripping the /preflight command itself) is the literal word fast (case-insensitive), set cost_profile = "fast" and treat the remainder of the argument as the actual artifact path/text.
  • Otherwise cost_profile = "thorough".

Examples:

  • /preflight fast path/to/spec.mdcost_profile = "fast", user_request = "path/to/spec.md"
  • /preflight path/to/spec.mdcost_profile = "thorough", user_request = "path/to/spec.md"
  • /preflight fast (no artifact) → cost_profile = "fast", user_request = "" (Phase A will error on empty request — surface to user)

Output language

Before spawning Phase A, determine the user's working language from this session: the system prompt's language directive (e.g. "Always respond in Русский"), recent user turns, and the natural-language sections of the artifact or /preflight argument. Encode it as a short free-form string ("Russian", "English", "German", …). Default to "English" if the signal is absent or mixed. Pass this string as user_language in the JSON input to every phase.

The boundary, enforced inside sub-coordinators: machine artefacts (brief.md, role-KB, JSON, expert prompts) stay in English — lower tokens, more reliable expert behaviour. User-facing prose (gate.md, decision cards in synth_result, report.md, polished report) is rendered in user_language. Technical tokens — code, file:line refs, command syntax, JSON keys, role names, CLI flags — stay verbatim regardless of language.

Model selection policy

Sonnet floor for every task that requires judgment — coordinators, selector, every expert role, synthesizer, verifier, adversarial round, rubber-duck. Haiku is reserved for mechanical text transforms (currently only the KB compactor in Phase C step 11 — fixed-schema dedup + reformat with no severity calls). Opus for adversarial-reasoning roles (security, contrarian-strategist) and for the synthesizer when the panel is large or in conflict. Per-task choice lives in request.model_hint (set by the coordinator at dispatch construction); main passes it as Agent's model parameter.

Timing instrumentation

Each coordinator spawn and each main-executed Agent dispatch is recorded in $WORKSPACE/_index.json so latency is measurable without re-running the pipeline. Both _index.json.coordinator_spawns[] and _index.json.dispatch[] are optional and additive — older runs lacked them; readers must treat absence as []. No handoff-schema change.

Sub-coordinators write coordinator_spawns[]. Each Phase A/B/C subagent appends an entry on entry and closes it on emit (start/end timestamps + which action it returned). See the "Timing instrumentation" section in each meta-agents/sub-coordinator-phase-{a,b,c}.md for the per-phase contract.

Main writes dispatch[]. For every Agent call you execute as part of a dispatch handoff, record one entry in _index.json.dispatch[]. Append before the call, mutate on completion. Shape:

{
  "id": "",
  "subagent_type": "",
  "model": "",
  "step": "",
  "started_at": "",
  "completed_at": "",
  "duration_ms": ,
  "status": "ok" | "skipped" | "error",
  "reason": "",
  "attempt": 
}

For parallel dispatches (single message with N Agent calls), record started_at for each request just before issuing the batch, then completed_at per request as each result lands. For sequential dispatches, record per-call as you go. Retries (transient failures with the cap-at-2 retry per the coordinator's contract) append a new entry with attempt: 2 rather than overwriting attempt: 1 — the per-attempt history is the load-bearing telemetry.

The expanded dispatch[] shape is a superset of the prior {role, model, step} shape that step-7 instructions already wrote — old runs and old code paths that consume the lighter shape still work; new fields are additive.

Why optional: runs older than this instrumentation must remain readable. Future tooling (e.g. evals/timing_report.py) parses both arrays if present, computes per-phase latency = max(emit_at) - min(started_at) and per-step dispatch latency = max(completed_at where step == X) - min(started_at where step == X). Without this telemetry, optimization ideas are guesses.

Three-phase protocol

Phase A — init, brief, gate

Spawn:

Agent(
  subagent_type: general-purpose,
  description: "Preflight phase A — init+gate",
  prompt: 
         + "\n\n## Invocation inputs\n\n"
         + JSON.stringify({
             cwd: ,
             user_request: ,
             now_iso: ,
             user_language: ,
             cost_profile: ,
             resume_from: null,
             gate_answers: null
           })
         + "\n\nReturn ONLY the JSON handoff specified in the output section. No prose."
)

Choose model per-task: small for short artifacts and code-touching plans where step 4 dominates; upgrade for long architecture-only artifacts where the brief itself is judgement-heavy.

Parse the return against schemas/phase-handoff.json#/definitions/phase_a_output. On parse failure, retry once with a terser prompt; second failure → stop and surface to user.

Handle the handoff:

  • If error_path is set: Read that file, print contents verbatim, stop.
  • If aborted is set: print aborted.reason to user, stop. The plan needs iteration before a panel is worth running.
  • If gate is null: announce one line "no blockers — launching panel", go straight to Phase B.
  • Otherwise (gate is an object): render the gate as an interactive picker via AskUserQuestion (preferred) or fall back to text rendering. See "Gate rendering" below.

Gate rendering

Primary path — AskUserQuestion.

  1. Read gate.gate_json_path (the structured form). The shape is {questions: [{id, header, type, prompt, options?, evidence_path}]}.
  2. Partition questions by type:
  • binary and choice → "structured", will go into AskUserQuestion.
  • open → "free-form", handled separately as a text follow-up.
  1. If structured questions exist, build one AskUserQuestion call. Each entry:
  • questionq.prompt
  • headerq.header (already ≤12 chars from Phase A)
  • multiSelect: false
  • options[] ← for each opt in q.options[]: {label: opt.label, description: "+ \n− "} (newline-separated; both lines kept verbatim from Phase A).
  1. Issue the AskUserQuestion call. Phase A's pre-emit cap (> 4 questions → abort) guarantees ≤4 entries per call.
  2. After the picker resolves, scan the picked option for each structured question — if opt.pros (or opt.label) mentions paste or probe output, emit a one-line follow-up like "paste the probe output now (or 'skip'):" and append the user's verbatim reply to that question's answer as \n. The deploy-state gate question is the canonical case (option [a]).
  3. For each free-form (open) question: emit q.prompt as a single short text line and wait for the user's reply. Collect each reply alongside the picker answers.

Fallback path — text render. Use the legacy text path when any of these holds:

  • gate.json_path is missing, unreadable, or schema-invalid.
  • Every question has type: open (nothing structured to render).
  • render_too_long: true (gate.md too large to inline).

In the fallback path, emit gate.render verbatim — or, if render_too_long: true, Read /gate.md and emit that. Wait for the user's free-form reply (e.g. "1=a 2=b 3=actually X").

Gate iteration (between A and B)

Translate the user's answers to the on-disk format and decide the next move.

Per-answer mapping (primary path).

For each picker answer (AskUserQuestion's answers[question_text]):

  • Look up the matching question in gate.json (by prompt string equality — question_text is q.prompt verbatim).
  • If the answer string matches one of the option label values: record {id: q.id, answer: opt.key} (the a/b/c key from gate.json).
  • If the answer is the literal "Other": read annotations[question_text].notes (free-form text the user typed). Record {id: q.id, answer: ""}. Treat "Other" without notes as the empty string "".

For each free-form follow-up reply (open question): record {id: q.id, answer: ""}.

Per-answer mapping (fallback path). Parse the user's free-form reply as before — "1=a 2=b 3=text" style or natural-language picks among the offered options.

Decide the next move based on the assembled answers:

  • Abort (user says "stop", "no", "cancel" anywhere in the gate, including via an Other+notes answer): write /aborted.json with the user's reason, stop.
  • Material change to load-bearing facts. Trigger when any answer contains free-form content with at least one of these signals: a path/to/file slash-bearing token, a file:line reference, the literal word actually, a four-or-more-digit line number adjacent to a file token, or explicit contradiction of a brief fact (e.g. "the migration target is X, not Y"). Re-spawn Phase A with resume_from: and gate_answers: {questions: [{id, answer}, ...]}. Phase A will patch brief.md / ground_truth.json and re-emit a (possibly empty) gate. Iterate until gate == null or user aborts. Apply the same heuristics to fallback-path replies.
  • Simple resolution (no abort signal, no material-change signal): write /gate_answers.json as {questions: [{id, answer}]} directly, proceed to Phase B.

If the answer set is ambiguous (e.g. Other+notes that could be either a material change or just a reworded pick), ask one short clarifying question — do not guess.

Phase B — dispatch, synth, render (round-trip loop, v0.7.0)

Phase B coordinator under v0.7.0 returns one of complete | dispatch | continue | error per spawn. Main session runs a loop: spawn coordinator → if dispatch, execute the requested Agent calls and write results to save_to paths → re-spawn coordinator with resume_token set; if continue, re-spawn immediately with response.resume_token (no Agent calls). Loop terminates on complete or error. Worst case: 6 coordinator spawns (initial + step-7 dispatch + step-7-validate + step-7.5 dispatch + step-8 dispatch + step-8.5 dispatch). Common case: 4–5. See docs/specs/2026-05-03-phase-b-main-driven-dispatch.md.

The loop matters because empirically Agent is not delivered to subagent contexts in current CC builds — only the main session can issue Agent calls. The coordinator is now a state machine over workspace files; main is the executor.

1. Initial spawn:

Agent(
  subagent_type: general-purpose,
  description: "Preflight phase B coordinator",
  prompt: 
         + "\n\n## Invocation inputs\n\n"
         + JSON.stringify({
             workspace_path: ,
             gate_answers_path: /gate_answers.json"> | null,
             user_language: ,
             cost_profile: ,
             resume_token: null
           })
         + "\n\nReturn ONLY the JSON handoff specified in the output section. No prose."
)

Coordinator model: sonnet floor — even though the coordinator is "just" state inspection + dispatch construction, picking the right model_hint per request and the right subagent_type per step is a judgment call that haiku gets wrong often enough to matter (verified empirically — the dispatch payloads it builds are the load-bearing input to every expert that follows). Upgrade to opus only if experiments show coordinator quality issues. Expert / synthesizer / verifier model choice is per-request via request.model_hint, set by the coordinator at dispatch construction time — pass it as Agent's model parameter when executing the dispatch.

2. Loop on response.action:

Parse the return against schemas/phase-handoff.json#/definitions/phase_b_output. On parse failure, retry the spawn once with a terser prompt; second parse failure → stop and surface to user.

  • "complete" — terminal success. Emit report (or Read report_path and emit if report_too_long: true). Append warnings:
  • if skipped_experts non-empty: "⚠ skipped experts: (reports failed twice)"
  • if drift_refreshed: true: "ground_truth refreshed at synth time — repo HEAD moved during review"

Spawn Phase C. Done.

  • "error" — terminal failure. Read error_path, print contents verbatim, stop. Do NOT spawn Phase C.
  • "continue" — coordinator completed a write-only step and needs a fresh context window. No Agent calls. Re-spawn coordinator immediately with response.resume_token (step 4). No dispatch[] entries written for this round-trip.
  • "dispatch" — execute the dispatch (step 3 below), then re-spawn coordinator (step 4).

3. Executing a dispatch:

Read response.dispatch.requests[]. If parallelism == "parallel", send a single message containing N Agent tool calls — one per request (parallel execution). If parallelism == "sequential", send them one at a time.

For each request:

  • Append a dispatch[] entry (see "Timing instrumentation" above) with started_at set to the ISO-8601 UTC moment immediately before the call, and status: "ok" provisionally. Carry id, subagent_type, model, step from the request; attempt: 1 (or attempt: on a retry — append, do not overwrite).
  • Spawn Agent(subagent_type=request.subagent_type, model=request.model_hint, description=request.description, prompt=request.prompt).
  • On success: Write the response body to request.save_to verbatim. Update the dispatch[] entry with completed_at and duration_ms.
  • On failure (timeout, malformed JSON the agent itself rejected, exception):
  • If request.on_failure == "mark_skipped": update the dispatch[] entry with status: "skipped", reason: "", completed_at, duration_ms, and continue.
  • If request.on_failure == "abort": update the dispatch[] entry with status: "error", reason, completed_at, duration_ms. Then stop the loop, surface error to the user, do NOT spawn Phase C.

Do NOT modify request.prompt — coordinator built it deliberately. Main is a dumb executor.

**4. Re-spawn coordin

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.