@themoltnet/agent-daemon 0.20.0 → 0.21.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 +11 -17
  2. package/dist/main.js +246 -235
  3. package/package.json +4 -4
package/README.md CHANGED
@@ -80,8 +80,8 @@ To force a non-repo Pi directory, set
80
80
 
81
81
  ### Host command auto-approval
82
82
 
83
- The daemon reads `sandbox.json` through `--sandbox` or by searching up from the
84
- current directory. Configure host-side auto-approval there, not in task data:
83
+ The daemon reads host-side auto-approval from the selected remote runtime
84
+ profile's sandbox policy. Configure it in the profile, not in task data:
85
85
 
86
86
  ```json
87
87
  {
@@ -143,12 +143,11 @@ registered task type; unknown task-type names remain invalid.
143
143
  gitignored. For API-key auth, keep `.pi/auth.json` absent and export the
144
144
  provider key referenced by `.pi/models.json`, for example `OLLAMA_API_KEY`.
145
145
  - `ssh-keygen` on `PATH`.
146
- - A `sandbox.json` at the repo root, or an explicit `--sandbox <path>` when
147
- starting the daemon. The daemon searches up for this file and uses its
148
- containing directory as the VM workspace mount.
146
+ - A runtime profile in the target team. The profile supplies provider, model,
147
+ sandbox policy, and runtime defaults. The daemon mounts the current working
148
+ directory as the VM workspace root.
149
149
 
150
- For `themoltnet`, prefer the checked-in repo `sandbox.json` as-is — it carries
151
- the current pnpm/VFS workaround. A minimal `sandbox.json` for another repo:
150
+ For `themoltnet`, prefer a profile sandbox equivalent to this minimal policy:
152
151
 
153
152
  ```json
154
153
  {
@@ -251,18 +250,14 @@ pnpm --filter @themoltnet/agent-daemon dev poll \
251
250
  --agent local-dev \
252
251
  --team "$MOLTNET_TEAM_ID" \
253
252
  --task-types fulfill_brief \
254
- --provider openai-codex \
255
- --model gpt-5.4-codex \
253
+ --profile "$MOLTNET_AGENT_PROFILE" \
256
254
  --debug
257
255
  ```
258
256
 
259
- - If you're starting from a directory without `sandbox.json` at or above it,
260
- pass `--sandbox <repo-root>/sandbox.json`.
261
257
  - `--task-types fulfill_brief` scopes the queue. Omit to accept any
262
258
  registered type.
263
- - Pick provider/model that matches your pi auth credits. Common choices:
264
- `--provider openai-codex --model gpt-5.4-codex`, or
265
- `--provider anthropic --model claude-sonnet-4-6`.
259
+ - Pick a runtime profile whose provider/model matches your Pi auth credits.
260
+ Set `MOLTNET_AGENT_PROFILE` to the profile UUID or team-scoped profile name.
266
261
  - `dev` (= `tsx watch src/main.ts`) is fine for local. Use `cli` for a
267
262
  one-shot run without watch.
268
263
 
@@ -333,8 +328,7 @@ pnpm --filter @themoltnet/agent-daemon dev poll \
333
328
  --agent local-dev \
334
329
  --team "$MOLTNET_TEAM_ID" \
335
330
  --task-types pr_review \
336
- --provider openai-codex \
337
- --model gpt-5.4-codex \
331
+ --profile "$MOLTNET_AGENT_PROFILE" \
338
332
  --debug
339
333
  ```
340
334
 
@@ -396,7 +390,7 @@ snapshot. The cheap parts of the runtime contract (prompt assembly, tool-side
396
390
  tests in `libs/pi-extension`. This flow exists for the parts unit tests can't
397
391
  reach: real LLM behaviour against the assembled system prompt, real VM, real
398
392
  API round-trips, and the interaction between `.moltnet/<agent>/` identity
399
- material and the active `sandbox.json`.
393
+ material and the selected runtime profile.
400
394
 
401
395
  ## License
402
396
 
package/dist/main.js CHANGED
@@ -12,7 +12,7 @@ import { parseArgs, promisify } from "node:util";
12
12
  import { AgentRuntime, ApiTaskReporter, ApiTaskSource, PollingApiTaskSource } from "@themoltnet/agent-runtime";
13
13
  import { createPiTaskExecutor, findMainWorktree } from "@themoltnet/pi-extension";
14
14
  import { execFile, execFileSync } from "node:child_process";
15
- import { accessSync, constants, existsSync, mkdirSync, readFileSync, readdirSync } from "node:fs";
15
+ import { accessSync, constants, existsSync, mkdirSync, readdirSync } from "node:fs";
16
16
  import { MoltNetError, connect } from "@themoltnet/sdk";
17
17
  import { once } from "node:events";
18
18
  import { pino, transport } from "pino";
@@ -21,6 +21,7 @@ import { resourceFromAttributes } from "@opentelemetry/resources";
21
21
  import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
22
22
  import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
23
23
  import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from "@opentelemetry/semantic-conventions";
24
+ import { AsyncLocalStorage } from "node:async_hooks";
24
25
  //#region ../../libs/observability/src/instrumentation.ts
25
26
  /**
26
27
  * Register OTel auto-instrumentation for common Node.js modules.
@@ -4571,7 +4572,7 @@ _Object_({
4571
4572
  minLength: 1,
4572
4573
  maxLength: 100
4573
4574
  }),
4574
- daemonProfileId: Union([String$1({ format: "uuid" }), Null()]),
4575
+ runtimeProfileId: Union([String$1({ format: "uuid" }), Null()]),
4575
4576
  provider: String$1({
4576
4577
  minLength: 1,
4577
4578
  maxLength: 100
@@ -4602,7 +4603,7 @@ _Object_({
4602
4603
  minLength: 1,
4603
4604
  maxLength: 100
4604
4605
  }),
4605
- daemonProfileId: Optional(String$1({ format: "uuid" })),
4606
+ runtimeProfileId: String$1({ format: "uuid" }),
4606
4607
  provider: String$1({
4607
4608
  minLength: 1,
4608
4609
  maxLength: 100
@@ -4633,6 +4634,7 @@ _Object_({
4633
4634
  minLength: 1,
4634
4635
  maxLength: 100
4635
4636
  }),
4637
+ runtimeProfileId: String$1({ format: "uuid" }),
4636
4638
  provider: String$1({
4637
4639
  minLength: 1,
4638
4640
  maxLength: 100
@@ -9121,23 +9123,14 @@ _Object_({
9121
9123
  //#region src/lib/help.ts
9122
9124
  var COMMON_REQUIRED_FLAGS = `\
9123
9125
  -a, --agent <name> MoltNet agent identity. Reads credentials
9124
- from <repo-root>/.moltnet/<name>/moltnet.json.`;
9125
- var COMMON_MODEL_FLAGS = `\
9126
- -p, --provider <id> LLM provider id (e.g. anthropic, openai-codex).
9127
- -m, --model <id> LLM model id for the provider (e.g.
9128
- claude-sonnet-4-5, gpt-5.3-codex). Required
9129
- unless --profile is set.`;
9126
+ from <repo-root>/.moltnet/<name>/moltnet.json.
9127
+ --profile <uuid|name> Remote runtime profile. Repeat for poll/drain
9128
+ to declare priority order. Provider, model,
9129
+ sandbox policy, prerequisites, and runtime
9130
+ defaults come from the selected profile.`;
9130
9131
  var COMMON_OPTIONAL_FLAGS = `\
9131
- --sandbox <path> Path to sandbox.json. Default: search up from
9132
- the daemon's CWD until found. The directory
9133
- containing sandbox.json is also used as the
9134
- VM mountPath. Cannot be used with --profile.
9135
- --profile <uuid|name> Remote runtime profile. When set, provider,
9136
- model, and sandbox policy come from the
9137
- profile; task listing/claiming is restricted
9138
- to unrestricted tasks plus tasks allowing this
9139
- profile. requiredEnv/requiredTools are checked
9140
- before claiming. Name lookup is team-scoped.
9132
+ --sandbox <path> Deprecated. Remote runtime profiles define
9133
+ sandbox policy.
9141
9134
  --lease-ttl-sec <n> Sliding liveness window. Silence longer than
9142
9135
  this ends the attempt with lease_expired.
9143
9136
  Default: 300.
@@ -9178,23 +9171,20 @@ Run \`agent-daemon <command> --help\` for command-specific flags.
9178
9171
 
9179
9172
  Prerequisites (all subcommands):
9180
9173
  - <repo-root>/.moltnet/<agent>/moltnet.json — credentials (see --agent)
9181
- - sandbox.json or --profile — local sandbox config is resolved by
9182
- searching up from CWD, or pass --sandbox <path>. With --profile, the
9183
- remote runtime profile supplies provider/model/sandbox policy and CWD
9184
- is used as the VM mountPath.
9174
+ - --profile — remote runtime profile supplies provider/model/sandbox
9175
+ policy and CWD is used as the VM mountPath.
9185
9176
 
9186
9177
  Registered task types: ${knownTaskTypesList()}`;
9187
9178
  var POLL_HELP = `\
9188
9179
  agent-daemon poll — long-running task worker.
9189
9180
 
9190
9181
  Usage:
9191
- agent-daemon poll --team <uuid> --agent <name> --provider <p> --model <m> [...]
9182
+ agent-daemon poll --team <uuid> --agent <name> --profile <uuid|name> [...]
9192
9183
 
9193
9184
  Required:
9194
9185
  --team <uuid> Team whose queue to serve. The daemon must be
9195
9186
  a member of this team (canAccessTeam permit).
9196
9187
  ${COMMON_REQUIRED_FLAGS}
9197
- ${COMMON_MODEL_FLAGS}
9198
9188
 
9199
9189
  Optional:
9200
9190
  --task-types <csv> Whitelist of task types to claim. Default:
@@ -9211,38 +9201,38 @@ Example:
9211
9201
  --team 6743b4b1-6b93-46e2-a048-19490f04f91a \\
9212
9202
  --task-types curate_pack,fulfill_brief \\
9213
9203
  --agent legreffier \\
9214
- --provider anthropic \\
9215
- --model claude-sonnet-4-5
9204
+ --profile github-linear \\
9205
+ --profile local-fallback
9216
9206
 
9217
9207
  Stops cleanly on SIGINT/SIGTERM (drains the in-flight task before exit).`;
9218
9208
  var ONCE_HELP = `\
9219
9209
  agent-daemon once — execute one specific queued task by id, then exit.
9220
9210
 
9221
9211
  Usage:
9222
- agent-daemon once --task-id <uuid> --agent <name> --provider <p> --model <m> [...]
9212
+ agent-daemon once --task-id <uuid> --agent <name> --profile <uuid|name> [...]
9223
9213
 
9224
9214
  Required:
9225
9215
  -t, --task-id <uuid> Task to claim and execute. Must already be
9226
9216
  in 'queued' status.
9227
9217
  ${COMMON_REQUIRED_FLAGS}
9228
- ${COMMON_MODEL_FLAGS}
9229
9218
 
9230
9219
  Optional:
9220
+ --team <uuid> Team scope for resolving --profile by name.
9221
+ Required only when --profile is a name.
9231
9222
  ${COMMON_OPTIONAL_FLAGS}
9232
9223
 
9233
9224
  Example:
9234
9225
  agent-daemon once \\
9235
9226
  --task-id 26004a77-bc10-43ef-a79f-c8e62faf59b1 \\
9236
9227
  --agent legreffier \\
9237
- --provider anthropic \\
9238
- --model claude-sonnet-4-5
9228
+ --profile github-linear
9239
9229
 
9240
9230
  Exits 0 on completed, 1 on failed/cancelled/runtime-error.`;
9241
9231
  var DRAIN_HELP = `\
9242
9232
  agent-daemon drain — poll until the queue is empty, then exit.
9243
9233
 
9244
9234
  Usage:
9245
- agent-daemon drain --team <uuid> --agent <name> --provider <p> --model <m> [...]
9235
+ agent-daemon drain --team <uuid> --agent <name> --profile <uuid|name> [...]
9246
9236
 
9247
9237
  Same flags as \`poll\`. The only behavioural difference: \`drain\` exits
9248
9238
  when a list call confirms no claimable tasks remain (vs \`poll\` which
@@ -9251,7 +9241,6 @@ sleeps and retries forever).
9251
9241
  Required:
9252
9242
  --team <uuid> Team whose queue to drain.
9253
9243
  ${COMMON_REQUIRED_FLAGS}
9254
- ${COMMON_MODEL_FLAGS}
9255
9244
 
9256
9245
  Optional:
9257
9246
  --task-types <csv> Whitelist. Known types: ${knownTaskTypesList()}
@@ -9266,8 +9255,7 @@ Example:
9266
9255
  --team 6743b4b1-6b93-46e2-a048-19490f04f91a \\
9267
9256
  --task-types judge_pack \\
9268
9257
  --agent legreffier \\
9269
- --provider anthropic \\
9270
- --model claude-sonnet-4-5`;
9258
+ --profile eval-judge`;
9271
9259
  function isHelpFlag(args) {
9272
9260
  return args.includes("--help") || args.includes("-h");
9273
9261
  }
@@ -9472,10 +9460,8 @@ function buildDaemonSlotId(identity, slotKey) {
9472
9460
  return [
9473
9461
  "agent",
9474
9462
  slugSlotIdentityComponent(identity.agentName),
9475
- "provider",
9476
- slugSlotIdentityComponent(identity.provider),
9477
- "model",
9478
- slugSlotIdentityComponent(identity.model),
9463
+ "profile",
9464
+ slugSlotIdentityComponent(identity.runtimeProfileId),
9479
9465
  "key",
9480
9466
  slotKey
9481
9467
  ].join(":");
@@ -9790,7 +9776,6 @@ var MissingRequiredOptionError = class extends Error {
9790
9776
  }
9791
9777
  };
9792
9778
  function parseCommonOptions(args, options = {}) {
9793
- const requireProviderModel = options.requireProviderModel ?? true;
9794
9779
  const runtimeDefaults = {
9795
9780
  leaseTtlSec: options.runtimeDefaults?.leaseTtlSec ?? DEFAULTS.leaseTtlSec,
9796
9781
  heartbeatIntervalMs: options.runtimeDefaults?.heartbeatIntervalMs ?? DEFAULTS.heartbeatIntervalMs,
@@ -9798,13 +9783,9 @@ function parseCommonOptions(args, options = {}) {
9798
9783
  warmSessionTtlSec: options.runtimeDefaults?.warmSessionTtlSec ?? DEFAULTS.warmSessionTtlSec
9799
9784
  };
9800
9785
  if (!args.agent) throw new MissingRequiredOptionError("agent");
9801
- if (requireProviderModel && !args.provider) throw new MissingRequiredOptionError("provider");
9802
- if (requireProviderModel && !args.model) throw new MissingRequiredOptionError("model");
9803
9786
  if (!/^[a-zA-Z0-9_-]+$/.test(args.agent)) throw new Error(`Invalid --agent "${args.agent}": must match /^[a-zA-Z0-9_-]+$/`);
9804
9787
  return {
9805
9788
  agent: args.agent,
9806
- ...args.provider ? { provider: args.provider } : {},
9807
- ...args.model ? { model: args.model } : {},
9808
9789
  leaseTtlSec: parsePositiveInt(args["lease-ttl-sec"], "lease-ttl-sec", runtimeDefaults.leaseTtlSec),
9809
9790
  heartbeatIntervalMs: parseNonNegativeInt(args["heartbeat-interval-ms"], "heartbeat-interval-ms", runtimeDefaults.heartbeatIntervalMs),
9810
9791
  maxBatchSize: parsePositiveInt(args["max-batch-size"], "max-batch-size", runtimeDefaults.maxBatchSize),
@@ -9833,14 +9814,6 @@ function commonOptionDefs() {
9833
9814
  type: "string",
9834
9815
  short: "a"
9835
9816
  },
9836
- model: {
9837
- type: "string",
9838
- short: "m"
9839
- },
9840
- provider: {
9841
- type: "string",
9842
- short: "p"
9843
- },
9844
9817
  "lease-ttl-sec": { type: "string" },
9845
9818
  "heartbeat-interval-ms": { type: "string" },
9846
9819
  "max-batch-size": { type: "string" },
@@ -9919,6 +9892,12 @@ function ensurePiAgentDir(repoRoot, explicitPath) {
9919
9892
  };
9920
9893
  }
9921
9894
  //#endregion
9895
+ //#region src/lib/runtime-context.ts
9896
+ var storage = new AsyncLocalStorage();
9897
+ function runWithDaemonRuntimeContext(context, callback) {
9898
+ return storage.run(context, callback);
9899
+ }
9900
+ //#endregion
9922
9901
  //#region src/lib/runtime-profile.ts
9923
9902
  var RuntimeProfilePrerequisiteError = class extends Error {
9924
9903
  constructor(profileName, missingEnv, missingTools) {
@@ -9952,6 +9931,22 @@ async function resolveRuntimeProfile(options) {
9952
9931
  source: `runtime-profile:${profile.id}`
9953
9932
  };
9954
9933
  }
9934
+ async function resolveRuntimeProfiles(options) {
9935
+ const seen = /* @__PURE__ */ new Set();
9936
+ const out = [];
9937
+ for (const profile of options.profiles) {
9938
+ const resolved = await resolveRuntimeProfile({
9939
+ agent: options.agent,
9940
+ profile,
9941
+ teamId: options.teamId,
9942
+ cwd: options.cwd
9943
+ });
9944
+ if (seen.has(resolved.id)) continue;
9945
+ seen.add(resolved.id);
9946
+ out.push(resolved);
9947
+ }
9948
+ return out;
9949
+ }
9955
9950
  function validateRuntimeProfilePrerequisites(profile, env, pathValue) {
9956
9951
  const missingEnv = profile.requiredEnv.filter((name) => !env[name]);
9957
9952
  const missingTools = profile.requiredTools.filter((tool) => !isExecutableOnPath(tool, pathValue));
@@ -9986,12 +9981,12 @@ function isExecutable(path) {
9986
9981
  //#endregion
9987
9982
  //#region src/lib/runtime-slots.ts
9988
9983
  function createApiRuntimeSlotStore(args) {
9989
- const { agent, daemonProfileId } = args;
9984
+ const { agent } = args;
9990
9985
  return {
9991
9986
  async beginSlot(input) {
9992
9987
  await agent.runtimeSlots.begin({
9993
9988
  agentName: input.agentName,
9994
- daemonProfileId: daemonProfileId ?? void 0,
9989
+ runtimeProfileId: input.runtimeProfileId,
9995
9990
  lastAttemptN: input.lastAttemptN,
9996
9991
  lastTaskId: input.lastTaskId,
9997
9992
  model: input.model,
@@ -10006,12 +10001,13 @@ function createApiRuntimeSlotStore(args) {
10006
10001
  worktreePath: input.worktreePath ?? void 0
10007
10002
  }, { teamId: input.teamId });
10008
10003
  },
10009
- async finishSlot(teamId, taskId, attemptN, identity, slotKey, sessionPath) {
10004
+ async finishSlot(teamId, taskId, attemptN, identity, slotKey, provider, model, sessionPath) {
10010
10005
  await agent.runtimeSlots.finish({
10011
10006
  agentName: identity.agentName,
10012
10007
  attemptN,
10013
- model: identity.model,
10014
- provider: identity.provider,
10008
+ runtimeProfileId: identity.runtimeProfileId,
10009
+ model,
10010
+ provider,
10015
10011
  sessionPath: sessionPath ?? void 0,
10016
10012
  slotKey,
10017
10013
  taskId
@@ -10041,34 +10037,6 @@ function createApiRuntimeSlotStore(args) {
10041
10037
  };
10042
10038
  }
10043
10039
  //#endregion
10044
- //#region src/lib/sandbox.ts
10045
- function resolveSandbox(startDir, explicitPath) {
10046
- const path = explicitPath ? isAbsolute(explicitPath) ? explicitPath : resolve(startDir, explicitPath) : findUp(startDir, "sandbox.json");
10047
- if (!path) throw new Error(`sandbox.json not found in ${startDir} or any parent directory. Pass --sandbox <path> or run the daemon from a directory with sandbox.json at or above it.`);
10048
- let config;
10049
- try {
10050
- config = JSON.parse(readFileSync(path, "utf8"));
10051
- } catch (err) {
10052
- const isEnoent = err instanceof Error && "code" in err && err.code === "ENOENT";
10053
- throw new Error(isEnoent ? `sandbox.json not found at ${path}.` : `Failed to read sandbox.json at ${path}: ${err instanceof Error ? err.message : String(err)}`);
10054
- }
10055
- return {
10056
- config,
10057
- rootDir: dirname(path),
10058
- path
10059
- };
10060
- }
10061
- function findUp(startDir, filename) {
10062
- let dir = resolve(startDir);
10063
- while (true) {
10064
- const candidate = resolve(dir, filename);
10065
- if (existsSync(candidate)) return candidate;
10066
- const parent = dirname(dir);
10067
- if (parent === dir) return null;
10068
- dir = parent;
10069
- }
10070
- }
10071
- //#endregion
10072
10040
  //#region src/lib/shutdown-signal.ts
10073
10041
  function installShutdownSignalHandlers(opts) {
10074
10042
  const proc = opts.proc ?? process;
@@ -10150,7 +10118,10 @@ async function runPolling(opts) {
10150
10118
  "max-poll-interval-ms": { type: "string" },
10151
10119
  "list-limit": { type: "string" },
10152
10120
  sandbox: { type: "string" },
10153
- profile: { type: "string" }
10121
+ profile: {
10122
+ type: "string",
10123
+ multiple: true
10124
+ }
10154
10125
  }
10155
10126
  });
10156
10127
  if (!values.team) {
@@ -10159,6 +10130,12 @@ async function runPolling(opts) {
10159
10130
  return 1;
10160
10131
  }
10161
10132
  const teamId = values.team;
10133
+ const profileValues = parseProfileValues(values.profile);
10134
+ if (profileValues.length === 0) {
10135
+ console.error("Missing required flag: --profile\n");
10136
+ console.error(opts.helpText);
10137
+ return 1;
10138
+ }
10162
10139
  let taskTypes;
10163
10140
  try {
10164
10141
  taskTypes = validateTaskTypes(parseCsv(values["task-types"]));
@@ -10168,9 +10145,9 @@ async function runPolling(opts) {
10168
10145
  return 1;
10169
10146
  }
10170
10147
  const diaryIds = parseCsv(values["diary-ids"]);
10171
- let common;
10148
+ let baseCommon;
10172
10149
  try {
10173
- common = parseCommonOptions(values, { requireProviderModel: !values.profile });
10150
+ baseCommon = parseCommonOptions(values);
10174
10151
  } catch (err) {
10175
10152
  if (err instanceof MissingRequiredOptionError) {
10176
10153
  console.error(`${err.message}\n`);
@@ -10182,84 +10159,82 @@ async function runPolling(opts) {
10182
10159
  const pollIntervalMs = optionalPositiveInt(values["poll-interval-ms"], "poll-interval-ms", 2e3);
10183
10160
  const maxPollIntervalMs = optionalPositiveInt(values["max-poll-interval-ms"], "max-poll-interval-ms", 3e4);
10184
10161
  const listLimit = optionalPositiveInt(values["list-limit"], "list-limit", 10);
10185
- if (values.profile && values.sandbox) {
10186
- console.error(`[${opts.modeLabel}] Cannot use --sandbox with --profile. Remote runtime profiles define sandbox policy.`);
10162
+ if (values.sandbox) {
10163
+ console.error(`[${opts.modeLabel}] Cannot use --sandbox. Remote runtime profiles define sandbox policy.`);
10187
10164
  return 1;
10188
10165
  }
10189
10166
  if (taskTypes.length === 0) console.error(`[${opts.modeLabel}] --task-types is empty — daemon will accept any registered type. Pass an explicit list to limit scope (e.g. --task-types fulfill_brief).`);
10190
10167
  const cfg = loadConfig();
10191
- const ctx = await resolveAgentContext(common.agent);
10192
- const profile = values.profile ? await resolveRuntimeProfile({
10168
+ const ctx = await resolveAgentContext(baseCommon.agent);
10169
+ const profiles = await resolveRuntimeProfiles({
10193
10170
  agent: ctx.agent,
10194
- profile: values.profile,
10171
+ profiles: profileValues,
10195
10172
  teamId,
10196
10173
  cwd: process.cwd()
10197
- }) : null;
10198
- if (profile) {
10199
- validateRuntimeProfilePrerequisites(profile, cfg.profilePrerequisiteEnv, cfg.profilePrerequisitePath);
10200
- common = parseCommonOptions(values, {
10201
- requireProviderModel: false,
10202
- runtimeDefaults: {
10203
- leaseTtlSec: profile.leaseTtlSec,
10204
- heartbeatIntervalMs: profile.heartbeatIntervalMs,
10205
- maxBatchSize: profile.maxBatchSize,
10206
- warmSessionTtlSec: resolveProfileWarmSessionTtlSec(profile)
10207
- }
10174
+ });
10175
+ for (const profile of profiles) validateRuntimeProfilePrerequisites(profile, cfg.profilePrerequisiteEnv, cfg.profilePrerequisitePath);
10176
+ const slotRegistry = createApiRuntimeSlotStore({ agent: ctx.agent });
10177
+ const mainRepo = findMainWorktree();
10178
+ const runtimes = /* @__PURE__ */ new Map();
10179
+ for (const profile of profiles) {
10180
+ const common = parseCommonOptions(values, { runtimeDefaults: {
10181
+ leaseTtlSec: profile.leaseTtlSec,
10182
+ heartbeatIntervalMs: profile.heartbeatIntervalMs,
10183
+ maxBatchSize: profile.maxBatchSize,
10184
+ warmSessionTtlSec: resolveProfileWarmSessionTtlSec(profile)
10185
+ } });
10186
+ const sandbox = {
10187
+ config: profile.sandboxConfig,
10188
+ rootDir: profile.mountPath,
10189
+ path: profile.source
10190
+ };
10191
+ const piAgentDir = ensurePiAgentDir(sandbox.rootDir, cfg.piCodingAgentDir);
10192
+ const stateDirs = ensureDaemonStateDirs(sandbox.rootDir);
10193
+ const slotIdentity = {
10194
+ agentName: common.agent,
10195
+ runtimeProfileId: profile.id
10196
+ };
10197
+ const executionPlans = createExecutionPlanCache({
10198
+ stateDirs,
10199
+ slotIdentity,
10200
+ warmSessionTtlSec: common.warmSessionTtlSec,
10201
+ slotRegistry
10202
+ });
10203
+ runtimes.set(profile.id, {
10204
+ common,
10205
+ profile,
10206
+ sandbox,
10207
+ stateDirs,
10208
+ piAgentDir,
10209
+ slotIdentity,
10210
+ executionPlans
10208
10211
  });
10209
10212
  }
10210
- const provider = profile?.provider ?? common.provider;
10211
- const model = profile?.model ?? common.model;
10212
- if (!provider || !model) throw new Error("provider/model missing after runtime profile resolution");
10213
- const sandbox = profile ? {
10214
- config: profile.sandboxConfig,
10215
- rootDir: profile.mountPath,
10216
- path: profile.source
10217
- } : resolveSandbox(process.cwd(), values.sandbox);
10218
- const piAgentDir = ensurePiAgentDir(sandbox.rootDir, cfg.piCodingAgentDir);
10213
+ const firstRuntime = runtimes.get(profiles[0].id);
10214
+ if (!firstRuntime) throw new Error("No runtime profiles resolved");
10215
+ const piAgentDir = firstRuntime.piAgentDir;
10219
10216
  activatePiCodingAgentDir(piAgentDir.path);
10220
- const stateDirs = ensureDaemonStateDirs(sandbox.rootDir);
10221
- const slotRegistry = createApiRuntimeSlotStore({
10222
- agent: ctx.agent,
10223
- daemonProfileId: profile?.id ?? null
10224
- });
10225
- const slotIdentity = {
10226
- agentName: common.agent,
10227
- provider,
10228
- model
10229
- };
10230
- const mainRepo = findMainWorktree();
10231
- const executionPlans = createExecutionPlanCache({
10232
- stateDirs,
10233
- slotIdentity,
10234
- warmSessionTtlSec: common.warmSessionTtlSec,
10235
- slotRegistry
10236
- });
10237
10217
  const otelShutdown = await initWorkerOtel({
10238
10218
  serviceName: opts.serviceName,
10239
10219
  agentDir: ctx.agentDir,
10240
10220
  endpoint: cfg.otelEndpoint,
10241
10221
  resourceAttributes: {
10242
10222
  "moltnet.team.id": teamId,
10243
- "moltnet.agent.name": common.agent,
10244
- "moltnet.llm.provider": provider,
10245
- "moltnet.llm.model": model,
10246
- ...profile ? { "moltnet.daemon_profile.id": profile.id } : {}
10223
+ "moltnet.agent.name": baseCommon.agent,
10224
+ "moltnet.runtime_profile.count": String(profiles.length),
10225
+ "moltnet.runtime_profile.ids": profiles.map((p) => p.id).join(",")
10247
10226
  }
10248
10227
  });
10249
10228
  const { logger, shutdown: shutdownLogger } = createRootLogger({
10250
10229
  name: `agent-daemon.${opts.modeLabel}`,
10251
- level: cfg.logLevel || (common.debug ? "debug" : "info")
10230
+ level: cfg.logLevel || (baseCommon.debug ? "debug" : "info")
10252
10231
  });
10253
10232
  const rootLogger = logger.child({
10254
10233
  mode: opts.modeLabel,
10255
- agent: common.agent,
10234
+ agent: baseCommon.agent,
10256
10235
  teamId,
10257
- provider,
10258
- model,
10259
- ...profile ? {
10260
- daemonProfileId: profile.id,
10261
- daemonProfileName: profile.name
10262
- } : {}
10236
+ runtimeProfileIds: profiles.map((p) => p.id),
10237
+ runtimeProfileNames: profiles.map((p) => p.name)
10263
10238
  });
10264
10239
  const abort = new AbortController();
10265
10240
  let runtime = null;
@@ -10285,90 +10260,91 @@ async function runPolling(opts) {
10285
10260
  }
10286
10261
  });
10287
10262
  rootLogger.info({
10288
- sandbox: sandbox.path,
10289
10263
  taskTypes: taskTypes.length > 0 ? taskTypes : ["*"],
10290
10264
  diaryIds: diaryIds.length > 0 ? diaryIds : ["*"],
10291
- leaseTtlSec: common.leaseTtlSec,
10292
- heartbeatIntervalMs: common.heartbeatIntervalMs,
10293
- warmSessionTtlSec: common.warmSessionTtlSec,
10294
10265
  pollIntervalMs,
10295
10266
  maxPollIntervalMs,
10296
- ...profile ? {
10297
- profileId: profile.id,
10298
- profileSessionTtlSec: profile.sessionTtlSec,
10299
- profileWorkspaceTtlSec: profile.workspaceTtlSec
10300
- } : {},
10267
+ profiles: profiles.map((profile) => {
10268
+ const runtime = requireRuntime(runtimes, profile.id);
10269
+ return {
10270
+ id: profile.id,
10271
+ name: profile.name,
10272
+ provider: profile.provider,
10273
+ model: profile.model,
10274
+ sandbox: runtime.sandbox.path,
10275
+ leaseTtlSec: runtime.common.leaseTtlSec,
10276
+ heartbeatIntervalMs: runtime.common.heartbeatIntervalMs,
10277
+ warmSessionTtlSec: runtime.common.warmSessionTtlSec,
10278
+ profileSessionTtlSec: profile.sessionTtlSec,
10279
+ profileWorkspaceTtlSec: profile.workspaceTtlSec
10280
+ };
10281
+ }),
10301
10282
  piAgentDir: piAgentDir.path,
10302
10283
  piAgentDirSource: piAgentDir.source
10303
10284
  }, "agent-daemon.starting");
10304
10285
  const outputs = [];
10305
10286
  try {
10306
- const rawExecuteTask = createPiTaskExecutor({
10307
- agentName: common.agent,
10308
- mountPath: sandbox.rootDir,
10309
- provider,
10310
- model,
10311
- sandboxConfig: sandbox.config,
10312
- makeExecutionPlan: (claimedTask) => executionPlans.getOrCreate(claimedTask),
10313
- makeOnTurnEvent: makeTurnEventHandlerFactory(rootLogger),
10314
- maxTurns: common.maxTurns,
10315
- maxBashTimeouts: common.maxBashTimeouts
10316
- });
10317
- const executeTask = async (claimedTask, reporter) => {
10318
- active = {
10319
- taskId: claimedTask.task.id,
10320
- attemptN: claimedTask.attemptN
10321
- };
10322
- try {
10323
- return await rawExecuteTask(claimedTask, reporter);
10324
- } finally {
10325
- active = null;
10326
- }
10327
- };
10328
10287
  runtime = new AgentRuntime({
10329
10288
  logger: rootLogger,
10330
10289
  source: new PollingApiTaskSource({
10331
10290
  agent: ctx.agent,
10332
10291
  teamId,
10333
10292
  taskTypes: taskTypes.length > 0 ? taskTypes : void 0,
10334
- ...profile ? { profileId: profile.id } : {},
10293
+ profiles: profiles.map((profile) => ({
10294
+ profileId: profile.id,
10295
+ leaseTtlSec: requireRuntime(runtimes, profile.id).common.leaseTtlSec
10296
+ })),
10335
10297
  diaryIds: diaryIds.length > 0 ? diaryIds : void 0,
10336
- leaseTtlSec: common.leaseTtlSec,
10298
+ leaseTtlSec: firstRuntime.common.leaseTtlSec,
10337
10299
  listLimit,
10338
10300
  pollIntervalMs,
10339
10301
  maxPollIntervalMs,
10340
10302
  signal: abort.signal,
10341
10303
  stopWhenEmpty: opts.stopWhenEmpty,
10342
- debug: common.debug,
10304
+ debug: baseCommon.debug,
10343
10305
  logger: rootLogger,
10344
10306
  slotRegistry
10345
10307
  }),
10346
- makeReporter: () => new ApiTaskReporter({
10347
- tasks: ctx.agent.tasks,
10348
- leaseTtlSec: common.leaseTtlSec,
10349
- heartbeatIntervalMs: common.heartbeatIntervalMs,
10350
- maxBatchSize: common.maxBatchSize,
10351
- flushIntervalMs: common.flushIntervalMs
10352
- }),
10308
+ makeReporter: (claimedTask) => {
10309
+ const selected = runtimeForClaimedTask(runtimes, claimedTask);
10310
+ return new ApiTaskReporter({
10311
+ tasks: ctx.agent.tasks,
10312
+ leaseTtlSec: selected.common.leaseTtlSec,
10313
+ heartbeatIntervalMs: selected.common.heartbeatIntervalMs,
10314
+ maxBatchSize: selected.common.maxBatchSize,
10315
+ flushIntervalMs: selected.common.flushIntervalMs
10316
+ });
10317
+ },
10353
10318
  onTaskFinished: async (output, claimedTask) => {
10319
+ const selected = runtimeForClaimedTask(runtimes, claimedTask);
10354
10320
  const resolved = await slotRegistry.findLatestSlotByTaskAttempt(claimedTask.task.teamId, claimedTask.task.id, claimedTask.attemptN);
10355
10321
  return finalizeTask(ctx.agent, output, {
10356
10322
  task: claimedTask.task,
10357
10323
  slot: resolved ? { expiresAtMs: resolved.slot.expiresAtMs } : null,
10358
10324
  writeCorrelationAnchors: makePrBodyAnchorWriter({
10359
10325
  gh: createGhCliClient(),
10360
- logger: rootLogger
10326
+ logger: rootLogger.child({
10327
+ runtimeProfileId: selected.profile.id,
10328
+ runtimeProfileName: selected.profile.name
10329
+ })
10361
10330
  }),
10362
10331
  log: (msg, err) => rootLogger.warn({ err }, msg)
10363
10332
  });
10364
10333
  },
10365
10334
  executeTask: async (claimedTask, reporter) => {
10335
+ const { common, executionPlans, profile, sandbox, slotIdentity, stateDirs } = runtimeForClaimedTask(runtimes, claimedTask);
10336
+ const taskLogger = rootLogger.child({
10337
+ runtimeProfileId: profile.id,
10338
+ runtimeProfileName: profile.name,
10339
+ provider: profile.provider,
10340
+ model: profile.model
10341
+ });
10366
10342
  let executionPlan;
10367
10343
  try {
10368
10344
  executionPlan = await executionPlans.getOrCreate(claimedTask);
10369
10345
  } catch (err) {
10370
10346
  const message = err instanceof Error ? err.message : String(err);
10371
- rootLogger.warn({
10347
+ taskLogger.warn({
10372
10348
  taskId: claimedTask.task.id,
10373
10349
  attemptN: claimedTask.attemptN,
10374
10350
  err: message
@@ -10392,7 +10368,7 @@ async function runPolling(opts) {
10392
10368
  };
10393
10369
  }
10394
10370
  const sessionDescriptor = executionPlan.descriptor;
10395
- rootLogger.debug({
10371
+ taskLogger.debug({
10396
10372
  taskId: claimedTask.task.id,
10397
10373
  taskType: claimedTask.task.taskType,
10398
10374
  resumable: sessionDescriptor.policy.resumable,
@@ -10441,6 +10417,9 @@ async function runPolling(opts) {
10441
10417
  };
10442
10418
  if (executionPlan.slotKey && executionPlan.sessionPersistence) await slotRegistry.beginSlot({
10443
10419
  ...slotIdentity,
10420
+ runtimeProfileId: profile.id,
10421
+ provider: profile.provider,
10422
+ model: profile.model,
10444
10423
  teamId: claimedTask.task.teamId,
10445
10424
  slotKey: executionPlan.slotKey,
10446
10425
  taskType: claimedTask.task.taskType,
@@ -10453,11 +10432,32 @@ async function runPolling(opts) {
10453
10432
  lastTaskId: claimedTask.task.id,
10454
10433
  lastAttemptN: claimedTask.attemptN
10455
10434
  });
10435
+ const rawExecuteTask = createPiTaskExecutor({
10436
+ agentName: common.agent,
10437
+ mountPath: sandbox.rootDir,
10438
+ provider: profile.provider,
10439
+ model: profile.model,
10440
+ sandboxConfig: sandbox.config,
10441
+ makeExecutionPlan: (task) => executionPlans.getOrCreate(task),
10442
+ makeOnTurnEvent: makeTurnEventHandlerFactory(taskLogger),
10443
+ maxTurns: common.maxTurns,
10444
+ maxBashTimeouts: common.maxBashTimeouts
10445
+ });
10456
10446
  try {
10457
- return await executeTask(claimedTask, reporter);
10447
+ active = {
10448
+ taskId: claimedTask.task.id,
10449
+ attemptN: claimedTask.attemptN
10450
+ };
10451
+ return await runWithDaemonRuntimeContext({
10452
+ profileId: profile.id,
10453
+ profileName: profile.name,
10454
+ provider: profile.provider,
10455
+ model: profile.model
10456
+ }, () => rawExecuteTask(claimedTask, reporter));
10458
10457
  } finally {
10458
+ active = null;
10459
10459
  executionPlans.delete(claimedTask);
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);
10460
+ if (executionPlan.slotKey) await slotRegistry.finishSlot(claimedTask.task.teamId, claimedTask.task.id, claimedTask.attemptN, slotIdentity, executionPlan.slotKey, profile.provider, profile.model, executionPlan.sessionPersistence ? resolveLatestPiSessionPath(executionPlan.sessionPersistence.sessionDir) : null);
10461
10461
  }
10462
10462
  }
10463
10463
  });
@@ -10476,6 +10476,18 @@ function resolveRecordedWorkspacePath$1(mainRepo, stateRootDir, executionPlan) {
10476
10476
  if (!executionPlan.workspaceId) return null;
10477
10477
  return executionPlan.workspaceMode === "scratch_mount" ? join(stateRootDir, "task-workspaces", executionPlan.workspaceId) : join(mainRepo, ".worktrees", executionPlan.workspaceId);
10478
10478
  }
10479
+ function runtimeForClaimedTask(runtimes, claimedTask) {
10480
+ if (!claimedTask.profileId) throw new Error(`Claimed task ${claimedTask.task.id} did not include a selected runtime profile`);
10481
+ return requireRuntime(runtimes, claimedTask.profileId);
10482
+ }
10483
+ function requireRuntime(runtimes, profileId) {
10484
+ const runtime = runtimes.get(profileId);
10485
+ if (!runtime) throw new Error(`No runtime profile configured for ${profileId}`);
10486
+ return runtime;
10487
+ }
10488
+ function parseProfileValues(raw) {
10489
+ return (raw ?? []).map((s) => s.trim()).filter((s) => s.length > 0);
10490
+ }
10479
10491
  function parseCsv(raw) {
10480
10492
  return (raw ?? "").split(",").map((s) => s.trim()).filter((s) => s.length > 0);
10481
10493
  }
@@ -10511,6 +10523,7 @@ async function runOnce(argv) {
10511
10523
  type: "string",
10512
10524
  short: "t"
10513
10525
  },
10526
+ team: { type: "string" },
10514
10527
  sandbox: { type: "string" },
10515
10528
  profile: { type: "string" }
10516
10529
  }
@@ -10521,9 +10534,14 @@ async function runOnce(argv) {
10521
10534
  return 1;
10522
10535
  }
10523
10536
  const taskId = values["task-id"];
10537
+ if (!values.profile) {
10538
+ console.error("Missing required flag: --profile\n");
10539
+ console.error(ONCE_HELP);
10540
+ return 1;
10541
+ }
10524
10542
  let opts;
10525
10543
  try {
10526
- opts = parseCommonOptions(values, { requireProviderModel: !values.profile });
10544
+ opts = parseCommonOptions(values);
10527
10545
  } catch (err) {
10528
10546
  if (err instanceof MissingRequiredOptionError) {
10529
10547
  console.error(`${err.message}\n`);
@@ -10532,48 +10550,37 @@ async function runOnce(argv) {
10532
10550
  }
10533
10551
  throw err;
10534
10552
  }
10535
- if (values.profile && values.sandbox) {
10536
- console.error("Cannot use --sandbox with --profile. Remote runtime profiles define sandbox policy.");
10553
+ if (values.sandbox) {
10554
+ console.error("Cannot use --sandbox. Remote runtime profiles define sandbox policy.");
10537
10555
  return 1;
10538
10556
  }
10539
10557
  const cfg = loadConfig();
10540
10558
  const ctx = await resolveAgentContext(opts.agent);
10541
- const profile = values.profile ? await resolveRuntimeProfile({
10559
+ const profile = await resolveRuntimeProfile({
10542
10560
  agent: ctx.agent,
10543
10561
  profile: values.profile,
10562
+ teamId: values.team,
10544
10563
  cwd: process.cwd()
10545
- }) : null;
10546
- if (profile) {
10547
- validateRuntimeProfilePrerequisites(profile, cfg.profilePrerequisiteEnv, cfg.profilePrerequisitePath);
10548
- opts = parseCommonOptions(values, {
10549
- requireProviderModel: false,
10550
- runtimeDefaults: {
10551
- leaseTtlSec: profile.leaseTtlSec,
10552
- heartbeatIntervalMs: profile.heartbeatIntervalMs,
10553
- maxBatchSize: profile.maxBatchSize,
10554
- warmSessionTtlSec: resolveProfileWarmSessionTtlSec(profile)
10555
- }
10556
- });
10557
- }
10558
- const provider = profile?.provider ?? opts.provider;
10559
- const model = profile?.model ?? opts.model;
10560
- if (!provider || !model) throw new Error("provider/model missing after runtime profile resolution");
10561
- const sandbox = profile ? {
10564
+ });
10565
+ validateRuntimeProfilePrerequisites(profile, cfg.profilePrerequisiteEnv, cfg.profilePrerequisitePath);
10566
+ opts = parseCommonOptions(values, { runtimeDefaults: {
10567
+ leaseTtlSec: profile.leaseTtlSec,
10568
+ heartbeatIntervalMs: profile.heartbeatIntervalMs,
10569
+ maxBatchSize: profile.maxBatchSize,
10570
+ warmSessionTtlSec: resolveProfileWarmSessionTtlSec(profile)
10571
+ } });
10572
+ const sandbox = {
10562
10573
  config: profile.sandboxConfig,
10563
10574
  rootDir: profile.mountPath,
10564
10575
  path: profile.source
10565
- } : resolveSandbox(process.cwd(), values.sandbox);
10576
+ };
10566
10577
  const piAgentDir = ensurePiAgentDir(sandbox.rootDir, cfg.piCodingAgentDir);
10567
10578
  activatePiCodingAgentDir(piAgentDir.path);
10568
10579
  const stateDirs = ensureDaemonStateDirs(sandbox.rootDir);
10569
- const slotRegistry = createApiRuntimeSlotStore({
10570
- agent: ctx.agent,
10571
- daemonProfileId: profile?.id ?? null
10572
- });
10580
+ const slotRegistry = createApiRuntimeSlotStore({ agent: ctx.agent });
10573
10581
  const slotIdentity = {
10574
10582
  agentName: opts.agent,
10575
- provider,
10576
- model
10583
+ runtimeProfileId: profile.id
10577
10584
  };
10578
10585
  const mainRepo = findMainWorktree();
10579
10586
  const executionPlans = createExecutionPlanCache({
@@ -10589,9 +10596,9 @@ async function runOnce(argv) {
10589
10596
  resourceAttributes: {
10590
10597
  "moltnet.task.id": taskId,
10591
10598
  "moltnet.agent.name": opts.agent,
10592
- "moltnet.llm.provider": provider,
10593
- "moltnet.llm.model": model,
10594
- ...profile ? { "moltnet.daemon_profile.id": profile.id } : {}
10599
+ "moltnet.llm.provider": profile.provider,
10600
+ "moltnet.llm.model": profile.model,
10601
+ "moltnet.runtime_profile.id": profile.id
10595
10602
  }
10596
10603
  });
10597
10604
  const { logger, shutdown: shutdownLogger } = createRootLogger({
@@ -10601,12 +10608,10 @@ async function runOnce(argv) {
10601
10608
  const rootLogger = logger.child({
10602
10609
  mode: "once",
10603
10610
  agent: opts.agent,
10604
- provider,
10605
- model,
10606
- ...profile ? {
10607
- daemonProfileId: profile.id,
10608
- daemonProfileName: profile.name
10609
- } : {}
10611
+ provider: profile.provider,
10612
+ model: profile.model,
10613
+ runtimeProfileId: profile.id,
10614
+ runtimeProfileName: profile.name
10610
10615
  });
10611
10616
  rootLogger.info({
10612
10617
  sandbox: sandbox.path,
@@ -10614,11 +10619,9 @@ async function runOnce(argv) {
10614
10619
  leaseTtlSec: opts.leaseTtlSec,
10615
10620
  heartbeatIntervalMs: opts.heartbeatIntervalMs,
10616
10621
  warmSessionTtlSec: opts.warmSessionTtlSec,
10617
- ...profile ? {
10618
- profileId: profile.id,
10619
- profileSessionTtlSec: profile.sessionTtlSec,
10620
- profileWorkspaceTtlSec: profile.workspaceTtlSec
10621
- } : {},
10622
+ profileId: profile.id,
10623
+ profileSessionTtlSec: profile.sessionTtlSec,
10624
+ profileWorkspaceTtlSec: profile.workspaceTtlSec,
10622
10625
  piAgentDir: piAgentDir.path,
10623
10626
  piAgentDirSource: piAgentDir.source
10624
10627
  }, "agent-daemon.starting");
@@ -10652,8 +10655,8 @@ async function runOnce(argv) {
10652
10655
  const rawExecuteTask = createPiTaskExecutor({
10653
10656
  agentName: opts.agent,
10654
10657
  mountPath: sandbox.rootDir,
10655
- provider,
10656
- model,
10658
+ provider: profile.provider,
10659
+ model: profile.model,
10657
10660
  sandboxConfig: sandbox.config,
10658
10661
  makeExecutionPlan: (claimedTask) => executionPlans.getOrCreate(claimedTask),
10659
10662
  onTurnEvent: makeTurnEventHandler(rootLogger, { taskId }),
@@ -10691,6 +10694,9 @@ async function runOnce(argv) {
10691
10694
  }
10692
10695
  if (executionPlan.slotKey && executionPlan.sessionPersistence) await slotRegistry.beginSlot({
10693
10696
  ...slotIdentity,
10697
+ runtimeProfileId: profile.id,
10698
+ provider: profile.provider,
10699
+ model: profile.model,
10694
10700
  teamId: claimedTask.task.teamId,
10695
10701
  slotKey: executionPlan.slotKey,
10696
10702
  taskType: claimedTask.task.taskType,
@@ -10705,11 +10711,16 @@ async function runOnce(argv) {
10705
10711
  });
10706
10712
  activeAttemptN = claimedTask.attemptN;
10707
10713
  try {
10708
- return await rawExecuteTask(claimedTask, reporter);
10714
+ return await runWithDaemonRuntimeContext({
10715
+ profileId: profile.id,
10716
+ profileName: profile.name,
10717
+ provider: profile.provider,
10718
+ model: profile.model
10719
+ }, () => rawExecuteTask(claimedTask, reporter));
10709
10720
  } finally {
10710
10721
  activeAttemptN = null;
10711
10722
  executionPlans.delete(claimedTask);
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);
10723
+ if (executionPlan.slotKey) await slotRegistry.finishSlot(claimedTask.task.teamId, claimedTask.task.id, claimedTask.attemptN, slotIdentity, executionPlan.slotKey, profile.provider, profile.model, executionPlan.sessionPersistence ? resolveLatestPiSessionPath(executionPlan.sessionPersistence.sessionDir) : null);
10713
10724
  }
10714
10725
  };
10715
10726
  const writeCorrelationAnchors = makePrBodyAnchorWriter({
@@ -10722,7 +10733,7 @@ async function runOnce(argv) {
10722
10733
  agent: ctx.agent,
10723
10734
  taskId,
10724
10735
  leaseTtlSec: opts.leaseTtlSec,
10725
- ...profile ? { profileId: profile.id } : {}
10736
+ profileId: profile.id
10726
10737
  }),
10727
10738
  makeReporter: () => new ApiTaskReporter({
10728
10739
  tasks: ctx.agent.tasks,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@themoltnet/agent-daemon",
3
- "version": "0.20.0",
3
+ "version": "0.21.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,8 +45,8 @@
45
45
  "@opentelemetry/semantic-conventions": "^1.39.0",
46
46
  "pino": "^10.3.1",
47
47
  "pino-pretty": "^13.1.3",
48
- "@themoltnet/agent-runtime": "0.27.0",
49
- "@themoltnet/pi-extension": "0.26.1",
48
+ "@themoltnet/agent-runtime": "0.28.0",
49
+ "@themoltnet/pi-extension": "0.26.2",
50
50
  "@themoltnet/sdk": "0.109.0"
51
51
  },
52
52
  "devDependencies": {
@@ -54,9 +54,9 @@
54
54
  "typescript": "~5.9.2",
55
55
  "vite": "^8.0.0",
56
56
  "vitest": "^3.0.0",
57
- "@moltnet/bootstrap": "0.1.0",
58
57
  "@moltnet/crypto-service": "0.1.0",
59
58
  "@moltnet/observability": "0.1.0",
59
+ "@moltnet/bootstrap": "0.1.0",
60
60
  "@moltnet/tasks": "0.1.0"
61
61
  },
62
62
  "nx": {