@themoltnet/agent-daemon 0.19.1 → 0.20.0

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.
Files changed (3) hide show
  1. package/README.md +3 -3
  2. package/dist/main.js +205 -50
  3. package/package.json +8 -10
package/README.md CHANGED
@@ -181,9 +181,9 @@ workspace shape in `input.execution.workspace`: `none` becomes a
181
181
  `scratch_mount`, `shared_mount` uses the daemon mount, and
182
182
  `dedicated_worktree` uses an isolated checkout. Downstream
183
183
  `judge_eval_attempt` tasks only resolve against a still-live producer
184
- session/workspace slot. If the producer slot has already expired and been
185
- reaped, the judge fails with `producer_context_missing`. When the judge does
186
- claim in time, it immediately forks the producer session and copies the
184
+ session/workspace slot. If the producer slot or its local session/workspace
185
+ files cannot be resolved, the judge fails with `producer_context_missing`.
186
+ When the judge does claim in time, it immediately forks the producer session and copies the
187
187
  producer workspace into judge-owned scratch state so the running judge no
188
188
  longer depends on the producer slot after claim time. Repo-specific
189
189
  `resumeCommands` that should not run in scratch mode must still be guarded
package/dist/main.js CHANGED
@@ -9,11 +9,10 @@ import { UndiciInstrumentation } from "@opentelemetry/instrumentation-undici";
9
9
  import crypto from "crypto";
10
10
  import { delimiter, dirname, isAbsolute, join, resolve } from "node:path";
11
11
  import { parseArgs, promisify } from "node:util";
12
- import { DaemonSlotRegistry, resolveDaemonStateStorageConfig, resolveLatestPiSessionPath } from "@themoltnet/agent-daemon-state";
13
12
  import { AgentRuntime, ApiTaskReporter, ApiTaskSource, PollingApiTaskSource } from "@themoltnet/agent-runtime";
14
13
  import { createPiTaskExecutor, findMainWorktree } from "@themoltnet/pi-extension";
15
14
  import { execFile, execFileSync } from "node:child_process";
16
- import { accessSync, constants, existsSync, mkdirSync, readFileSync } from "node:fs";
15
+ import { accessSync, constants, existsSync, mkdirSync, readFileSync, readdirSync } from "node:fs";
17
16
  import { MoltNetError, connect } from "@themoltnet/sdk";
18
17
  import { once } from "node:events";
19
18
  import { pino, transport } from "pino";
@@ -4547,6 +4546,117 @@ _Object_({
4547
4546
  additionalProperties: false
4548
4547
  });
4549
4548
  //#endregion
4549
+ //#region ../../libs/tasks/src/runtime-slots.ts
4550
+ var RuntimeWorkspaceKind = Union([
4551
+ Literal("origin"),
4552
+ Literal("fork"),
4553
+ Literal("scratch")
4554
+ ]);
4555
+ var RuntimeSlotState = Union([Literal("active"), Literal("idle")]);
4556
+ var RuntimeWorkspace = _Object_({
4557
+ id: String$1({ format: "uuid" }),
4558
+ teamId: String$1({ format: "uuid" }),
4559
+ workspaceId: String$1({ minLength: 1 }),
4560
+ worktreePath: String$1({ minLength: 1 }),
4561
+ worktreeBranch: Union([String$1({ minLength: 1 }), Null()]),
4562
+ kind: RuntimeWorkspaceKind,
4563
+ createdAtMs: Integer({ minimum: 0 }),
4564
+ lastUsedAtMs: Integer({ minimum: 0 })
4565
+ }, { $id: "RuntimeWorkspace" });
4566
+ _Object_({
4567
+ slot: _Object_({
4568
+ id: String$1({ format: "uuid" }),
4569
+ teamId: String$1({ format: "uuid" }),
4570
+ agentName: String$1({
4571
+ minLength: 1,
4572
+ maxLength: 100
4573
+ }),
4574
+ daemonProfileId: Union([String$1({ format: "uuid" }), Null()]),
4575
+ provider: String$1({
4576
+ minLength: 1,
4577
+ maxLength: 100
4578
+ }),
4579
+ model: String$1({
4580
+ minLength: 1,
4581
+ maxLength: 200
4582
+ }),
4583
+ slotKey: String$1({ minLength: 1 }),
4584
+ taskType: String$1({
4585
+ minLength: 1,
4586
+ maxLength: 100
4587
+ }),
4588
+ state: RuntimeSlotState,
4589
+ lastTaskId: String$1({ format: "uuid" }),
4590
+ lastAttemptN: Integer({ minimum: 1 }),
4591
+ sessionDir: Union([String$1({ minLength: 1 }), Null()]),
4592
+ sessionPath: Union([String$1({ minLength: 1 }), Null()]),
4593
+ workspaceRowId: Union([String$1({ format: "uuid" }), Null()]),
4594
+ createdAtMs: Integer({ minimum: 0 }),
4595
+ lastUsedAtMs: Integer({ minimum: 0 }),
4596
+ expiresAtMs: Integer({ minimum: 0 })
4597
+ }, { $id: "RuntimeSlot" }),
4598
+ workspace: Union([RuntimeWorkspace, Null()])
4599
+ }, { $id: "ResolvedRuntimeSlot" });
4600
+ _Object_({
4601
+ agentName: String$1({
4602
+ minLength: 1,
4603
+ maxLength: 100
4604
+ }),
4605
+ daemonProfileId: Optional(String$1({ format: "uuid" })),
4606
+ provider: String$1({
4607
+ minLength: 1,
4608
+ maxLength: 100
4609
+ }),
4610
+ model: String$1({
4611
+ minLength: 1,
4612
+ maxLength: 200
4613
+ }),
4614
+ slotKey: String$1({ minLength: 1 }),
4615
+ taskType: String$1({
4616
+ minLength: 1,
4617
+ maxLength: 100
4618
+ }),
4619
+ sessionDir: Optional(String$1({ minLength: 1 })),
4620
+ sessionPath: Optional(String$1({ minLength: 1 })),
4621
+ workspaceId: Optional(String$1({ minLength: 1 })),
4622
+ worktreePath: Optional(String$1({ minLength: 1 })),
4623
+ worktreeBranch: Optional(String$1({ minLength: 1 })),
4624
+ workspaceKind: Optional(RuntimeWorkspaceKind),
4625
+ lastTaskId: String$1({ format: "uuid" }),
4626
+ lastAttemptN: Integer({ minimum: 1 })
4627
+ }, {
4628
+ $id: "BeginRuntimeSlotBody",
4629
+ additionalProperties: false
4630
+ });
4631
+ _Object_({
4632
+ agentName: String$1({
4633
+ minLength: 1,
4634
+ maxLength: 100
4635
+ }),
4636
+ provider: String$1({
4637
+ minLength: 1,
4638
+ maxLength: 100
4639
+ }),
4640
+ model: String$1({
4641
+ minLength: 1,
4642
+ maxLength: 200
4643
+ }),
4644
+ slotKey: String$1({ minLength: 1 }),
4645
+ taskId: String$1({ format: "uuid" }),
4646
+ attemptN: Integer({ minimum: 1 }),
4647
+ sessionPath: Optional(String$1({ minLength: 1 }))
4648
+ }, {
4649
+ $id: "FinishRuntimeSlotBody",
4650
+ additionalProperties: false
4651
+ });
4652
+ _Object_({
4653
+ taskId: String$1({ format: "uuid" }),
4654
+ attemptN: Integer({ minimum: 1 })
4655
+ }, {
4656
+ $id: "FindLatestRuntimeSlotForAttemptQuery",
4657
+ additionalProperties: false
4658
+ });
4659
+ //#endregion
4550
4660
  //#region ../../libs/tasks/src/success-criteria.ts
4551
4661
  /**
4552
4662
  * SuccessCriteria — proposer-stated acceptance criteria, evaluated in two
@@ -9042,9 +9152,8 @@ var COMMON_OPTIONAL_FLAGS = `\
9042
9152
  this, the pi session aborts and the attempt
9043
9153
  fails with code 'max_bash_timeouts_exceeded'.
9044
9154
  0 = disabled. Default: 3.
9045
- --warm-session-ttl-sec <n> Retain resumable daemon slots (Pi sessions +
9046
- reusable worktrees) in local daemon state for
9047
- this many seconds after use. 0 = disable reuse.
9155
+ --warm-session-ttl-sec <n> Resumability window for runtime slots
9156
+ (Pi sessions + reusable worktrees) after use.
9048
9157
  Default: 1800, or min(profile session/workspace
9049
9158
  TTL) when --profile is set.
9050
9159
  --debug Verbose logging: also log successful list/claim
@@ -9166,7 +9275,6 @@ function isHelpFlag(args) {
9166
9275
  //#region src/config.ts
9167
9276
  function loadConfig() {
9168
9277
  return {
9169
- agentDaemonStateDatabaseUrl: process.env["MOLTNET_AGENT_DAEMON_STATE_DATABASE_URL"] ?? "",
9170
9278
  otelEndpoint: process.env["MOLTNET_OTEL_ENDPOINT"] ?? "",
9171
9279
  logLevel: process.env["LOG_LEVEL"] ?? "",
9172
9280
  profilePrerequisiteEnv: process.env,
@@ -9265,6 +9373,16 @@ function createGhCliClient() {
9265
9373
  };
9266
9374
  }
9267
9375
  //#endregion
9376
+ //#region src/lib/session-files.ts
9377
+ function resolveLatestPiSessionPath(sessionDir) {
9378
+ try {
9379
+ const latestEntry = readdirSync(sessionDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".jsonl")).map((entry) => entry.name).sort().at(-1);
9380
+ return latestEntry ? join(sessionDir, latestEntry) : null;
9381
+ } catch {
9382
+ return null;
9383
+ }
9384
+ }
9385
+ //#endregion
9268
9386
  //#region src/lib/slugify.ts
9269
9387
  function slugifyAsciiLower(input, maxLen, preserveChars = []) {
9270
9388
  const preserved = new Set(preserveChars.map((char) => char.toLowerCase()));
@@ -9419,8 +9537,8 @@ function createExecutionPlanCache(args) {
9419
9537
  function buildClaimedTaskKey(task) {
9420
9538
  return `${task.task.id}:${task.attemptN}`;
9421
9539
  }
9422
- async function resolveWarmSlot(slotRegistry, sourceTaskId, sourceAttemptN, stateDirs) {
9423
- const producerContext = await slotRegistry.findLatestProducerSlotByTaskAttempt(sourceTaskId, sourceAttemptN);
9540
+ async function resolveWarmSlot(slotRegistry, teamId, sourceTaskId, sourceAttemptN, stateDirs) {
9541
+ const producerContext = await slotRegistry.findLatestSlotByTaskAttempt(teamId, sourceTaskId, sourceAttemptN);
9424
9542
  if (!producerContext) return { kind: "missing" };
9425
9543
  const sourceSessionPath = resolveProducerSessionPath(producerContext);
9426
9544
  if (!sourceSessionPath) return { kind: "no-session-path" };
@@ -9435,8 +9553,8 @@ async function maybeAttachWarmSlotContext(claimedTask, basePlan, stateDirs, slot
9435
9553
  if (claimedTask.task.taskType === "freeform") {
9436
9554
  const continueFrom = claimedTask.task.input.continueFrom;
9437
9555
  if (!continueFrom) return basePlan;
9438
- const resolution = await resolveWarmSlot(slotRegistry, continueFrom.taskId, continueFrom.attemptN, stateDirs);
9439
- if (resolution.kind === "missing") throw new ProducerContextResolutionError(`Continuation source task ${continueFrom.taskId} attempt ${continueFrom.attemptN} has no live daemon slot on this daemon — claim affinity filter should have prevented this claim`);
9556
+ const resolution = await resolveWarmSlot(slotRegistry, claimedTask.task.teamId, continueFrom.taskId, continueFrom.attemptN, stateDirs);
9557
+ if (resolution.kind === "missing") throw new ProducerContextResolutionError(`Continuation source task ${continueFrom.taskId} attempt ${continueFrom.attemptN} has no live runtime slot on this daemon — claim affinity filter should have prevented this claim`);
9440
9558
  if (resolution.kind === "no-session-path") throw new ProducerContextResolutionError(`Continuation source attempt ${continueFrom.taskId}/${continueFrom.attemptN} has no persisted Pi session path`);
9441
9559
  const sessionDir = `${stateDirs.piSessionsDir}/continue-${claimedTask.task.id}-attempt-${claimedTask.attemptN}`;
9442
9560
  const parentBranch = resolution.producerSlot.workspace?.worktreeBranch ?? null;
@@ -9472,8 +9590,8 @@ async function maybeAttachWarmSlotContext(claimedTask, basePlan, stateDirs, slot
9472
9590
  const targetTaskId = typeof claimedTask.task.input.targetTaskId === "string" ? claimedTask.task.input.targetTaskId : null;
9473
9591
  const targetAttemptN = typeof claimedTask.task.input.targetAttemptN === "number" ? claimedTask.task.input.targetAttemptN : null;
9474
9592
  if (!targetTaskId || !targetAttemptN) throw new ProducerContextResolutionError("judge_eval_attempt is missing targetTaskId/targetAttemptN");
9475
- const resolution = await resolveWarmSlot(slotRegistry, targetTaskId, targetAttemptN, stateDirs);
9476
- if (resolution.kind === "missing") throw new ProducerContextResolutionError(`No live producer daemon slot found for task ${targetTaskId} attempt ${targetAttemptN}`);
9593
+ const resolution = await resolveWarmSlot(slotRegistry, claimedTask.task.teamId, targetTaskId, targetAttemptN, stateDirs);
9594
+ if (resolution.kind === "missing") throw new ProducerContextResolutionError(`No live producer runtime slot found for task ${targetTaskId} attempt ${targetAttemptN}`);
9477
9595
  if (resolution.kind === "no-session-path") throw new ProducerContextResolutionError(`Producer task ${targetTaskId} attempt ${targetAttemptN} has no persisted Pi session path`);
9478
9596
  return {
9479
9597
  ...basePlan,
@@ -9520,7 +9638,7 @@ function recoverScratchWorkspacePath(producer, stateDirs) {
9520
9638
  //#region src/lib/finalize.ts
9521
9639
  /**
9522
9640
  * Build the `daemonState` payload for a `/complete` call. Only freeform
9523
- * attempts that ran with a warm slot are eligible for continuation
9641
+ * attempts that ran with a runtime slot are eligible for continuation
9524
9642
  * (`tasks_continue`, see issue #1287). Other task types and slot-less
9525
9643
  * freeform completions report `null` for `slotResumableUntil`, which
9526
9644
  * the server persists verbatim — continuations against such attempts
@@ -9866,6 +9984,63 @@ function isExecutable(path) {
9866
9984
  }
9867
9985
  }
9868
9986
  //#endregion
9987
+ //#region src/lib/runtime-slots.ts
9988
+ function createApiRuntimeSlotStore(args) {
9989
+ const { agent, daemonProfileId } = args;
9990
+ return {
9991
+ async beginSlot(input) {
9992
+ await agent.runtimeSlots.begin({
9993
+ agentName: input.agentName,
9994
+ daemonProfileId: daemonProfileId ?? void 0,
9995
+ lastAttemptN: input.lastAttemptN,
9996
+ lastTaskId: input.lastTaskId,
9997
+ model: input.model,
9998
+ provider: input.provider,
9999
+ sessionDir: input.sessionDir ?? void 0,
10000
+ sessionPath: input.sessionPath ?? void 0,
10001
+ slotKey: input.slotKey,
10002
+ taskType: input.taskType,
10003
+ workspaceId: input.workspaceId ?? void 0,
10004
+ workspaceKind: input.workspaceKind,
10005
+ worktreeBranch: input.worktreeBranch ?? void 0,
10006
+ worktreePath: input.worktreePath ?? void 0
10007
+ }, { teamId: input.teamId });
10008
+ },
10009
+ async finishSlot(teamId, taskId, attemptN, identity, slotKey, sessionPath) {
10010
+ await agent.runtimeSlots.finish({
10011
+ agentName: identity.agentName,
10012
+ attemptN,
10013
+ model: identity.model,
10014
+ provider: identity.provider,
10015
+ sessionPath: sessionPath ?? void 0,
10016
+ slotKey,
10017
+ taskId
10018
+ }, { teamId });
10019
+ },
10020
+ async findLatestSlotByTaskAttempt(teamId, taskId, attemptN) {
10021
+ const resolved = await agent.runtimeSlots.findLatestForAttempt({
10022
+ attemptN,
10023
+ taskId
10024
+ }, { teamId });
10025
+ if (!resolved) return null;
10026
+ return {
10027
+ slot: { expiresAtMs: resolved.slot.expiresAtMs },
10028
+ session: resolved.slot.sessionDir ? {
10029
+ sessionDir: resolved.slot.sessionDir,
10030
+ sessionPath: resolved.slot.sessionPath
10031
+ } : null,
10032
+ workspace: resolved.workspace ? {
10033
+ kind: resolved.workspace.kind,
10034
+ workspaceId: resolved.workspace.workspaceId,
10035
+ worktreeBranch: resolved.workspace.worktreeBranch,
10036
+ worktreePath: resolved.workspace.worktreePath
10037
+ } : null
10038
+ };
10039
+ },
10040
+ async close() {}
10041
+ };
10042
+ }
10043
+ //#endregion
9869
10044
  //#region src/lib/sandbox.ts
9870
10045
  function resolveSandbox(startDir, explicitPath) {
9871
10046
  const path = explicitPath ? isAbsolute(explicitPath) ? explicitPath : resolve(startDir, explicitPath) : findUp(startDir, "sandbox.json");
@@ -9933,8 +10108,7 @@ function ensureDaemonStateDirs(mountPath) {
9933
10108
  mkdirSync(piSessionsDir, { recursive: true });
9934
10109
  return {
9935
10110
  rootDir,
9936
- piSessionsDir,
9937
- registryDbPath: join(rootDir, "daemon-state.sqlite")
10111
+ piSessionsDir
9938
10112
  };
9939
10113
  }
9940
10114
  //#endregion
@@ -10044,7 +10218,10 @@ async function runPolling(opts) {
10044
10218
  const piAgentDir = ensurePiAgentDir(sandbox.rootDir, cfg.piCodingAgentDir);
10045
10219
  activatePiCodingAgentDir(piAgentDir.path);
10046
10220
  const stateDirs = ensureDaemonStateDirs(sandbox.rootDir);
10047
- const slotRegistry = new DaemonSlotRegistry(resolveDaemonStateStorageConfig(stateDirs.registryDbPath, cfg.agentDaemonStateDatabaseUrl));
10221
+ const slotRegistry = createApiRuntimeSlotStore({
10222
+ agent: ctx.agent,
10223
+ daemonProfileId: profile?.id ?? null
10224
+ });
10048
10225
  const slotIdentity = {
10049
10226
  agentName: common.agent,
10050
10227
  provider,
@@ -10174,7 +10351,7 @@ async function runPolling(opts) {
10174
10351
  flushIntervalMs: common.flushIntervalMs
10175
10352
  }),
10176
10353
  onTaskFinished: async (output, claimedTask) => {
10177
- const resolved = await slotRegistry.findLatestProducerSlotByTaskAttempt(claimedTask.task.id, claimedTask.attemptN);
10354
+ const resolved = await slotRegistry.findLatestSlotByTaskAttempt(claimedTask.task.teamId, claimedTask.task.id, claimedTask.attemptN);
10178
10355
  return finalizeTask(ctx.agent, output, {
10179
10356
  task: claimedTask.task,
10180
10357
  slot: resolved ? { expiresAtMs: resolved.slot.expiresAtMs } : null,
@@ -10215,19 +10392,6 @@ async function runPolling(opts) {
10215
10392
  };
10216
10393
  }
10217
10394
  const sessionDescriptor = executionPlan.descriptor;
10218
- let expired;
10219
- try {
10220
- expired = await slotRegistry.reapExpiredSlots();
10221
- if (expired.length > 0) rootLogger.info({
10222
- expiredCount: expired.length,
10223
- slotKeys: expired.map((item) => item.slot.slotKey)
10224
- }, "agent-daemon.daemon_slots_reaped");
10225
- } catch (err) {
10226
- rootLogger.error({
10227
- phase: "daemon_slot_reap",
10228
- err: err instanceof Error ? err.message : String(err)
10229
- }, "agent-daemon.daemon_slot_reap_failed");
10230
- }
10231
10395
  rootLogger.debug({
10232
10396
  taskId: claimedTask.task.id,
10233
10397
  taskType: claimedTask.task.taskType,
@@ -10277,6 +10441,7 @@ async function runPolling(opts) {
10277
10441
  };
10278
10442
  if (executionPlan.slotKey && executionPlan.sessionPersistence) await slotRegistry.beginSlot({
10279
10443
  ...slotIdentity,
10444
+ teamId: claimedTask.task.teamId,
10280
10445
  slotKey: executionPlan.slotKey,
10281
10446
  taskType: claimedTask.task.taskType,
10282
10447
  sessionDir: executionPlan.sessionPersistence.sessionDir,
@@ -10286,14 +10451,13 @@ async function runPolling(opts) {
10286
10451
  worktreeBranch: executionPlan.worktreeBranch,
10287
10452
  workspaceKind: executionPlan.workspaceKind,
10288
10453
  lastTaskId: claimedTask.task.id,
10289
- lastAttemptN: claimedTask.attemptN,
10290
- ttlSec: common.warmSessionTtlSec
10454
+ lastAttemptN: claimedTask.attemptN
10291
10455
  });
10292
10456
  try {
10293
10457
  return await executeTask(claimedTask, reporter);
10294
10458
  } finally {
10295
10459
  executionPlans.delete(claimedTask);
10296
- if (executionPlan.slotKey) await slotRegistry.finishSlot(slotIdentity, executionPlan.slotKey, common.warmSessionTtlSec, executionPlan.sessionPersistence ? resolveLatestPiSessionPath(executionPlan.sessionPersistence.sessionDir) : null);
10460
+ if (executionPlan.slotKey) await slotRegistry.finishSlot(claimedTask.task.teamId, claimedTask.task.id, claimedTask.attemptN, slotIdentity, executionPlan.slotKey, executionPlan.sessionPersistence ? resolveLatestPiSessionPath(executionPlan.sessionPersistence.sessionDir) : null);
10297
10461
  }
10298
10462
  }
10299
10463
  });
@@ -10402,7 +10566,10 @@ async function runOnce(argv) {
10402
10566
  const piAgentDir = ensurePiAgentDir(sandbox.rootDir, cfg.piCodingAgentDir);
10403
10567
  activatePiCodingAgentDir(piAgentDir.path);
10404
10568
  const stateDirs = ensureDaemonStateDirs(sandbox.rootDir);
10405
- const slotRegistry = new DaemonSlotRegistry(resolveDaemonStateStorageConfig(stateDirs.registryDbPath, cfg.agentDaemonStateDatabaseUrl));
10569
+ const slotRegistry = createApiRuntimeSlotStore({
10570
+ agent: ctx.agent,
10571
+ daemonProfileId: profile?.id ?? null
10572
+ });
10406
10573
  const slotIdentity = {
10407
10574
  agentName: opts.agent,
10408
10575
  provider,
@@ -10494,18 +10661,6 @@ async function runOnce(argv) {
10494
10661
  maxBashTimeouts: opts.maxBashTimeouts
10495
10662
  });
10496
10663
  const executeTask = async (claimedTask, reporter) => {
10497
- try {
10498
- const expired = await slotRegistry.reapExpiredSlots();
10499
- if (expired.length > 0) rootLogger.info({
10500
- expiredCount: expired.length,
10501
- slotKeys: expired.map((item) => item.slot.slotKey)
10502
- }, "agent-daemon.daemon_slots_reaped");
10503
- } catch (err) {
10504
- rootLogger.error({
10505
- phase: "daemon_slot_reap",
10506
- err: err instanceof Error ? err.message : String(err)
10507
- }, "agent-daemon.daemon_slot_reap_failed");
10508
- }
10509
10664
  let executionPlan;
10510
10665
  try {
10511
10666
  executionPlan = await executionPlans.getOrCreate(claimedTask);
@@ -10536,6 +10691,7 @@ async function runOnce(argv) {
10536
10691
  }
10537
10692
  if (executionPlan.slotKey && executionPlan.sessionPersistence) await slotRegistry.beginSlot({
10538
10693
  ...slotIdentity,
10694
+ teamId: claimedTask.task.teamId,
10539
10695
  slotKey: executionPlan.slotKey,
10540
10696
  taskType: claimedTask.task.taskType,
10541
10697
  sessionDir: executionPlan.sessionPersistence.sessionDir,
@@ -10545,8 +10701,7 @@ async function runOnce(argv) {
10545
10701
  worktreeBranch: executionPlan.worktreeBranch,
10546
10702
  workspaceKind: executionPlan.workspaceKind,
10547
10703
  lastTaskId: claimedTask.task.id,
10548
- lastAttemptN: claimedTask.attemptN,
10549
- ttlSec: opts.warmSessionTtlSec
10704
+ lastAttemptN: claimedTask.attemptN
10550
10705
  });
10551
10706
  activeAttemptN = claimedTask.attemptN;
10552
10707
  try {
@@ -10554,7 +10709,7 @@ async function runOnce(argv) {
10554
10709
  } finally {
10555
10710
  activeAttemptN = null;
10556
10711
  executionPlans.delete(claimedTask);
10557
- if (executionPlan.slotKey) await slotRegistry.finishSlot(slotIdentity, executionPlan.slotKey, opts.warmSessionTtlSec, executionPlan.sessionPersistence ? resolveLatestPiSessionPath(executionPlan.sessionPersistence.sessionDir) : null);
10712
+ if (executionPlan.slotKey) await slotRegistry.finishSlot(claimedTask.task.teamId, claimedTask.task.id, claimedTask.attemptN, slotIdentity, executionPlan.slotKey, executionPlan.sessionPersistence ? resolveLatestPiSessionPath(executionPlan.sessionPersistence.sessionDir) : null);
10558
10713
  }
10559
10714
  };
10560
10715
  const writeCorrelationAnchors = makePrBodyAnchorWriter({
@@ -10577,7 +10732,7 @@ async function runOnce(argv) {
10577
10732
  flushIntervalMs: opts.flushIntervalMs
10578
10733
  }),
10579
10734
  onTaskFinished: async (output, claimedTask) => {
10580
- const resolved = await slotRegistry.findLatestProducerSlotByTaskAttempt(claimedTask.task.id, claimedTask.attemptN);
10735
+ const resolved = await slotRegistry.findLatestSlotByTaskAttempt(claimedTask.task.teamId, claimedTask.task.id, claimedTask.attemptN);
10581
10736
  return finalizeTask(ctx.agent, output, {
10582
10737
  task: claimedTask.task,
10583
10738
  slot: resolved ? { expiresAtMs: resolved.slot.expiresAtMs } : null,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@themoltnet/agent-daemon",
3
- "version": "0.19.1",
3
+ "version": "0.20.0",
4
4
  "license": "AGPL-3.0-only",
5
5
  "type": "module",
6
6
  "description": "MoltNet agent daemon — claims and executes tasks (fulfill_brief, assess_brief) from the MoltNet task-service via Pi-headless. CLI: moltnet-agent.",
@@ -45,20 +45,19 @@
45
45
  "@opentelemetry/semantic-conventions": "^1.39.0",
46
46
  "pino": "^10.3.1",
47
47
  "pino-pretty": "^13.1.3",
48
- "@themoltnet/agent-daemon-state": "0.3.0",
49
- "@themoltnet/sdk": "0.108.0",
50
- "@themoltnet/agent-runtime": "0.26.0",
51
- "@themoltnet/pi-extension": "0.26.0"
48
+ "@themoltnet/agent-runtime": "0.27.0",
49
+ "@themoltnet/pi-extension": "0.26.1",
50
+ "@themoltnet/sdk": "0.109.0"
52
51
  },
53
52
  "devDependencies": {
54
53
  "tsx": "^4.7.0",
55
54
  "typescript": "~5.9.2",
56
55
  "vite": "^8.0.0",
57
56
  "vitest": "^3.0.0",
57
+ "@moltnet/bootstrap": "0.1.0",
58
58
  "@moltnet/crypto-service": "0.1.0",
59
59
  "@moltnet/observability": "0.1.0",
60
- "@moltnet/tasks": "0.1.0",
61
- "@moltnet/bootstrap": "0.1.0"
60
+ "@moltnet/tasks": "0.1.0"
62
61
  },
63
62
  "nx": {
64
63
  "tags": [
@@ -94,8 +93,7 @@
94
93
  "dev": "tsx watch src/main.ts",
95
94
  "start": "node dist/main.js",
96
95
  "build": "vite build",
97
- "check:pack": "tsx ../../tools/src/check-pack.ts --package . && pnpm run smoke:pack && pnpm run smoke:pack:state",
98
- "smoke:pack": "tsx ../../tools/src/smoke-pack.ts --package . --bin moltnet-agent --args --help --expect \"long-running task worker for MoltNet\"",
99
- "smoke:pack:state": "tsx ../../tools/src/smoke-agent-daemon-state-pack.ts"
96
+ "check:pack": "tsx ../../tools/src/check-pack.ts --package . && pnpm run smoke:pack",
97
+ "smoke:pack": "tsx ../../tools/src/smoke-pack.ts --package . --bin moltnet-agent --args --help --expect \"long-running task worker for MoltNet\""
100
98
  }
101
99
  }