Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion src/emergence/EmergenceHooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ export class EmergenceHooks {
private readonly archive: ArchivePlacement;
private readonly critic: EmergenceCritic;
private readonly fsAdapter: ArchiveEntriesFSAdapter;
/** Whether the live archive has been replayed from persisted entries yet (lazy, once). */
private hydrated = false;
private readonly archiveConfig: {
binsPerAxis?: number;
nearEliteCapacity?: number;
Expand Down Expand Up @@ -168,8 +170,28 @@ export class EmergenceHooks {
return { descriptor, lineage, signals, placement };
}

/** Get the archive for direct querying (used by CLI and Cortex). */
/**
* Get the archive for direct querying (used by CLI and Cortex). Lazily replays
* everything previous runs persisted into the LIVE archive on first access, so
* consumers (garden status/start/rebalance, lineage, stats) see accumulated history
* instead of a cold, empty archive — previously they read `this.archive` empty even
* with dozens of persisted cells, which floored garden health at the 10% "no data"
* default. The per-generation `onCreativeRun` path does NOT call getArchive(), so it
* stays lean (no replay on every generation).
*/
getArchive(): ArchivePlacement {
if (!this.hydrated) {
this.hydrated = true;
for (const entry of this.fsAdapter.readAllArchiveEntries()) {
this.archive.place({
artifactRef: entry.artifactRef,
descriptor: entry.descriptor,
lineage: entry.lineage,
qualityScore: entry.qualityScore,
signals: entry.signals,
});
}
}
return this.archive;
}

Expand Down
18 changes: 18 additions & 0 deletions test/unit/EmergenceHooks.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,24 @@ describe('EmergenceHooks', () => {
expect(result.placement.outcome).toBe('new-cell');
}, emergenceHooksTestTimeoutMs);

it('getArchive() hydrates persisted cells for a fresh instance (was a cold empty archive)', async () => {
// Persist a creative run via one instance.
const writer = new EmergenceHooks(liminalFs);
const run = await writer.onCreativeRun({
output: 'function draw() { background(10); rect(20, 20, 60, 60); }',
qualityScore: 0.85,
provenance: 'fresh-generation',
seed: 'persist-seed',
});
expect(run.placement.accepted).toBe(true);

// A fresh instance on the same store (the `garden status` / gardener scenario) must see
// the persisted cell through getArchive() — previously it read a cold, empty archive,
// which floored garden health at the 10% "no data" default.
const reader = new EmergenceHooks(SinterFS.open(tmpDir));
expect(reader.getArchive().getAllCells().length).toBe(1);
}, emergenceHooksTestTimeoutMs);

it('rejects low-quality artifacts', async () => {
const hooks = new EmergenceHooks(liminalFs, { minQuality: 0.5 });
const result = await hooks.onCreativeRun({
Expand Down