bob asks
bob asks
Section titled “bob asks”bob asks lists every open ask of the operator across the portfolio. It is a
projection: the GitHub issues are the store, and the only thing the command
writes is the ask-waiting label, under --reconcile --apply. bob asks recheck --apply is the other write: it closes or comments on a to-do nobody
has touched (Re-checking). bob asks done
and bob asks snooze answer one ask (Answering an ask). Engine: scripts/asks/asks.js. Task guide:
See Everything Waiting on You.
bob asks [--json] [--all] [--reconcile [--apply]]bob asks recheck [--json] [--apply]bob asks done <owner/repo#N> [--json]bob asks snooze <owner/repo#N> --until <YYYY-MM-DD> [--json]bob asks verify [--apply] [--json]What an ask is
Section titled “What an ask is”| Kind | Listed when | Never listed when |
|---|---|---|
todo |
the issue is open, carries todo and does not carry completed |
it carries completed, or it is closed |
brief |
the issue is open, carries brief and is in brief:new or brief:acting |
it is in brief:done or brief:parked, or it is closed |
The open brief states are the non-terminal states in
scripts/brief/states.json.
Which projects are read
Section titled “Which projects are read”Every project in the operator repo’s projects.json (#3432) that is not "active": false, is
checked out on this machine and has a GitHub origin. Two registrations of one
repo are read once. An active project that is not checked out here, or has no
GitHub origin, cannot be read; --json names it under projects.not_here.
Each repo costs two gh issue list calls, one per kind. Six repos are read at
a time. Every call waits for GitHub quota instead of failing on a limit. A
repo with an ask that names what it gates costs two more
(What an ask unblocks).
What an ask unblocks
Section titled “What an ask unblocks”The operator’s rule for what comes first is an ask that holds up work BoB could
be doing (#3915). Every ask
carries unblocks, and the list is ordered by it. Engine:
scripts/asks/unblocks.js.
| Tier | The ask gates |
|---|---|
running |
an unticked item of an open flight plan that has a live run |
ready |
an unticked item of an open flight plan with no live run |
approved |
an open approved issue that is on no flight plan |
unknown |
not known: the forge could not be read. reason carries what it said. It is never reported as none |
none |
nothing that is open |
| Rule | Value |
|---|---|
| The gate links | the Blocked by #NN and Gates #NN lines of the ask’s body, and the gates an open flight plan seats beside the ask’s to-do row |
| A live run | a warp-drive run whose kickoff names the plan: in this machine’s checkout of the project, or in another machine’s fleet snapshot less than 24 hours old |
items |
how many open items the ask gates: unticked plan items and approved issues on no plan. null when the tier is unknown |
plan |
the flight plan’s number for running and ready; the plan holding the most gated items when there are several. Otherwise null |
summary |
one sentence, for example Unblocks 1 item in the running alpha plan. |
| The forge reads | the repo’s open flight plans and its open approved issues, read only when one of the repo’s asks carries a gate link. When either read fails, every ask of that repo with a gate link is unknown; the project is still listed |
The asks are ordered by tier (running, ready, approved, unknown,
none), then items descending, then priority (p1-critical first, no
priority last), then oldest first.
The attention budget
Section titled “The attention budget”The list is capped, so BoB has to choose what is worth the operator’s attention (#3805).
| Lever | Default | Caps | Set it in |
|---|---|---|---|
_workflow.ask_budget |
5 | the asks shown for one project | the project’s manifest or its .claude/settings.local.json |
_workflow.ask_budget_portfolio |
15 | the asks shown across every project | provisions/_default.json; $BOB_HOME/.claude/settings.local.json overrides it on one machine |
The asks are put in most-blocking order and taken from the top.
An ask is inside the budget while its project has room and the portfolio has
room. Every other ask waits behind it: it is counted, not listed. --all
lists the waiting asks under the ones inside. A budget of 0 is no cap.
bob warp config shows both levers and which layer set each
(Workflow configuration).
Sittings
Section titled “Sittings”A sitting is every ask the operator can clear in one place
(#3806). The asks inside
the budget are grouped by the place each one states on its Place: line.
| Rule | Value |
|---|---|
| One sitting per | place, compared in lower case |
| A sitting’s minutes | the sum of its asks’ Minutes: |
| Order inside a sitting | decide, then do, then check; within one kind, the list’s order |
| Order of the sittings | by each sitting’s first-listed ask, so the first sitting holds the most-blocking ask |
| An ask with no minutes or no place | in no sitting; listed under unestimated, never dropped |
An ask filed before the admission gate (the ask fields) states neither, and a brief states neither, so both are unestimated. An ask waiting behind the budget is in no sitting.
Asks that wait: ask-waiting
Section titled “Asks that wait: ask-waiting”An ask beyond the budget carries the label ask-waiting. While it carries
it, BoB does not interrupt the operator for it:
| Surface | A waiting ask |
|---|---|
| Phone push | Not sent. Its handoff record is stored waiting: true (the handoff record). |
| bob viz and warp viz banners | Not shown. |
bob asks |
Counted as waiting; listed only with --all. |
Two things set the label.
bob asks --reconcilecompares the label with the budget across the portfolio. It reports each ask beyond the budget that lacks the label and each ask inside it that still carries it. With--applyit adds or removes the label, onegh issue editper ask. For each to-do it releases, it sweeps that project’s handoff records, which sends the push that was held back.bob tickruns it on every cycle.ask-gate.js filedecides at filing. The new ask is the newest of its priority. If the project’s budget is already full of asks that rank above it, it is filed withask-waiting. Only the project budget is applied here, because one project’s read cannot apply the portfolio budget; the next reconcile does.
--json gains reconcile: applied, held[] and released[] (each
project, repo, number, kind, url, changed, error) and swept[]
(project and the handoff keys it released). Each row of asks[] and
waiting[] carries ask_waiting: whether the issue has the label now.
Re-checking: bob asks recheck
Section titled “Re-checking: bob asks recheck”bob asks recheck finds the to-dos nobody has touched for a long time and
says whether each still matters
(#3808). Engine:
scripts/asks/recheck.js.
| Rule | Value |
|---|---|
| What is re-checked | an open todo without completed, in a project bob asks reads. A brief is not: its states belong to bob brief --reconcile |
| Untouched | days since the issue’s updatedAt. A comment, a label change or an edit touches it |
| The window | _workflow.ask_recheck_days, default 30, set per project. A to-do is re-checked when it has gone untouched for longer than the window. 0 never re-checks that project |
| The issues it names | every #NN and owner/repo#NN in its body, except its own number |
| Verdict | When |
|---|---|
moot |
it names at least one issue, and every issue it names is closed (a pull request: closed or merged) |
needed |
an issue it names is open or cannot be found; or it names no issue; or its body carries the operator’s <!-- not-stale: … --> marker |
unchecked |
the issues it names could not be read. It is never treated as moot |
The named issues of every to-do are read together, in as few
gh api graphql calls as they allow.
bob asks recheck: 3 to-dos untouched for longer than 30 days — 1 moot, 2 still neededmoot 45d alpha #11 TODO: approve the old DNS change https://github.com/acme/alpha/issues/11 — every issue it names is closed: #5, acme/beta#6needed 45d alpha #12 TODO: rotate the deploy token https://github.com/acme/alpha/issues/12 — open: #7needed 40d alpha #13 TODO: pick a name https://github.com/acme/alpha/issues/13 — it names no issue, so nothing says it is mootreport only — pass --apply to close the moot to-dos and record the re-check on the restOne line per to-do: verdict, days untouched, project, number and title, link, and the reason for the verdict.
Without --apply nothing is written. With it:
| Verdict | What --apply writes |
|---|---|
moot |
The to-do is closed as not planned, with a comment naming the closed issues that made it moot. The completed label is not added, so nothing is resumed (the to-do lifecycle). Its line ends — closed |
needed |
One comment: the re-check date and why it is still needed. The comment touches the issue, so the to-do is not re-checked again until it has gone a full window untouched. Its line ends — re-check recorded |
unchecked |
Nothing |
A write the forge refuses is reported on the to-do’s line as ERROR with the
reason, and in asks[].error. That to-do is re-checked again on the next run.
--json prints one bob-asks-recheck/1 document.
| Key | Value |
|---|---|
schema |
bob-asks-recheck/1 |
generated_at |
ISO 8601 time the days are measured from |
verdict |
ok · partial a project or the named issues could not be read · unread no project could be read |
applied |
whether --apply was passed |
window_days |
{ "<project>": <days or null> } for every project that was read |
count, moot, needed, unchecked |
the number of rows in asks, and of each verdict |
asks[] |
project, repo, number, title, priority, url, untouched_days, window_days, verdict, why, closed[] and open[] (the named issues in each state), action (close, comment or null), changed, error |
projects |
as in bob-asks/1 |
error |
null, or { code: "forge", state, message } |
Exit codes are those of bob asks.
bob tick runs bob asks recheck --json on every cycle, without --apply,
and prints one line:
bob tick: asks recheck → 3 untouched past the window: 1 moot, 2 still needed, 0 unchecked — bob asks recheck --apply withdraws the mootSources: bob asks sources
Section titled “Sources: bob asks sources”bob asks sources says which filing path files the asks that turn out not to
matter (#3810). Engine:
scripts/asks/sources.js.
Every ask the admission gate files carries a marker naming its filing path:
<!-- ask:source <name> -->
(the todo format contract). The
command reads every todo filed in the window, open and closed, in each
project bob asks reads, and counts them per source. Nothing is written.
| Rule | Value |
|---|---|
| The window | to-dos created in the last 90 days. --days <n> sets another |
| The source | the name in the to-do’s marker. A to-do with no marker is counted under unrecorded: it was filed before the marker, or not through the gate |
| Done | the to-do carries completed |
| Moot | the to-do is closed without completed: it did not need the operator |
| Open | the to-do is open and does not carry completed |
| Median to done | the median days from filed to closed, over the done to-dos. - when none is done |
bob asks sources: 10 asks filed in the last 90 days by 3 sources (2 projects read)SOURCE FILED DONE MOOT OPEN MOOT% MEDIAN TO DONEhost-preflight 5 1 3 1 60% ! 2dsession 4 3 0 1 0% 6dunrecorded 1 0 1 0 100% -moot: closed without `completed` — the ask did not need the operatorabove the 30% moot threshold (!): host-preflight — bob asks sources --file-bugs lists the bug for eachThe moot threshold
Section titled “The moot threshold”A source is above the threshold when its moot share (closed without
completed ÷ filed) is greater than _workflow.ask_moot_threshold: default
0.3, a share from 0 to 1, resolved at the fleet layer. A source at the
threshold is not above it, and 1 marks none. unrecorded names no filing
path, so it is never marked.
A source above the threshold is reported in three places:
| Where | What |
|---|---|
bob asks sources |
! beside its share, and a line naming every such source |
bob brief |
one needs_you item of kind ask-source per source, naming its counts (briefing.md) |
bob asks sources --file-bugs |
the bug for each source |
Filing the bugs: –file-bugs
Section titled “Filing the bugs: –file-bugs”bob asks sources --file-bugs lists the bug each source above the threshold
should have. Without --apply nothing is written. With --apply it files one
bug per source in the BoB tracker, the repo of the BoB source checkout.
| Line | Meaning |
|---|---|
bug: <source> — would file in <repo> |
no open bug carries the source’s marker; --apply files it |
bug: <source> — filed: <url> |
--apply filed it |
bug: <source> — already open: <url> |
an open bug carries <!-- ask-source-bug: <source> -->; none is filed |
bugs not filed: … |
the open bugs could not be read, or the BoB tracker could not be resolved. Nothing is filed |
The bug’s title is Ask source <source> files asks that turn out not to matter. Its body names the counts: filed, done, closed without completed,
open, the median days to done, the share and the threshold.
--json prints one bob-asks-sources/1 document.
| Key | Value |
|---|---|
schema |
bob-asks-sources/1 |
generated_at |
ISO 8601 time the window is measured back from |
verdict |
ok · partial a project could not be read · unread no project could be read |
window |
{ days, since } |
threshold |
the moot threshold, or null when none is set |
above[] |
the names of the sources above it |
totals |
filed, done, closed_without_completed, open over every source |
sources[] |
source, filed, done, closed_without_completed, open, median_days_to_done (null when none is done), moot_rate (closed_without_completed ÷ filed), above, projects[]. Most filed first |
bugs |
with --file-bugs only: applied, repo, rows[] (source, action file or exists, number, url, changed, error) and error |
projects |
as in bob-asks/1 |
error |
null, or { code: "forge", state, message } |
Exit codes are those of bob asks.
The lifecycle: bob asks draft, transition, status
Section titled “The lifecycle: bob asks draft, transition, status”An ask moves through one declared machine, scripts/asks/machine.json (#3811).
It runs on the shared machine engine (scripts/warp-drive/machine-engine.js,
#2975), so a change of state is a checked event and not a label edit.
| Phase | What it means | Events it allows |
|---|---|---|
drafted |
A draft nobody has checked yet. | admitted |
admitted |
The admission gate passed the draft. | queued, presented |
queued |
Filed, and waiting behind the attention budget. | presented, done, declined |
presented |
In front of the operator: listed by bob asks. |
queued, done, declined, snoozed |
snoozed |
Put off by the operator. It comes back. | presented, declined |
done |
The operator did it. | consumed, presented |
declined |
Closed without being done. | presented |
consumed |
Done, and the work it gated has resumed. | none |
An event is named for the phase it reaches. Each ask has one state file,
.claude/asks/<id>.json, in the project checkout. The managed ignore block
lists the directory.
bob asks draft renew-token --title "TODO: renew the deploy token" --label todo,p2-high --body-file draft.mdbob asks transition renew-token admittedbob asks status renew-token --jsondraft <id>records a draft atdrafted.--body-file -reads the body from stdin.--kindistodo(the default) orbrief. An id is lower-case letters, digits and dashes. A second draft under the same id is refused.transition <id> <event>fires one event. An event the ask’s phase does not allow exits 1 and names the events it does allow. The state file is not written.--number <n>records the issue the ask was filed as.status <id>prints the phase and the events it allows.
admitted is the admission gate’s to give. The
gate reads the draft first. When it refuses, the command exits 3, the phase
stays drafted, and the refusal is recorded on the ask:
$ bob asks transition renew-token admittedbob asks transition: refused by the ask gate (#3803): minutes: …; tried: …$ bob asks status renew-tokenask renew-token: drafted — TODO: renew the deploy token refused by the ask-gate: missing minutes, tried minutes: … tried: … next: admitteddone on a to-do is the completed done-signal. The command adds
completed to the ask’s issue first, and the phase moves only when the forge
took the label. A forge that refuses it exits 4 and the phase stays where it
was. An ask with no recorded issue number is refused: record it with
--number.
consumed is fired on evidence. A bare transition <id> consumed is refused.
todo-consume.js consume --apply fires it, and only after it has removed
blocked from every open issue the to-do named and closed the to-do. When
one unblock fails, the ask stays at done.
An ask filed before the machine existed has no state file. When the id is an
issue number, transition adopts it first: it reads the issue and starts the
record at the phase the labels say.
| The issue | Phase |
|---|---|
| open to-do | presented |
open to-do with ask-waiting |
queued |
open to-do with completed |
done |
closed to-do with completed |
consumed |
closed to-do without completed |
declined |
| brief | the phase of its state label in scripts/brief/states.json |
todo-consume.js adopts a completed to-do the same way, at done, before it
fires consumed. An issue that cannot be read exits 4 and records nothing.
The brief states map onto the same phases: brief:new is queued,
brief:acting is presented, brief:done is done and brief:parked is
declined. make check-brief-labels fails when a state names no declared
phase, or a brief transition has no event in the machine.
--json on any of the three prints one bob-ask/1 document: id, kind,
phase, terminal, allowed_events, evidence_events, title, repo,
number, refusal ({gate, missing, problems, at}, or null) and history.
make check-ask-machine (part of make check) fails when the definition
lacks one of the eight phases, or names a transition to a phase it does not
declare.
Answering an ask: bob asks done and snooze
Section titled “Answering an ask: bob asks done and snooze”These two answer one ask from a shell (#3807). The asks page gives the same
two answers through the brief-tick Worker (apps/brief-tick), in every repo
the Worker lists. Engine: scripts/asks/answer.js.
| Command | What it writes |
|---|---|
bob asks done acme/beta#7 |
A to-do gets the completed label: the done-signal todo-consume.js acts on. A brief moves to brief:done, from a state scripts/brief/states.json lets reach it. An issue that is neither a to-do nor a brief, a closed one, or a brief:parked brief is refused with exit 1 |
bob asks snooze acme/beta#7 --until 2026-10-20 |
One <!-- ask:snooze-until 2026-10-20 --> marker in the issue body. A second snooze replaces the date. The date must be after today |
The same answer twice writes nothing. An ask whose marker is dated after
today is not listed: the text output ends with
snoozed: 1 ask hidden until their date — the soonest back 2026-10-20, and
--json names each one under snoozed. From that date on, the ask is listed
again. The marker the Worker writes and the one this command writes are the
same, so a snooze from the page and one from a shell look the same to
bob asks.
--json prints one bob-ask-answer/1 document: verdict (answered,
refused, forge or error), answer (done / snooze), repo, number, kind
(todo / brief), changed, until, from and to (the label states),
and error.
Done-checks: bob asks verify
Section titled “Done-checks: bob asks verify”A to-do may declare a machine-runnable Done-check:
(the field). bob asks runs it
before listing the to-do (#3986). When the check passes, the to-do gets
completed and is closed with a comment naming the evidence, so it is never
listed, on BoB Control’s Now page either, since Control reads bob asks --json.
When it fails with a reason the body does not already record, the reason is
written into the body’s ## Latest check section first. --json counts the
results under done_checks. The briefing runs the same check on the to-dos it
lists.
bob asks verify runs every open to-do’s done-check across the portfolio.
Without --apply it only reports. With --apply it closes the to-dos that
pass, records each new failure reason, and re-runs a failed CI run once per
run id. That is what bob tick runs. --json prints one bob-asks-verify/1
document with these keys:
verdict,applied,checkedclosed[],passing[],pending[],unknown[],recorded[]:owner/repo#Nkeysfailing[],errors[]: each{key, reason}gaps[]
bob asks verify: 3 to-dos with a done-check — 1 closed, 1 failing, 1 pending, 0 unknownclosed: acme/site#63failing: acme/api#12 — deploy.yml failure: Error: Missing permission Workers Scripts: Edit (https://github.com/acme/api/actions/runs/9)Results are cached in $BOB_HOME/.ask-done-checks.json for
_workflow.ask_done_check_ttl_minutes.
Text output
Section titled “Text output”bob asks: 5 open asks in 2 projects (2 read)sitting: phone — 17 min, 2 asksp3-medium 9d beta todo do 15m #8 TODO: run the migration https://github.com/acme/beta/issues/8 — Unblocks 1 item in the running beta plan.p1-critical 2d beta todo decide 2m #7 TODO: approve the DNS change https://github.com/acme/beta/issues/7sitting: farm-01 — 10 min, 1 askp2-high 12d alpha todo do 10m #11 TODO: rotate the deploy token https://github.com/acme/alpha/issues/11not yet estimated: 2 asks with no minutes or place statedp2-high 20d beta brief #31 Brief: mobile area https://github.com/acme/beta/issues/31- 3d alpha todo #14 TODO: pick a name https://github.com/acme/alpha/issues/14gap: gamma (acme/gamma) not read — forge rate-limited: API rate limit exceeded| Line | Shape |
|---|---|
sitting: |
the place, the sitting’s total minutes and its number of asks; one per sitting, in sitting order |
| An ask in a sitting | priority, age in days, project, kind, what it asks for (decide / do / check), its minutes, number and title, link. When its tier is not none the line ends with — and its summary |
not yet estimated: |
heads the last group: the asks that state no minutes or no place; printed only when there is one |
| An ask not yet estimated | priority, age in days, project, kind, number and title, link, and the same ending; in the list’s order |
gap: |
one per project that could not be read |
When asks wait behind the budget, the first line ends
— 2 more waiting behind the budget, and a line under the list reports them:
waiting: 2 more asks behind the budget (5 per project, 15 for the portfolio) — bob asks --all lists themThe bob-asks/1 document
Section titled “The bob-asks/1 document”--json prints one document.
| Key | Value |
|---|---|
schema |
bob-asks/1 |
generated_at |
ISO 8601 time the ages are measured from |
verdict |
ok every project was read · partial at least one gap · unread no project could be read |
count |
number of rows in asks: the asks inside the budget |
open |
every open ask that was read, inside the budget or waiting |
by_project |
{ "<project>": <count> }, the open asks of every project that was read, zero included; an unread project has no entry |
asks[] |
kind (todo / brief), project, repo (owner/repo), number, title, priority (a p1–p4 label or null), state (the brief state, null for a todo), created_at, age_days, ask_kind (the body’s Kind: line: decide, do or check, else null), minutes (the whole number on the body’s Minutes: line, else null), place (the body’s Place: line in lower case: a fleet machine id or phone, else null), url, unblocks (tier, items, plan, summary, reason: see What an ask unblocks) |
sittings[] |
one per place, over the rows of asks: place (string), minutes (integer, the sum of its asks’ minutes), asks (an array of owner/repo#N keys). See Sittings |
unestimated[] |
the owner/repo#N keys of the rows of asks that state no minutes or no place; they are in no sitting |
waiting[] |
the asks behind the budget, in the same order and with the same keys as asks[] |
done_checks |
the to-dos whose done-check ran before listing: checked, closed[] (closed and left out of every list), passing[], failing[] ({key, reason}), pending[], unknown[], recorded[], errors[] |
snoozed[] |
the open asks snoozed past today, soonest back first: kind, project, repo, number, until, url. They are in no other list |
budget |
portfolio (the cap, or null for none), per_project { "<project>": <cap or null> }, waiting (the number of rows in waiting) |
projects.total |
number of repos tried |
projects.read[] |
names of the projects that were read |
projects.gaps[] |
project, repo, state, reason for each project that could not be read |
projects.not_here[] |
active projects not checked out on this machine, or with no GitHub origin |
error |
null, or { code: "forge", state, message } when the verdict is unread |
A gap’s state is the forge state of the failed call: rate-limited,
unauthenticated, timeout, gh-missing, unavailable, or invalid when
gh answered with something that is not a JSON list. If either of a repo’s two
calls fails, the whole project is a gap and contributes no rows.
Exit codes
Section titled “Exit codes”| Code | Meaning |
|---|---|
| 0 | listed; a project that could not be read is named as a gap |
| 1 | transition: the event is not one the ask’s phase allows. draft: the ask already exists. done / snooze: the issue is neither a to-do nor a brief, is closed, or its brief state cannot reach brief:done. Nothing changed |
| 2 | usage error, or no readable projects.json. transition and status: no such ask |
| 3 | no project could be read: every forge read failed. transition <id> admitted: the admission gate refused the draft. done / snooze: the issue could not be read, or the forge did not take the write |
| 4 | transition: the forge did not take the completed label, or the issue to adopt could not be read. The phase did not move |
tests/test-bob-asks.js drives bin/bob asks against a scratch BOB_HOME,
scratch git repos and a stub gh on PATH. tests/test-bob-asks-unblocks.js
does the same over to-dos that gate a running plan, a ready plan, an approved
issue and nothing, and one whose plans cannot be read. tests/test-asks-recheck.js
drives bin/bob asks recheck the same way, and tests/test-asks-sources.js
drives bin/bob asks sources. tests/test-asks-lifecycle.js drives
bin/bob asks draft, transition and status in a scratch checkout, and
check-ask-machine.js over definitions that lack a phase. It also runs
todo-consume.js consume --apply and check-brief-labels.js there.
tests/test-bob-asks-answer.js drives bin/bob asks done and snooze
against a stub gh that keeps its issues in one file, then lists them with
bin/bob asks. It also checks that the Worker’s marker hides an ask. tests/test-brief-tick.js covers the Worker’s repo list, Done and Snooze.