Move Agent Bus to Laravel Zero - #20
Merged
Merged
Conversation
Agent Bus was started with `laravel new` but is a console app that talks to a broker. It has no route, model, or view. The hot path already bypassed the framework: `bin/agent-bus` loaded Composer's autoloader and nothing else, so Laravel booted for exactly two commands while the repo carried Eloquent, an HTTP kernel, sessions, queues, Blade, Vite, Tailwind, a User model, and three migrations. Laravel Zero keeps what is used — container, config, Process, Sleep, Pest's console helpers, and a home for the NATS service provider — and adds app:build. One binary, two paths. `bin/agent-bus` dispatches on the first argument before booting anything. Hook verbs (Cli::HOT_VERBS) fire on every tool call and run on Composer's autoloader alone; provision and sidecar boot the Zero kernel. This is now enforced rather than described: a probe fixture runs each hot verb in a subprocess and fails if any Illuminate, LaravelZero, or Symfony Console class loads. The old test only grepped the file for a string the new binary legitimately contains. NATS_URL becomes the only broker setting. NatsUrl parses it for both paths and NatsServiceProvider projects it onto the package config, so a remote broker needs one variable instead of two mechanisms. Connect timeout moves to AGENT_BUS_CONNECT_TIMEOUT; 250ms is right for loopback and wrong for a tailnet. Fix a crash found by running the suite against a live broker: Laravel Zero does not register Illuminate's ArtisanServiceProvider, where Laravel installs the Signals availability resolver, so Command::trap() dereferences a null callable and fatals. The sidecar traps SIGTERM/SIGINT to leave the bus politely instead of waiting out the 90s KV TTL, so this is the main path. AppServiceProvider now installs the same resolver. CI runs a real broker. Every JetStream and KV test used to skip there, so the core of the product was proven only on a laptop. CI starts nats:2-alpine with the same flags as docker-compose.yml, and AGENT_BUS_REQUIRE_BROKER=1 turns a skip into a failure so a broker that fails to start cannot look green. A third job compiles the binary and runs it. The PHAR is not for hooks. It adds ~140ms to every tool call (206ms against 66ms for the script; GZ compression made it 529ms, so box.json uses none and accepts 30MB over 109MB). It is for the sidecar, which pays once and stays up, and for running verbs by hand. README says so where hooks get configured. Delete app/Http, app/Models, app/Console, database, resources, public, routes, nine config files, Vite, Tailwind, npm lockfiles, artisan, bin/agent-bus-sidecar, and the vendored .grok/skills tree of Laravel web-app guidance. Laravel Boost goes with them: its tools query databases and browsers this app does not have. CLAUDE.md and AGENTS.md were Boost-generated guidelines for a web app, pointing at a .ai/rules directory that does not exist; they now describe this app. Verified: Pint passes. 68 tests pass with zero skips against nats-server 2.14.6. The guard fails correctly when the broker is down. app:build compiles and the binary runs both paths. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FyZJ58TB6pGudJb4nreZUh
There was a problem hiding this comment.
Ship it.
- One-variable broker config and strict hot-path enforcement are the right call for this console app.
- CI now actually exercises JetStream/KV instead of skipping; the
AGENT_BUS_REQUIRE_BROKERguard is clean. - The
Command::trapnull-callable fix via AppServiceProvider is the concrete justification for the Laravel Zero move. - One visible typo in .env.example (HERDR); everything else is mechanical scaffolding removal.
No blocking issues.
Contributor
Author
|
On the flagged
'herdr' => [
'binary' => env('AGENT_BUS_HERDR', 'herdr'),
],Renaming it would break the config lookup. There is no "herder" spelling anywhere in the repo. Generated by Claude Code |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Agent Bus was started with
laravel newbut is a console app that talks to a broker. It has no route, model, or view. Full spec indocs/laravel-zero-migration.md.Why
The hot path already bypassed the framework.
bin/agent-busloaded Composer's autoloader and nothing else, so Laravel booted for exactly two commands while the repo carried Eloquent, an HTTP kernel, sessions, queues, Blade, Vite, Tailwind, aUsermodel, and three migrations.Measured on the pre-migration checkout:
bin/agent-bus emitcold startIlluminate\classesphp artisanbootphp -rLaravel Zero keeps what is actually used — container,
config(),Process,Sleep, Pest's console helpers, and a home for the NATS service provider — and addsapp:build.One binary, two paths
bin/agent-busdispatches on the first argument before booting anything.emitsendheartbeatsession-endsessionshookopencodeprovisionsidecarapp:buildThis is now enforced rather than described.
tests/Fixtures/hot-path-probe.phpruns each hot verb through the real binary in a subprocess and fails if anyIlluminate\,LaravelZero\, orSymfony\Component\Console\class loads. The old test only grepped the file forbootstrap/app.php, a string the new binary legitimately contains.Commands renamed:
php artisan nats:provision→bin/agent-bus provision,php artisan agent-bus:sidecar→bin/agent-bus sidecar.bin/agent-bus-sidecaris gone.NATS_URL is the only broker setting
The hot path read
NATS_URLwhile the framework path readnats_basis.connections.default.*— two ways to point at one broker.App\Bus\NatsUrlnow parsesnats://[user:pass@]host[:port]for both, andApp\Providers\NatsServiceProviderprojects it onto the package config. Issue #16's tailnet broker needs one variable. Connect timeout moves toAGENT_BUS_CONNECT_TIMEOUT, since 250 ms is right for loopback and wrong across a tailnet.Bug fixed, found by running against a live broker
Laravel Zero does not register Illuminate's
ArtisanServiceProvider, which is where Laravel callsSignals::resolveAvailabilityUsing(). Without itCommand::trap()dereferences a null callable and fatals. The sidecar trapsSIGTERM/SIGINTto leave the bus politely instead of waiting out the 90-second KV TTL, so this is the main path, not an edge.AppServiceProvider::register()installs the same resolver.This is invisible without a broker — the command returns early on the reachability check. It is the concrete argument for the CI change.
CI runs a real broker
Every JetStream and KV test skipped in CI, and the workflow carried a comment telling you not to add one, so the core of the product was proven only on somebody's laptop.
CI now starts
nats:2-alpine -js -m 8222with the same flags asdocker-compose.ymland waits on/healthz. A service container cannot pass-js, so it runs as a plaindocker runstep. Skipping stays correct on a laptop with no broker, butAGENT_BUS_REQUIRE_BROKER=1turns it into a failure and CI sets it — a broker that quietly fails to start now fails the build instead of producing a green run that proved nothing. A third job compiles the binary and runs it soapp:buildcannot rot.The PHAR, honestly
app:buildproduces one ~30 MB file that runs with no checkout and nocomposer install. That was the headline reason to move, and it does not work for hooks:bin/agent-busscriptPHAR stub and signature overhead adds ~140 ms to every tool call. GZ triples it, so
box.jsonusescompression: NONEand accepts 30 MB over 109 MB. The binary is for the sidecar, which pays the cost once and stays up, and for running verbs by hand. Hooks keep pointing atbin/agent-busin a checkout, and the README says so where hooks get configured.This weakens the original case for the move. The rest — deleting a web framework from a CLI app, one broker setting, and CI that actually proves the bus — still holds.
Deleted
app/Http,app/Models,app/Console,database/,resources/,public/,routes/, nineconfig/files, Vite, Tailwind, npm lockfiles,artisan,bin/agent-bus-sidecar, and the vendored.grok/skillstree of Laravel web-app guidance.Laravel Boost goes with them — its tools (
database-query,database-schema,browser-logs) have nothing to operate on here, and.mcp.json,boost.json,opencode.json,.grok/config.tomlonly configured it.CLAUDE.mdandAGENTS.mdwere Boost-generated guidelines for a web app pointing at a.ai/rulesdirectory that does not exist; they now describe this application.Verification
Run against
nats-server v2.14.6with JetStream on loopback.Not in scope
Packaging only. Tracked separately: stream retention limits on
AGENT_BUS, afromfield in the envelope, moving the OpenCode allowlist into the plugin so it stops spawning PHP per event, the non-deterministic fallback session id, broker auth (#16), MCP tools (#9).🤖 Generated with Claude Code
https://claude.ai/code/session_01FyZJ58TB6pGudJb4nreZUh
Generated by Claude Code