Releases: mrsirg97-rgb/rig
Release list
v1.5.8
frontend/tui gains WithStatusTick(d time.Duration): while the TUI
waits for input (no turn streaming, no compaction), the status function
is re-read every d on the Input loop and the region redraws only when
the rows changed. The embedder's rows used to change at command time
only — a background fire wrote a snapshot the next command would show;
now a write the status function sees lands on the next idle tick. Zero
is off (the default; rig's own main never sets it), and the recapture
rides the Input loop, which a turn's start leaves, so the tick never
fires mid-turn; a buffered tick lands immediately when the next Input
starts. The Used reset stays at the session boundaries (/new,
sessions resume).
Tests: a status callback that flips a row after two 10 ms ticks
redraws with the new row under a 10 ms tick without any command, the
unchanged rows never repaint, and a streaming turn blocks the tick
until it ends.
v1.5.7
provider/openai retried 429/5xx with backoff only for hosted rows; a
local llama-swap proxy could answer 502 with an empty body before the
first token (a keep-alive race with llama-server — nothing reached the
model), and the local row faulted the turn. A 5xx whose body is empty
now retries for every row under the hosted policy: the identical request
body, exponential backoff from RetryBase (500ms, 1s, 2s by default)
with the row's jitter, under the hosted 3-retry bound. The retry lives
at the status gate before the stream: once any streamed byte has
arrived a failure is never retried, and a 5xx carrying a body is still
a real error — it faults immediately on a local row.
v1.5.6
tui.StatusIn gains Rows []string: the embedder's footer band. The
rows render under the status line behind the same four-cell dim rule the
swarm band uses (nothing when empty, so rig's own main is unchanged).
The status recaptures after every successful command — the embedder's
commands included — with the Used reset staying at the session
boundaries (/new, sessions resume); docs/EMBED.md lists the field
beside WithCommands and WithStatus. Rows recapture at command time
only: a mid-turn tool effect shows stale until the next command, while
the swarm band stays live because it rides a notify event. A turn-end
hook is a later version.
v1.5.5
frontend/tui gains WithTitle(name string, rows []string, tagline string): an embedder replaces the welcome block's block-letter rows
with their own and adds a line under them (default: the rig rows, no
tagline). The ascii glyph fallback prints the plain name (name,
"rig" for the default), never the first art row, and docs/EMBED.md
lists the option beside WithCommands and WithStatus.
v1.5.4
The TUI band moves below the existing footer rows behind a short dim
rule and densifies each row (workers 1 · +7 ✓0 ✗0 · w1 t423 12s /
reviewer 1 · ⧗0 ✓0 ✗0 · w2 — —); /swarm's replies say added N agents and stopped N agents; and every abnormal spawn end records a
reason on the run.
- The band (
frontend/tui, SPEC_TUI 3a): one row per role below
the status rows, separated by a four-cell dim rule (no rule and no
rows when nothing runs). No new colors: labels, markers and
separators dim, counts text, ✓ the success slot, ✕ the fault slot;
the glyph switch carries the ascii fallback (....,~for the
review clock). - The replies (
swarm/, SPEC_SWARM 4):swarm: added N agents (role X · model M)— one phrasing whether the swarm was empty or
running (agentfor one,agentsfor more, neverstarted) — and
swarm: stopped N agents. - The heartbeat resets on each spawn: a restarted task shows a
fresh age (—until the new run's first beat) instead of the dead
run's last heartbeat. - Run reasons (
store/scheduler, the j23 investigation): the
runner captured no signal and recorded no reason when a spawn's
process group died — the only proof of a stall or timeout was a log
marker, and a cancel or external signal was a bare exit -1 with
reason NULL.RealSpawnnow captures the signal from the wait
status,DelegateandRunJobrecord the reason on the run
(killed after timeout,killed after stall,canceled,killed by signal N), the spawn's process group carriesPdeathsig(a hard
runner death does not orphan the worker), the swarm controller's
context derives from the session context, andcmd/rigstops the
swarm on session teardown so those deaths are recorded before the
process ends. - README.md: the measured stats refresh (720 swarm lines, 34,336 of
Go, 53,815 of tests).
v1.5.3
The README, the docs, and the landing page now say what rig is: a small
operating system for agents.
- README (
README.md): the top screen becomes the thesis, "A small
operating system for agents. The kernel is a few hundred lines.", with
## measured(each number names its mechanism: 99.1% cache hit over
2,925 turns, 297M of 299M prompt tokens, 208M on 2026-09-22, 7k
byte-stable preamble, swarm 723 lines, 34,301 lines of Go, 53,626 of
tests),## what's different, and an OS mapping table. A new
a day with rigwalkthrough covers first prompt, tools, the queue,
memory, schedules, the swarm, and resume. The tools section says the
default menu is 17 built-in tools (viewvision-gated,scheduler
anddelegatefleet-gated), and the dashboard bullets match the real
web surface (plain-text create, start/complete/retry, transcript open). - docs/EMBED.md (new): rig as a Go module, the core seams,
loop.Run, the five seams the root wires, a worked HTTP-service
example (kernel, board-backed job, in-process worker, one process),
the freeze, and local vs hosted. The example compiles. - docs/SETUP.md: the models.json block gains a hosted row example
(remote,provider,baseUrl,apiKey,concurrency,
reasoning,providerPin,cacheControl,retries). - docs/USAGE.md: the /swarm section names the hosted-row path (a
remote worker skips the local swap and the busy probe, riding the
row'sconcurrencytokens) and gains a short hosted-rows paragraph
(auth, retry, cost in the usage line, reasoning field names). - site/index.html: the title, og:title, and hero become "a small
operating system for agents"; the stats refresh (417 loop lines,
34k/53k code/tests, commit count) and the freeze line names the real
story (core frozen at 1.5.0's bytes, loop open to pure addition with
named reopenings). - core/PACKAGE.md: the 1.5.0 hosted extension is the named reopening,
closed when the gate re-froze core at 1.5.0's bytes, not "the named
reopening of the frozen surface" (the re-freeze, 8441fd8). - The freeze gate (
frontend/tui/freeze_test.go): the frozen-surface
loop honors the allowlist's PACKAGE.md entry, a PACKAGE.md change in
core/ or loop/ is a docs change, not a reopening.
v1.5.2
The task queue's one dependency became two named links, requires and
blocks, and read stopped inlining note text.
- Two edges per task (
store/todo,tool/todo, SPEC_TODO_EDGES): a
task carriesrequiresandblocks, one each, each an id (tN) or
exact text, null clears.requires tN: I cannot start until tN is
done;blocks tN: tN cannot complete until I am done. Blocked
means a task's requires target is unfinished or any task whose blocks
names it is unfinished (pending, in_progress, review, failed), so
claimtakes only unblocked pending tasks andcomplete/accepton
a blocked task refuse naming what it waits for.createrefuses
loudly naming the tasks: an unknown link, a self-link, and a cycle
through either relation (cyclePathwalks both). The waits-for graph
isrequirest -> required andblockstarget -> blocker; the read
line shows· requires tN,· blocks tN, and· waits for kon a
target. OlddependsOnpayloads (create events and compact
snapshots) fold asrequiresat replay; the snapshot carries both
links and note times. - The notes door (
store/todo,tool/todo,/todo):readno
longer inlines note text — a task with notes shows· N notesunder
it, and the newnotes <id>action lists them in order with their
session and time, headed by the task's link lines; a task with none
repliesno notes on tN.read <id>renders one task, summary-only,
and points atnotes. The swarm brief'sTaskInfostill carries the
full notes; workers need them. - Schema 4 (
store/todo):task_depsgains thekindcolumn
(requires|blocks), the disposable projection rebuilt from the log in
every transaction.EdgeMigration(3->4) drops and recreates the
projection table; the log carries the edges, so replay is total. - The TUI's todo render (
frontend/tui): the parser dims the new
suffixes (requires/blocks/waits for/claimed by) and keeps the
note-count line. - Tests: seven leaves blocking a root (claim and complete of the
root refused until the last leaf is done), a requires-chain, both
links on one task, cycles refused with the task names, old
dependsOnpayloads folding, replay across compaction, note counts
and the notes action (order, session, time, the no-notes reply), and
read-one's summary-only pointer.
v1.5.1
The controller captured the frontend at wiring time (Frontend: r.rec),
but r.rec is assigned later and swapIn replaces it on /new and
/resume — the first SwarmStatus frame hit a typed-nil recorder and
panicked in Recorder.ensure, crashing the session.
- The swarm's frontend is a resolver (
swarm):Opts.Frontendis
func() core.Frontend(theModelsidiom), resolved on every notify,
so a controller wired before the recorder exists emits safely once it
does and a session swap routes its frames to the new recorder. A
panicking frontend is recovered into a stderr line — the drain worker
keeps draining. - The recorder tolerates a nil receiver (
store/state):Notifyon
a nil*Recorderis a no-op, so a seam wired before the recorder
exists cannot crash onensure. - The delegate's notice seam resolves at call time (
cmd/rig): the
delegate'sNotifyno longer captures the recorder's method value at
wiring time. - Tests: a controller wired with no recorder drains a task and
routes the next frames once the recorder appears; a session swap
routes notices to the new recorder; a panicking frontend leaves the
drain loop running with the panic loud on stderr;Recorder.Notifyon
a nil receiver is a no-op.
v1.5.0
The provider already spoke the OpenAI wire; hosted endpoints (OpenRouter,
DeepSeek's API, any remote OpenAI-compatible server) needed what a local
llama-server does not: auth, retry, cost, per-provider reasoning field
names, and a remote spawn path that never touches the local swap. Model
rows now carry where they run, and swarms and scheduled jobs take dollar
budgets.
- Model rows gain their run site (
models,config):remote(bool),
provider(a name implies remote),baseUrl(required for a remote
row),apiKey(never logged or rendered),concurrency(the row's
token bound, default 1),reasoning(reasoning_contentdefault,
reasoningfor OpenRouter),providerPinandcacheControl
(openrouter-only), andretries(default 3 for remote rows). The env
overlay gainsRIG_MODEL_BASE_URL,RIG_MODEL_API_KEY,
RIG_MODEL_REMOTE,RIG_MODEL_CONCURRENCY,RIG_MODEL_REASONING, and
RIG_MODEL_RETRIES, so a key can live in the environment and never in
a file. - The provider's hosted behavior (
provider/openai):NewWithConfig
sendsAuthorization: Bearer <key>, retries 429 and 5xx with
exponential backoff and jitter under the row's bound (the loop sees no
event until the bound is exhausted, then the existing loud fault),
parsesusage.costintocore.Usage.Cost, reads reasoning under the
row's field names (delta.reasoning/delta.reasoning_detailsfor
OpenRouter,delta.reasoning_contentotherwise), echoes both back on
later turns, omitschat_template_kwargsfor remote rows, and sends
the OpenRouterprovider.orderpin andcache_controlswitch when
the row asks for them. - Cost lands in the usage column (
store/state,core,loop):
usage.cost(schema v4),RecordUsage/AddUsagetake it,
SessionUsage/SessionCostread it, the sessions list carries it,
and the TUI footer shows the session's dollars beside the token
totals. - Remote spawns (
store/scheduler,tool/delegate,swarm): a
remote row's delegate and job fire skip the llama-swap busy probe and
WaitBusyentirely; parallelism is the row'sconcurrencytoken
flock beside the per-session slot bound. The worker resolves the row
from the sharedmodels.json, so a remote row'sbaseUrland key
ride the worker without the parent passing them. - Budgets (
swarm,store/scheduler):swarm <n> budget=<dollars>
stops claiming at the cap with a notice; scheduled jobs takebudget
on create/update and record a skip at the cap. The delegate and the
runner record each run's cost (read from the cost column), so
scheduler runsshows the spend and the job's sum is auditable. - Tests: a fake OpenAI-compatible server asserts the bearer, the
bounded 429/5xx backoff (deterministic base and jitter), cost parsing,
reasoning echoed under the row's field names, and the remote wire
(nochat_template_kwargs, pin, cache switch); the cost column is
recorded and summed; a remote delegate and a remote swarm fire never
consult the swap; a swarm budget and a scheduled job budget stop at
the cap.
v1.4.4
The swarm ran silently beside the session: the transcript showed nothing
when a task failed or a reviewer bounced the work, and the only progress
read was the bare /swarm. The TUI now gets two surfaces, both through
the frontend's existing Notify door.
- The transcript notices (
core.SwarmNotice,swarm): the
controller emits one line per decision-worthy event and nothing else —
a task failed with its note (swarm: t1 failed — the worker died twice), a reviewer rejected with the reason (swarm: t1 rejected — tests are missing), a worker died and was restarted or exited
(swarm: w1 died — t1 restarted/swarm: w1 died — t1 exited), and
the board emptied or the swarm exited (swarm: the board emptied — all workers exited/swarm: /swarm exited — N workers stopped). The
worker failure now notes the reason on the task too. - The status band (
core.SwarmStatus,swarm,frontend/tui): the
controller emits a snapshot on claim, stream bytes, complete, verdict,
and exit — throttled to a few per second, the exit's last frame always
landing — carrying the roster and the bound queue's fold counts
(store/todo.Counts, the new structured read). The TUI folds the
latest into the footer: two rows above the existing status line while a
swarm runs (workers 2 · todo 3 · done 5 · failed 1 · w2 t388 12s,
reviewer 1 · review 1 · done 1 · failed 0 · w3 t386 4m), zero rows
when nothing runs, one row for a delegate. - The delegate's row (
tool/delegate): the optionalNotifyseam —
an interactive delegate emits the same snapshot shape (one worker row,
zero queue counts), so the band shows the worker row only. CLI and
oneshot ignore both events (the compat rule). - The band is a status-string extension: no
live.goline, no loop
line — the region's existing height-changing machinery covers the two
extra rows, and the mid-swarm resize test replays the stream under the
freeze harness. - Tests: each notice with the fake spawn (failed, rejected, died,
board/stop), the throttled status emission,todo.Counts, the band's
exact rows, the footer growing/updating/returning, the resize under the
freeze harness, the delegate's snapshot.