plugins/ainb-fleet/skills/fleet-needs/SKILL.md
Workflow-backed Jarvis control panel. Runs the deterministic `hangar` workflow with verb=needs (discover → enrich → prioritize), renders the Jarvis HUD from its render-ready cards, fires AskUserQuestion per blocked session, and routes each answer back via tmux send-keys (broker fallback only). Requires the workflow gate (CLAUDE_CODE_WORKFLOWS=1). If the gate is off, fall back to the prompt-driven `/ainb-fleet:needs` skill.
npx skillsauth add stevengonsalvez/agents-in-a-box ainb-fleet:fleet-needsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
The session "face" of the sensor-fusion hybrid. The deterministic brain is the
hangar workflow (verb=needs); this skill renders its output and handles the
irreducibly-interactive last mile (HUD + AskUserQuestion + routing).
SESSION (this skill) ──Workflow({name:'ainb-fleet:hangar', args:{verb:'needs'}})──▶ brain
render HUD ◀──{banner,cards,asks}──────────────────────────────────────────┘
AskUserQuestion per ask ──answer──▶ tmux send-keys (write leg)
| Direction | Channel | Why |
|-----------|---------|-----|
| Reads | JSONL (source of truth) — ainb fleet needs/standup tails JSONL + pane | content already committed; replayable; ground truth |
| Writes | tmux send-keys — direct keystrokes to the target pane (verify with capture-pane) | deterministic, no broker latency or delivery gap |
| Fallback writes | peers/broker via ainb fleet broadcast | only when no tmux_session known; broker has a known delivery gap |
All write routing below uses tmux first. Broker is a last resort, not the default.
[ -n "$CLAUDE_CODE_WORKFLOWS" ] && echo "gate on" || echo "gate off"
If gate off → stop and invoke /ainb-fleet:needs (the prompt-skill).
The workflow path only works when CLAUDE_CODE_WORKFLOWS=1 is set.
hangar is the single multi-verb workflow under ainb-fleet. The needs
verb runs the Discover ▸ Enrich ▸ Prioritize chain and returns the render-ready
panel data. Other verbs (standup, sequence) are wired into the same workflow.
Workflow({ name: 'ainb-fleet:hangar', args: { verb: 'needs' } })
It runs in background; wait for completion. The result is render-ready:
{ "banner": { "need", "err", "ask", "idle", "wait", "top": {session,kind} },
"cards": [ { "emoji", "kind", "session", "line", "enriched", "options?" } ],
"asks": [ { "question", "header", "options[{label,description}]",
"multiSelect", "suggestion", "route": {target,hint} } ] }
If the result has a non-empty error field, the fleet read genuinely failed
(e.g. an API throttle that persisted across the workflow's retries) — this is
NOT an empty fleet. Render the error, do not show "0 NEED YOU":
╔════════════════════════════════════╗
║ ⚠ FLEET READ FAILED ║
║ <result.error> ║
║ retry in a moment ║
╚════════════════════════════════════╝
Then stop (offer to re-run). Only proceed to Step 2 when error is absent.
From banner + cards, render this exact layout in chat:
╔════════════════════════════════════╗
║ ⚡ FLEET STATUS · N NEED YOU ⚡ ║
║ 🔴 X err 🟡 Y ask ⚪ Z idle ║
║ top: <banner.top.session> (<KIND>) ║
╚════════════════════════════════════╝
▸ <emoji> <session> ─ <line>
[suggest: <enriched>]
① <option> ② <option> ③ <option> (ASK only)
Rules:
▸ card per cards[] entry, in order (already priority-sorted).[suggest: …] line only when enriched is non-null.options as ① ② ③ ④ ⑤.+ N more.asks[] are AskUserQuestion-ready. The tool caps at 4 questions per call —
batch in groups of 4 (highest-priority first, asks[] is already sorted):
for batch of up to 4 asks:
AskUserQuestion({ questions: batch.map(a => ({
question: a.question, header: a.header,
options: a.options, multiSelect: a.multiSelect })) })
When suggestion is set, mention it ("recommended: <suggestion>") so Stevie
can one-tap the drafted choice.
Two routes, picked by ask kind:
The target session already has its own AskUserQuestion picker open and is
waiting for Down/Enter. Sending the answer as TEXT would land as a fresh
user message the target then has to re-interpret. Worse, the broker-delivery
path has a known gap. So for ASK answers, press the picker keys directly via
tmux send-keys — the same UI keystrokes the human would use:
# idx = 0-based index of the option Stevie picked in asks[].options
# target = the ask's route.target (the target's tmux_session)
for _ in $(seq 1 "$idx"); do tmux send-keys -t "$target" Down; done
tmux send-keys -t "$target" Enter
The picker explicitly advertises Enter to select · Tab/Arrow keys to navigate
in its footer — that's the contract.
Caveats:
route.hint == 'broker' doesn't help — the underlying race
is the same.context.options[] is returned verbatim).No picker on the target; the answer is a normal prompt. Per the read/write principle, prefer tmux directly — it lands reliably and is verifiable via capture-pane. Broker is fallback only.
# default: write via tmux, verify via capture-pane
tmux send-keys -t "$target" -l "<answer text>"
tmux send-keys -t "$target" Enter
# verify it landed in the pane (the matching read)
tmux capture-pane -t "$target" -p -S -40 | grep -F "<answer text>" && echo "✓"
Only when tmux_session is unknown (rare — bg jobs, dead tmux), fall back to:
ainb fleet broadcast "<answer>" --filter "<route.target>" # broker — last resort
# the read leg that matches the write
tmux capture-pane -t "$target" -p -S -50 | grep -F "<answer>"
# slower confirmation: target's JSONL transcript records the user message
ls -t ~/.claude/projects/<cwd-slug>/*.jsonl | head -1 | xargs grep -F "<answer>"
asks[] shape.unknown session targets — sessions ainb can't name (bg jobs, dead tmux)
surface with route.target: "unknown"; those can't auto-route — tell Stevie.documentation
Report reflect drain spend over a time window — tokens split by cached (cache_read), uncached writes (cache_creation), and io (input+output), with a $ estimate, grouped by day / outcome / model / transcript. Reads the drainer's cost log and surfaces outlier runs and cache-reuse health (the 41.5M-token failure mode = low cache reuse + high cache writes). Use to answer "what is reflection costing me" for the last day / week.
development
Show fleet status — every claude session running on the host, merged across ainb + claude-peers broker + background jobs. Use when you need to enumerate sessions before composing an action, see which sessions have a peer registered (broker-routable) vs tmux-only, check the `summary` of each session, or pipe the list into jq for filtering. Default output: text table. Pass --format json for LLM consumption.
testing
Ordered multi-step prompts to fleet targets, ack-gated between steps via JSONL assistant-turn-end detection. Use for cycles like disconnect→reconnect→verify, or any flow where step N+1 requires step N to have completed first. The skill BLOCKS until each target's transcript shows the next assistant turn finishing OR per-step timeout fires (default 300s).
development
Center control panel — enumerate every claude session that is blocked waiting on something: a user answer (AskUserQuestion fired), an API error retry, an idle assistant turn-end with no follow-up, or an explicit WAITING: marker. Returns rich JSON with signal kind + context per session. Use this when you've stepped away from the fleet and want one place to see everything that wants your attention and answer it.