v0.8.0 — one-command install
One-command install. A new swarm:install orchestrator plus four targeted sub-installers (swarm:install:durable, swarm:install:audit, swarm:install:pulse, swarm:install:examples) cut the read-the-docs-and-hand-wire-five-things ritual down to a single Artisan command, and a curated runnable starter example pack ships so a fresh Laravel app is up and dispatching swarms inside a minute. New concrete LogChannelSwarmAuditSink (the zero-config dev/staging sink the audit installer binds), new BuiltByBerry\LaravelSwarm\Testing\ScriptedAgent provider-free agent base class (the starter examples extend it; make:swarm:agent scaffolds it), and a polished make:swarm:swarm + make:swarm:agent generator pair with topology-aware stubs that match the starter pack. The legacy make:swarm keeps working as a deprecated alias. Getting Started and Advanced Setup docs rewritten around the new install flow. No migration changes. No breaking changes.
Added
php artisan swarm:installmain interactive installer (#85). New orchestrator command — the single entry point an operator runs aftercomposer require builtbyberry/laravel-swarm. Walks the user through Swarm setup withlaravel/prompts(selectfor the persistence driver choice,confirmfor every sub-installer offer), publishesconfig/swarm.phpvia a direct file copy (sidesteppingvendor:publish's boot-time destination capture so the installer works inside the test harness from #92), seeds the canonical Swarm.envkeys (SWARM_PERSISTENCE_DRIVER,SWARM_TOPOLOGY,SWARM_TIMEOUT,SWARM_MAX_AGENT_STEPS,SWARM_AUDIT_FAILURE_POLICY, the fourSWARM_CAPTURE_*toggles) plus safe defaults additively to.envand.env.example(operator-overridden values are left untouched; missing keys are appended under a# Laravel Swarm — added by swarm:installheader), runsphp artisan migrate --forceon the database persistence path or scaffolds aLaravelSwarm::ignoreMigrations()call intoAppServiceProvider::register()(cache-only path, sentinel-fenced with// swarm:install — cache-only persistence; do not edit between markersso re-runs are no-ops, using the sameBODY_INDENT+ relaxed-regex patternInstallAuditCommandestablished post-#102), warns to stderr via$this->output->getErrorStyle()->writeln(...)whenQUEUE_CONNECTION=sync(refuses nothing — mutatingconfig/queue.phpis out of scope by design), then offers and dispatches each sub-installer via$this->call('swarm:install:durable', $args)etc., forwarding--no-interactionthrough. Sub-installer dispatch honors--with-<name>/--without-<name>flags for CI use, defaults to "yes" interactively for audit / examples (and durable when the database persistence driver was chosen), gatesswarm:install:pulseonclass_exists(\Laravel\Pulse\Pulse::class)so Pulse-absent hosts never see the offer, and forwards--alltoswarm:install:examplesunder--no-interactionso the sub-installer does not refuse its "pass --all or --example" precondition. Closing "you're ready" panel emits a per-step summary plus next-step pointers (swarm:health,make:swarm:swarm,docs/getting-started.md). Idempotent by default — a second--no-interactionrun is a byte-for-byte no-op (proven viaassertSecondRunIsNoOp()from the #92 harness) for both the database and cache-only persistence paths.--forcere-publishesconfig/swarm.phpin place. Eleven feature tests intests/Installer/InstallCommandTest.phpcover the happy path, cache-only scaffold, both idempotency contracts,.env-override preservation,--persistencevalidation, the sync-queue warning,--with-examples/--with-auditdispatch (verified via on-disk effects of the sub-installers, not output scraping), the Pulse-absent silent skip (via aPulseAbsentInstallCommandsubclass that overrides the detection method), and--forceoverwrite. Registered inSwarmServiceProvider::commands()asBuiltByBerry\LaravelSwarm\Commands\Install\InstallCommand. Documented at the top of the README Installation section as the recommended path; the legacy manual flow is retained as an "Advanced setup" subsection. The fulldocs/getting-started.mdrewrite lands in #93.php artisan swarm:install:pulsesub-installer (#88). New targeted installer that wires up the Laravel Pulse integration without hand-edits. Detects Pulse viaclass_exists(\Laravel\Pulse\Pulse::class)(nocomposer showshell-out) and refuses with an actionable copy-paste hint when Pulse is missing (composer require laravel/pulse→vendor:publish→migrate). Confirmsconfig/pulse.phpis published and refuses with a publish hint when it is not. Inserts theSwarmRunsandSwarmStepDurationsrecorders inside the existingrecordersarray via balanced-bracket scan (not regex), fenced with// swarm:install:pulse recorders — managedsentinels. Publishes the stock Pulse dashboard view toresources/views/vendor/pulse/dashboard.blade.phpif absent, then injects the<livewire:swarm.runs />,<livewire:swarm.steps />, and<livewire:swarm.audit-outbox />cards inside a{{-- swarm:install:pulse cards --}}managed block immediately before the closing</x-pulse>tag. Card selection via interactivemultiselect(laravel/prompts) or--cards=runs,steps,audit-outbox(with validation that surfaces unknown card names);--no-interactiondefaults to all three. Each mutated file is copied to<file>.bakexactly once before the first write — re-running with--forcerewrites the managed blocks in place but never clobbers the pre-install backup. Idempotent by default: a clean re-run with no--forcedetects both managed-block sentinels and exits 0 with no file mutations (proven byte-level by the installer test harness'sassertSecondRunIsNoOp()). Registered inSwarmServiceProvider. Documented as the new "Quick Setup" section at the top ofdocs/pulse.md(manual two-edit instructions retained below for operators who prefer to install by hand). Feature tests intests/Installer/InstallPulseCommandTest.phpcover the refusal-when-Pulse-absent path (using a test-only subclass that overrides the detection method), the refusal-when-config-not-published path,--cardsvalidation, the happy path on a clean skeleton (default cards + recorders + dashboard publication +.bak),--cards=runs,stepsrestricting the dashboard tags written, default-mode idempotency, and the--forcerewrite +.bak-preservation contract.- Runnable starter example pack at
stubs/examples/(#89). Three curated, end-to-end runnable starter examples that the upcomingswarm:install:examplescommand (#90) will copy into a user's app:sequential-blog-pipeline(the three-agent hello world — outline → draft → polish, in-memoryprompt()),parallel-research-fanout(three scouts run concurrently on the same task, demonstrating Parallel topology + container-resolvable agents + fan-out / join), anddurable-approval-workflow(the showcase — two-step sequential swarm in durable mode with aRoutesDurableWaitscheckpoint between the steps, resumed by apolicy_decisionsignal). Each example ships as a completeapp/Ai/Swarms/<Name>/,app/Ai/Agents/<Name>/, andapp/Console/Commands/SwarmExample<Name>Command.phptree plus a per-example README; the namespace uses a{{ rootNamespace }}placeholder so the installer (#90) can rewrite to the user'sApp\namespace. Index lives atstubs/examples/README.md. Each runner registers aswarm:example:<name>artisan command. Smoke tests intests/Feature/Examples/render the stubs into a temp directory under a test-only namespace and run them against the realSwarmRunner/DurableSwarmManagerso the shipped stubs are proven runnable on every CI pass. Docs published as newdocs/examples.md, cross-linked fromdocs/README.md. The existing 14 read-the-README reference examples underexamples/are unaffected. BuiltByBerry\LaravelSwarm\Testing\ScriptedAgentabstract base class (#89). Runnable, provider-free agent that returns scripted text so starter examples, smoke tests, and "show the shape" demos execute end-to-end without configuring a Laravel AI provider or burning API credit. Subclasses implementinstructions()andreply(string $prompt): string; the shippedprompt()wraps the reply in a standardAgentResponse.stream(),queue(), and the three broadcast helpers raise with a clear message pointing callers at the right next step (use aPromptableagent withAgent::fake()). Lives undersrc/Testing/alongsideSwarmFakeandFakeDurableSwarmManager.swarm:install:examplessub-installer (#90). New Artisan command that copies the curated starter example pack (#89) from the package'sstubs/examples/directory into the host Laravel app, rewriting the{{ rootNamespace }}(and legacy{{ namespace }}) placeholder in every PHP file to the host app's PSR-4 root read fromcomposer.json(App\by default; non-App\PSR-4 layouts pointed atapp/are detected automatically). Auto-discovers available examples by scanning the bundled stubs directory and reads each one's one-line description from itsREADME.md, so a new starter dropped understubs/examples/<name>/shows up in the picker with no installer change required. Interactive mode useslaravel/promptsmultiselect()to pick one, several, or every example; CI / scripted use is covered by--example=<name>(repeatable, single-shot),--all(install everything), and--force(overwrite existing files in the host app);--no-interactionrequires one of--allor--example, errors loudly otherwise. The full example tree is preserved on copy —app/Ai/Swarms/<Name>/,app/Ai/Agents/<Name>/, andapp/Console/Commands/SwarmExample<Name>Command.php— so Laravel 11+ Artisan auto-discovery picks the runner commands up on the next boot without touchingroutes/console.php. The package-internalstubs/examples/<name>/README.mdfiles are deliberately not copied; they remain reference material inside the package, not noise in the user'sapp/tree. Skipping is per-example and additive: ifapp/Ai/Swarms/SequentialBlogPipeline/BlogPipeline.phpalready exists, the installer warns and leaves the on-disk file untouched while still installing the other selected examples in the same run. Idempotent by default (a second invocation with no--forceis a byte-level no-op on every file in the skeleton). Prints "you can now run" hints with the exactphp artisan swarm:example:<name>command for each freshly-installed example. Registered inSwarmServiceProvider; lives under the newsrc/Commands/Install/namespace alongside the other v0.8.0 sub-installers. Documented indocs/examples.mdas the recommended install path. Tests attests/Installer/InstallExamplesCommandTest.phpexercise discovery, namespace rewriting (defaultApp\plus a customAcme\Platform\PSR-4 root), refusal on existing files,--forceoverwrite, idempotency double-run, unknown-example rejection, non-interactive flag enforcement, the--all-vs---examplemutual exclusion, and the runnable-hint output.make:swarm:swarmandmake:swarm:agentgenerators (#91). Split the v0.7-era singlemake:swarmgenerator into two dedicated commands so the generator surface matches the two kinds of class an app actually writes.make:swarm:swarm <Name>scaffolds a swarm class underapp/Ai/Swarms/and accepts--topology=sequential|parallel|hierarchical|static-hierarchical(default sequential; prompts interactively when omitted on a TTY, defaults silently to sequential underArtisan::call()or CI so scripts stay scriptable).make:swarm:agent <Name>is brand new — it scaffolds an agent underapp/Ai/Agents/extendingBuiltByBerry\LaravelSwarm\Testing\ScriptedAgent(the same shape the starter examples instubs/examples/use), withinstructions()andreply()carrying TODO markers pointing at the swap-to-Promptableupgrade path for plugging in a real LLM. Both generators ship publishable stubs (swarm.stub,swarm.parallel.stub,swarm.hierarchical.stub,swarm.static-hierarchical.stub,swarm.agent.stub) under the existingswarm-stubspublish tag and checkbase_path('stubs/')for a customized copy before falling back to the shipped version. Stubs were rewritten so generated code is visually consistent with the runnable starter examples instubs/examples/— same class header docblocks, sameagents()return type, same// new \App\Ai\Agents\…placeholder shape. Documented in newdocs/generators.md(linked fromdocs/README.md);docs/sequential.md,docs/parallel.md,docs/static-hierarchical-topology.md,docs/public-surface.md, and the README were updated to use the new commands.swarm:install:auditsub-installer (#87). New targeted installer that wires the audit pipeline into a host application in a single command. Without it, audit users have to discover that the defaultSwarmAuditSinkbinding isNoOpSwarmAuditSink(silent discard), write the sink binding by hand, and remember the four optional extension contracts (SwarmAuditSigner,ActorResolver,CapturePolicy,SinkFailureHandler) that turn on the regulated-deployment behaviors. The installer scaffolds aSwarmAuditSinkbinding intoapp/Providers/AppServiceProvider::register()behind unique// swarm:install:audit — managed bindingssentinel comments (so re-runs are no-ops), accepts--sink=readable|noop|customto pick the binding shape (defaultreadableinteractively,customunder--no-interactionso CI runs do not silently route evidence to the application log), confirms theswarm_audit_outboxtable is present (and offers to runphp artisan migratewhen it is not, on the database persistence driver), prints the currentSWARM_AUDIT_FAILURE_POLICYandSWARM_CAPTURE_*flags with one-line explainers so the operator sees what is being recorded before shipping, and cross-links toswarm:audit:status,swarm:audit:reconcile, andswarm:tracefor verification. Three optional flags (--with-signer,--with-actor-resolver,--with-capture-policy) emit additional TODO-marker bindings for regulated deployments. Refuses cleanly when AppServiceProvider is missing or itsregister()method body cannot be located. Registered inSwarmServiceProvider, with full feature coverage via the installer test harness (tests/Installer/InstallAuditCommandTest.php).php artisan swarm:install:durablesub-installer (#86). Targeted setup command for the durable execution runtime — drops the read-the-docs-and-hand-wire-five-things ritual that durable adopters previously faced. The command (a) verifiesswarm.persistence.driverisdatabaseand refuses with an actionable error when it iscache, (b) probes the durable runtime tables (swarm_durable_runs,swarm_durable_outbox) and offers to runphp artisan migrate --forcewhen they are absent (or warns when--skip-migrateis set), (c) appends a managed scheduler block toroutes/console.phpregisteringSchedule::command('swarm:relay')->everyMinute(),swarm:recover(every five minutes), andswarm:prune(daily) — guarded by an idempotency marker (// swarm:install:durable schedule entries — managed; do not edit) and a per-line presence check so it never duplicates entries the user wired by hand, (d) inspectsQUEUE_CONNECTIONand refuses onsync(bypassable with--allow-sync-queuefor local experiments only), and (e) prints copy-paste worker snippets for plainqueue:work, theconfig/horizon.phpsupervisorqueuelist, and a Forge/Supervisor.confblock. Flags:--queue=<name>overrides the printed queue name (defaults to the configuredswarm.durable.queue.nameor the package conventionswarm-durable);--migrate/--skip-migratemake the migration step explicit for non-interactive use;--allow-sync-queueopts in to the local-only sync path;--no-interactionskips prompts and warns when migrations are pending. Out of scope by design: installing Horizon, writing toconfig/queue.php, or spawning worker processes. Ships with a feature test suite undertests/Installer/InstallDurableCommandTest.php(eight tests via the v0.8.0 installer test harness, #92) covering the success path, double-run idempotency, both refusal paths, the sync-queue bypass, the--queueoverride, missing-tables warning, and the "user already wired schedule entries" merge case. Registered inSwarmServiceProvider::commands()asBuiltByBerry\LaravelSwarm\Commands\Install\InstallDurableCommand; documented indocs/durable-execution.mdas the new Quick Setup entry point.BuiltByBerry\LaravelSwarm\Audit\LogChannelSwarmAuditSinkconcrete sink. Log-channel-backedSwarmAuditSinkimplementation that writes every audit record as a structured log entry (swarm.audit.<category>) to the configured Laravel log channel (defaults toaudit, falls back to the default channel whenauditis not configured). Ships as the concrete class theswarm:install:audit --sink=readableinstaller binds — replacing an earlier draft that mistakenly pointed the binding at theReadableSwarmAuditSinkextension contract (an interface, not instantiable). Implements onlySwarmAuditSink, notReadableSwarmAuditSink, because log channels are not queryable;swarm:tracedegrades gracefully when this sink is bound. Lives undersrc/Audit/alongsideNoOpSwarmAuditSink. Production deployments should still ship a bounded backend (database, queue, SIEM export); this sink is the zero-config dev/staging default.- Shared installer command test harness at
tests/Installer/(#92). NewBuiltByBerry\LaravelSwarm\Tests\Installer\InstallerTestCasebase class that materializes a minimal Laravel-shaped scratch skeleton (config/,routes/console.php,app/Providers/AppServiceProvider.php,.env,composer.json, plus the standarddatabase/,resources/,storage/,bootstrap/,public/,tests/directories) into a temp directory per test, re-points the booted testbench application at it via$this->app->setBasePath(...)soapp_path()/config_path()/base_path()resolve into the fixture, and tears the directory down ontearDown()for full isolation and parallel safety. Ships ergonomic helpers —runInstaller(),assertInstallerFailsWith(),assertFileContains(),assertEnvKey(),assertScheduleEntry(),assertProviderBinding(),writeSkeletonFile(),skeletonPath(),snapshotSkeleton()— plus a fluent idempotency double-run check (runInstaller(...)->twice()->assertSecondRunIsNoOp()) that hashes every file in the skeleton before/after and fails loudly on any byte-level drift. Includes aNoOpInstallerCommandfixture and end-to-end smoke test (tests/Installer/NoOpInstallerSmokeTest.php) plus eleven self-tests covering every helper (tests/Installer/InstallerTestCaseHelpersTest.php). The harness uses lightweight filesystem fixtures only — no newcomposer require-deventries beyond the already-presentorchestra/testbench. Wired into the defaultcomposer testlane and theInstallerPHPUnit testsuite; downstream consumers documented intests/Installer/README.md. Foundational infrastructure for #85, #86, #87, #88, and #90 — no public swarm-runtime surface change.
Changed
make:swarmis now a deprecated alias formake:swarm:swarm(#91). Running it continues to produce a swarm class underapp/Ai/Swarms/exactly as before — no shape change, no flag change, no behavior change — but it now prints a deprecation notice on stderr pointing callers atmake:swarm:swarm(for swarms) andmake:swarm:agent(for agents). Existing scripts, docs, and tutorials keep working. The alias is slated for removal in a future major release; track #91 for the deprecation window.- Getting Started and Advanced Setup docs rewritten around
swarm:install(#93). Newdocs/getting-started.mdis the canonical new-user landing page: a single-command install viaswarm:install, post-install verification withswarm:health, and a five-minute walkthrough of running thesequential-blog-pipelinestarter from #89 end-to-end with no API key. Newdocs/advanced-setup.mdpreserves the manual flow as a stable appendix — it covers the manual equivalent of every step the installer performs (config publish, env seeding, database vs. cache persistence with theLaravelSwarm::ignoreMigrations()scaffold pattern, scheduler entries forswarm:relay/swarm:recover/swarm:prune, audit sink binding, Pulse recorder + dashboard registration, and copying the starter examples) for environments where the installer cannot run. README Installation section now points at both docs for further reading and at the per-feature Quick Setup sections (swarm:install:durable,swarm:install:audit,swarm:install:pulse,swarm:install:examples) for the targeted sub-installers. Cross-link audit pass added a one-paragraph "swarm:installis the broader entry point" pointer to the top of each Quick Setup section indocs/durable-execution.md,docs/audit-evidence-contract.md,docs/pulse.md, anddocs/examples.md.docs/configuration.mdnow leads withswarm:installand demotes the barevendor:publish --tag=swarm-configinvocation to a manual fallback.docs/README.mdindex gains entries for both new docs at the top of the Getting Started section. The legacy 6-step bash block in the README is gone — the manual flow lives indocs/advanced-setup.mdnow. No public-surface change, no migration change, no breaking change.
Full entry in the CHANGELOG.