v0.9.0 — memory foundation
Memory Foundation — first-class scoped, snapshot-replayable memory subsystem with RunContext consolidation. Foundation for v0.10.0 propagation/operator surface and v0.11.0 memory-as-tool DX. Vector-backed recall ships as the laravel-swarm-memory-vector companion package.
Added
SwarmMemorycontract,DatabaseMemoryStore,CacheMemoryStore, andSwarmfacade integration (#108). Foundation of the v0.9.0 memory subsystem. Introduces theSwarmMemorycontract as the central read/write authority for scoped, key-value memory in a swarm run. Four memory scopes:MemoryScope::Run(bound to arunId, frozen for replay),MemoryScope::Conversation(spans multiple runs on the same conversation handle),MemoryScope::Agent(per-agent-class within a run), andMemoryScope::Swarm(shared across all runs of a swarm class). Two store implementations ship:DatabaseMemoryStore(persists toswarm_memoriesvia Eloquent, first-classMemoryEntryvalue objects, event dispatch) andCacheMemoryStore(Laravel Cache-backed ephemeral store, zero-migration path for smaller workloads). Both stores are injected withMemoryEventDispatcherto emit lifecycle events.SwarmMemoryis bound in the container as a singleton resolved bySwarmServiceProvider::resolvePersistenceStore()—databasedriver →DatabaseMemoryStore; any other driver →CacheMemoryStore.NullMemoryStoreships for test isolation. TheSwarmfacade gains amemory()helper returning the resolvedSwarmMemoryinstance.MemoryScope,MemoryEntry,MemoryKey, and related value objects form the type vocabulary used throughout the subsystem.- Two new database tables:
swarm_memoriesandswarm_memory_snapshots(#109, #110).swarm_memoriesstores individual scoped memory entries:scope(enum),scope_id(polymorphic owner),run_id(nullable FK →swarm_run_histories.run_idfor Run-scope cascade-delete on run completion),key,value(JSON),type(PHP type tag),expires_at, and timestamps.swarm_memory_snapshotscaptures frozen point-in-time views at agent invocation:run_id(FK →swarm_run_histories.run_id),step_index,snapshot(JSON array of entries),created_at. Composite unique on(run_id, step_index)ties each snapshot to exactly one step without a literal composite FK toswarm_run_steps. Both tables are published as standard package migrations and registered in theswarm.tablesconfig map. Runphp artisan migrateon the database persistence driver to add both tables. Theswarm:install:memorysub-installer verifies their presence at setup time. - Memory snapshot mechanism — frozen Run-scope view at agent invocation (#111).
DatabaseMemorySnapshotRecordercaptures a serialized snapshot of allMemoryScope::Runentries for a givenrunIdbefore each agent invocation and writes it toswarm_memory_snapshots. Wired into all four durable runners (DurableSequentialStepAdvancer,DurableBranchAdvancer,DurableHierarchicalCoordinator,DurableStaticHierarchicalCoordinator) via theSwarmMemorySnapshotRecorderbinding.NullSnapshotsMemoryships for non-durable and cache-backed paths; missing-table errors are narrowed toQueryExceptionon table-not-found and swallowed gracefully so non-durable runs are unaffected. - Durable replay reads from frozen snapshot —
MemoryReplayCoordinatorandReplaySwarmMemory(#112, #142). When a durable agent retries after a crash,MemoryReplayCoordinator::during()wraps the agent invocation and swaps the container'sSwarmMemorybinding to aReplaySwarmMemorydecorator — a frozen, read-only view of the snapshot recorded at the original invocation. The agent sees the sameMemoryScope::Runstate it saw before the crash, regardless of any memory mutations made between the failed attempt and the retry (for example, observability writes, operator corrections, or concurrent agents). The coordinator handles all three durable topologies:DurableBranchAdvancer(parallel),DurableSequentialStepAdvancer, andDurableHierarchicalCoordinator::runStep(). The frozen binding is always restored in afinallyblock for exception safety.ReplayModeenum controls the policy:FrozenView(default) — serve the frozen snapshot;FreshExecution— bypass the decorator and pass live memory through. Configure globally viaswarm.memory.replay_mode(envSWARM_MEMORY_REPLAY_MODE) or per-swarm via#[MemoryReplay(mode: ReplayMode::FreshExecution)].ReplayDriftExceptionandSnapshotFrozenExceptionship for v0.10.0 operator-surface and as programmer-safety guards on frozen-view writes. - Memory lifecycle events (#115). Four events dispatched by the store layer on every memory operation:
MemoryWritten(onput()),MemoryRead(onget()),MemoryForgotten(onforget(); carries anexistedflag so listeners can distinguish no-op forgets from actual removals), andMemorySnapshotted(on snapshot record). All four extendMemoryEventand carryscope,scopeId,key, and a timestamp. Subscribe via standard Laravel event listeners inEventServiceProviderorAppServiceProvider::boot(). Events are dispatched throughMemoryEventDispatcherinjected into both stores. php artisan swarm:install:memorysub-installer (#114). Targeted setup command for the v0.9.0 memory subsystem. Verifies theswarm_memoriesandswarm_memory_snapshotstables are present (offering to runphp artisan migrate --forcewhen they are absent, or warning when--skip-migrateis set); prints the effective memory driver (checksswarm.memory.driverfirst, falls back toswarm.persistence.driver, mirroringSwarmServiceProvider::resolvePersistenceStore()); prints the currentSWARM_MEMORY_REPLAY_MODEwith a one-line explainer so the operator confirms their replay strategy before shipping; and cross-links toswarm:health,docs/memory.md, and the#[MemoryReplay]attribute. Unlikeswarm:install:durable, a non-databasedriver produces a warning rather than a refusal —CacheMemoryStoreis a valid ephemeral workload.SWARM_MEMORY_REPLAY_MODE=frozen_viewis seeded into.envby the mainswarm:installorchestrator (not this sub-installer) as part of its global env-seeding pass. Flags:--migrate/--skip-migratefor non-interactive control;--no-interactiondefaults to skip-and-warn rather than migrate. Registered inSwarmServiceProvider; wired intoswarm:installas default-on (suppressible via--without-memory).SWARM_MEMORY_REPLAY_MODEadded toInstallCommand::ENV_DEFAULTS. Eight tests intests/Installer/InstallMemoryCommandTest.phpcover: success path, idempotency, cache-driver warning (exits 0), per-subsystemswarm.memory.driveroverride, missing-tables warning under--skip-migrate,--migrateflag runs migrations and reports tables present,--no-interactionskips without prompting, customreplay_modeoutput, and next-step hints. Documented indocs/getting-started.md(sub-installer list + non-interactive example) anddocs/advanced-setup.md(new## Set up Swarm Memorysection with replay-mode table and manual migration steps).docs/memory.md— Swarm Memory keystone reference (#116). New page covering the full memory subsystem: four-scope hierarchy (Run / Conversation / Agent / Swarm),SwarmMemorycontract read/write API with examples,MemoryEntryandMemoryScopevalue object reference, store drivers (DatabaseMemoryStore/CacheMemoryStore/ custom), all five lifecycle events (MemoryWritten,MemoryRead,MemoryForgotten,MemorySnapshotted,MemoryScopeOutOfSnapshot) with listener examples, snapshot mechanism andMemorySnapshotvalue object, replay semantics (frozen_viewvsfresh_execution) including the#[MemoryReplay]per-swarm attribute, the binding-restore constraint onMemoryReplayCoordinator::during(), and the RunContext ArrayAccess write-through bridge. Cross-linked fromREADME.md,docs/README.md,docs/getting-started.md, anddocs/advanced-setup.md.- Crash-resume replay-determinism test suite (#118).
tests/Feature/Memory/ReplayDeterminismTest.phpproves the frozen-snapshot guarantee end-to-end across all three durable topologies: Sequential, Parallel, and Hierarchical. Each test dispatches a durable swarm, writes a known value toMemoryScope::Run, lets the spy agent crash on attempt 1 (recording what it saw), mutates memory in between, time-travels past the retry backoff, recovers, and asserts the retry agent sees the original frozen value — not the mutated one. A fourth test covers theFreshExecutionopt-out anti-test (asserts the mutated value is visible underReplayMode::FreshExecution). 1,110 tests / 4,307 assertions green.
Changed
RunContextnow implementsArrayAccessand writes through toSwarmMemory(Run scope) (#113).RunContext::mergeData()— the path all internal mutation goes through — now mirrors each key intoSwarmMemoryviaput(MemoryScope::Run, $runId, $key, $value)in addition to updating the in-memory$dataarray.RunContextitself implementsArrayAccessso callers can use it as a direct memory accessor:$context['my-key'] = 'value'and$context['my-key']work transparently alongside the existingmergeData()and fluent builder API. The in-memory cache ($data) is retained for prompt-compose performance (hot path) and inter-step relays; write-through is silently skipped when noSwarmMemorybinding is present (null-bind tolerance — POPO test setups keep working). This change is non-breaking: an audit ofsrc/found zero direct$context->data['x'] = ...mutations — all internal state changes already route throughmergeData(). Consuming code requires no changes. The$dataand$metadataarrays remain as the primary in-memory state containers in v0.9.x; a future v1.0 release will promoteSwarmMemoryas the sole state source and remove them.
Full entry in the CHANGELOG.