simfile 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +143 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +348 -0
- package/dist/coverage/index.d.ts +2 -0
- package/dist/coverage/index.d.ts.map +1 -0
- package/dist/coverage/index.js +1 -0
- package/dist/coverage/matrix.d.ts +21 -0
- package/dist/coverage/matrix.d.ts.map +1 -0
- package/dist/coverage/matrix.js +173 -0
- package/dist/coverage/render.d.ts +3 -0
- package/dist/coverage/render.d.ts.map +1 -0
- package/dist/coverage/render.js +26 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/kernel/duration.d.ts +9 -0
- package/dist/kernel/duration.d.ts.map +1 -0
- package/dist/kernel/duration.js +44 -0
- package/dist/kernel/index.d.ts +4 -0
- package/dist/kernel/index.d.ts.map +1 -0
- package/dist/kernel/index.js +3 -0
- package/dist/kernel/range.d.ts +6 -0
- package/dist/kernel/range.d.ts.map +1 -0
- package/dist/kernel/range.js +16 -0
- package/dist/kernel/stochastic.d.ts +10 -0
- package/dist/kernel/stochastic.d.ts.map +1 -0
- package/dist/kernel/stochastic.js +25 -0
- package/dist/ledger/index.d.ts +4 -0
- package/dist/ledger/index.d.ts.map +1 -0
- package/dist/ledger/index.js +3 -0
- package/dist/ledger/markers.d.ts +46 -0
- package/dist/ledger/markers.d.ts.map +1 -0
- package/dist/ledger/markers.js +108 -0
- package/dist/ledger/stable.d.ts +65 -0
- package/dist/ledger/stable.d.ts.map +1 -0
- package/dist/ledger/stable.js +78 -0
- package/dist/ledger/validation.d.ts +16 -0
- package/dist/ledger/validation.d.ts.map +1 -0
- package/dist/ledger/validation.js +127 -0
- package/dist/moltnet/index.d.ts +3 -0
- package/dist/moltnet/index.d.ts.map +1 -0
- package/dist/moltnet/index.js +2 -0
- package/dist/moltnet/transcript-export.d.ts +34 -0
- package/dist/moltnet/transcript-export.d.ts.map +1 -0
- package/dist/moltnet/transcript-export.js +68 -0
- package/dist/moltnet/world-participant.d.ts +77 -0
- package/dist/moltnet/world-participant.d.ts.map +1 -0
- package/dist/moltnet/world-participant.js +221 -0
- package/dist/observe/artifacts.d.ts +16 -0
- package/dist/observe/artifacts.d.ts.map +1 -0
- package/dist/observe/artifacts.js +31 -0
- package/dist/observe/causalStreams.d.ts +19 -0
- package/dist/observe/causalStreams.d.ts.map +1 -0
- package/dist/observe/causalStreams.js +38 -0
- package/dist/observe/compute.d.ts +17 -0
- package/dist/observe/compute.d.ts.map +1 -0
- package/dist/observe/compute.js +97 -0
- package/dist/observe/index.d.ts +11 -0
- package/dist/observe/index.d.ts.map +1 -0
- package/dist/observe/index.js +10 -0
- package/dist/observe/manifest.d.ts +79 -0
- package/dist/observe/manifest.d.ts.map +1 -0
- package/dist/observe/manifest.js +87 -0
- package/dist/observe/memoryBanks.d.ts +38 -0
- package/dist/observe/memoryBanks.d.ts.map +1 -0
- package/dist/observe/memoryBanks.js +86 -0
- package/dist/observe/observe.d.ts +32 -0
- package/dist/observe/observe.d.ts.map +1 -0
- package/dist/observe/observe.js +73 -0
- package/dist/observe/report.d.ts +165 -0
- package/dist/observe/report.d.ts.map +1 -0
- package/dist/observe/report.js +138 -0
- package/dist/observe/seedSpread.d.ts +49 -0
- package/dist/observe/seedSpread.d.ts.map +1 -0
- package/dist/observe/seedSpread.js +262 -0
- package/dist/observe/seedSpreadArtifacts.d.ts +49 -0
- package/dist/observe/seedSpreadArtifacts.d.ts.map +1 -0
- package/dist/observe/seedSpreadArtifacts.js +118 -0
- package/dist/observe/spreadMatcher.d.ts +45 -0
- package/dist/observe/spreadMatcher.d.ts.map +1 -0
- package/dist/observe/spreadMatcher.js +124 -0
- package/dist/report/index.d.ts +4 -0
- package/dist/report/index.d.ts.map +1 -0
- package/dist/report/index.js +3 -0
- package/dist/report/probeArtifact.d.ts +26 -0
- package/dist/report/probeArtifact.d.ts.map +1 -0
- package/dist/report/probeArtifact.js +119 -0
- package/dist/report/probes.d.ts +52 -0
- package/dist/report/probes.d.ts.map +1 -0
- package/dist/report/probes.js +164 -0
- package/dist/report/score.d.ts +27 -0
- package/dist/report/score.d.ts.map +1 -0
- package/dist/report/score.js +14 -0
- package/dist/report/transcripts.d.ts +12 -0
- package/dist/report/transcripts.d.ts.map +1 -0
- package/dist/report/transcripts.js +17 -0
- package/dist/runtime/causal-fixture.d.ts +26 -0
- package/dist/runtime/causal-fixture.d.ts.map +1 -0
- package/dist/runtime/causal-fixture.js +36 -0
- package/dist/runtime/clock.d.ts +25 -0
- package/dist/runtime/clock.d.ts.map +1 -0
- package/dist/runtime/clock.js +63 -0
- package/dist/runtime/condition.d.ts +55 -0
- package/dist/runtime/condition.d.ts.map +1 -0
- package/dist/runtime/condition.js +122 -0
- package/dist/runtime/emit-causal-fixture.d.ts +2 -0
- package/dist/runtime/emit-causal-fixture.d.ts.map +1 -0
- package/dist/runtime/emit-causal-fixture.js +45 -0
- package/dist/runtime/expression.d.ts +11 -0
- package/dist/runtime/expression.d.ts.map +1 -0
- package/dist/runtime/expression.js +305 -0
- package/dist/runtime/numeric.d.ts +7 -0
- package/dist/runtime/numeric.d.ts.map +1 -0
- package/dist/runtime/numeric.js +11 -0
- package/dist/runtime/rule-actions.d.ts +5 -0
- package/dist/runtime/rule-actions.d.ts.map +1 -0
- package/dist/runtime/rule-actions.js +82 -0
- package/dist/runtime/run-record.d.ts +73 -0
- package/dist/runtime/run-record.d.ts.map +1 -0
- package/dist/runtime/run-record.js +179 -0
- package/dist/runtime/step-tick.d.ts +43 -0
- package/dist/runtime/step-tick.d.ts.map +1 -0
- package/dist/runtime/step-tick.js +177 -0
- package/dist/runtime/trace-compile.d.ts +66 -0
- package/dist/runtime/trace-compile.d.ts.map +1 -0
- package/dist/runtime/trace-compile.js +157 -0
- package/dist/runtime/trace-export.d.ts +5 -0
- package/dist/runtime/trace-export.d.ts.map +1 -0
- package/dist/runtime/trace-export.js +4 -0
- package/dist/runtime/trace-run.d.ts +15 -0
- package/dist/runtime/trace-run.d.ts.map +1 -0
- package/dist/runtime/trace-run.js +92 -0
- package/dist/runtime/trace.d.ts +10 -0
- package/dist/runtime/trace.d.ts.map +1 -0
- package/dist/runtime/trace.js +9 -0
- package/dist/runtime/types.d.ts +61 -0
- package/dist/runtime/types.d.ts.map +1 -0
- package/dist/runtime/types.js +1 -0
- package/dist/runtime/viewer-trace.d.ts +64 -0
- package/dist/runtime/viewer-trace.d.ts.map +1 -0
- package/dist/runtime/viewer-trace.js +164 -0
- package/dist/runtime/world-act.d.ts +55 -0
- package/dist/runtime/world-act.d.ts.map +1 -0
- package/dist/runtime/world-act.js +156 -0
- package/dist/schema/binding.d.ts +23 -0
- package/dist/schema/binding.d.ts.map +1 -0
- package/dist/schema/binding.js +249 -0
- package/dist/schema/index.d.ts +5 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/index.js +4 -0
- package/dist/schema/model.d.ts +244 -0
- package/dist/schema/model.d.ts.map +1 -0
- package/dist/schema/model.js +222 -0
- package/dist/schema/parse.d.ts +10 -0
- package/dist/schema/parse.d.ts.map +1 -0
- package/dist/schema/parse.js +15 -0
- package/dist/schema/semantic.d.ts +3 -0
- package/dist/schema/semantic.d.ts.map +1 -0
- package/dist/schema/semantic.js +135 -0
- package/dist/sims/composeRunManifest.d.ts +54 -0
- package/dist/sims/composeRunManifest.d.ts.map +1 -0
- package/dist/sims/composeRunManifest.js +40 -0
- package/dist/sims/composedJungianSimDriver.d.ts +50 -0
- package/dist/sims/composedJungianSimDriver.d.ts.map +1 -0
- package/dist/sims/composedJungianSimDriver.js +199 -0
- package/dist/sims/composedOfficeSimDriver.d.ts +53 -0
- package/dist/sims/composedOfficeSimDriver.d.ts.map +1 -0
- package/dist/sims/composedOfficeSimDriver.js +162 -0
- package/dist/sims/exchangeWait.d.ts +56 -0
- package/dist/sims/exchangeWait.d.ts.map +1 -0
- package/dist/sims/exchangeWait.js +66 -0
- package/dist/sims/index.d.ts +14 -0
- package/dist/sims/index.d.ts.map +1 -0
- package/dist/sims/index.js +13 -0
- package/dist/sims/moltnetRoomClient.d.ts +47 -0
- package/dist/sims/moltnetRoomClient.d.ts.map +1 -0
- package/dist/sims/moltnetRoomClient.js +41 -0
- package/dist/sims/poll.d.ts +19 -0
- package/dist/sims/poll.d.ts.map +1 -0
- package/dist/sims/poll.js +23 -0
- package/dist/sims/spawnfileCli.d.ts +49 -0
- package/dist/sims/spawnfileCli.d.ts.map +1 -0
- package/dist/sims/spawnfileCli.js +90 -0
- package/dist/sims/spawnfileReceipts.d.ts +76 -0
- package/dist/sims/spawnfileReceipts.d.ts.map +1 -0
- package/dist/sims/spawnfileReceipts.js +107 -0
- package/dist/sims/worldDrivenOfficeSimDriver.d.ts +59 -0
- package/dist/sims/worldDrivenOfficeSimDriver.d.ts.map +1 -0
- package/dist/sims/worldDrivenOfficeSimDriver.js +182 -0
- package/dist/sims/worldLedgerWriter.d.ts +32 -0
- package/dist/sims/worldLedgerWriter.d.ts.map +1 -0
- package/dist/sims/worldLedgerWriter.js +39 -0
- package/dist/sims/worldSeedLint.d.ts +42 -0
- package/dist/sims/worldSeedLint.d.ts.map +1 -0
- package/dist/sims/worldSeedLint.js +73 -0
- package/dist/sims/worldTickIngest.d.ts +27 -0
- package/dist/sims/worldTickIngest.d.ts.map +1 -0
- package/dist/sims/worldTickIngest.js +35 -0
- package/dist/sims/worldTickLoop.d.ts +64 -0
- package/dist/sims/worldTickLoop.d.ts.map +1 -0
- package/dist/sims/worldTickLoop.js +122 -0
- package/dist/view/engineProvenance.d.ts +46 -0
- package/dist/view/engineProvenance.d.ts.map +1 -0
- package/dist/view/engineProvenance.js +76 -0
- package/dist/view/index.d.ts +14 -0
- package/dist/view/index.d.ts.map +1 -0
- package/dist/view/index.js +107 -0
- package/dist/view/membranes.d.ts +14 -0
- package/dist/view/membranes.d.ts.map +1 -0
- package/dist/view/membranes.js +127 -0
- package/dist/view/runDetect.d.ts +2 -0
- package/dist/view/runDetect.d.ts.map +1 -0
- package/dist/view/runDetect.js +47 -0
- package/dist/view/runRawArtifacts.d.ts +48 -0
- package/dist/view/runRawArtifacts.d.ts.map +1 -0
- package/dist/view/runRawArtifacts.js +143 -0
- package/dist/view/runTimeline.d.ts +24 -0
- package/dist/view/runTimeline.d.ts.map +1 -0
- package/dist/view/runTimeline.js +204 -0
- package/dist/view/runTimelineRecords.d.ts +42 -0
- package/dist/view/runTimelineRecords.d.ts.map +1 -0
- package/dist/view/runTimelineRecords.js +244 -0
- package/dist/view/runTimelineRefs.d.ts +27 -0
- package/dist/view/runTimelineRefs.d.ts.map +1 -0
- package/dist/view/runTimelineRefs.js +31 -0
- package/dist/view/runTimelineTypes.d.ts +99 -0
- package/dist/view/runTimelineTypes.d.ts.map +1 -0
- package/dist/view/runTimelineTypes.js +1 -0
- package/dist/view/runViewModel.d.ts +21 -0
- package/dist/view/runViewModel.d.ts.map +1 -0
- package/dist/view/runViewModel.js +66 -0
- package/dist/view/runViewModelCompute.d.ts +24 -0
- package/dist/view/runViewModelCompute.d.ts.map +1 -0
- package/dist/view/runViewModelCompute.js +157 -0
- package/dist/view/runViewModelTypes.d.ts +177 -0
- package/dist/view/runViewModelTypes.d.ts.map +1 -0
- package/dist/view/runViewModelTypes.js +1 -0
- package/dist/view/runWorldTrace.d.ts +97 -0
- package/dist/view/runWorldTrace.d.ts.map +1 -0
- package/dist/view/runWorldTrace.js +88 -0
- package/dist/view/server.d.ts +12 -0
- package/dist/view/server.d.ts.map +1 -0
- package/dist/view/server.js +306 -0
- package/package.json +65 -0
- package/web/dist/assets/index-CJHLjfsG.js +49 -0
- package/web/dist/assets/index-DJOsPsU8.css +1 -0
- package/web/dist/index.html +13 -0
- package/web/dist/models/AnimatedMushnub.glb +0 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { exportMoltnetTranscript, roomTranscriptTargetFromScope } from "../moltnet/transcript-export.js";
|
|
4
|
+
import { parseSimfileSource } from "../schema/parse.js";
|
|
5
|
+
import { parseClockSpec } from "../runtime/clock.js";
|
|
6
|
+
import { compileRuntime } from "../runtime/trace-compile.js";
|
|
7
|
+
import { clampAndRound } from "../runtime/numeric.js";
|
|
8
|
+
import { composeRunManifest, sha256HexOfBuffer } from "./composeRunManifest.js";
|
|
9
|
+
import { DEFAULT_QUIET_GRACE_MS, resolveTargetTurns } from "./exchangeWait.js";
|
|
10
|
+
import { allMembersConnected, getMoltnetRoom, listMoltnetAgents, moltnetHealthy } from "./moltnetRoomClient.js";
|
|
11
|
+
import { defaultSleep, pollUntilReady } from "./poll.js";
|
|
12
|
+
import { runSpawnfileArtifactsExport, runSpawnfileDown, runSpawnfileUp } from "./spawnfileCli.js";
|
|
13
|
+
import { createWorldLedgerWriter } from "./worldLedgerWriter.js";
|
|
14
|
+
import { assertWorldRuleContentClean, buildSeedDeclarationFromMemoryDoc } from "./worldSeedLint.js";
|
|
15
|
+
import { runLiveTickLoop } from "./worldTickLoop.js";
|
|
16
|
+
const DEFAULT_NETWORK_ID = "office_lab";
|
|
17
|
+
const DEFAULT_ROOM_ID = "office-room";
|
|
18
|
+
const DEFAULT_AGENT_IDS = ["eleanor", "sam"];
|
|
19
|
+
const DEFAULT_SEED_AGENT_ID = "eleanor";
|
|
20
|
+
const DEFAULT_TOKEN_SET = ["Rosa Delgado"];
|
|
21
|
+
const DEFAULT_TIMEOUT_MS = 240_000;
|
|
22
|
+
const DEFAULT_POLL_INTERVAL_MS = 2_000;
|
|
23
|
+
const DEFAULT_MAX_TICKS = 200;
|
|
24
|
+
const collapseEngineDisclosure = (engines) => {
|
|
25
|
+
const distinct = [...new Set((engines ?? []).map((entry) => entry.engine))].sort();
|
|
26
|
+
return distinct.length === 0 ? "unknown" : distinct.join("+");
|
|
27
|
+
};
|
|
28
|
+
const waitForMoltnetReady = async (baseUrl, roomId, agentIds, pollOptions) => {
|
|
29
|
+
await pollUntilReady("moltnet health", pollOptions, async () => ((await moltnetHealthy(baseUrl)) ? true : null));
|
|
30
|
+
await pollUntilReady(`moltnet room ${roomId} membership`, pollOptions, async () => {
|
|
31
|
+
const room = await getMoltnetRoom(baseUrl, roomId);
|
|
32
|
+
const expected = [...agentIds].sort();
|
|
33
|
+
const actual = [...room.members].sort();
|
|
34
|
+
return expected.length === actual.length && expected.every((id, index) => actual[index] === id) ? room : null;
|
|
35
|
+
});
|
|
36
|
+
await pollUntilReady(`moltnet bridge attachments for ${roomId}`, pollOptions, async () => {
|
|
37
|
+
const agents = await listMoltnetAgents(baseUrl);
|
|
38
|
+
return allMembersConnected(agents, roomId, agentIds) ? agents : null;
|
|
39
|
+
});
|
|
40
|
+
};
|
|
41
|
+
const relativeArtifactPath = (runDir, absolutePath) => path.relative(runDir, absolutePath).split(path.sep).join("/");
|
|
42
|
+
const hashedArtifact = async (runDir, absolutePath) => ({
|
|
43
|
+
path: relativeArtifactPath(runDir, absolutePath),
|
|
44
|
+
sha256: sha256HexOfBuffer(await readFile(absolutePath))
|
|
45
|
+
});
|
|
46
|
+
const writeTranscriptArtifact = async (runDir, transcript) => {
|
|
47
|
+
const absolutePath = path.join(runDir, "raw", "moltnet", "transcript.json");
|
|
48
|
+
await writeFile(absolutePath, `${JSON.stringify(transcript, null, 2)}\n`, "utf8");
|
|
49
|
+
return hashedArtifact(runDir, absolutePath);
|
|
50
|
+
};
|
|
51
|
+
export const runWorldDrivenOfficeSim = async (options) => {
|
|
52
|
+
const cliContext = { spawnfileBin: options.spawnfileBin };
|
|
53
|
+
const networkId = options.networkId ?? DEFAULT_NETWORK_ID;
|
|
54
|
+
const roomId = options.roomId ?? DEFAULT_ROOM_ID;
|
|
55
|
+
const agentIds = options.agentIds ?? DEFAULT_AGENT_IDS;
|
|
56
|
+
const seedAgentId = options.seedAgentId ?? DEFAULT_SEED_AGENT_ID;
|
|
57
|
+
const tokenSet = options.tokenSet ?? DEFAULT_TOKEN_SET;
|
|
58
|
+
const memoryDocPath = options.memoryDocPath ?? path.join(options.orgPath, "agents", seedAgentId, "MEMORY.md");
|
|
59
|
+
const targetTurns = resolveTargetTurns(options.targetTurns);
|
|
60
|
+
const quietGraceMs = options.quietGraceMs ?? DEFAULT_QUIET_GRACE_MS;
|
|
61
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
62
|
+
const pollOptions = {
|
|
63
|
+
intervalMs: options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS,
|
|
64
|
+
sleep: defaultSleep,
|
|
65
|
+
timeoutMs
|
|
66
|
+
};
|
|
67
|
+
const maxTicks = options.maxTicks ?? DEFAULT_MAX_TICKS;
|
|
68
|
+
const now = options.now ?? (() => new Date());
|
|
69
|
+
const worldSource = await readFile(options.worldPath, "utf8");
|
|
70
|
+
const { simfile } = parseSimfileSource(worldSource, { path: options.worldPath });
|
|
71
|
+
assertWorldRuleContentClean(simfile, tokenSet);
|
|
72
|
+
const upReceipt = await runSpawnfileUp(cliContext, {
|
|
73
|
+
orgPath: options.orgPath,
|
|
74
|
+
containerName: options.containerName,
|
|
75
|
+
deploymentName: options.deploymentName,
|
|
76
|
+
compiledOutputDirectory: options.compiledOutputDirectory
|
|
77
|
+
});
|
|
78
|
+
if (!upReceipt.run_id) {
|
|
79
|
+
throw new Error("spawnfile up-receipt had no run_id; cannot assemble a run directory");
|
|
80
|
+
}
|
|
81
|
+
const baseUrl = upReceipt.readiness.moltnet_base_url;
|
|
82
|
+
if (!baseUrl) {
|
|
83
|
+
throw new Error("spawnfile up-receipt had no moltnet_base_url; is human_ingress enabled on the network?");
|
|
84
|
+
}
|
|
85
|
+
const runId = upReceipt.run_id;
|
|
86
|
+
const runDir = path.join(options.runsRootDirectory, runId);
|
|
87
|
+
const createdAt = now().toISOString();
|
|
88
|
+
let loopResult;
|
|
89
|
+
try {
|
|
90
|
+
await waitForMoltnetReady(baseUrl, roomId, agentIds, pollOptions);
|
|
91
|
+
const clock = parseClockSpec(simfile.clock);
|
|
92
|
+
const runtime = compileRuntime(simfile);
|
|
93
|
+
const precision = 6;
|
|
94
|
+
const state = {};
|
|
95
|
+
for (const [id, value] of Object.entries(runtime.initialState)) {
|
|
96
|
+
const range = runtime.ranges.get(id);
|
|
97
|
+
if (!range) {
|
|
98
|
+
throw new Error(`variable ${id} missing range`);
|
|
99
|
+
}
|
|
100
|
+
state[id] = clampAndRound(value, range, precision);
|
|
101
|
+
}
|
|
102
|
+
const ledgerWriter = await createWorldLedgerWriter(runDir, runId);
|
|
103
|
+
loopResult = await runLiveTickLoop({
|
|
104
|
+
baseUrl,
|
|
105
|
+
networkId,
|
|
106
|
+
roomId,
|
|
107
|
+
roomScope: `room:${networkId}:${roomId}`,
|
|
108
|
+
agentIds,
|
|
109
|
+
clock,
|
|
110
|
+
runtime,
|
|
111
|
+
simfile,
|
|
112
|
+
state,
|
|
113
|
+
precision,
|
|
114
|
+
runId,
|
|
115
|
+
seed: simfile.clock.seed,
|
|
116
|
+
ledgerWriter,
|
|
117
|
+
pollOptions,
|
|
118
|
+
targetTurns,
|
|
119
|
+
quietGraceMs,
|
|
120
|
+
timeoutMs,
|
|
121
|
+
maxTicks
|
|
122
|
+
});
|
|
123
|
+
const exportResult = await runSpawnfileArtifactsExport(cliContext, {
|
|
124
|
+
orgPath: options.orgPath,
|
|
125
|
+
deploymentName: options.deploymentName,
|
|
126
|
+
compiledOutputDirectory: options.compiledOutputDirectory,
|
|
127
|
+
destinationDirectory: runDir
|
|
128
|
+
});
|
|
129
|
+
const transcript = await exportMoltnetTranscript({
|
|
130
|
+
baseUrl,
|
|
131
|
+
targets: [roomTranscriptTargetFromScope(`room:${networkId}:${roomId}`)]
|
|
132
|
+
});
|
|
133
|
+
const transcriptArtifact = await writeTranscriptArtifact(runDir, transcript);
|
|
134
|
+
await runSpawnfileDown(cliContext, {
|
|
135
|
+
orgPath: options.orgPath,
|
|
136
|
+
deploymentName: options.deploymentName,
|
|
137
|
+
compiledOutputDirectory: options.compiledOutputDirectory,
|
|
138
|
+
removeVolumes: options.removeVolumesOnDown ?? true
|
|
139
|
+
});
|
|
140
|
+
const worldLedgerWriter = ledgerWriter;
|
|
141
|
+
const worldArtifacts = await Promise.all([
|
|
142
|
+
hashedArtifact(runDir, worldLedgerWriter.causalJsonlPath),
|
|
143
|
+
hashedArtifact(runDir, worldLedgerWriter.telemetryJsonPath),
|
|
144
|
+
hashedArtifact(runDir, worldLedgerWriter.ingestedMessagesPath)
|
|
145
|
+
]);
|
|
146
|
+
const seedDeclaration = await buildSeedDeclarationFromMemoryDoc({
|
|
147
|
+
memoryDocPath,
|
|
148
|
+
tokenSet,
|
|
149
|
+
seedAgent: seedAgentId,
|
|
150
|
+
seedEpoch: createdAt
|
|
151
|
+
});
|
|
152
|
+
const world = { network_id: networkId, room_id: roomId, members: [...agentIds].sort() };
|
|
153
|
+
const manifest = composeRunManifest({
|
|
154
|
+
runId,
|
|
155
|
+
createdAt,
|
|
156
|
+
fingerprint: upReceipt.fingerprint,
|
|
157
|
+
engine: collapseEngineDisclosure(upReceipt.engines),
|
|
158
|
+
world,
|
|
159
|
+
exportedArtifacts: exportResult.index.files,
|
|
160
|
+
extraArtifacts: [transcriptArtifact, ...worldArtifacts],
|
|
161
|
+
seedDeclaration
|
|
162
|
+
});
|
|
163
|
+
await writeFile(path.join(runDir, "manifest.json"), `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
await runSpawnfileDown(cliContext, {
|
|
167
|
+
orgPath: options.orgPath,
|
|
168
|
+
deploymentName: options.deploymentName,
|
|
169
|
+
compiledOutputDirectory: options.compiledOutputDirectory,
|
|
170
|
+
removeVolumes: options.removeVolumesOnDown ?? true
|
|
171
|
+
}).catch(() => undefined);
|
|
172
|
+
throw error;
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
runDir,
|
|
176
|
+
runId,
|
|
177
|
+
endedReason: loopResult.endedReason,
|
|
178
|
+
ticksRun: loopResult.ticksRun,
|
|
179
|
+
markerFired: loopResult.markerFired,
|
|
180
|
+
agentTurnCount: loopResult.agentTurnCount
|
|
181
|
+
};
|
|
182
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { RuntimeTraceEvent, RuntimeVariableSample } from "../runtime/types.js";
|
|
2
|
+
export interface WorldLedgerTickInput {
|
|
3
|
+
tick: number;
|
|
4
|
+
tickEvents: readonly RuntimeTraceEvent[];
|
|
5
|
+
sample: RuntimeVariableSample;
|
|
6
|
+
/** Every Moltnet room message id folded into this tick — the determinism
|
|
7
|
+
* contract: without recording exactly which messages a tick ingested,
|
|
8
|
+
* "the world replays byte-identically" would be unfalsifiable. */
|
|
9
|
+
ingestedMessageIds: readonly string[];
|
|
10
|
+
}
|
|
11
|
+
export interface WorldLedgerWriter {
|
|
12
|
+
causalJsonlPath: string;
|
|
13
|
+
telemetryJsonPath: string;
|
|
14
|
+
ingestedMessagesPath: string;
|
|
15
|
+
appendTick: (input: WorldLedgerTickInput) => Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Live-loop counterpart of `run-record.ts`'s batch artifact writer, scoped
|
|
19
|
+
* to just the world authority's own stream: appends every tick's minted
|
|
20
|
+
* events to `raw/world/causal.jsonl` in the `noopolis.causal-event.v1` wire
|
|
21
|
+
* shape (via `causal-fixture.ts`'s `toCausalFixtureRecord` — the same
|
|
22
|
+
* mapping B92's conformance harness already validates), grows
|
|
23
|
+
* `world/telemetry.json` with each tick's variable sample, and appends the
|
|
24
|
+
* tick's ingested-message-id list to `world/ingested-messages.jsonl`. All
|
|
25
|
+
* three are real appends (one `appendFile`/rewrite per tick call, not a
|
|
26
|
+
* single write at the end) so the live loop's own on-disk ledger reflects
|
|
27
|
+
* ticks as they happen, matching what the composed driver is meant to be
|
|
28
|
+
* doing here: driving the run, not just producing a side artifact after
|
|
29
|
+
* the fact.
|
|
30
|
+
*/
|
|
31
|
+
export declare const createWorldLedgerWriter: (runDir: string, runId: string) => Promise<WorldLedgerWriter>;
|
|
32
|
+
//# sourceMappingURL=worldLedgerWriter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worldLedgerWriter.d.ts","sourceRoot":"","sources":["../../src/sims/worldLedgerWriter.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAEpF,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,SAAS,iBAAiB,EAAE,CAAC;IACzC,MAAM,EAAE,qBAAqB,CAAC;IAC9B;;sEAEkE;IAClE,kBAAkB,EAAE,SAAS,MAAM,EAAE,CAAC;CACvC;AAED,MAAM,WAAW,iBAAiB;IAChC,eAAe,EAAE,MAAM,CAAC;IACxB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,UAAU,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5D;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,uBAAuB,GAAU,QAAQ,MAAM,EAAE,OAAO,MAAM,KAAG,OAAO,CAAC,iBAAiB,CAoCtG,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { appendFile, mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { buildCausalFixtureRecords } from "../runtime/causal-fixture.js";
|
|
4
|
+
/**
|
|
5
|
+
* Live-loop counterpart of `run-record.ts`'s batch artifact writer, scoped
|
|
6
|
+
* to just the world authority's own stream: appends every tick's minted
|
|
7
|
+
* events to `raw/world/causal.jsonl` in the `noopolis.causal-event.v1` wire
|
|
8
|
+
* shape (via `causal-fixture.ts`'s `toCausalFixtureRecord` — the same
|
|
9
|
+
* mapping B92's conformance harness already validates), grows
|
|
10
|
+
* `world/telemetry.json` with each tick's variable sample, and appends the
|
|
11
|
+
* tick's ingested-message-id list to `world/ingested-messages.jsonl`. All
|
|
12
|
+
* three are real appends (one `appendFile`/rewrite per tick call, not a
|
|
13
|
+
* single write at the end) so the live loop's own on-disk ledger reflects
|
|
14
|
+
* ticks as they happen, matching what the composed driver is meant to be
|
|
15
|
+
* doing here: driving the run, not just producing a side artifact after
|
|
16
|
+
* the fact.
|
|
17
|
+
*/
|
|
18
|
+
export const createWorldLedgerWriter = async (runDir, runId) => {
|
|
19
|
+
const worldRawDir = path.join(runDir, "raw", "world");
|
|
20
|
+
const worldDir = path.join(runDir, "world");
|
|
21
|
+
await mkdir(worldRawDir, { recursive: true });
|
|
22
|
+
await mkdir(worldDir, { recursive: true });
|
|
23
|
+
const causalJsonlPath = path.join(worldRawDir, "causal.jsonl");
|
|
24
|
+
const telemetryJsonPath = path.join(worldDir, "telemetry.json");
|
|
25
|
+
const ingestedMessagesPath = path.join(worldDir, "ingested-messages.jsonl");
|
|
26
|
+
await writeFile(causalJsonlPath, "", "utf8");
|
|
27
|
+
await writeFile(ingestedMessagesPath, "", "utf8");
|
|
28
|
+
const samples = [];
|
|
29
|
+
const appendTick = async (input) => {
|
|
30
|
+
if (input.tickEvents.length > 0) {
|
|
31
|
+
const lines = buildCausalFixtureRecords(input.tickEvents).map((record) => JSON.stringify(record));
|
|
32
|
+
await appendFile(causalJsonlPath, `${lines.join("\n")}\n`, "utf8");
|
|
33
|
+
}
|
|
34
|
+
samples.push(input.sample);
|
|
35
|
+
await writeFile(telemetryJsonPath, `${JSON.stringify({ run_id: runId, samples, version: "simfile.telemetry.v1" }, null, 2)}\n`, "utf8");
|
|
36
|
+
await appendFile(ingestedMessagesPath, `${JSON.stringify({ tick: input.tick, message_ids: [...input.ingestedMessageIds] })}\n`, "utf8");
|
|
37
|
+
};
|
|
38
|
+
return { causalJsonlPath, telemetryJsonPath, ingestedMessagesPath, appendTick };
|
|
39
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { SeedDeclaration } from "../observe/manifest.js";
|
|
2
|
+
import type { Simfile } from "../schema/model.js";
|
|
3
|
+
/**
|
|
4
|
+
* Every `moltnet:message`/`moltnet:dm` action content string across every
|
|
5
|
+
* world rule (the kickoff included) — the full surface the token lint below
|
|
6
|
+
* checks. Kept as its own export so a test can assert the lint actually
|
|
7
|
+
* looked at every rule, not just the one named `kickoff`.
|
|
8
|
+
*/
|
|
9
|
+
export declare const worldRuleMessageContents: (simfile: Simfile) => {
|
|
10
|
+
ruleId: string;
|
|
11
|
+
content: string;
|
|
12
|
+
}[];
|
|
13
|
+
/**
|
|
14
|
+
* Lints every world rule's message content against the memetics
|
|
15
|
+
* `token_set` at run start. A hit here means the WORLD ITSELF would speak a
|
|
16
|
+
* seed token — if that ever happened, any later `marker.seen` on that token
|
|
17
|
+
* would be meaningless (the world can't measure spread of a fact it just
|
|
18
|
+
* announced itself). Returns violation strings; an empty array means the
|
|
19
|
+
* world is clean. Case-insensitive, same as the marker matcher
|
|
20
|
+
* (`../ledger/markers.ts`'s `containsAlias`).
|
|
21
|
+
*/
|
|
22
|
+
export declare const lintWorldRuleContentAgainstTokenSet: (simfile: Simfile, tokenSet: readonly string[]) => string[];
|
|
23
|
+
/** Throws with every violation listed if the world would leak a seed token. */
|
|
24
|
+
export declare const assertWorldRuleContentClean: (simfile: Simfile, tokenSet: readonly string[]) => void;
|
|
25
|
+
export interface BuildSeedDeclarationOptions {
|
|
26
|
+
/** Absolute path to the seed agent's doc-seeded memory file. */
|
|
27
|
+
memoryDocPath: string;
|
|
28
|
+
tokenSet: readonly string[];
|
|
29
|
+
seedAgent: string;
|
|
30
|
+
seedEpoch: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Builds the `seed_declaration` manifest field (Decision-13) from the
|
|
34
|
+
* agent's own `workspace.docs.memory` file rather than hand-typing the
|
|
35
|
+
* secret line's hash twice: locates the one line containing every
|
|
36
|
+
* `token_set` entry, hashes that exact (trimmed) line, and stamps
|
|
37
|
+
* `matcher_policy: "exact"` / `entry_channel: "doc-seeded"`. Ground truth
|
|
38
|
+
* for the seed lives in this manifest field, never inferred from the
|
|
39
|
+
* substrate — see `../observe/manifest.ts`'s `SeedDeclaration` doc comment.
|
|
40
|
+
*/
|
|
41
|
+
export declare const buildSeedDeclarationFromMemoryDoc: (options: BuildSeedDeclarationOptions) => Promise<SeedDeclaration>;
|
|
42
|
+
//# sourceMappingURL=worldSeedLint.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worldSeedLint.d.ts","sourceRoot":"","sources":["../../src/sims/worldSeedLint.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAIlD;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,GAAI,SAAS,OAAO,KAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,EAU9F,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,mCAAmC,GAC9C,SAAS,OAAO,EAChB,UAAU,SAAS,MAAM,EAAE,KAC1B,MAAM,EAWR,CAAC;AAEF,+EAA+E;AAC/E,eAAO,MAAM,2BAA2B,GAAI,SAAS,OAAO,EAAE,UAAU,SAAS,MAAM,EAAE,KAAG,IAK3F,CAAC;AAEF,MAAM,WAAW,2BAA2B;IAC1C,gEAAgE;IAChE,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,iCAAiC,GAC5C,SAAS,2BAA2B,KACnC,OAAO,CAAC,eAAe,CAkBzB,CAAC"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
const sha256Hex = (value) => createHash("sha256").update(value, "utf8").digest("hex");
|
|
4
|
+
/**
|
|
5
|
+
* Every `moltnet:message`/`moltnet:dm` action content string across every
|
|
6
|
+
* world rule (the kickoff included) — the full surface the token lint below
|
|
7
|
+
* checks. Kept as its own export so a test can assert the lint actually
|
|
8
|
+
* looked at every rule, not just the one named `kickoff`.
|
|
9
|
+
*/
|
|
10
|
+
export const worldRuleMessageContents = (simfile) => {
|
|
11
|
+
const contents = [];
|
|
12
|
+
for (const [ruleId, rule] of Object.entries(simfile.rules)) {
|
|
13
|
+
for (const action of rule.do) {
|
|
14
|
+
if (action.action === "moltnet:message" || action.action === "moltnet:dm") {
|
|
15
|
+
contents.push({ ruleId, content: action.content });
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return contents;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Lints every world rule's message content against the memetics
|
|
23
|
+
* `token_set` at run start. A hit here means the WORLD ITSELF would speak a
|
|
24
|
+
* seed token — if that ever happened, any later `marker.seen` on that token
|
|
25
|
+
* would be meaningless (the world can't measure spread of a fact it just
|
|
26
|
+
* announced itself). Returns violation strings; an empty array means the
|
|
27
|
+
* world is clean. Case-insensitive, same as the marker matcher
|
|
28
|
+
* (`../ledger/markers.ts`'s `containsAlias`).
|
|
29
|
+
*/
|
|
30
|
+
export const lintWorldRuleContentAgainstTokenSet = (simfile, tokenSet) => {
|
|
31
|
+
const violations = [];
|
|
32
|
+
for (const { ruleId, content } of worldRuleMessageContents(simfile)) {
|
|
33
|
+
const lowerContent = content.toLowerCase();
|
|
34
|
+
for (const token of tokenSet) {
|
|
35
|
+
if (lowerContent.includes(token.toLowerCase())) {
|
|
36
|
+
violations.push(`rule "${ruleId}" message content contains seed token "${token}": ${content}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return violations;
|
|
41
|
+
};
|
|
42
|
+
/** Throws with every violation listed if the world would leak a seed token. */
|
|
43
|
+
export const assertWorldRuleContentClean = (simfile, tokenSet) => {
|
|
44
|
+
const violations = lintWorldRuleContentAgainstTokenSet(simfile, tokenSet);
|
|
45
|
+
if (violations.length > 0) {
|
|
46
|
+
throw new Error(`world rule content lint failed — the world would emit a seed token itself:\n${violations.join("\n")}`);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Builds the `seed_declaration` manifest field (Decision-13) from the
|
|
51
|
+
* agent's own `workspace.docs.memory` file rather than hand-typing the
|
|
52
|
+
* secret line's hash twice: locates the one line containing every
|
|
53
|
+
* `token_set` entry, hashes that exact (trimmed) line, and stamps
|
|
54
|
+
* `matcher_policy: "exact"` / `entry_channel: "doc-seeded"`. Ground truth
|
|
55
|
+
* for the seed lives in this manifest field, never inferred from the
|
|
56
|
+
* substrate — see `../observe/manifest.ts`'s `SeedDeclaration` doc comment.
|
|
57
|
+
*/
|
|
58
|
+
export const buildSeedDeclarationFromMemoryDoc = async (options) => {
|
|
59
|
+
const text = await readFile(options.memoryDocPath, "utf8");
|
|
60
|
+
const lines = text.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
|
|
61
|
+
const seedLine = lines.find((line) => options.tokenSet.every((token) => line.includes(token)));
|
|
62
|
+
if (!seedLine) {
|
|
63
|
+
throw new Error(`no line in ${options.memoryDocPath} contains every token_set entry (${options.tokenSet.join(", ")})`);
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
content_hash: sha256Hex(seedLine),
|
|
67
|
+
token_set: [...options.tokenSet],
|
|
68
|
+
matcher_policy: "exact",
|
|
69
|
+
seed_agent: options.seedAgent,
|
|
70
|
+
seed_epoch: options.seedEpoch,
|
|
71
|
+
entry_channel: "doc-seeded"
|
|
72
|
+
};
|
|
73
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { LedgerEvent } from "../ledger/markers.js";
|
|
2
|
+
import type { MoltnetRoomMessage } from "./moltnetRoomClient.js";
|
|
3
|
+
/**
|
|
4
|
+
* Lowers one Moltnet room message to the `LedgerEvent` shape `scanMarkers`
|
|
5
|
+
* (`../ledger/markers.ts`) expects, so the live world-driven loop can scan
|
|
6
|
+
* real agent utterances for marker tokens exactly like the batch
|
|
7
|
+
* `runSimfileTrace` scans its own minted events. `kind` here
|
|
8
|
+
* ("moltnet.message") is a display-only label for this ingestion path — it
|
|
9
|
+
* is never merged into `SimfileEventKind`'s own kind space and never minted
|
|
10
|
+
* as a world-ledger event itself; only the `marker.seen` event a hit
|
|
11
|
+
* produces gets written to `raw/world/causal.jsonl`.
|
|
12
|
+
*/
|
|
13
|
+
export declare const moltnetMessageToLedgerEvent: (message: MoltnetRoomMessage, roomScope: string, simTime: number) => LedgerEvent;
|
|
14
|
+
export interface IngestNewRoomMessagesResult {
|
|
15
|
+
newMessages: MoltnetRoomMessage[];
|
|
16
|
+
ingestedIds: string[];
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Fetches the room's current message list and returns only the messages not
|
|
20
|
+
* already present in `cursor` (mutated in place: registering an id here is
|
|
21
|
+
* the ingestion cursor for the whole live loop — a message is folded into
|
|
22
|
+
* exactly one tick, never re-scanned on a later poll). This is the ONLY read
|
|
23
|
+
* the live loop performs against Moltnet each tick; delivery of world-minted
|
|
24
|
+
* events is a separate write path (`../moltnet/world-participant.ts`).
|
|
25
|
+
*/
|
|
26
|
+
export declare const ingestNewRoomMessages: (baseUrl: string, roomId: string, cursor: Set<string>, listMessages: (baseUrl: string, roomId: string, limit: number) => Promise<MoltnetRoomMessage[]>) => Promise<IngestNewRoomMessagesResult>;
|
|
27
|
+
//# sourceMappingURL=worldTickIngest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worldTickIngest.d.ts","sourceRoot":"","sources":["../../src/sims/worldTickIngest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAEjE;;;;;;;;;GASG;AACH,eAAO,MAAM,2BAA2B,GACtC,SAAS,kBAAkB,EAC3B,WAAW,MAAM,EACjB,SAAS,MAAM,KACd,WAQD,CAAC;AAEH,MAAM,WAAW,2BAA2B;IAC1C,WAAW,EAAE,kBAAkB,EAAE,CAAC;IAClC,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,qBAAqB,GAChC,SAAS,MAAM,EACf,QAAQ,MAAM,EACd,QAAQ,GAAG,CAAC,MAAM,CAAC,EACnB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,kBAAkB,EAAE,CAAC,KAC9F,OAAO,CAAC,2BAA2B,CAOrC,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lowers one Moltnet room message to the `LedgerEvent` shape `scanMarkers`
|
|
3
|
+
* (`../ledger/markers.ts`) expects, so the live world-driven loop can scan
|
|
4
|
+
* real agent utterances for marker tokens exactly like the batch
|
|
5
|
+
* `runSimfileTrace` scans its own minted events. `kind` here
|
|
6
|
+
* ("moltnet.message") is a display-only label for this ingestion path — it
|
|
7
|
+
* is never merged into `SimfileEventKind`'s own kind space and never minted
|
|
8
|
+
* as a world-ledger event itself; only the `marker.seen` event a hit
|
|
9
|
+
* produces gets written to `raw/world/causal.jsonl`.
|
|
10
|
+
*/
|
|
11
|
+
export const moltnetMessageToLedgerEvent = (message, roomScope, simTime) => ({
|
|
12
|
+
event_id: message.id,
|
|
13
|
+
kind: "moltnet.message",
|
|
14
|
+
sim_time: simTime,
|
|
15
|
+
actor: message.from.id,
|
|
16
|
+
target: roomScope,
|
|
17
|
+
scope: roomScope,
|
|
18
|
+
payload: { content: (message.parts ?? []).map((part) => part.text ?? "").join(" ") }
|
|
19
|
+
});
|
|
20
|
+
/**
|
|
21
|
+
* Fetches the room's current message list and returns only the messages not
|
|
22
|
+
* already present in `cursor` (mutated in place: registering an id here is
|
|
23
|
+
* the ingestion cursor for the whole live loop — a message is folded into
|
|
24
|
+
* exactly one tick, never re-scanned on a later poll). This is the ONLY read
|
|
25
|
+
* the live loop performs against Moltnet each tick; delivery of world-minted
|
|
26
|
+
* events is a separate write path (`../moltnet/world-participant.ts`).
|
|
27
|
+
*/
|
|
28
|
+
export const ingestNewRoomMessages = async (baseUrl, roomId, cursor, listMessages) => {
|
|
29
|
+
const messages = await listMessages(baseUrl, roomId, 200);
|
|
30
|
+
const newMessages = messages.filter((message) => !cursor.has(message.id));
|
|
31
|
+
for (const message of newMessages) {
|
|
32
|
+
cursor.add(message.id);
|
|
33
|
+
}
|
|
34
|
+
return { newMessages, ingestedIds: newMessages.map((message) => message.id) };
|
|
35
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { Simfile } from "../schema/model.js";
|
|
2
|
+
import { type ClockRuntime } from "../runtime/clock.js";
|
|
3
|
+
import type { CompiledTraceRuntime } from "../runtime/trace-compile.js";
|
|
4
|
+
import { type ExchangeEndReason } from "./exchangeWait.js";
|
|
5
|
+
import type { WorldLedgerWriter } from "./worldLedgerWriter.js";
|
|
6
|
+
export interface LiveTickLoopContext {
|
|
7
|
+
baseUrl: string;
|
|
8
|
+
networkId: string;
|
|
9
|
+
roomId: string;
|
|
10
|
+
roomScope: string;
|
|
11
|
+
agentIds: readonly string[];
|
|
12
|
+
clock: ClockRuntime;
|
|
13
|
+
runtime: CompiledTraceRuntime;
|
|
14
|
+
simfile: Simfile;
|
|
15
|
+
state: Record<string, number>;
|
|
16
|
+
precision: number;
|
|
17
|
+
runId: string;
|
|
18
|
+
seed: string;
|
|
19
|
+
ledgerWriter: WorldLedgerWriter;
|
|
20
|
+
pollOptions: {
|
|
21
|
+
intervalMs: number;
|
|
22
|
+
sleep: (ms: number) => Promise<void>;
|
|
23
|
+
timeoutMs: number;
|
|
24
|
+
};
|
|
25
|
+
targetTurns: number;
|
|
26
|
+
quietGraceMs: number;
|
|
27
|
+
timeoutMs: number;
|
|
28
|
+
maxTicks: number;
|
|
29
|
+
}
|
|
30
|
+
export interface LiveTickLoopResult {
|
|
31
|
+
endedReason: ExchangeEndReason;
|
|
32
|
+
ticksRun: number;
|
|
33
|
+
markerFired: boolean;
|
|
34
|
+
agentTurnCount: number;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The live loop itself (memetics increment (a)): one iteration per
|
|
38
|
+
* wall-clock `clock.tick`. Ingest -> step -> scan -> deliver -> append, then
|
|
39
|
+
* decide stop/continue with the same pure `evaluateExchangeCompletion`
|
|
40
|
+
* decision the batch composed driver uses for its own exchange wait — never
|
|
41
|
+
* a coax/resend, per this package's derail-guard rule
|
|
42
|
+
* (`composedOfficeSimDriver.ts`'s own doc comment; unchanged here).
|
|
43
|
+
*
|
|
44
|
+
* - Ingest: `ingestNewRoomMessages` GETs the room and returns only messages
|
|
45
|
+
* not yet folded into a tick (the cursor IS the determinism contract).
|
|
46
|
+
* - Step: `stepSimfileTick` advances the world exactly one tick (clock
|
|
47
|
+
* resolve -> world-acts -> generators -> derived -> rules).
|
|
48
|
+
* - Scan: `scanMarkers` runs LIVE over this tick's own newly ingested
|
|
49
|
+
* messages (never the whole transcript, never the world's own minted
|
|
50
|
+
* events) so a `marker.seen` hit is always evidence of a REAL agent
|
|
51
|
+
* utterance. Its `cause_event_ids` is this tick's own `clock.sync` only —
|
|
52
|
+
* the moltnet message id is carried as `source_event_id` in payload, a
|
|
53
|
+
* display/measurement join, never a synthesized causal link — so stele
|
|
54
|
+
* reconciliation over the world stream stays complete.
|
|
55
|
+
* - Deliver: any rule-emitted `world.message`/`world.dm`/`wake.recommended`
|
|
56
|
+
* from this tick goes out through the existing moltnet world-participant
|
|
57
|
+
* bridge, unchanged.
|
|
58
|
+
* - Append: the tick's events (plus any minted `marker.seen`), its variable
|
|
59
|
+
* sample, and the list of ingested message ids all land in
|
|
60
|
+
* `raw/world/causal.jsonl` / `world/telemetry.json` /
|
|
61
|
+
* `world/ingested-messages.jsonl` via `ledgerWriter`.
|
|
62
|
+
*/
|
|
63
|
+
export declare const runLiveTickLoop: (ctx: LiveTickLoopContext) => Promise<LiveTickLoopResult>;
|
|
64
|
+
//# sourceMappingURL=worldTickLoop.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worldTickLoop.d.ts","sourceRoot":"","sources":["../../src/sims/worldTickLoop.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAqB,KAAK,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAC3E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AAKxE,OAAO,EAA8B,KAAK,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAGvF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAEhE,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,KAAK,EAAE,YAAY,CAAC;IACpB,OAAO,EAAE,oBAAoB,CAAC;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,iBAAiB,CAAC;IAChC,WAAW,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7F,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,kBAAkB;IACjC,WAAW,EAAE,iBAAiB,CAAC;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,OAAO,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,eAAO,MAAM,eAAe,GAAU,KAAK,mBAAmB,KAAG,OAAO,CAAC,kBAAkB,CAiH1F,CAAC"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { isWorldMessageEvent, sendWorldEventToMoltnet } from "../moltnet/world-participant.js";
|
|
2
|
+
import { scanMarkers } from "../ledger/markers.js";
|
|
3
|
+
import { resolveClockState } from "../runtime/clock.js";
|
|
4
|
+
import { ingestWorldActs } from "../runtime/world-act.js";
|
|
5
|
+
import { createTraceEvent, stepSimfileTick } from "../runtime/step-tick.js";
|
|
6
|
+
import { evaluateExchangeCompletion } from "./exchangeWait.js";
|
|
7
|
+
import { listMoltnetRoomMessages } from "./moltnetRoomClient.js";
|
|
8
|
+
import { ingestNewRoomMessages, moltnetMessageToLedgerEvent } from "./worldTickIngest.js";
|
|
9
|
+
/**
|
|
10
|
+
* The live loop itself (memetics increment (a)): one iteration per
|
|
11
|
+
* wall-clock `clock.tick`. Ingest -> step -> scan -> deliver -> append, then
|
|
12
|
+
* decide stop/continue with the same pure `evaluateExchangeCompletion`
|
|
13
|
+
* decision the batch composed driver uses for its own exchange wait — never
|
|
14
|
+
* a coax/resend, per this package's derail-guard rule
|
|
15
|
+
* (`composedOfficeSimDriver.ts`'s own doc comment; unchanged here).
|
|
16
|
+
*
|
|
17
|
+
* - Ingest: `ingestNewRoomMessages` GETs the room and returns only messages
|
|
18
|
+
* not yet folded into a tick (the cursor IS the determinism contract).
|
|
19
|
+
* - Step: `stepSimfileTick` advances the world exactly one tick (clock
|
|
20
|
+
* resolve -> world-acts -> generators -> derived -> rules).
|
|
21
|
+
* - Scan: `scanMarkers` runs LIVE over this tick's own newly ingested
|
|
22
|
+
* messages (never the whole transcript, never the world's own minted
|
|
23
|
+
* events) so a `marker.seen` hit is always evidence of a REAL agent
|
|
24
|
+
* utterance. Its `cause_event_ids` is this tick's own `clock.sync` only —
|
|
25
|
+
* the moltnet message id is carried as `source_event_id` in payload, a
|
|
26
|
+
* display/measurement join, never a synthesized causal link — so stele
|
|
27
|
+
* reconciliation over the world stream stays complete.
|
|
28
|
+
* - Deliver: any rule-emitted `world.message`/`world.dm`/`wake.recommended`
|
|
29
|
+
* from this tick goes out through the existing moltnet world-participant
|
|
30
|
+
* bridge, unchanged.
|
|
31
|
+
* - Append: the tick's events (plus any minted `marker.seen`), its variable
|
|
32
|
+
* sample, and the list of ingested message ids all land in
|
|
33
|
+
* `raw/world/causal.jsonl` / `world/telemetry.json` /
|
|
34
|
+
* `world/ingested-messages.jsonl` via `ledgerWriter`.
|
|
35
|
+
*/
|
|
36
|
+
export const runLiveTickLoop = async (ctx) => {
|
|
37
|
+
const worldActIngestion = ingestWorldActs([], {
|
|
38
|
+
ticks: ctx.maxTicks,
|
|
39
|
+
ranges: ctx.runtime.ranges,
|
|
40
|
+
variableFedBy: ctx.runtime.variableFedBy
|
|
41
|
+
});
|
|
42
|
+
const cursor = new Set();
|
|
43
|
+
const agentTurns = [];
|
|
44
|
+
let seq = 1;
|
|
45
|
+
let markerFired = false;
|
|
46
|
+
const startedAt = Date.now();
|
|
47
|
+
let lastAgentMessageAt = startedAt;
|
|
48
|
+
for (let tick = 0; tick < ctx.maxTicks; tick += 1) {
|
|
49
|
+
const { simTime } = resolveClockState(ctx.clock, tick);
|
|
50
|
+
const { newMessages, ingestedIds } = await ingestNewRoomMessages(ctx.baseUrl, ctx.roomId, cursor, listMoltnetRoomMessages);
|
|
51
|
+
const step = stepSimfileTick(tick, {
|
|
52
|
+
runId: ctx.runId,
|
|
53
|
+
seed: ctx.seed,
|
|
54
|
+
precision: ctx.precision,
|
|
55
|
+
clock: ctx.clock,
|
|
56
|
+
runtime: ctx.runtime,
|
|
57
|
+
state: ctx.state,
|
|
58
|
+
seq,
|
|
59
|
+
worldActsForTick: worldActIngestion.queueByTick.get(tick),
|
|
60
|
+
worldActResultsByActId: worldActIngestion.resultsByActId
|
|
61
|
+
});
|
|
62
|
+
seq = step.nextSeq;
|
|
63
|
+
const clockSyncEventId = step.events[0]?.event_id;
|
|
64
|
+
if (!clockSyncEventId) {
|
|
65
|
+
throw new Error(`tick ${tick} produced no clock.sync event`);
|
|
66
|
+
}
|
|
67
|
+
const ledgerCandidates = newMessages.map((message) => moltnetMessageToLedgerEvent(message, ctx.roomScope, simTime));
|
|
68
|
+
const hits = scanMarkers(ledgerCandidates, ctx.simfile.markers);
|
|
69
|
+
const markerEvents = [];
|
|
70
|
+
for (const [markerId, markerHits] of Object.entries(hits)) {
|
|
71
|
+
for (const hit of markerHits) {
|
|
72
|
+
markerFired = true;
|
|
73
|
+
markerEvents.push(createTraceEvent(ctx.runId, seq, "marker.seen", simTime, markerId, hit.scope, hit.scope, {
|
|
74
|
+
alias: hit.alias,
|
|
75
|
+
marker: markerId,
|
|
76
|
+
// Display/measurement join to the Moltnet message that carried
|
|
77
|
+
// the token — NOT a synthesized causal link. The real
|
|
78
|
+
// `cause_event_ids` below is this tick's own `clock.sync` only,
|
|
79
|
+
// so stele reconciliation over the world stream stays complete.
|
|
80
|
+
source_event_id: hit.eventId,
|
|
81
|
+
source_event_kind: hit.eventKind
|
|
82
|
+
}, [clockSyncEventId]));
|
|
83
|
+
seq += 1;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
for (const event of step.events) {
|
|
87
|
+
if (isWorldMessageEvent(event)) {
|
|
88
|
+
await sendWorldEventToMoltnet(event, { baseUrl: ctx.baseUrl, networkId: ctx.networkId });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
await ctx.ledgerWriter.appendTick({
|
|
92
|
+
tick,
|
|
93
|
+
tickEvents: [...step.events, ...markerEvents],
|
|
94
|
+
sample: step.sample,
|
|
95
|
+
ingestedMessageIds: ingestedIds
|
|
96
|
+
});
|
|
97
|
+
for (const message of newMessages) {
|
|
98
|
+
if (ctx.agentIds.includes(message.from.id)) {
|
|
99
|
+
agentTurns.push({ id: message.from.id });
|
|
100
|
+
lastAgentMessageAt = Date.now();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const evaluation = evaluateExchangeCompletion({
|
|
104
|
+
agentTurnCount: agentTurns.length,
|
|
105
|
+
elapsedSinceLastAgentMessageMs: Date.now() - lastAgentMessageAt,
|
|
106
|
+
elapsedSinceStartMs: Date.now() - startedAt,
|
|
107
|
+
quietGraceMs: ctx.quietGraceMs,
|
|
108
|
+
targetTurns: ctx.targetTurns,
|
|
109
|
+
timeoutMs: ctx.timeoutMs
|
|
110
|
+
});
|
|
111
|
+
if (evaluation.done) {
|
|
112
|
+
return {
|
|
113
|
+
endedReason: evaluation.reason,
|
|
114
|
+
ticksRun: tick + 1,
|
|
115
|
+
markerFired,
|
|
116
|
+
agentTurnCount: agentTurns.length
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
await ctx.pollOptions.sleep(ctx.pollOptions.intervalMs);
|
|
120
|
+
}
|
|
121
|
+
throw new Error(`world tick fuse tripped at ${ctx.maxTicks} ticks without the exchange concluding`);
|
|
122
|
+
};
|