Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message
Agents
builtbyberry/laravel-swarm
claude-code
cursor
multi-agent
orchestration
durable-execution
swarms
topology
fakes

This file is operational context for AI coding agents. Human contributors should start with README.md, CONTRIBUTING.md, CHANGELOG.md, and the user-facing docs in docs/.

Install
php artisan boost:add-skill builtbyberry/laravel-swarm

Save this content to: AGENTS.md

# Laravel Swarm — Agent Context

<!-- swarm-channel: laravel-swarm -->

This file is operational context for AI coding agents. Human contributors should
start with [README.md](README.md), [CONTRIBUTING.md](CONTRIBUTING.md),
[CHANGELOG.md](CHANGELOG.md), and the user-facing docs in [docs/](docs/).

## What This Is

`builtbyberry/laravel-swarm` is a Laravel package that adds reusable multi-agent orchestration on top of the official `laravel/ai` package. Laravel AI handles single-agent LLM interactions and low-level workflow primitives. Laravel Swarm turns repeated multi-agent workflows into first-class application objects with sync, queued, streamed, and durable execution modes.

## Core Design Principle — Laravel Native Feel

Every implementation decision must follow existing Laravel and Laravel AI conventions. A developer who knows Laravel AI should look at a swarm class and understand it immediately. Do not invent new framework patterns when Laravel already has a recognizable convention.

- Laravel AI uses `make:agent`; this package uses `make:swarm`.
- Laravel AI agents use `Promptable`; swarms use `Runnable`.
- Laravel AI uses PHP attributes like `#[Provider]` and `#[Model]`; swarms use `#[Topology]`, `#[MaxAgentSteps]`, and `#[Timeout]`.
- Laravel AI agents use `prompt()`, `queue()`, `stream()`, `broadcast()`, `broadcastNow()`, and `broadcastOnQueue()`; swarms use those same public verbs plus `dispatchDurable()`.
- `run()` remains available as a compatibility alias for synchronous `prompt()`.
- Laravel AI fakes with `Agent::fake()`; swarms fake with `Swarm::fake()` / `YourSwarm::fake()`.
- Config lives in `config/swarm.php`, not `config/ai.php`.
- Generated swarm classes live in `app/Ai/Swarms/`, extending the `app/Ai/` namespace Laravel AI establishes.

## Product Completeness Standard

- Build the full product behavior, not an MVP approximation.
- Do not choose the path of least resistance if it leaves operational, security, developer experience, documentation, or testing gaps.
- Solve the underlying problem completely, including failure modes, operator workflows, rollout and maintenance implications, and reviewer expectations.
- If a shortcut would trade away correctness, resilience, or product completeness, do not take it silently. Surface the tradeoff explicitly and treat it as a decision, not an implementation detail.
- Default to closing gaps during implementation rather than leaving follow-up work unless the deferral is explicitly approved.

## Definition of Done

A feature or fix is complete only when all of the following are true. Reviewers should treat missing items as blockers, not follow-up work.

- **Artisan command** — if the feature introduces an operational concern (relay, prune, recover, status), it ships with a corresponding `swarm:*` command with a `--help` description.
- **Config key** — every new tuneable has a key in `config/swarm.php` with an inline comment explaining intent and a safe production default.
- **Prune / retention hook** — any new persistent data has a `swarm:prune` integration or an explicit documented reason it is exempt.
- **Test coverage** — new behavior has deterministic Pest tests; concurrency-sensitive paths have a `test:process-concurrency` lane entry.
- **CHANGELOG.md** — every PR includes a changelog entry. Breaking changes also require an `UPGRADING.md` section.
- **Docs parity** — public surface changes update the relevant file in `docs/` or `README.md` in the same PR, not a follow-up.
- **Docblock truth boundary** — a docblock may describe the code in its own file and explain why that code is shaped that way. At review, ask: “Does this docblock assert something about a file it isn’t in?” If it restates another component's API, behavior, or infrastructure, replace the duplicated claim with a link to the owning source. Fine: [`ScriptedAgent`](src/Testing/ScriptedAgent.php) explains why it retains its own deprecated marker. Not fine: [`VendorOnlySequentialSwarm`](tests/Fixtures/Swarms/VendorOnlySequentialSwarm.php) claiming that [`PendingRun`](src/Support/PendingRun.php) requires a declared class for streaming; that duplicates another component's API and is false because `PendingRun::stream()` exists.

[`DocumentationReferenceTest`](tests/Unit/DocumentationReferenceTest.php), shipped for #450, checks only that references resolve. It cannot validate semantic claims. A green run means the links exist, not that the documentation is true; this rule is a manual review reminder, not an automated truth check.

If a gap is deferred, it must be recorded as a named follow-up with an owner, not silently dropped.

## Tech Stack

Read `composer.json` for the authoritative versions. Not in the manifest:
Pulse observability is optional and lives in the separate
[`builtbyberry/laravel-swarm-pulse`](https://github.com/builtbyberry/laravel-swarm-pulse)
companion package — deliberately **not** a core dependency.

## Dependency And Upgrade Path

Swarm sits directly on **PHP**, **Laravel**, and **`laravel/ai`** with semver ranges. The package **cannot fully insulate** applications from upstream API, streaming, or provider contract shifts. Treat **Laravel** and **`laravel/ai` bumps** (minor or patch) as **integration-test events**: run your automated suite and any swarm-heavy smoke paths after resolving Composer updates. **This package’s changelog** is the contract for Swarm-owned breaking or behavior changes; it does not replace verifying your app against new Laravel or Laravel AI releases.

## Package Shape

`src/` is organized by responsibility and reads directly — list it rather than
relying on a copy here. Two pointers the layout does not give you:

- `src/Runners` — `DurableSwarmManager` is a facade over the `src/Runners/Durable/*`
  collaborators; see [docs/durable-runtime-architecture.md](docs/durable-runtime-architecture.md).
- `src/Persistence` — `SwarmPersistenceCipher` seals designated string columns when
  database persistence uses encrypter-backed at-rest sealing (see Persistence below).

## Execution Modes

- `prompt()` executes synchronously and returns a `SwarmResponse`.
- `run()` is a compatibility alias for `prompt()`.
- `queue()` defaults to one Laravel queue job owning the workflow. Generated hierarchical swarms can opt into database-backed `multi_worker` branch/join coordination; static hierarchy retains its in-process queued path. See [queued hierarchy](docs/hierarchical-routing.md#queue). Queued swarms are re-resolved from the container, so they must not rely on runtime instance state. Pass per-run data in the task payload or `RunContext`.
- `stream()` supports sequential, generated hierarchical (synchronous coordinator, streamed workers), and static hierarchical swarms. Top-level parallel live multiplexing is default-off (`swarm.streaming.parallel.enabled`) and requires Laravel's `process` concurrency driver; its branch events carry explicit branch/attempt/sequence identity and have no invented global order. See [SwarmRunner](src/Runners/SwarmRunner.php) and [stream validation](src/Runners/DispatchValidator.php). It returns a lazy `StreamableSwarmResponse`, emits typed progress and final streamed output, and supports in-memory replay after completion. Persisted replay is opt-in via `storeForReplay()` or `swarm.streaming.replay.enabled`; replay with `SwarmHistory::replay($runId)`. Replay write failures default to failing the stream, unless `swarm.streaming.replay.failure_policy` is set to `continue`; in continue mode, already-written replay events are discarded so partial playback is unavailable. For upstream final-agent streamed events, persisted replay preserves upstream IDs and timestamps; invocation IDs are passed through when present. `broadcast()`, `broadcastNow()`, and `broadcastOnQueue()` are stream-event helpers for those same supported streaming topologies; they broadcast typed swarm stream events, not lifecycle events for every topology.
- `dispatchDurable()` is database-backed, checkpointed execution for sequential, parallel, generated hierarchical, and static hierarchical swarms. It persists a durable cursor and advances the swarm through durable jobs. Use events such as `SwarmCompleted` and `SwarmFailed`, or the opt-in terminal `then()` / `catch()` callbacks below.

Whole-workflow terminal callbacks are opt-in via `swarm.callbacks.enabled` (default off, database driver required). When enabled, [QueuedSwarmResponse](src/Responses/QueuedSwarmResponse.php) and [DurableSwarmResponse](src/Responses/DurableSwarmResponse.php) expose `then()` / `catch()`, and [StreamableSwarmResponse](src/Responses/StreamableSwarmResponse.php) adds `catch()` alongside its existing `each()` / `then()`. `then` is armed once only on a settled completion and `catch` is armed once only on a settled failure (including the permanent `UnsupportedNativeApprovalException` boundary); neither is armed on cancellation, an intermediate agent success, or a recoverable error, and a callback's own failure never replays model/tool effects or changes the settled result. Queue/durable callbacks are persisted as HMAC-signed, cipher-sealed serializable closures in `swarm_callback_deliveries` and delivered **at-least-once** (never exactly-once — make them idempotent) by `swarm:relay --type=callback`; the stream `catch()` runs in-process. With the flag off, queued/durable `then()` / `catch()` throw `BadMethodCallException` exactly as before the feature existed, and `SwarmCompleted` / `SwarmFailed` remain the driver-agnostic path. See [error-handling.md](docs/error-handling.md#terminal-workflow-callbacks).

## Topologies

- **Sequential:** agents run in order; each output becomes the next input.
- **Parallel:** agents run concurrently and each receives the original task. Parallel agents must be stateless, container-resolvable Laravel AI agents because Laravel concurrency serializes worker callbacks.
- **Hierarchical:** the first agent is the coordinator. It must use Laravel AI structured output and return the full route plan. Laravel Swarm validates the plan as a DAG and executes `worker`, `parallel`, and `finish` nodes. In `prompt()`, parallel groups execute concurrently. In default `queue()` mode, hierarchical parallel groups execute sequentially in declaration order while retaining parallel-safe validation rules. Opt-in `multi_worker` uses branch jobs and a coordinated join. [ResumeQueuedHierarchicalSwarm](src/Jobs/ResumeQueuedHierarchicalSwarm.php) uses [ConfiguresDurableAdvanceJob](src/Jobs/Concerns/ConfiguresDurableAdvanceJob.php), not the ordinary queued-job retry profile.

For hierarchical routing, there is no separate `route()` callback. The coordinator is the single source of truth for what should run next. `#[MaxAgentSteps]` counts the coordinator plus reachable worker executions and fails before worker execution if the validated plan exceeds the limit.

## Persistence, History, And Capture

Laravel Swarm persists run context, artifacts, and run history through configurable stores.

- `RunContext` carries input, structured data, metadata, artifacts, and run ID control.
- `ContextStore`, `ArtifactRepository`, `RunHistoryStore`, `StreamEventStore`, and `DurableRunStore` are the persistence contracts.
- `SwarmHistory` provides application and console inspection over persisted runs.
- Cache and database drivers are supported; database persistence uses package migrations loaded by the service provider.
- **Capture defaults** — Shipped `config/swarm.php` defaults `swarm.capture.inputs`, `outputs`, `artifacts`, and `active_context` to **false** so prompts and agent payloads are not persisted unless the application opts in (`SWARM_CAPTURE_*` or config). Treat prompts, outputs, lifecycle events, streamed reasoning/tool payloads, and automatic step artifacts as sensitive whenever capture is enabled. With output capture off, streamed function-tool/reasoning payloads redact values to `[redacted]` while preserving keys where applicable. Under Redact, provider-tool payloads withhold the entire arbitrary structure, including keys; see [provider-tool capture and bounds](docs/provider-tool-events.md#capture-and-bounds).
- **Encryption at rest (package scope)** — When `swarm.persistence.driver` is `database`, `swarm.persistence.encrypt_at_rest` defaults to **true**. Designated sensitive string columns (for example context `input`, history final `output` and per-step I/O, durable branch I/O, hierarchical node outputs, child run outputs and nested `context_payload` input) are sealed with Laravel’s encrypter (`APP_KEY`), same primitive family as encrypted casts. Stored values use a `sw0:` prefix before the ciphertext so legacy plaintext rows still read correctly. Set `SWARM_ENCRYPT_AT_REST=false` only when you intentionally rely on database- or volume-level encryption instead. Rotating `APP_KEY` without re-encrypting leaves existing rows undecipherable; plan key rotation with your operational model. This is application-level sealing, not a claim of transparent database (TDE) encryption.
- When encrypt-at-rest is enabled, `RunHistoryStore::findMatching` skips SQL JSON-path prefiltering on persisted context (randomized ciphertext cannot satisfy equality predicates in SQL); PHP-side `PersistedRunContextMatcher` still applies after rows are loaded.
- Database retention is prune-based. Expired database rows remain queryable until `swarm:prune` runs, and active runs are protected from partial pruning.
- `run_id` referential integrity is enforced at the database level for the history and durable child tables covered by the foreign-key migration: they carry `ON DELETE CASCADE` FKs to their parent (`swarm_run_histories` for the history family, `swarm_durable_runs` for the durable family). `swarm_native_inputs.run_id` and `swarm_callback_deliveries.run_id` are indexed without FKs and are pruned explicitly. `parent_run_id` (self-reference), `signal_id`, and `webhook_idempotency.run_id` use `ON DELETE SET NULL`; `child_run_id` has no FK.

## Commands And Operations

Run `php artisan list swarm` (or read `src/Commands`) for the current command set —
do not maintain a copy here, it goes stale. Operational notes that are not
discoverable from `--help`:

- **Schedule `swarm:prune`** for database-backed persistence, and schedule
  `swarm:recover` frequently when using durable execution. Neither runs on its own.
- `swarm:status` and `swarm:history` include a **Phase** column; `parallel_join`
  means a hierarchical coordinated queue run is waiting on parallel branches.

## Pulse

Pulse observability lives in the separate companion package [`builtbyberry/laravel-swarm-pulse`](https://github.com/builtbyberry/laravel-swarm-pulse) (namespace `BuiltByBerry\LaravelSwarmPulse\*`), extracted from core in v0.17.1. Install it to get the `SwarmRuns`, `SwarmStepDurations`, and `SwarmMemoryMetrics` recorders and the `swarm.runs` / `swarm.steps` / `swarm.audit-outbox` / `swarm.memory` dashboard cards, registered by its `swarm:install:pulse` sub-installer. Core is no longer aware of Pulse and no longer depends on `laravel/pulse`; it retains only the shared `swarm.pulse.memory.sample_rate` config knob that the companion's memory-metrics recorder reads. See [docs/pulse.md](docs/pulse.md).

Pulse is aggregate observability. For live per-run operations feeds, listen to Laravel Swarm lifecycle events and broadcast application-owned events.

## Key Architecture Decisions

- **Transaction boundaries** — any code that coordinates two systems (DB + queue, DB + cache, two tables, outbox + checkpoint) must either execute inside a single transaction or explicitly document in the code why eventual consistency is acceptable. Flag this at design time. Do not discover it in review.
- Do not use facades inside orchestration internals; inject services and contracts through the container.
- `ParallelRunner` and hierarchical parallel execution use `ConcurrencyManager` from the container, not the facade directly.
- `SwarmRunner` resolves attributes via reflection and falls back to `config('swarm.*')`.
- `Runnable::make()` returns `mixed` so a bound `SwarmFake` can be returned.
- `SwarmFake` intercepts `prompt()`, `run()`, `queue()`, `stream()`, `broadcast()`, `broadcastNow()`, `broadcastOnQueue()`, and `dispatchDurable()` and records assertions. Stream fakes stay lazy and record only when iterated or consumed by broadcast helpers. Broadcast helpers reuse existing buckets: `broadcast()` / `broadcastNow()` satisfy `assertStreamed()`, and `broadcastOnQueue()` satisfies `assertQueued()`.
- **Testing limits:** `SwarmFake::queue()` records dispatch intent only; it does not execute `SwarmRunner`, simulate coordinated hierarchical `multi_worker` branch jobs, durable coordination rows, or `ResumeQueuedHierarchicalSwarm`. Validate that path with database-backed feature tests (see `tests/Feature/QueuedHierarchicalParallelCoordinationTest.php`).
- Queued and parallel safety checks should fail before dispatch when a swarm or worker cannot be container-resolved safely.
- Timeouts are best-effort orchestration deadlines. Ordinary modes check before and between steps. Process-backed top-level parallel streaming also enforces the absolute deadline while multiplexing and terminates local branch processes; neither path guarantees cancellation of remote provider work already accepted.
- `BuiltByBerry\LaravelSwarm\LaravelSwarm` is the plain helper class for package-level static toggles and overrides (Cashier/Sanctum/Passport/Horizon/Telescope idiom). Add to it for things like service-provider opt-outs (`ignoreMigrations()`), Eloquent model overrides if the package adopts them, or first-party dashboard auth callbacks if one ships later. Do **not** mirror configuration that already lives in `config/swarm.php` — pick one home for each setting.

## Review Method

Use multi-expert review for meaningful changes, especially streaming contracts, persistence/replay, migrations, security surfaces, and public API drift. Invoke the `marshall:change-review` skill rather than doing lens reviews inline — it runs the configured lenses against the component diff and records findings in the shared Marshall store. Release-level readiness is `marshall:release-readiness`.

Default review lenses:

- Laravel maintainer: convention fit, DI patterns, contract shape, upgrade safety.
- CTO: enterprise readiness, operational clarity, and long-term maintainability.
- Engineering manager: blast radius, cohesion, rollback safety, and delivery slicing.
- Security specialist: redaction/capture behavior, sensitive surfaces, and failure paths.
- QA specialist: deterministic regression coverage, edge cases, and assertion resilience.
- Docs engineer: behavior/docs/changelog parity and configuration clarity; apply the Definition of Done's **Docblock truth boundary** to cross-component docblock assertions.
- Systems integrator: migration/config compatibility and deployment/runtime wiring risk.
- Regulatory specialist: auditability, provenance requirements, and compliance evidence quality.

Required review output:

- What passes.
- Findings ordered by severity (`high`, `medium`, `low`).
- Open gaps or follow-up items.
- Release impact (`blocker` or `non-blocker`).
- Consolidated verdict: `approve`, `approve-with-followups`, or `changes-required`.

Severity gate:

- `high`: fix before release.
- `medium`: fix now unless explicitly deferred with an owner.
- `low`: may defer if documented.

When making an intentional tradeoff (for example replay provenance or redaction
shape), record the chosen option, rejected option, rationale, and test/doc
updates in the PR notes or changelog entry.

## Release Workflow

Releases are session-shaped: a thematic group of work ships as one tagged release, not one issue per patch. The branching and PR layout below reflects what the v0.5.x and v0.6.x lines actually used; follow it for every release unless the maintainer says otherwise.

**Long-lived release branch**

- Cut `release/v<X.Y.Z>` from `main` when a release session opens.
- Topic branches target the release branch, not `main`. The release branch is the only branch that ever merges back into `main`.
- Open the release with a `chore(release): scaffold v<X.Y.Z> changelog entry` commit so the changelog has a stub from day one.

**Topic branches**

`<version>` here is the **bare number with no `v` prefix** — `0.23.0`, not `v0.23.0`.
This differs from the release branch, which *does* carry the `v` (`release/v0.23.0`).
CI enforces the topic-branch form and rejects the PR otherwise.

- `feat/<version>-<topic>` — features.
- `fix/<version>-<topic>` — bug fixes that ride a release session.
- `docs/<version>-<topic>` — docs-only work.
- `test/<version>-<topic>` — test-only work.
- `chore/<version>-<topic>` — scoped chores.

Example: `chore/0.23.0-tighten-minimum-stability` into `release/v0.23.0`.

- One topic per PR. PR titles end with the originating issue `(#NN)` when there is one.

**Commits — Conventional Commits with a scope**

- Use `feat(scope):`, `fix(scope):`, `docs(scope):`, `test(scope):`, `refactor(scope):`, `chore(scope):`, `build(scope):`, `ci:`.
- Scope tracks the area, not the version: `audit`, `runner`, `pulse`, `release`, etc.

**Three-phase wrap (in order)**

1. `chore/<v>-review-followups` — addresses the multi-expert review findings. Commits are suffixed with the stable finding ID, e.g. `(review F1)`, `(review F2, F3)`. Finding numbers are assigned by the review and do not get renumbered.
2. `chore/<v>-release-wrap` — fills in `CHANGELOG.md` and `UPGRADING.md` for the version. No behavior changes here.
3. `chore/<v>-readiness-followups` — final readiness-gate fixes only. Commits suffixed `(readiness F1)`, `(readiness F2)`. If readiness surfaces anything larger than a small fix, send it back through `review-followups` instead.

**Cutting the release**

- After the wrap phases land on the release branch, open the `release/v<X.Y.Z> → main` PR. Its title is `release: v<X.Y.Z> — <one-line theme>`.
- Tag `v<X.Y.Z>` on `main` after that PR merges. Do not tag on the release branch.

**Deferrals**

- A deferred finding must be recorded as a GitHub issue with an owner and a target version. Do not drop it into a TODO comment or a plan doc.

## Known Gaps / Next Work

- Commercial dashboard layer is a separate future repo/product. The package intentionally exposes history, events, and artifacts (with Pulse cards available via the [`builtbyberry/laravel-swarm-pulse`](https://github.com/builtbyberry/laravel-swarm-pulse) companion package) instead of owning a full dashboard API.

## Testing And Quality

From the package root:

```bash
composer test
composer lint
composer analyse
composer format
```

For parallel or hierarchical **concurrency** changes, also run
`composer test:process-concurrency` (optional locally; skips cleanly when the
real `process` driver cannot run). Continuous integration uses
`composer test:process-concurrency:ci`, which fails the build if any test in
that lane is skipped.

The package `TestCase` sets `swarm.capture.*` to **true** and `swarm.persistence.encrypt_at_rest` to **false** so the suite exercises full persisted payloads without coupling every test to the conservative production defaults.

If running phpstan directly, use:

```bash
vendor/bin/phpstan analyse --memory-limit=2G --no-progress
```

`composer format` rewrites files with Pint. Use `composer lint` when you need a non-mutating formatting check.

Laravel Swarm — Agent Context

This file is operational context for AI coding agents. Human contributors should start with README.md, CONTRIBUTING.md, CHANGELOG.md, and the user-facing docs in docs/.

What This Is

builtbyberry/laravel-swarm is a Laravel package that adds reusable multi-agent orchestration on top of the official laravel/ai package. Laravel AI handles single-agent LLM interactions and low-level workflow primitives. Laravel Swarm turns repeated multi-agent workflows into first-class application objects with sync, queued, streamed, and durable execution modes.

Core Design Principle — Laravel Native Feel

Every implementation decision must follow existing Laravel and Laravel AI conventions. A developer who knows Laravel AI should look at a swarm class and understand it immediately. Do not invent new framework patterns when Laravel already has a recognizable convention.

  • Laravel AI uses make:agent; this package uses make:swarm.
  • Laravel AI agents use Promptable; swarms use Runnable.
  • Laravel AI uses PHP attributes like #[Provider] and #[Model]; swarms use #[Topology], #[MaxAgentSteps], and #[Timeout].
  • Laravel AI agents use prompt(), queue(), stream(), broadcast(), broadcastNow(), and broadcastOnQueue(); swarms use those same public verbs plus dispatchDurable().
  • run() remains available as a compatibility alias for synchronous prompt().
  • Laravel AI fakes with Agent::fake(); swarms fake with Swarm::fake() / YourSwarm::fake().
  • Config lives in config/swarm.php, not config/ai.php.
  • Generated swarm classes live in app/Ai/Swarms/, extending the app/Ai/ namespace Laravel AI establishes.

Product Completeness Standard

  • Build the full product behavior, not an MVP approximation.
  • Do not choose the path of least resistance if it leaves operational, security, developer experience, documentation, or testing gaps.
  • Solve the underlying problem completely, including failure modes, operator workflows, rollout and maintenance implications, and reviewer expectations.
  • If a shortcut would trade away correctness, resilience, or product completeness, do not take it silently. Surface the tradeoff explicitly and treat it as a decision, not an implementation detail.
  • Default to closing gaps during implementation rather than leaving follow-up work unless the deferral is explicitly approved.

Definition of Done

A feature or fix is complete only when all of the following are true. Reviewers should treat missing items as blockers, not follow-up work.

  • Artisan command — if the feature introduces an operational concern (relay, prune, recover, status), it ships with a corresponding swarm:* command with a --help description.
  • Config key — every new tuneable has a key in config/swarm.php with an inline comment explaining intent and a safe production default.
  • Prune / retention hook — any new persistent data has a swarm:prune integration or an explicit documented reason it is exempt.
  • Test coverage — new behavior has deterministic Pest tests; concurrency-sensitive paths have a test:process-concurrency lane entry.
  • CHANGELOG.md — every PR includes a changelog entry. Breaking changes also require an UPGRADING.md section.
  • Docs parity — public surface changes update the relevant file in docs/ or README.md in the same PR, not a follow-up.
  • Docblock truth boundary — a docblock may describe the code in its own file and explain why that code is shaped that way. At review, ask: “Does this docblock assert something about a file it isn’t in?” If it restates another component's API, behavior, or infrastructure, replace the duplicated claim with a link to the owning source. Fine: ScriptedAgent explains why it retains its own deprecated marker. Not fine: VendorOnlySequentialSwarm claiming that PendingRun requires a declared class for streaming; that duplicates another component's API and is false because PendingRun::stream() exists.

DocumentationReferenceTest, shipped for #450, checks only that references resolve. It cannot validate semantic claims. A green run means the links exist, not that the documentation is true; this rule is a manual review reminder, not an automated truth check.

If a gap is deferred, it must be recorded as a named follow-up with an owner, not silently dropped.

Tech Stack

Read composer.json for the authoritative versions. Not in the manifest: Pulse observability is optional and lives in the separate builtbyberry/laravel-swarm-pulse companion package — deliberately not a core dependency.

Dependency And Upgrade Path

Swarm sits directly on PHP, Laravel, and laravel/ai with semver ranges. The package cannot fully insulate applications from upstream API, streaming, or provider contract shifts. Treat Laravel and laravel/ai bumps (minor or patch) as integration-test events: run your automated suite and any swarm-heavy smoke paths after resolving Composer updates. This package’s changelog is the contract for Swarm-owned breaking or behavior changes; it does not replace verifying your app against new Laravel or Laravel AI releases.

Package Shape

src/ is organized by responsibility and reads directly — list it rather than relying on a copy here. Two pointers the layout does not give you:

  • src/Runners — DurableSwarmManager is a facade over the src/Runners/Durable/* collaborators; see docs/durable-runtime-architecture.md.
  • src/Persistence — SwarmPersistenceCipher seals designated string columns when database persistence uses encrypter-backed at-rest sealing (see Persistence below).

Execution Modes

  • prompt() executes synchronously and returns a SwarmResponse.
  • run() is a compatibility alias for prompt().
  • queue() defaults to one Laravel queue job owning the workflow. Generated hierarchical swarms can opt into database-backed multi_worker branch/join coordination; static hierarchy retains its in-process queued path. See queued hierarchy. Queued swarms are re-resolved from the container, so they must not rely on runtime instance state. Pass per-run data in the task payload or RunContext.
  • stream() supports sequential, generated hierarchical (synchronous coordinator, streamed workers), and static hierarchical swarms. Top-level parallel live multiplexing is default-off (swarm.streaming.parallel.enabled) and requires Laravel's process concurrency driver; its branch events carry explicit branch/attempt/sequence identity and have no invented global order. See SwarmRunner and stream validation. It returns a lazy StreamableSwarmResponse, emits typed progress and final streamed output, and supports in-memory replay after completion. Persisted replay is opt-in via storeForReplay() or swarm.streaming.replay.enabled; replay with SwarmHistory::replay($runId). Replay write failures default to failing the stream, unless swarm.streaming.replay.failure_policy is set to continue; in continue mode, already-written replay events are discarded so partial playback is unavailable. For upstream final-agent streamed events, persisted replay preserves upstream IDs and timestamps; invocation IDs are passed through when present. broadcast(), broadcastNow(), and broadcastOnQueue() are stream-event helpers for those same supported streaming topologies; they broadcast typed swarm stream events, not lifecycle events for every topology.
  • dispatchDurable() is database-backed, checkpointed execution for sequential, parallel, generated hierarchical, and static hierarchical swarms. It persists a durable cursor and advances the swarm through durable jobs. Use events such as SwarmCompleted and SwarmFailed, or the opt-in terminal then() / catch() callbacks below.

Whole-workflow terminal callbacks are opt-in via swarm.callbacks.enabled (default off, database driver required). When enabled, QueuedSwarmResponse and DurableSwarmResponse expose then() / catch(), and StreamableSwarmResponse adds catch() alongside its existing each() / then(). then is armed once only on a settled completion and catch is armed once only on a settled failure (including the permanent UnsupportedNativeApprovalException boundary); neither is armed on cancellation, an intermediate agent success, or a recoverable error, and a callback's own failure never replays model/tool effects or changes the settled result. Queue/durable callbacks are persisted as HMAC-signed, cipher-sealed serializable closures in swarm_callback_deliveries and delivered at-least-once (never exactly-once — make them idempotent) by swarm:relay --type=callback; the stream catch() runs in-process. With the flag off, queued/durable then() / catch() throw BadMethodCallException exactly as before the feature existed, and SwarmCompleted / SwarmFailed remain the driver-agnostic path. See error-handling.md.

Topologies

  • Sequential: agents run in order; each output becomes the next input.
  • Parallel: agents run concurrently and each receives the original task. Parallel agents must be stateless, container-resolvable Laravel AI agents because Laravel concurrency serializes worker callbacks.
  • Hierarchical: the first agent is the coordinator. It must use Laravel AI structured output and return the full route plan. Laravel Swarm validates the plan as a DAG and executes worker, parallel, and finish nodes. In prompt(), parallel groups execute concurrently. In default queue() mode, hierarchical parallel groups execute sequentially in declaration order while retaining parallel-safe validation rules. Opt-in multi_worker uses branch jobs and a coordinated join. ResumeQueuedHierarchicalSwarm uses ConfiguresDurableAdvanceJob, not the ordinary queued-job retry profile.

For hierarchical routing, there is no separate route() callback. The coordinator is the single source of truth for what should run next. #[MaxAgentSteps] counts the coordinator plus reachable worker executions and fails before worker execution if the validated plan exceeds the limit.

Persistence, History, And Capture

Laravel Swarm persists run context, artifacts, and run history through configurable stores.

  • RunContext carries input, structured data, metadata, artifacts, and run ID control.
  • ContextStore, ArtifactRepository, RunHistoryStore, StreamEventStore, and DurableRunStore are the persistence contracts.
  • SwarmHistory provides application and console inspection over persisted runs.
  • Cache and database drivers are supported; database persistence uses package migrations loaded by the service provider.
  • Capture defaults — Shipped config/swarm.php defaults swarm.capture.inputs, outputs, artifacts, and active_context to false so prompts and agent payloads are not persisted unless the application opts in (SWARM_CAPTURE_* or config). Treat prompts, outputs, lifecycle events, streamed reasoning/tool payloads, and automatic step artifacts as sensitive whenever capture is enabled. With output capture off, streamed function-tool/reasoning payloads redact values to [redacted] while preserving keys where applicable. Under Redact, provider-tool payloads withhold the entire arbitrary structure, including keys; see provider-tool capture and bounds.
  • Encryption at rest (package scope) — When swarm.persistence.driver is database, swarm.persistence.encrypt_at_rest defaults to true. Designated sensitive string columns (for example context input, history final output and per-step I/O, durable branch I/O, hierarchical node outputs, child run outputs and nested context_payload input) are sealed with Laravel’s encrypter (APP_KEY), same primitive family as encrypted casts. Stored values use a sw0: prefix before the ciphertext so legacy plaintext rows still read correctly. Set SWARM_ENCRYPT_AT_REST=false only when you intentionally rely on database- or volume-level encryption instead. Rotating APP_KEY without re-encrypting leaves existing rows undecipherable; plan key rotation with your operational model. This is application-level sealing, not a claim of transparent database (TDE) encryption.
  • When encrypt-at-rest is enabled, RunHistoryStore::findMatching skips SQL JSON-path prefiltering on persisted context (randomized ciphertext cannot satisfy equality predicates in SQL); PHP-side PersistedRunContextMatcher still applies after rows are loaded.
  • Database retention is prune-based. Expired database rows remain queryable until swarm:prune runs, and active runs are protected from partial pruning.
  • run_id referential integrity is enforced at the database level for the history and durable child tables covered by the foreign-key migration: they carry ON DELETE CASCADE FKs to their parent (swarm_run_histories for the history family, swarm_durable_runs for the durable family). swarm_native_inputs.run_id and swarm_callback_deliveries.run_id are indexed without FKs and are pruned explicitly. parent_run_id (self-reference), signal_id, and webhook_idempotency.run_id use ON DELETE SET NULL; child_run_id has no FK.

Commands And Operations

Run php artisan list swarm (or read src/Commands) for the current command set — do not maintain a copy here, it goes stale. Operational notes that are not discoverable from --help:

  • Schedule swarm:prune for database-backed persistence, and schedule swarm:recover frequently when using durable execution. Neither runs on its own.
  • swarm:status and swarm:history include a Phase column; parallel_join means a hierarchical coordinated queue run is waiting on parallel branches.

Pulse

Pulse observability lives in the separate companion package builtbyberry/laravel-swarm-pulse (namespace BuiltByBerry\LaravelSwarmPulse\*), extracted from core in v0.17.1. Install it to get the SwarmRuns, SwarmStepDurations, and SwarmMemoryMetrics recorders and the swarm.runs / swarm.steps / swarm.audit-outbox / swarm.memory dashboard cards, registered by its swarm:install:pulse sub-installer. Core is no longer aware of Pulse and no longer depends on laravel/pulse; it retains only the shared swarm.pulse.memory.sample_rate config knob that the companion's memory-metrics recorder reads. See docs/pulse.md.

Pulse is aggregate observability. For live per-run operations feeds, listen to Laravel Swarm lifecycle events and broadcast application-owned events.

Key Architecture Decisions

  • Transaction boundaries — any code that coordinates two systems (DB + queue, DB + cache, two tables, outbox + checkpoint) must either execute inside a single transaction or explicitly document in the code why eventual consistency is acceptable. Flag this at design time. Do not discover it in review.
  • Do not use facades inside orchestration internals; inject services and contracts through the container.
  • ParallelRunner and hierarchical parallel execution use ConcurrencyManager from the container, not the facade directly.
  • SwarmRunner resolves attributes via reflection and falls back to config('swarm.*').
  • Runnable::make() returns mixed so a bound SwarmFake can be returned.
  • SwarmFake intercepts prompt(), run(), queue(), stream(), broadcast(), broadcastNow(), broadcastOnQueue(), and dispatchDurable() and records assertions. Stream fakes stay lazy and record only when iterated or consumed by broadcast helpers. Broadcast helpers reuse existing buckets: broadcast() / broadcastNow() satisfy assertStreamed(), and broadcastOnQueue() satisfies assertQueued().
  • Testing limits: SwarmFake::queue() records dispatch intent only; it does not execute SwarmRunner, simulate coordinated hierarchical multi_worker branch jobs, durable coordination rows, or ResumeQueuedHierarchicalSwarm. Validate that path with database-backed feature tests (see tests/Feature/QueuedHierarchicalParallelCoordinationTest.php).
  • Queued and parallel safety checks should fail before dispatch when a swarm or worker cannot be container-resolved safely.
  • Timeouts are best-effort orchestration deadlines. Ordinary modes check before and between steps. Process-backed top-level parallel streaming also enforces the absolute deadline while multiplexing and terminates local branch processes; neither path guarantees cancellation of remote provider work already accepted.
  • BuiltByBerry\LaravelSwarm\LaravelSwarm is the plain helper class for package-level static toggles and overrides (Cashier/Sanctum/Passport/Horizon/Telescope idiom). Add to it for things like service-provider opt-outs (ignoreMigrations()), Eloquent model overrides if the package adopts them, or first-party dashboard auth callbacks if one ships later. Do not mirror configuration that already lives in config/swarm.php — pick one home for each setting.

Review Method

Use multi-expert review for meaningful changes, especially streaming contracts, persistence/replay, migrations, security surfaces, and public API drift. Invoke the marshall:change-review skill rather than doing lens reviews inline — it runs the configured lenses against the component diff and records findings in the shared Marshall store. Release-level readiness is marshall:release-readiness.

Default review lenses:

  • Laravel maintainer: convention fit, DI patterns, contract shape, upgrade safety.
  • CTO: enterprise readiness, operational clarity, and long-term maintainability.
  • Engineering manager: blast radius, cohesion, rollback safety, and delivery slicing.
  • Security specialist: redaction/capture behavior, sensitive surfaces, and failure paths.
  • QA specialist: deterministic regression coverage, edge cases, and assertion resilience.
  • Docs engineer: behavior/docs/changelog parity and configuration clarity; apply the Definition of Done's Docblock truth boundary to cross-component docblock assertions.
  • Systems integrator: migration/config compatibility and deployment/runtime wiring risk.
  • Regulatory specialist: auditability, provenance requirements, and compliance evidence quality.

Required review output:

  • What passes.
  • Findings ordered by severity (high, medium, low).
  • Open gaps or follow-up items.
  • Release impact (blocker or non-blocker).
  • Consolidated verdict: approve, approve-with-followups, or changes-required.

Severity gate:

  • high: fix before release.
  • medium: fix now unless explicitly deferred with an owner.
  • low: may defer if documented.

When making an intentional tradeoff (for example replay provenance or redaction shape), record the chosen option, rejected option, rationale, and test/doc updates in the PR notes or changelog entry.

Release Workflow

Releases are session-shaped: a thematic group of work ships as one tagged release, not one issue per patch. The branching and PR layout below reflects what the v0.5.x and v0.6.x lines actually used; follow it for every release unless the maintainer says otherwise.

Long-lived release branch

  • Cut release/v<X.Y.Z> from main when a release session opens.
  • Topic branches target the release branch, not main. The release branch is the only branch that ever merges back into main.
  • Open the release with a chore(release): scaffold v<X.Y.Z> changelog entry commit so the changelog has a stub from day one.

Topic branches

<version> here is the bare number with no v prefix — 0.23.0, not v0.23.0. This differs from the release branch, which does carry the v (release/v0.23.0). CI enforces the topic-branch form and rejects the PR otherwise.

  • feat/<version>-<topic> — features.
  • fix/<version>-<topic> — bug fixes that ride a release session.
  • docs/<version>-<topic> — docs-only work.
  • test/<version>-<topic> — test-only work.
  • chore/<version>-<topic> — scoped chores.

Example: chore/0.23.0-tighten-minimum-stability into release/v0.23.0.

  • One topic per PR. PR titles end with the originating issue (#NN) when there is one.

Commits — Conventional Commits with a scope

  • Use feat(scope):, fix(scope):, docs(scope):, test(scope):, refactor(scope):, chore(scope):, build(scope):, ci:.
  • Scope tracks the area, not the version: audit, runner, pulse, release, etc.

Three-phase wrap (in order)

  1. chore/<v>-review-followups — addresses the multi-expert review findings. Commits are suffixed with the stable finding ID, e.g. (review F1), (review F2, F3). Finding numbers are assigned by the review and do not get renumbered.
  2. chore/<v>-release-wrap — fills in CHANGELOG.md and UPGRADING.md for the version. No behavior changes here.
  3. chore/<v>-readiness-followups — final readiness-gate fixes only. Commits suffixed (readiness F1), (readiness F2). If readiness surfaces anything larger than a small fix, send it back through review-followups instead.

Cutting the release

  • After the wrap phases land on the release branch, open the release/v<X.Y.Z> → main PR. Its title is release: v<X.Y.Z> — <one-line theme>.
  • Tag v<X.Y.Z> on main after that PR merges. Do not tag on the release branch.

Deferrals

  • A deferred finding must be recorded as a GitHub issue with an owner and a target version. Do not drop it into a TODO comment or a plan doc.

Known Gaps / Next Work

  • Commercial dashboard layer is a separate future repo/product. The package intentionally exposes history, events, and artifacts (with Pulse cards available via the builtbyberry/laravel-swarm-pulse companion package) instead of owning a full dashboard API.

Testing And Quality

From the package root:

composer test
composer lint
composer analyse
composer format

For parallel or hierarchical concurrency changes, also run composer test:process-concurrency (optional locally; skips cleanly when the real process driver cannot run). Continuous integration uses composer test:process-concurrency:ci, which fails the build if any test in that lane is skipped.

The package TestCase sets swarm.capture.* to true and swarm.persistence.encrypt_at_rest to false so the suite exercises full persisted payloads without coupling every test to the conservative production defaults.

If running phpstan directly, use:

vendor/bin/phpstan analyse --memory-limit=2G --no-progress

composer format rewrites files with Pint. Use composer lint when you need a non-mutating formatting check.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle