Releases: moda-labs/bobi-agent
Release list
v0.58.0
Minor release: fleet usage answers over MCP, a durable WorkflowRun ledger with period-deduped admission, operator alerts when a brain cannot authenticate or is out of credits, one executor and one turn primitive behind every run, the mcp 2.0 breakage closed, and the review-remediation initiative finished.
Added
- Rolling fleet usage summary over MCP: per-fleet job counts, tokens, and cost over a 1-365 day window, with per-instance detail and partial results (#1061)
- A WorkflowRun ledger entry for every run;
period: hourly|daily|weekly|monthlydedupes admission per bucket across every dispatch path (#1052, #1049) - Alerts on brain authentication and credit failures; auth failures are terminal and visible, triggering messages stay replayable (#1042, #1041)
- A framework communication baseline prepended to every team's instructions; opt out with
BOBI_COMMUNICATION_STYLE=off(#1044)
Fixed
- Framework protection via launch-time file-integrity checks instead of runtime chmod, unblocking
uv tool upgrade/pipx/pip (#1060) - mcp pinned
>=1.23.0,<2plus shims for the 2.0 renames that broke fresh installs (#1046, #1054) connect()is never a turn: dispatch text no longer executes before step 0 (#1017)- Sleep cycle no longer wedges on over-budget memory (#1069)
- Liveness verdicts deferred under legitimate heavy load (#1022)
doctordetects processes running code an in-place upgrade replaced (#1035)- Failed monitor publishes parked and retried; duplicate suppression on by default; dated runtime logs; attributed preflight failures (#1019, #1036, #1033, #1034)
- Login destination no longer caller-controlled; team archives fail closed; issue-assignment dispatch scoped to self (#1009, #1031, #1032)
- Distribution builds accept Node 20+ (#1010)
Changed
- Ad-hoc
--waitruns through the workflow executor;spawn_adhoc's executor role retired (#1058) - Review-remediation initiative closed: consolidation refactors, deferred web-UI items, test-suite cleanup (#1065, #1059)
Full detail in CHANGELOG.md 0.58.0.
v0.57.0
Minor release: agents can author their own OTLP telemetry, subscription login
gets five fixes along one path (gateways, provider config dirs, stale bubbles,
and two Slack failure modes), and the review-remediation sweep closes Lane A
and Phase 5 and lands all five Phase 6 consolidation batches.
Added
bobi agent <name> otel— agent-authored OTLP telemetry (#980, #978).
Three commands (metric,gauge/counter/histogramkinds;log;
check [--send]) that let an agent record telemetry of its own choosing
to any OTLP endpoint: the agent supplies the observation, bobi supplies the
wire format and the fleet identity labels the agent cannot resolve for
itself. Opt-in per team viatool_library: [otel]— deliberately not in the
base prompt. Operator setup and the resource-attribute table are in
docs/OTEL.md.- Browser-level e2e coverage for both web UIs (#990, #991). Setup
back-navigation, the shared top-bar contract acrossbobi setupand
bobi app(which had already regressed once, silently), a type-rendering
guard, and 36 tests over the web app itself. Both surfaces now boot through
one_serve()fixture.
Fixed
Subscription login, end to end:
- A Claude-gateway team could not use subscription login (#1002). The
login guard reused the gateway-declaration predicate as an auth-mode
predicate and raised before selecting the PTY flow; the Docker entrypoint
rejected such teams before bootstrap could run; and bootstrap discarded the
configured base URL. A declaredbase_urlwith noANTHROPIC_AUTH_TOKEN
now runs the ordinary subscription bootstrap; explicitly token-authenticated
Claude gateways and all OpenAI-compatible gateways still fail closed. - A successful login could be reported as failed under
CLAUDE_CONFIG_DIR
(#1003). Credentials were resolved relative to$HOMEregardless of the
provider's own config-dir override, so bobi missed what the CLI had just
written and repeated the login ceremony on every start. Claude credentials
now followCLAUDE_CONFIG_DIRand CodexCODEX_HOME, with the HOME
fallback preserved. - Stale bubble credentials wedged login bootstrap after an event-server
restart (#996). Failed channel registration/JOIN is now probed to
distinguish a stale bubble, credentials re-mint through the compare-and-swap
guard, and the JOIN retries once. Stored subscription credentials are also
structurally validated — missing, malformed, blank, or expired refresh
tokens trigger bootstrap instead of being assumed healthy. - Slack channel login told users to paste the code where the adapter could
not hear it (#1001). A top-level channel message is not a routable inbound
shape, so the pasted code was dropped and bootstrap waited to timeout. The
prompt now says reply-in-thread or @mention the bot, matching the routes
that actually exist. - A Slack bot token missing
users:readlooked healthy while every inbound
event was silently dropped (#998).bots.infofailing left an empty app
id, and subscription detection fell back to a workspace-only topic that no
publisher uses — exact topic matching then delivered nothing. Identity
resolution now fails closed, naming the missing scope.
Review-remediation Lane A closes (#970, #972 — the final 20 bugs, each with a
test proven to fail first):
- Codex sessions wiped each other's MCP servers (D009). Codex re-reads
config.tomlon everycodex exec, and any session built without
mcp_serversin its options — every monitor check, gate, and workflow step
— read the absent key as "the team has none" and erased the bobi-managed
block out from under the manager and every other live session. --projectnever matched a repo with a hyphen in its name (D069). The
lossy cwd decode (-Users-z-dev-bobi-agent→/Users/z/dev/bobi/agent) is
replaced by the transcript's real cwd, with the decode kept only as a
fallback.- Plus 18 more across the history indexer, both search paths, the doctor
probes, packaging, and the long-running/blocking surfaces.
Test infrastructure:
- The container-image integration tests were unrunnable locally (#975).
The fixture's fallbackdocker buildused a source mode that cannot succeed
anywhere (.dockerignoredrops both.gitand the prebuilt event server,
and the image is deliberately Node-free), reading as "docker unavailable."
It now builds the wheel first, the way CI always has. - A Worker MCP test measured the runner, not the code (#973). The 3s
stopwatch flaked on a loaded 2-core runner; the wait contract is now
asserted by poll count.
Changed / Removed
- Dead-code purge — review-remediation Phase 5 complete, 42 items (#979,
#982, #983, #984). Fourteen zero-consumer names, the expired
curator→sleep-cycle and policy→long_term_memory rename compat layers, dead
CLI helpers duplicating the live start/stop paths, and the callerless
terminalrun_setupentry point (the web UI is the entry point). - Consolidation — review-remediation Phase 6, all five batches (#993, #995,
#997, #999, #1000). Behavior-preserving single-sourcing acrossevents/,
cli.py,setup/,monitors/+sdk.py, and compose/build: one
definition each for Slack identity resolution, event-server port
resolution,subscribe:parsing, launch-admission defaults,_parse_iso,
and credential precedence (two setup screens had it in the opposite order
from the runtime). Two deliberate deltas: monitornotify_channelno
longer re-parsesagent.yamlon every notification, and the hiddenask
command loses its unused--sourceoption. - The
dogfood-content-reviewagent pack is deleted (#974) — no
auto-dispatch, no CI reference, no substantive change since 2026-07-01. - Docs (#985): the single-agent page is documented in
README.md; the
never-built "Edit design" affordance is cut from the single-agent-view
plan's scope rather than left claimed.
v0.56.0
Minor release: the single-agent page that 0.55.0 gave the local dashboard now
works on a hosted fleet too — the runtime that connects the published
pieces moves into the framework, and the six read/write commands the hosted
page was missing are added to the admin protocol. Plus the review-remediation
sweep's Phase 4 security and user-surface batches.
⚠ Breaking for out-of-tree TeamRuntime implementations
Seven TeamRuntime methods — runs, overview, run_details, resume_run,
remind_run, close_run and the widened transcript — are now
@abstractmethod, and their base-class fallbacks are deleted. A subclass that
does not implement all seven cannot be instantiated. Both in-tree
implementations (LocalRuntime, EventBusRuntime) satisfy the ABC; anyone
carrying a private subclass must implement them or delegate to the shared
builders, which are importable for exactly that reason.
Added
EventBusRuntimeis part of the framework (#967, #963). This repo
shipped the sidecar (bobi/supervisor/), the Cloudflare event bus
(event-server/worker/) and the console UI (bobi/webapp/) while
withholding the ~470 lines that connect them, so every published piece could
be self-hosted and a hosted console still could not be assembled. The class
now lives atbobi/webapp/event_bus.pyas a pure move — the only diff
against its former home is five docstring phrases that became false once the
file changed repos, and two import lines in its tests.- The hosted single-agent page gains history and composition (#968, #963).
/overviewand/runsanswered 409 on a hosted agent while/health,
/status,/spendand the lifecycle verbs answered 200 — an agent could be
watched and recovered, but what it had done and what it was made of were
missing. Six new admin commands (runs,overview,run_details,
resume_run,remind_run,close_run), each a thin delegate to the same
pure builderLocalRuntimealready calls, so there is one read
implementation and the two runtimes cannot drift.SUPERVISOR_VERSIONmoves
0.1.0 → 0.2.0 (additive, per the compatibility promise). transcripttakes an optionaldetail: true. The chat view (messages)
has already discarded every tool call, so a hosted debugging transcript built
by reshaping it would silently omit most of what an agent did between
speaking. The reply gainsentries+usage; a supervisor too old to know
the argument replies withoutentries, and that is reported as unavailable
rather than rendered as a debugging view with the tool calls missing.
Fixed
- A
prune:entry could delete host files (#961). Prune names an item on a
surface, never a path, but nothing validated it: an absolute name collapsed
the staging join to that absolute path, and a..segment walked out of the
staging directory, sobobi agents installrmtree'd host directories.
Compose now raisesComposeError. /api/credential/valueserved ambient secrets (#961). The setup wizard's
endpoint fell back toos.environfor any requested name, so anything
holding the page's per-launch nonce could read a secret merely exported in
the launching shell — never saved through setup and outside the endpoint's
own justification.run/.envis now the whole surface.- The setup picker confined paths to the wrong tree (#961). The boundary
was BOBI_HOME rather than the user's home directory that the comment,
DESIGN.md and the 400 message all claimed, so/api/mcp/detectrejected
every real project folder with a message the path already satisfied. - An unescaped Slack app name broke the manifest (#961).
Bobi: Staging
rendered an unparseable manifest andBobi #1silently created an app named
Bobi; names are now emitted through the YAML emitter, byte-identically for
ordinary ones. - A commented-out line in
agent.yamltook down every start/status/dispatch
path (#965). A key present with an empty value is YAML null, so
raw.get(key, default)returned None and the default never applied —
services:,requires:,event_server:andspend_cap:all crashed with a
traceback naming neither the key nor the file. - The setup wizard silently discarded an MCP edit made during a probe
(#965). The handler captured the entry, awaited a probe of up to 60s, then
wrote that stale snapshot back — and a result for the old command would
have marked the new one connected, rendering a never-tested config green. event-server stopwedged on a truncated pid file (#965). It raised on
garbage and onPermissionError, leaving the stale files behind so every
later stop failed identically.agents updateexited 0 when every pack failed (#965), while the
named-pack form exited 1 for the identical failure.agents browsedied on an unquotedversion: 1.0(#965), taking down
the whole listing over one row; the same coercion fixes a str-vs-float
comparison that made an installed pack read as an upgrade to itself.- One malformed session crashed a team's cost rollup (#965). The fold's
comment promised it must not 500 on one bad session and the token fields were
guarded; the cost fields were not, and a string is truthy. - Remove-then-re-add crashed
bobi agents install(#965) — the natural
idiom for wholesale-replacing an inherited keyed entry deep-merged into the
tombstone and raised a raw TypeError. - An open-mode pack could install from unvalidated source (#965). The
validation-freshness gate sat behindif state.mode == "create":although
DESIGN.md already called a fresh validation to install a hard floor. - Switching a team off Slack left it running the Slack adapter (#965).
chatis a setup-managed overlay key but the overlay only ever wrote it. - One binary file 500'd the setup review viewer (#965) —
/api/filehad no
decode handling while/api/fileslists binaries with no suffix filter.
Changed
--resumeis removed frombobi setup(#965). The webapp resumes an
unfinished session unconditionally, so the flag named the default. All four
documentation sites are corrected, including the disconnect overlay that
printed the now-erroring command to users.- The public/private line is documented where it is enforced (#967).
AGENTS.mdanddocs/ADMIN_PROTOCOL.mdrecord which side each piece lives
on and point at the reference client.
v0.55.0
Minor release: the dashboard gains a real per-agent page, and the review-remediation sweep lands its first three Lane A phases — session lifecycle honesty, persistence atomicity, and agent-pack routes that had been failing silently.
Added
The single-agent view (#948, MOD-261). The machine-scoped dashboard becomes a real per-agent page. Behind it is a unified runs read model: a monitor firing, a chat, and a workflow run are one list of runs rather than three unrelated event sprays — which is also why a monitor now records one run per firing instead of a spray of events. Agent state becomes a tri-state (running / stalled / stopped) rather than a bare pid check, so "the process exists" stops being mistaken for "the agent is working", and the page can surface a stalled workflow run and offer to resume it.
New per-agent nouns under /api/agents/{name}: GET runs, overview, health, sessions, details, spend, and POST workflows/runs/{run_id}/resume. The overview also reports what the script cache did not spend. Integrates #906, #912, #913, #914, #915, #916, #919 and the #941 restyle.
Fixed
- A session could not be stopped while its startup turn was still in flight (#949) —
stop()silently no-opped and the thread plus its brain subprocess kept running, leaking a live, token-burning agent for every timed-out phase while reporting "session failed to start". Setting an event cannot interrupt a turn parked inawait client.receive_response(), so_runis now a cancellable task. start()waited out the full timeout after the session thread had already crashed (#949) — a launch that failed in milliseconds measured 30.006s, and launch paths pass timeouts up to 3600s.- A supervised agent's terminal failure was never persisted (#949) — the
except asyncio.TimeoutErrorhandler was unreachable, soTERMINAL_FAILEDnever landed and the reconciler had nothing to re-emit. - Every durable writer now shares one atomic-write helper (#951) — the pattern had been re-implemented in six places while a comparable set of writers used a bare
write_text. Because every loader treats unparseable state as empty, the cost is never "lose one field": a torn monitor state file re-fires every monitor, a torn spend file zeroes the runaway-loop backstop's window, a tornsetup.jsondiscards the whole wizard session, and a tornconfig.tomlloses the operator's foreign codex keys. - Agent-pack routes that silently matched nothing (#957) —
eng-team'sgithub.issues.assignedmatches a type no adapter emits, soissue-lifecyclenever fired on assignment (this is why issue pickup has needed a Slack directive); the dogfood workflow'sissues_count > 0used an unsupported operator and fell through to a truthy check, closing a 3-issue audit as having passed review. Validation now catches this class via a per-source event-type table that fails open on unknown adapters.eng-team1.5.2 → 1.5.3,dogfood-content-review1.2.1 → 1.2.2. - A raise from
_make_sessionescaped the workflow orchestrator's terminal-honesty handler (#957) — the registry entry stuck atrunningwith nosession.failedorworkflow.failed. - The Worker deploy smoke gated on a version carrying the previous run's credentials (#954) —
wrangler deployand thesecret bulkafter it publish two Worker versions reporting the same release sha, so a sha-only gate could not tell them apart. Readiness now also requires that an operator-authenticated route answers 200 to this run's freshly minted token and thatversion_idholds still.
Changed
AGENTS.mdstates the dated plan-filename convention the repo already follows (#950).- Lane C of the review-remediation plan is 2 PRs, not 3 (#947) — its six remaining web-UI items are deferred, since the single-agent work above rewrites those surfaces.
Full detail in CHANGELOG.md.
v0.54.0
Minor release: the MCP fleet-control surface gains the tools that act, and a security + reuse sweep lands across the event server.
Added
MCP fleet control gains its write half (#944). POST /mcp on the event-server Worker adds bobi_read_transcript, bobi_send_message, and bobi_lifecycle alongside 0.53.0's three read tools, so an agent can act on a fleet rather than only observe it. No new authority and no new admin vocabulary — the same FLEET_OPERATOR_TOKEN gate runs before any tool body, and the tools drive the same nine commands the sidecar already answers.
Commands resolve through a bounded server-side wait (5s, MCP_COMMAND_WAIT_MS, 0 disables): fast commands return in one tool call instead of three, slow ones return status: "pending" plus a command_id. pending means not-yet-answered, never failed.
Three behaviors are stated in the tool descriptions because an agent would otherwise believe the opposite: bobi_send_message does not return the agent's reply (the supervisor resolves chat only when the whole turn ends, minutes later — read it back with bobi_read_transcript); transcript output is untrusted third-party content, returned in its own block between explicit warnings; and bobi_lifecycle requires a reason, recorded as an audit control, with self-targeting allowed and annotated rather than refused.
Fixed
- Attribute injection in the agent-reply markdown renderer (#942) — agent output could close an
hrefattribute and land a live event handler, and agent replies are prompt-injectable from anything the agent reads. - Unvalidated register payload on the unauthenticated mint path (#942) — a bare string registered one subscription per character; a non-string element threw after an orphan bubble had been persisted.
- Unbounded request-body buffering (#942) — the 413 arrived after the memory was spent, and on a non-loopback bind the peak was unauthenticated. Now capped at 8 MiB (
BOBI_ES_MAX_BODY_BYTES). - Slack file uploads in DMs and thread replies never arrived (#945) — every
file_share-subtyped message was skipped before classification. - The circuit breaker's pause buffer was unbounded and never released (#945) — a conversation that tripped then went quiet held its buffer for the life of the process.
/channels/sendaccepted required text and delivered it nowhere (#945) — on WhatsApp the user got the file and never the sentence explaining it.- Concurrent Slack workspace registrations raced away a bot's signing secret (#945) — a fleet roll restarting both agents could 401 one app's inbound events.
Changed
- The release workflow publishes the pins the image dispatch needs (#943).
- Event-server reuse sweep (#945) — eight duplicated or dead surfaces converged.
Full detail in CHANGELOG.md.
v0.53.0
Minor release: the fleet control plane grows an agent-shaped surface, and Claude token telemetry stops reading zero.
Added
The fleet control plane is served as MCP from the event-server Worker (#923). POST /mcp on the Worker exposes the same control plane the hosted console already drives, so an agent binds to named tools with declared schemas instead of re-deriving the /fleet/* URL shapes, the 202-then-poll contract, and the per-command argument shapes. The read half only — bobi_fleet_status, bobi_instance_detail, bobi_command_result — each calling in-process the same builder its HTTP route calls, so there is no second implementation of the read model and no HTTP hop. Stateless: one McpServer per request, no Durable Object, no session id. Auth is the existing FLEET_OPERATOR_TOKEN via requireOperator, checked in the route before any tool body runs — the MCP route widens the interface, not the authority. Nothing here can restart, stop, or message an instance. A team consumes the endpoint with no framework change, via mcp_servers type: http plus headers.
Self-hosters redeploying their own Worker:
wrangler.jsoncnow setscompatibility_flags: ["nodejs_compat"], and it is not optional.createMcpHandlercarries its per-request context in anAsyncLocalStorage, so the bundle importsnode:async_hooks; without the flag workerd refuses to start and every route on the script goes down with it, bus included. The compatibility date is past 2024-09-23, so the flag is additive rather than replacing workerd's own APIs.
bobi agent <name> costs backfill recovers lost token telemetry (#935, #936). Reads Claude's retained JSONL transcripts and fills the token counters the defect below left at zero. Dry run by default, --write to apply, idempotent across runs. It only ever fills: recorded tokens and provider dollars are never overwritten, last_activity is never bumped, and a session whose transcript is gone stays honestly unknown rather than estimated into place. Against a real 151-session registry it repaired 112 with provider dollars byte-identical before and after. costs became a command group; the bare costs invocation is unchanged.
Fixed
Claude token counts were recorded as zero while the dollars arrived (#935, #936). claude_agent_sdk 0.2.128 passes the CLI's modelUsage through verbatim, so its keys are camelCase, but _one_model_usage_to_cost read only the legacy snake_case spellings — every token field came back 0 while total_cost_usd arrived on a separate attribute, producing exactly the reported shape of rows with dollars and no tokens. Each field is now read across both spellings, first match wins. AssistantMessage.usage is deliberately untouched: it is the raw Anthropic API shape, which is genuinely snake_case.
Changed
Documentation re-verified against the tree it describes (#937, #938, #939). The review-remediation plan re-checked all 229 items against main and its Lane D swept 24 of them: repo-root and pack docs, stale specs, and the engine, monitor, event-server, and setup-skill references that had drifted from the code.
Full changelog: https://github.com/moda-labs/bobi-agent/blob/v0.53.0/CHANGELOG.md
v0.52.0
Bobi looks like Bobi, and public CI proves the product rather than trusting it.
This is also the first release published entirely from bobi-agent — the
reference container image now builds and pushes from this repo rather than the
archived private deploy repo, completing the public half of the 2026-07 reorg.
Added
- Public CI proves both brains, against the real image (#909, #911). The
container-imagejob runs the built image through one real Claude ask and one
real Codex ask, against an ephemeral event server started from the real Worker
sources. Off by default: nightly, on dispatch, or on theci:livelabel —
never on a fork PR. - A real
wrangler deployis exercised (#909, #911).wrangler devproves
the Worker's code but never a deployment — the KV binding, thev1
new_sqlite_classesDurable Object migration, and account-side provisioning
were covered nowhere.worker-deploy-smoke.ymldeploys a dedicated
bobi-events-ci-smokeWorker in a separate Cloudflare account and runs a
health check plus a publish→subscribe round-trip against the deployed URL. - The live lanes must prove they RAN (#909, #911). Every test in both lanes
carries askipif, so a renamed secret would have skipped to green.
scripts/assert_junit_ran.pynow rejects any skip, any wrong count, and any
missing named test;tests/test_ci_live_wiring.pyfails if a live step is
deleted. - The reference image is published from this repo (#898).
Dockerfile,
docker/,release-image.yml,container.ymland the container contract
tests moved here, with a newdocs/REFERENCE_IMAGE.md. A self-hoster runs
Bobi from a public pull instead of a repo grant.
Changed
bobi setupandbobi appreskinned onto the Bobi design system (#883).
Vendored asdocs/design-system/, now the source of truth for anything visual
on any Bobi surface. Violet means state — live, enforced, gated, focused —
never decoration. Geist / Geist Mono / Inter vendored as woff2, so the offline
constraint holds: no CDN, no network at runtime, no build step.- The supervisor arrives via the wheel, not a
COPY(#898). The
bobi-supervisorshim is gone; the entrypoint execs
bobi agent "${AGENT_NAME}" supervise -- --foreground. - The public surface no longer points at an archived repo (#905, #907).
Including the setup wizard's Cloud card, which now points at
ghcr.io/moda-labs/bobianddocs/REFERENCE_IMAGE.md. docs/RELEASE_RUNBOOK.mddescribes the two-repo train (#900).
Fixed
- Subagent launch chains are bounded before a process is spawned (#849,
#888). Launches recorded no ancestry, so a workflow step launching its own
workflow was unbounded: one scheduled trigger became 50 runs in 44 minutes,
stopped only by the spend governor. Launches now carryBOBI_LAUNCH_LINEAGE
and are refused up front when self-recursive or deeper thanmax_launch_depth
(default 8). - Two supervisor test files now test the shipped code, not a private copy
(#904). - Three integration tests failed on ambient
BOBI_HOME, not on the code
(#908).
Full changelog: https://github.com/moda-labs/bobi-agent/blob/v0.52.0/CHANGELOG.md
v0.51.1
Patch release: an agent no longer needs a build-time secret in order to run.
Fixed
-
Build-time-only
${VAR}refs stop gating a runtime (#886). A variable
referenced only by abuild:step was classified a required runtime secret,
sobobi agents install --non-interactiverefused to install an agent whose
dependency was already baked into its image. It took a team down during the
0.51.0 fleet roll: the deploy side deliberately withholds build secrets from
the runtime env-file (and enforces them host-side before a build instead),
while this side refused to proceed without one — and because the deploy pauses
the old runtime before pushing, the box was left frozen rather than merely
un-updated. The dependency the secret would have installed was already
present, and itssuccesscheck passed.The fix is structural rather than a name list: a ref found only under
top-levelbuild:— theapt/npm/run_root/runimage layer — is marked
build_onlyand excluded from the install and startup gates. A name used both
underbuild:and anywhere else stays required, because the runtime use is
real. An unparseableagent.yamlyields no build-only names at all, so a
classification bug over-requires a secret rather than quietly ceasing to
require one.docs/TOOL_LIBRARY.mdnow states the rule where dependency
authors will meet it.
v0.51.0
Minor release: the product surface goes public. The Cloudflare event-server
Worker and the admin sidecar were previously reachable only through a grant on
the private deploy repo; both now ship in this repo, and the admin wire format
they speak is a documented, versioned contract rather than an internal
agreement between two halves of one team's infrastructure. Self-hosting the
durable event tier no longer requires anything private. This is Lane 1 of the
repo reorg (plans/2026-07-29-repo-reorg.md, Phases 1-5); the reference
container image and the ops-repo consolidation follow in later releases.
Added
- The Cloudflare Worker event server is public (#880).
event-server/worker/
is a third npm workspace package alongsidesrc/(local) andcore/. It is
the durable variant of the same protocol the Node servers speak - same webhook
verification, same bubble model, same topics - with per-deployment sessions in
Durable Objects and replay in KV, so registrations and cursors survive a
restart. It lands as its own workspace rather than more files under
event-server/src/because the two compile units are mutually exclusive: the
local unit is Node-only (lib es2024+@types/node), the Worker needs
Cloudflare's globals andworker-configuration.d.ts, and their vitest configs
are node-environment vs.defineWorkersConfig.wrangler.jsoncships with a
placeholder KV namespace id that fails loudly atwrangler deploy- a fresh
clone cannot silently deploy against someone else's namespace. Deployment
walkthrough indocs/SELF_HOSTED_EVENT_SERVER.md("Deploying the Worker").
Requires a Workers Paid plan, because the Durable Objects are SQLite-backed. bobi agent <name> supervise(#880). The admin sidecar, restored as a
first-class public CLI surface. It spawns and probes the manager, publishes
heartbeat and lifecycle telemetry, and listens on the admin topic so a wedged
manager can still be restarted from outside the box. Everything after--
forwards to the manager's start command
(bobi agent <name> supervise -- --foreground). This is what a container
entrypoint runs as PID 1, not an interactive command. The agent name is a CLI
positional; the privatebobi-supervisorshim that read it fromBOBI_ROOT
is gone.docs/ADMIN_PROTOCOL.md(#880). The admin channel as a documented,
versioned contract - topics, envelopes, command and telemetry schemas, and the
auth model. This, not the code move, is what an external consumer binds to.
bobi/supervisor/admin.pybinds the two sides byte-for-byte, so the server
half of a documented contract cannot stay private.
Changed
- Positioning: the Worker is a self-host option, not a paid tier (#880).
README.mdanddocs/SELF_HOSTED_EVENT_SERVER.mdnow present three
self-hosted shapes - tunnel, standalone Node, Worker - and tell you to pick on
durability rather than on price. The in-memory restart caveat carries an
explicit pointer to the durable answer. - Packaging: neither the wheel nor the sdist carries the Worker sources, and
now that is stated rather than incidental. The wheel's event-server inputs
are declared assrc/**/*.ts+core/src/**/*.ts(bobi/events/artifact.py),
which cannot matchworker/. The sdist takesevent-serveras a whole
directory, so the Worker workspace was in scope and fell out only by
accident: Hatch'ssafe_walkfollows the npm workspace symlink
node_modules/bobi-events-worker -> ../workerand then prunes the real
directory as an already-visited inode, which made the sdist's contents depend
on whethernpm cihad run (coreescaped only by sorting ahead of
node_modules).event-server/workeris now an explicit sdistexclude,
pinned bytests/test_sdist_contents.py. No published bytes change - this is
what the release workflow already emitted - but the result is now
deterministic. Nothing in the Python distribution builds or runs the Worker;
its deploy path is a git clone pluswrangler deploy. - The event-server npm tree audit is default-deny (#880). Only
extraneous:
is treated as non-fatal inbobi/events/server.py; everything else blocks.
npm 10.8.2 (bundled with Node 20) reports the Worker's optional dev deps as
extraneous from a lockfile npm 11 calls clean, and the audit must not be
loosened wholesale to absorb that.
Removed
- The repo-split bridges (#880). The npm publish path for
@moda-labs/bobi-events-core(core/scripts/{pack,smoke}.mjs) and the
worker-integration.ymlcross-repo CI existed only to carry code across the
private/public boundary. With the Worker home, they dissolve.
Fixed
validate_teamno longer invalidates its own hash (#873). Finalizing a
team could land in a state where install was permanently unreachable -
"the team source changed since validate_team last passed" - and re-running
validate_teamre-armed the same trap. When the setup'srun/directory sits
inside the source tree, savingrun/state/setup.jsonrewrites a file the
freshness hash just covered, so the frozen digest is stale the instant it is
recorded.source_tree_hashgains anexcludeset and both call sites pass
the setup state file. Two guards close the way in: create mode now applies the
same empty-target bar the registry branch enforced, and therun/containment
check rejects a location that enclosesrun/(the agents root) and not only
one nested inside it.- Plan-artifact CI reads heading-style phase markers (#885). The check
recognized list-style markers only, so a plan whose phases are headings passed
vacuously. - The unit-test matrix reports under a stable required check (#877). A
skipped matrix job never expands its matrix, so requiredUnit tests (3.12)
contexts never reported and the PR hung forever. A non-matrix gate job now
checks the matrix result explicitly. - A plan's review surface freezes at approval, not before (#878).
v0.50.0
Long agent jobs stop dying silently.
Honest turn errors (#845, PR #847). turn failed was a literal fallback — the
brain had already diagnosed max_turns_reached (max=…, turns=…), but the workflow
drain read only result_text (empty on exactly that path) and threw the diagnosis
away. One shared composition now backs the drain, Session.last_error() and
_run_agent_supervised, and the session log's stop record carries every terminal
fact the brain reported.
A configurable, resumable turn budget (#845, PR #847). max_turns resolves
step override → roles.<role>.max_turns → brain.max_turns → DEFAULT_MAX_TURNS
(1000, up from a hardcoded 200), at every site that carried the literal. A step
override now actually wins on steps after the first — it previously no-opped
silently while documented as working. Hitting the cap restarts the step on the
saved session id with a fresh budget instead of discarding the run mid-edit.
The checklist-execution worker protocol (#852, PR #865). skills/checklist-execution.md
— how an agent works a long job from a committed markdown checklist: read once,
one item at a time, verify, commit per item. Persist-per-item is the durability
primitive; there is no engine and no framework module behind it, pinned by
tests/test_no_checklist_engine.py. A verify: is a proposed proof, never
something the framework executes.
--fresh on dispatch (#852, PR #865). spawn_adhoc names sessions from
sha256(task)[:8], so re-dispatching an identical task string reused the previous
session — the checklist shape exactly. The name stays stable (it is the git branch)
and the resume became optional.
A plan-artifact CI check (#852, PR #865) in its own workflow, asserting the
review-surface freeze, an append-only appendix, [f] state tags, and gate-line
classification — and never executing a verify:.
Full detail in CHANGELOG.md.