- CHANGELOG.md: fix five literal \' heredoc-escape artifacts in the 0.43.0.0 entry (wording only, content preserved) - docs/progress-events.md: add `gbrain eval brainbench` to the streaming commands list + the `eval.brainbench` phase (no `total` on its ticks) - docs/TESTING.md: inventory entries for the 7 BrainBench unit suites + the CLI e2e; CI section now names the dedicated `brainbench` gate job - README.md: `gbrain eval brainbench` joins the Eval framework story, linking docs/eval/BRAINBENCH.md - docs/architecture/KEY_FILES.md + RETRIEVAL.md: disambiguate the sibling-repo retrieval BrainBench from the in-repo memory conformance suite (per the naming note in docs/eval/BRAINBENCH.md) - llms-full.txt: regenerated (README is inlined in the bundle) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
6.9 KiB
Progress events
Canonical reference for the JSONL progress stream that gbrain writes to
stderr when a bulk command runs with --progress-json. Stable from
v0.15.2. Additive changes only; no renames or removals without a major
version bump.
Most humans won't read this page. Agents parsing progress will.
When do I get these events?
Any of these commands stream events when --progress-json is set:
gbrain doctor(DB checks, JSONB integrity, markdown body completeness, integrity sample)gbrain orphansgbrain embedgbrain files syncgbrain exportgbrain extract [links|timeline|all](fs or db source)gbrain importgbrain syncgbrain migrate --to …gbrain repair-jsonbgbrain check-backlinksgbrain lintgbrain integrity autogbrain evalgbrain eval brainbenchgbrain apply-migrations(the orchestrator + every child command)
Non-bulk commands (stats, graph-query, get, put, etc.) don't emit
events — they return in under a second.
Channel
- Progress events:
stderr, one JSON object per line,\n-terminated. - Data results (
--jsonpayloads from each command):stdout. - Final human summaries:
stdout.
Agents can safely capture stdout for their result parsing and read stderr separately for progress.
Flags
| Flag | Behavior |
|---|---|
| (none) | Auto. TTY: \r-rewriting single line. Non-TTY: plain line-per-event on stderr. |
--progress-json |
Force JSON-lines mode on stderr (this doc). |
--quiet |
Suppress progress entirely. Warnings and final output still print. |
--progress-interval=<ms> |
Override the minimum interval between tick emits (default 1000). |
Global flags: parsed by src/core/cli-options.ts before command dispatch,
so gbrain --progress-json doctor works the same as
gbrain doctor --progress-json (the latter also works — per-command
parsers see the flag via the shared CliOptions singleton).
Event types
Every event is a single-line JSON object with these common fields:
| Field | Type | Notes |
|---|---|---|
event |
string | One of: start, tick, heartbeat, finish, abort. |
phase |
string | Machine-stable snake_case, dot-separated. See "Phase names" below. |
ts |
ISO 8601 UTC string | Event emission time. |
elapsed_ms |
number | Ms since the phase started. Present on tick/heartbeat/finish/abort. |
start
Emitted when a phase begins.
{"event":"start","phase":"doctor.db_checks","ts":"2026-04-20T12:34:56.789Z"}
{"event":"start","phase":"import.files","total":52000,"ts":"2026-04-20T12:34:56.789Z"}
Optional fields:
total— the total item count if known at start.
tick
Emitted periodically during iteration. Time- and item-gated: the reporter
won't emit more often than minIntervalMs (default 1000) and
minItems (default max(10, ceil(total/100))).
{"event":"tick","phase":"orphans.scan","done":15000,"total":52000,"pct":28.8,"elapsed_ms":4200,"eta_ms":10300,"ts":"..."}
Fields:
done— items completed in this phase.total— total items, if known. Omitted when the scan doesn't have a total up front (e.g. a streaming iterator).pct—done/total * 100, one decimal. Omitted whentotalis unknown.eta_ms— projected ms untildone === total, from the observed rate. Omitted whentotalis unknown.note— optional string with the current item (e.g. a slug or filename).
heartbeat
Emitted for long-running single operations that don't iterate
(e.g. SELECT against a 50K-row table). No done, no total — just a
signal that work is still happening.
{"event":"heartbeat","phase":"doctor.markdown_body_completeness","note":"scanning pages for truncation…","elapsed_ms":1000,"ts":"..."}
finish
Emitted when a phase completes normally.
{"event":"finish","phase":"import.files","done":52000,"total":52000,"elapsed_ms":187000,"ts":"..."}
abort
Emitted by a single process-level SIGINT/SIGTERM handler that tracks every
live phase. After abort, no further events emit for that phase.
{"event":"abort","phase":"doctor.markdown_body_completeness","reason":"SIGINT","elapsed_ms":5300,"ts":"..."}
Phase names
Phases use snake_case.dot.path naming. A fresh reporter starts at the
root; child() composition appends to the parent's current phase, so a
sync that calls import emits sync.import.<file>, not import.<file>.
Stable phase names shipped in v0.15.2:
doctor.db_checks(umbrella for all DB-side doctor checks)orphans.scanembed.pagesextract.links_fs,extract.timeline_fs,extract.links_db,extract.timeline_dbimport.filessync.deletes,sync.renames,sync.importsmigrate.copy_pages,migrate.copy_linksrepair_jsonb.run,repair_jsonb.<table>.<column>backlinks.scanlint.pagesintegrity.autoeval.single,eval.abeval.brainbench— ticks carry anotebut nototal: continuity pairs replay once per (writer, reader) ordering, so the tick count exceeds the fixture count and a percentage would lieexport.pagesfiles.sync
Sub-phases exposed via child():
sync.import.files— nested inside a syncapply_migrations.v0_12_2.jsonb_repair— nested inside the orchestrator
Subprocess inheritance
When a parent CLI spawns gbrain … child processes (mostly in
src/commands/migrations/*), global flags (--quiet, --progress-json,
--progress-interval) are propagated to the child's argv via the
childGlobalFlags() helper in src/core/cli-options.ts. Child stderr
passes straight through stdio: 'inherit' so the event stream is one
merged JSONL feed on the parent's stderr.
One exception: the orchestrator phase in migrations/v0_12_2.ts that
captures child stdout (repair-jsonb --dry-run --json for verification)
does not pass --progress-json to avoid any risk of stdout pollution
breaking the orchestrator's JSON.parse. Its stdio is explicit:
['ignore', 'pipe', 'inherit'] so stderr still flows through.
Minion jobs
gbrain jobs work (the Minion worker daemon) keeps progress in the DB,
not on stderr. Each Minion handler that runs a bulk core (embed, sync,
extract, import, backlinks) calls job.updateProgress({done, total, …}) per iteration. Agents read per-job progress via the
get_job_progress MCP operation or gbrain jobs get <id>.
The jobs work daemon itself emits coarse one-line-per-job stderr output
for liveness only. Per-page detail lives in the DB.
Compatibility
- Added: only. A new event type, a new field, a new phase name — all safe. Agents must ignore unknown fields and unknown event types.
- Removed/renamed: never without a major version bump.
- Schema changes: announced in
CHANGELOG.mdand inskills/migrations/v<next>.md.
If your agent depends on this schema and something surprises you, open an issue with the event you received and what you expected.