@themoltnet/agent-daemon 0.43.0 → 0.44.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 CHANGED
@@ -102,32 +102,33 @@ any team where the agent is authorized. It fails fast if the key is rejected,
102
102
  is not an agent, or a team binding mismatches. See
103
103
  [Run the daemon with an agent key](../../docs/operate/running-agents.md#run-the-daemon-with-an-agent-key).
104
104
 
105
- Daemon authentication and guest credential projection are two separate
106
- boundaries. How the daemon authenticates (an agent key, or OAuth2 resolved
107
- from `.moltnet/<agent>/moltnet.json` through the host secret provider) decides
108
- how the host-side SDK `Agent` is built. `--guest-credential-mode` decides
109
- whether the local agent credential tree is copied into Gondolin. Pi guests
110
- default to the `host-authenticated` boundary in **both** auth modes, regardless
111
- of local files: no `.moltnet` file, gitconfig, SSH signing key, GitHub App PEM,
112
- or MoltNet environment credential is injected into Gondolin, and mounted
113
- `.moltnet` paths are hidden. Structured MoltNet tools execute through the
114
- host-side `Agent`. Operators may explicitly opt into the legacy
115
- credential-bearing boundary with `--guest-credential-mode guest-config`; that
116
- mode requires a complete local `moltnet.json` + `env` pair and copies the
117
- complete agent configuration and signing credentials into the guest. Reserve it
118
- for local or otherwise operator-trusted tasks that genuinely need guest-shell
119
- MoltNet CLI, Git signing, or GitHub authentication. OAuth2 daemons that relied
120
- on the previous `guest-config` default for such tasks must now pass the flag
121
- explicitly.
122
-
123
- Trusted custom runtimes can deliver destination-bound HTTP credentials without
124
- switching to `guest-config`: the guest receives an opaque placeholder and the
125
- Gondolin host proxy substitutes the value only for the attested protocol,
126
- hostname pattern, and port (HTTPS/443 by default). See
105
+ Daemon authentication and the guest boundary are two separate concerns. How the
106
+ daemon authenticates (an agent key, or OAuth2 resolved from
107
+ `.moltnet/<agent>/moltnet.json` through the host secret provider) decides how the
108
+ host-side SDK `Agent` is built. The guest boundary is fixed: **the guest never
109
+ receives MoltNet credential material.** No `.moltnet` file, gitconfig, SSH
110
+ signing key, GitHub App PEM, or MoltNet environment credential is injected into
111
+ Gondolin, and mounted `.moltnet` paths are hidden. Structured MoltNet tools
112
+ execute through the host-side `Agent`.
113
+
114
+ Trusted custom runtimes can deliver destination-bound HTTP credentials to the
115
+ guest: it receives an opaque placeholder and the Gondolin host proxy substitutes
116
+ the value only for the attested protocol, hostname pattern, and port (HTTPS/443
117
+ by default). See
127
118
  [Host-brokered HTTP credentials](../../docs/operate/running-agents.md#host-brokered-http-credentials).
128
119
  This does not provide diary or Git commit signing; private-key operations remain
129
120
  host capabilities.
130
121
 
122
+ Host capabilities carry in-guest signing: the stock runtime
123
+ declares `agent-signing`, so `git commit -S` and `moltnet entry create-signed`
124
+ work inside the guest while the seed stays on the host (the daemon injects a
125
+ signer bound to the authenticated identity and projects a non-secret gitconfig,
126
+ an `SSH_AUTH_SOCK` service, and `MOLTNET_SIGNER_URL`). Grant
127
+ `capability:agent-signing` (or per-operation `capability:agent-signing:<op>`)
128
+ in the tool policy. `--git-author "Name <email>"` / `MOLTNET_GIT_AUTHOR`
129
+ overrides the projected git identity. See
130
+ [Host capabilities](../../docs/operate/running-agents.md#host-capabilities).
131
+
131
132
  `sync-sessions` does not prepare or attest executors, so it remains independent
132
133
  of `MOLTNET_PRIVATE_KEY`.
133
134
 
package/dist/cli.js CHANGED
@@ -8,20 +8,20 @@ import "multiformats/hashes/sha2";
8
8
  import "typebox/value";
9
9
  import { dirname, join, resolve } from "node:path";
10
10
  import { parseArgs, promisify } from "node:util";
11
- import { AgentRuntime, ApiTaskReporter, ApiTaskSource, PollingApiTaskSource, resolveProfileWarmSessionTtlSec, resolveRuntimeProfile, resolveRuntimeProfiles, validateRuntimeProfilePrerequisites } from "@themoltnet/agent-runtime";
11
+ import { AgentRuntime, ApiTaskReporter, ApiTaskSource, PollingApiTaskSource, createLocalSeedSigner, resolveAgentIdentity, resolveProfileWarmSessionTtlSec, resolveRuntimeProfile, resolveRuntimeProfiles, validateRuntimeProfilePrerequisites } from "@themoltnet/agent-runtime";
12
12
  import { GuestEnvironmentBoundaryError, assertGuestEnvironmentBoundary, createPiRetryTriage, findMainWorktree, isResolvedPathInsideRoot, normalizeRetryTriageResult, redactRetryTriageSecrets } from "@themoltnet/pi-runtime";
13
13
  import { execFile, execFileSync } from "node:child_process";
14
14
  import { createReadStream, createWriteStream, existsSync, mkdirSync, readdirSync, realpathSync, rmSync } from "node:fs";
15
15
  import { AuthenticationError, MoltNetError, connect, createExecutorAttestor, readConfig } from "@themoltnet/sdk";
16
16
  import { createNodeSecretProviderRegistry } from "@themoltnet/sdk/node";
17
17
  import { createHash, randomUUID } from "node:crypto";
18
+ import * as ed from "@noble/ed25519";
19
+ import { createHash as createHash$1, randomBytes } from "crypto";
18
20
  import "multiformats/codecs/raw";
19
21
  import "multiformats/hashes/digest";
20
22
  import "@noble/hashes/sha2";
21
23
  import "multiformats/bases/base32";
22
24
  import { ed25519 } from "@noble/curves/ed25519.js";
23
- import * as ed from "@noble/ed25519";
24
- import { createHash as createHash$1, randomBytes } from "crypto";
25
25
  import "@ipld/dag-cbor";
26
26
  import "@noble/ciphers/chacha";
27
27
  import "@noble/hashes/hkdf";
@@ -690,12 +690,12 @@ var RUNTIME_PROFILE_CONTEXT_CATALOGUE = {
690
690
  },
691
691
  "accountable-delivery-v1": {
692
692
  binding: "prompt_prefix",
693
- content: "# Accountable delivery\n\n- Pair every commit made during this task with a task-provenance diary entry created by the `moltnet_create_entry` custom tool. Put the returned id in a `MoltNet-Diary: <id>` commit trailer. The tool does not currently promise a content signature; never describe an entry as signed unless the active runtime exposes and verifies a signing capability.\n- Do not disable commit signing when the active runtime provides it. A host-authenticated guest has no injected signing key, so do not recover one from host configuration or claim an unsigned commit is signed.\n- Push a branch and open or update a pull request only when the task asks for it. Use a host-brokered GitHub placeholder only when the runtime kernel declares one; if no GitHub credential is active, the authenticated operation is unavailable.\n- Keep changes, commits, and any requested pull request coherent enough to review independently.",
693
+ content: "# Accountable delivery\n\n- Pair every commit made during this task with a task-provenance diary entry created by the `moltnet_create_entry` custom tool. Put the returned id in a `MoltNet-Diary: <id>` commit trailer. The tool does not currently promise a content signature unless you pass `signed: true` while the runtime kernel declares the `agent-signing` host capability; never describe an entry as signed otherwise.\n- When the runtime kernel declares `agent-signing`, sign commits normally with `git commit -S`: the signature is brokered to the trusted host through `SSH_AUTH_SOCK` and no private key exists in the guest. Without that capability commits are unsigned; do not disable signing the runtime provides, and never try to obtain a key from host configuration.\n- Push a branch and open or update a pull request only when the task asks for it. Use a host-brokered GitHub placeholder only when the runtime kernel declares one; if no GitHub credential is active, the authenticated operation is unavailable.\n- Keep changes, commits, and any requested pull request coherent enough to review independently.",
694
694
  slug: "accountable-delivery-v1"
695
695
  },
696
696
  "judgment-diary-v1": {
697
697
  binding: "prompt_prefix",
698
- content: "# Judgment diary discipline\n\n- For an `assess_brief`, `judge_pack`, or `pr_review` task, create a diary entry with the `moltnet_create_entry` custom tool before submitting the structured judgment. Capture the rationale and evidence that support the verdict. Do not claim a content signature unless the active runtime exposes and verifies a signing capability.\n- Add the `judgment` tag and the active task type tag (`assess_brief`, `judge_pack`, or `pr_review`). For `judge_pack`, also add `rubric:<rubricId>` from the task facts.\n- Do not use a shell `moltnet entry` command: task provenance is injected only by the custom tool.",
698
+ content: "# Judgment diary discipline\n\n- For an `assess_brief`, `judge_pack`, or `pr_review` task, create a diary entry with the `moltnet_create_entry` custom tool before submitting the structured judgment. Capture the rationale and evidence that support the verdict. Do not claim a content signature unless you created the entry with `signed: true` under a runtime that declares the `agent-signing` host capability.\n- Add the `judgment` tag and the active task type tag (`assess_brief`, `judge_pack`, or `pr_review`). For `judge_pack`, also add `rubric:<rubricId>` from the task facts.\n- Do not use a shell `moltnet entry` command: task provenance is injected only by the custom tool.",
699
699
  slug: "judgment-diary-v1"
700
700
  },
701
701
  "proactive-memory-v1": {
@@ -710,7 +710,7 @@ var RUNTIME_PROFILE_CONTEXT_CATALOGUE = {
710
710
  },
711
711
  "task-diary-discipline-v1": {
712
712
  binding: "prompt_prefix",
713
- content: "# Task diary discipline\n\n- During a daemon task, create diary entries only through the `moltnet_create_entry` custom tool. It binds entries to the current task diary and injects task, type, attempt, and correlation provenance tags.\n- Do not shell out to `moltnet entry create`, `moltnet entry create-signed`, or any other `moltnet entry` subcommand from bash while a task is running. Those paths bypass the custom tool's task-tag injection, so task-filtered diary queries cannot find the entry.\n- You may add useful tags, but do not try to replace task provenance supplied by the runtime.",
713
+ content: "# Task diary discipline\n\n- During a daemon task, create diary entries only through the `moltnet_create_entry` custom tool. It binds entries to the current task diary and injects task, type, attempt, and correlation provenance tags.\n- Do not shell out to `moltnet entry create`, `moltnet entry create-signed`, or any other `moltnet entry` subcommand from bash while a task is running. For a content-signed entry pass `signed: true` to the custom tool instead; it signs on the trusted host. Those shell paths bypass the custom tool's task-tag injection, so task-filtered diary queries cannot find the entry.\n- You may add useful tags, but do not try to replace task provenance supplied by the runtime.",
714
714
  slug: "task-diary-discipline-v1"
715
715
  },
716
716
  "verification-and-artifacts-v1": {
@@ -1132,7 +1132,11 @@ Type.Object({ name: Type.String({
1132
1132
  maxLength: 255
1133
1133
  }) });
1134
1134
  Type.Object({
1135
- role: Type.Optional(Type.Union([Type.Literal("manager"), Type.Literal("member")])),
1135
+ role: Type.Optional(Type.Union([
1136
+ Type.Literal("manager"),
1137
+ Type.Literal("executor"),
1138
+ Type.Literal("member")
1139
+ ])),
1136
1140
  maxUses: Type.Optional(Type.Integer({
1137
1141
  minimum: 1,
1138
1142
  default: 1
@@ -1144,10 +1148,15 @@ Type.Object({
1144
1148
  }))
1145
1149
  });
1146
1150
  Type.Object({ code: Type.String({ minLength: 1 }) });
1147
- Type.Object({ role: Type.Union([Type.Literal("manager"), Type.Literal("member")]) });
1151
+ Type.Object({ role: Type.Union([
1152
+ Type.Literal("manager"),
1153
+ Type.Literal("executor"),
1154
+ Type.Literal("member")
1155
+ ]) });
1148
1156
  var TeamRoleSchema = Type.Union([
1149
1157
  Type.Literal("owner"),
1150
1158
  Type.Literal("manager"),
1159
+ Type.Literal("executor"),
1151
1160
  Type.Literal("member")
1152
1161
  ]);
1153
1162
  Type.Object({
@@ -1158,7 +1167,11 @@ var DateTimeUnsafe = Type.Unsafe(Type.String({ format: "date-time" }));
1158
1167
  Type.Object({
1159
1168
  id: UuidSchema,
1160
1169
  code: Type.String(),
1161
- role: Type.Union([Type.Literal("manager"), Type.Literal("member")]),
1170
+ role: Type.Union([
1171
+ Type.Literal("manager"),
1172
+ Type.Literal("executor"),
1173
+ Type.Literal("member")
1174
+ ]),
1162
1175
  maxUses: Type.Integer(),
1163
1176
  useCount: Type.Integer(),
1164
1177
  expiresAt: DateTimeUnsafe,
@@ -1190,11 +1203,19 @@ Type.Object({
1190
1203
  });
1191
1204
  Type.Object({
1192
1205
  teamId: UuidSchema,
1193
- role: Type.Union([Type.Literal("manager"), Type.Literal("member")])
1206
+ role: Type.Union([
1207
+ Type.Literal("manager"),
1208
+ Type.Literal("executor"),
1209
+ Type.Literal("member")
1210
+ ])
1194
1211
  });
1195
1212
  Type.Object({
1196
1213
  updated: Type.Boolean(),
1197
- role: Type.Union([Type.Literal("manager"), Type.Literal("member")])
1214
+ role: Type.Union([
1215
+ Type.Literal("manager"),
1216
+ Type.Literal("executor"),
1217
+ Type.Literal("member")
1218
+ ])
1198
1219
  });
1199
1220
  Type.Object({ deleted: Type.Boolean() });
1200
1221
  Type.Object({ removed: Type.Boolean() });
@@ -1204,6 +1225,7 @@ var FoundingMemberSchema = Type.Object({
1204
1225
  role: Type.Union([
1205
1226
  Type.Literal("owner"),
1206
1227
  Type.Literal("manager"),
1228
+ Type.Literal("executor"),
1207
1229
  Type.Literal("member")
1208
1230
  ])
1209
1231
  });
@@ -3359,12 +3381,12 @@ var COMMON_OPTIONAL_FLAGS = `\
3359
3381
  sandbox policy.
3360
3382
  --agent-root <path> Directory that owns .moltnet/<agent>. Default:
3361
3383
  CWD, with git root fallback when available.
3362
- --guest-credential-mode <mode>
3363
- Guest trust boundary: host-authenticated or
3364
- guest-config. Defaults to host-authenticated for
3365
- both agent-key and OAuth2 authentication.
3366
- Explicit guest-config exposes the complete local
3367
- agent credential tree to the VM.
3384
+ --git-author <"Name <email>">
3385
+ Non-secret git identity projected into the
3386
+ guest for host-brokered commit signing. Default:
3387
+ host git config (OAuth2) or
3388
+ <identityId>+<agent>[bot]@users.noreply.github.com.
3389
+ Env: MOLTNET_GIT_AUTHOR.
3368
3390
  --lease-ttl-sec <n> Sliding liveness window. Silence longer than
3369
3391
  this ends the attempt with lease_expired.
3370
3392
  Default: 300.
@@ -3539,21 +3561,6 @@ function isHelpFlag(args) {
3539
3561
  //#endregion
3540
3562
  //#region src/lib/agent-context.ts
3541
3563
  /**
3542
- * Guest credentials are an explicit trust decision, never an incidental
3543
- * consequence of files found on disk. The guest boundary is independent of
3544
- * how the daemon itself authenticates: both agent-key and OAuth2 default to
3545
- * the host-authenticated boundary, where structured MoltNet operations reuse
3546
- * the trusted host-side Agent and the guest receives no credential material.
3547
- * OAuth2 still resolves that Agent from the local config and secret provider
3548
- * on the host — reading `moltnet.json` on the host does not imply projecting
3549
- * it into the guest. `guest-config` remains an explicit compatibility opt-in
3550
- * for tasks that need credential-bearing guest-shell operations.
3551
- */
3552
- function resolveDaemonGuestCredentialMode(requested) {
3553
- if (requested !== void 0 && requested !== "guest-config" && requested !== "host-authenticated") throw new Error(`Invalid --guest-credential-mode "${requested}": expected guest-config or host-authenticated.`);
3554
- return requested ?? "host-authenticated";
3555
- }
3556
- /**
3557
3564
  * Report which auth mode `connect()` will use, without ever reading the secret
3558
3565
  * value into anything logged. Agent-key mode is selected when
3559
3566
  * `MOLTNET_AGENT_KEY` holds a non-blank value — mirroring the SDK precedence
@@ -3625,20 +3632,15 @@ async function validateStartupBinding(options) {
3625
3632
  async function resolveAgentContext(agentName, options = {}) {
3626
3633
  if (!/^[a-zA-Z0-9_-]+$/.test(agentName)) throw new Error(`Invalid agent name "${agentName}": must match /^[a-zA-Z0-9_-]+$/`);
3627
3634
  const roots = resolveCredentialRoots(options.agentRootDir);
3628
- const guestCredentialMode = resolveDaemonGuestCredentialMode(options.guestCredentialMode);
3629
3635
  if (options.authMode === "agent-key") {
3630
- const { rootDir, agentDir } = guestCredentialMode === "guest-config" ? resolveCompleteGuestCredentials(roots, agentName) : {
3631
- rootDir: roots[0] ?? process.cwd(),
3632
- agentDir: join(roots[0] ?? process.cwd(), ".moltnet", agentName)
3633
- };
3636
+ const rootDir = roots[0] ?? process.cwd();
3634
3637
  return {
3635
- agentDir,
3638
+ agentDir: join(rootDir, ".moltnet", agentName),
3636
3639
  agentRootDir: rootDir,
3637
- agent: await connect(),
3638
- guestCredentialMode
3640
+ agent: await connect()
3639
3641
  };
3640
3642
  }
3641
- const located = guestCredentialMode === "guest-config" ? resolveCompleteGuestCredentials(roots, agentName) : locateAgentConfig(roots, agentName);
3643
+ const located = locateAgentConfig(roots, agentName);
3642
3644
  if (located) {
3643
3645
  const agent = await connect({
3644
3646
  configDir: located.agentDir,
@@ -3647,8 +3649,7 @@ async function resolveAgentContext(agentName, options = {}) {
3647
3649
  return {
3648
3650
  agentDir: located.agentDir,
3649
3651
  agentRootDir: located.rootDir,
3650
- agent,
3651
- guestCredentialMode
3652
+ agent
3652
3653
  };
3653
3654
  }
3654
3655
  const tried = roots.map((root) => join(root, ".moltnet", agentName));
@@ -3669,22 +3670,6 @@ function locateAgentConfig(roots, agentName) {
3669
3670
  };
3670
3671
  }
3671
3672
  }
3672
- function resolveCompleteGuestCredentials(roots, agentName) {
3673
- const partial = [];
3674
- for (const rootDir of roots) {
3675
- const agentDir = join(rootDir, ".moltnet", agentName);
3676
- const hasConfig = existsSync(join(agentDir, "moltnet.json"));
3677
- const hasEnv = existsSync(join(agentDir, "env"));
3678
- if (hasConfig && hasEnv) return {
3679
- rootDir,
3680
- agentDir
3681
- };
3682
- if (hasConfig || hasEnv) partial.push(agentDir);
3683
- }
3684
- if (partial.length > 0) throw new Error(`Incomplete guest credentials in ${partial.join(", ")}: moltnet.json and env must both exist for --guest-credential-mode guest-config.`);
3685
- const tried = roots.map((root) => join(root, ".moltnet", agentName));
3686
- throw new Error(`--guest-credential-mode guest-config requires an explicitly provisioned moltnet.json and env. Checked ${tried.join(", ")}.`);
3687
- }
3688
3673
  function resolveCredentialRoots(agentRootDir) {
3689
3674
  const roots = agentRootDir ? [agentRootDir] : [];
3690
3675
  try {
@@ -3714,6 +3699,7 @@ function loadConfig() {
3714
3699
  piCodingAgentDir: process.env["PI_CODING_AGENT_DIR"] ?? "",
3715
3700
  authMode: detectAuthMode(process.env),
3716
3701
  signingPrivateKey: process.env["MOLTNET_PRIVATE_KEY"] ?? "",
3702
+ gitAuthor: process.env["MOLTNET_GIT_AUTHOR"] ?? "",
3717
3703
  traceIdlePolling: readBoolean("MOLTNET_TRACE_IDLE_POLLING", process.env["MOLTNET_TRACE_IDLE_POLLING"])
3718
3704
  };
3719
3705
  }
@@ -3738,6 +3724,30 @@ async function abortActiveAttemptOnSignal(opts) {
3738
3724
  }
3739
3725
  }
3740
3726
  //#endregion
3727
+ //#region src/lib/agent-identity.ts
3728
+ /**
3729
+ * Build the non-secret identity projected into guests. Host git config is a
3730
+ * non-secret input and is consulted only on OAuth2 hosts, which already read
3731
+ * that configuration for the signing seed; configless agent-key hosts never
3732
+ * touch a config directory.
3733
+ */
3734
+ async function resolveDaemonAgentIdentity(input) {
3735
+ let hostGit;
3736
+ if (input.gitAuthor === void 0 && input.authMode === "oauth2") {
3737
+ const config = await readConfig(input.agentDir);
3738
+ hostGit = config?.git ? {
3739
+ name: config.git.name,
3740
+ email: config.git.email
3741
+ } : void 0;
3742
+ }
3743
+ return resolveAgentIdentity({
3744
+ agentName: input.agentName,
3745
+ whoami: input.whoami,
3746
+ gitAuthor: input.gitAuthor,
3747
+ hostGit
3748
+ });
3749
+ }
3750
+ //#endregion
3741
3751
  //#region src/lib/correlation.ts
3742
3752
  var execFileAsync = promisify(execFile);
3743
3753
  var CORRELATION_TRAILER_KEY = "Moltnet-Correlation-Id";
@@ -4256,6 +4266,19 @@ function recoverScratchWorkspacePath(producer, stateDirs) {
4256
4266
  const fallback = join(stateDirs.rootDir, "task-workspaces", producer.workspace.workspaceId);
4257
4267
  return existsSync(fallback) ? fallback : null;
4258
4268
  }
4269
+ //#endregion
4270
+ //#region ../../libs/crypto-service/src/ssh.ts
4271
+ /**
4272
+ * SSH key format conversion for MoltNet Ed25519 keys
4273
+ *
4274
+ * Converts MoltNet agent keys (ed25519:<base64>) to OpenSSH format
4275
+ * for use with git commit signing and SSH authentication.
4276
+ */
4277
+ if (!ed.etc.sha512Sync) ed.etc.sha512Sync = (...m) => {
4278
+ const hash = createHash$1("sha512");
4279
+ m.forEach((msg) => hash.update(msg));
4280
+ return hash.digest();
4281
+ };
4259
4282
  new TextEncoder();
4260
4283
  //#endregion
4261
4284
  //#region ../../libs/crypto-service/src/crypto.service.ts
@@ -4408,19 +4431,7 @@ ed.etc.sha512Sync = (...m) => {
4408
4431
  m.forEach((msg) => hash.update(msg));
4409
4432
  return hash.digest();
4410
4433
  };
4411
- //#endregion
4412
- //#region ../../libs/crypto-service/src/ssh.ts
4413
- /**
4414
- * SSH key format conversion for MoltNet Ed25519 keys
4415
- *
4416
- * Converts MoltNet agent keys (ed25519:<base64>) to OpenSSH format
4417
- * for use with git commit signing and SSH authentication.
4418
- */
4419
- if (!ed.etc.sha512Sync) ed.etc.sha512Sync = (...m) => {
4420
- const hash = createHash$1("sha512");
4421
- m.forEach((msg) => hash.update(msg));
4422
- return hash.digest();
4423
- };
4434
+ new TextEncoder().encode("SSHSIG");
4424
4435
  //#endregion
4425
4436
  //#region src/lib/executor-attestation.ts
4426
4437
  var DAEMON_REQUIRED_SCOPES = AGENT_CREDENTIAL_SCOPES;
@@ -4941,7 +4952,7 @@ function commonOptionDefs() {
4941
4952
  short: "a"
4942
4953
  },
4943
4954
  "agent-root": { type: "string" },
4944
- "guest-credential-mode": { type: "string" },
4955
+ "git-author": { type: "string" },
4945
4956
  "lease-ttl-sec": { type: "string" },
4946
4957
  "heartbeat-interval-ms": { type: "string" },
4947
4958
  "max-batch-size": { type: "string" },
@@ -5652,13 +5663,12 @@ async function runPolling(opts) {
5652
5663
  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).`);
5653
5664
  const cfg = loadConfig();
5654
5665
  const agentRootDir = resolve(process.cwd(), values["agent-root"] ?? process.cwd());
5655
- const { ctx, signingPrivateKey, startupWhoami } = await (async () => {
5666
+ const { ctx, signingPrivateKey, startupWhoami, agentIdentity, hostCapabilitySigner } = await (async () => {
5656
5667
  let gate = "resolve_agent_context";
5657
5668
  try {
5658
5669
  const resolvedContext = await resolveAgentContext(baseCommon.agent, {
5659
5670
  agentRootDir,
5660
- authMode: cfg.authMode,
5661
- guestCredentialMode: values["guest-credential-mode"]
5671
+ authMode: cfg.authMode
5662
5672
  });
5663
5673
  gate = "authenticate_and_bind";
5664
5674
  const whoami = await validateStartupBinding({
@@ -5678,10 +5688,24 @@ async function runPolling(opts) {
5678
5688
  whoami,
5679
5689
  signingPrivateKey: privateKey
5680
5690
  });
5691
+ gate = "resolve_agent_identity";
5692
+ const agentIdentity = await resolveDaemonAgentIdentity({
5693
+ agentName: baseCommon.agent,
5694
+ whoami,
5695
+ authMode: cfg.authMode,
5696
+ agentDir: resolvedContext.agentDir,
5697
+ gitAuthor: values["git-author"] ?? (cfg.gitAuthor || void 0)
5698
+ });
5681
5699
  return {
5682
5700
  ctx: resolvedContext,
5683
5701
  signingPrivateKey: privateKey,
5684
- startupWhoami: whoami
5702
+ startupWhoami: whoami,
5703
+ agentIdentity,
5704
+ hostCapabilitySigner: createLocalSeedSigner({
5705
+ privateKeySeed: privateKey,
5706
+ agent: resolvedContext.agent,
5707
+ identity: agentIdentity
5708
+ })
5685
5709
  };
5686
5710
  } catch (error) {
5687
5711
  await logDaemonStartupFailure({
@@ -5713,7 +5737,6 @@ async function runPolling(opts) {
5713
5737
  executables: prepared.executables
5714
5738
  });
5715
5739
  assertGuestEnvironmentBoundary({
5716
- guestCredentialMode: ctx.guestCredentialMode,
5717
5740
  forwardEnv: profile.requiredEnv,
5718
5741
  sandboxEnv: profile.sandboxConfig.env
5719
5742
  });
@@ -5844,7 +5867,6 @@ async function runPolling(opts) {
5844
5867
  bindingScope: startupWhoami.credentialBinding?.bindingScope ?? null,
5845
5868
  credentialKeyId: startupWhoami.credentialBinding?.keyId ?? null,
5846
5869
  boundTeamId: startupWhoami.credentialBinding?.bindingScope === "team" ? startupWhoami.credentialBinding.boundTeamId : null,
5847
- guestCredentialMode: ctx.guestCredentialMode,
5848
5870
  taskTypes: taskTypes.length > 0 ? taskTypes : ["*"],
5849
5871
  correlationId: values["correlation-id"] ?? null,
5850
5872
  diaryIds: diaryIds.length > 0 ? diaryIds : ["*"],
@@ -6112,7 +6134,9 @@ async function runPolling(opts) {
6112
6134
  const rawExecuteTask = selected.preparedRuntime.createTaskExecutor({
6113
6135
  agentName: common.agent,
6114
6136
  moltnetAgent: ctx.agent,
6115
- guestCredentialMode: ctx.guestCredentialMode,
6137
+ agentIdentity,
6138
+ hostCapabilitySigner,
6139
+ hostCapabilityLogger: taskLogger,
6116
6140
  agentRootDir: ctx.agentRootDir,
6117
6141
  mountPath: sandbox.rootDir,
6118
6142
  provider: profile.provider,
@@ -6127,7 +6151,6 @@ async function runPolling(opts) {
6127
6151
  onVmDiagnostic: (diagnostic) => {
6128
6152
  const fields = {
6129
6153
  event: diagnostic.event,
6130
- credentialMode: diagnostic.credentialMode,
6131
6154
  ...diagnostic.brokeredSecretCount !== void 0 && { brokeredSecretCount: diagnostic.brokeredSecretCount }
6132
6155
  };
6133
6156
  if (diagnostic.level === "warning") taskLogger.warn(fields, diagnostic.message);
@@ -6276,13 +6299,12 @@ async function runOnce(argv, runtimeAdapter = defaultPiDaemonAdapter) {
6276
6299
  const cfg = loadConfig();
6277
6300
  const initialOpts = opts;
6278
6301
  const agentRootDir = resolve(process.cwd(), values["agent-root"] ?? process.cwd());
6279
- const { ctx, signingPrivateKey } = await (async () => {
6302
+ const { ctx, signingPrivateKey, agentIdentity, hostCapabilitySigner } = await (async () => {
6280
6303
  let gate = "resolve_agent_context";
6281
6304
  try {
6282
6305
  const resolvedContext = await resolveAgentContext(initialOpts.agent, {
6283
6306
  agentRootDir,
6284
- authMode: cfg.authMode,
6285
- guestCredentialMode: values["guest-credential-mode"]
6307
+ authMode: cfg.authMode
6286
6308
  });
6287
6309
  gate = "authenticate_and_bind";
6288
6310
  const whoami = await validateStartupBinding({
@@ -6302,9 +6324,23 @@ async function runOnce(argv, runtimeAdapter = defaultPiDaemonAdapter) {
6302
6324
  whoami,
6303
6325
  signingPrivateKey: privateKey
6304
6326
  });
6327
+ gate = "resolve_agent_identity";
6328
+ const agentIdentity = await resolveDaemonAgentIdentity({
6329
+ agentName: initialOpts.agent,
6330
+ whoami,
6331
+ authMode: cfg.authMode,
6332
+ agentDir: resolvedContext.agentDir,
6333
+ gitAuthor: values["git-author"] ?? (cfg.gitAuthor || void 0)
6334
+ });
6305
6335
  return {
6306
6336
  ctx: resolvedContext,
6307
- signingPrivateKey: privateKey
6337
+ signingPrivateKey: privateKey,
6338
+ agentIdentity,
6339
+ hostCapabilitySigner: createLocalSeedSigner({
6340
+ privateKeySeed: privateKey,
6341
+ agent: resolvedContext.agent,
6342
+ identity: agentIdentity
6343
+ })
6308
6344
  };
6309
6345
  } catch (error) {
6310
6346
  await logDaemonStartupFailure({
@@ -6331,7 +6367,6 @@ async function runOnce(argv, runtimeAdapter = defaultPiDaemonAdapter) {
6331
6367
  executables: preparedRuntime.executables
6332
6368
  });
6333
6369
  assertGuestEnvironmentBoundary({
6334
- guestCredentialMode: ctx.guestCredentialMode,
6335
6370
  forwardEnv: profile.requiredEnv,
6336
6371
  sandboxEnv: profile.sandboxConfig.env
6337
6372
  });
@@ -6419,7 +6454,6 @@ async function runOnce(argv, runtimeAdapter = defaultPiDaemonAdapter) {
6419
6454
  profileId: profile.id,
6420
6455
  profileSessionTtlSec: profile.sessionTtlSec,
6421
6456
  profileWorkspaceTtlSec: profile.workspaceTtlSec,
6422
- guestCredentialMode: ctx.guestCredentialMode,
6423
6457
  piAgentDir: piAgentDir.path,
6424
6458
  piAgentDirSource: piAgentDir.source
6425
6459
  }, "agent-daemon.starting");
@@ -6479,7 +6513,9 @@ async function runOnce(argv, runtimeAdapter = defaultPiDaemonAdapter) {
6479
6513
  const rawExecuteTask = preparedRuntime.createTaskExecutor({
6480
6514
  agentName: opts.agent,
6481
6515
  moltnetAgent: ctx.agent,
6482
- guestCredentialMode: ctx.guestCredentialMode,
6516
+ agentIdentity,
6517
+ hostCapabilitySigner,
6518
+ hostCapabilityLogger: rootLogger,
6483
6519
  agentRootDir: ctx.agentRootDir,
6484
6520
  mountPath: sandbox.rootDir,
6485
6521
  provider: profile.provider,
@@ -6494,7 +6530,6 @@ async function runOnce(argv, runtimeAdapter = defaultPiDaemonAdapter) {
6494
6530
  onVmDiagnostic: (diagnostic) => {
6495
6531
  const fields = {
6496
6532
  event: diagnostic.event,
6497
- credentialMode: diagnostic.credentialMode,
6498
6533
  ...diagnostic.brokeredSecretCount !== void 0 && { brokeredSecretCount: diagnostic.brokeredSecretCount }
6499
6534
  };
6500
6535
  if (diagnostic.level === "warning") rootLogger.warn(fields, diagnostic.message);
package/dist/pi.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"pi.d.ts","sourceRoot":"","sources":["../src/pi.ts"],"names":[],"mappings":"AAAA,OAAO,EAQL,KAAK,mBAAmB,EACzB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,KAAK,EAAE,oBAAoB,EAAyB,MAAM,cAAc,CAAC;AAEhF,eAAO,MAAM,oBAAoB,kkBAIvB,CAAC;AAEX,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,mBAAmB,GAC3B,oBAAoB,CA4DtB;AAED,eAAO,MAAM,0BAA0B,qBAQrC,CAAC;AAEH,eAAO,MAAM,sBAAsB,sBAElC,CAAC"}
1
+ {"version":3,"file":"pi.d.ts","sourceRoot":"","sources":["../src/pi.ts"],"names":[],"mappings":"AAAA,OAAO,EASL,KAAK,mBAAmB,EACzB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,KAAK,EAAE,oBAAoB,EAAyB,MAAM,cAAc,CAAC;AAEhF,eAAO,MAAM,oBAAoB,kkBAIvB,CAAC;AAEX,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,mBAAmB,GAC3B,oBAAoB,CA4DtB;AAED,eAAO,MAAM,0BAA0B,qBASrC,CAAC;AAEH,eAAO,MAAM,sBAAsB,sBAElC,CAAC"}
package/dist/pi.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { GONDOLIN_BASE_EXECUTABLES, GONDOLIN_TOOL_NAMES, MOLTNET_TOOL_NAMES, buildPiExecutorManifest, createPiTaskExecutor, defineGondolinTemplate, definePiRuntime } from "@themoltnet/pi-runtime";
2
+ import { GONDOLIN_BASE_EXECUTABLES, GONDOLIN_TOOL_NAMES, MOLTNET_TOOL_NAMES, agentSigningCapability, buildPiExecutorManifest, createPiTaskExecutor, defineGondolinTemplate, definePiRuntime } from "@themoltnet/pi-runtime";
3
3
  //#region src/pi.ts
4
4
  var PI_KERNEL_TOOL_NAMES = [
5
5
  ...GONDOLIN_TOOL_NAMES,
@@ -59,7 +59,8 @@ var defaultPiRuntimeDefinition = definePiRuntime({
59
59
  id: "moltnet-default-gondolin",
60
60
  version: "1",
61
61
  executables: GONDOLIN_BASE_EXECUTABLES
62
- })
62
+ }),
63
+ hostCapabilities: [agentSigningCapability]
63
64
  });
64
65
  var defaultPiDaemonAdapter = createPiDaemonAdapter(defaultPiRuntimeDefinition);
65
66
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@themoltnet/agent-daemon",
3
- "version": "0.43.0",
3
+ "version": "0.44.1",
4
4
  "license": "AGPL-3.0-only",
5
5
  "type": "module",
6
6
  "description": "Universal MoltNet agent daemon host with a built-in Pi/Gondolin runtime and support for trusted operator-owned runtime modules. CLI: moltnet-agent.",
@@ -47,7 +47,7 @@
47
47
  "node": ">=24"
48
48
  },
49
49
  "dependencies": {
50
- "@earendil-works/gondolin": "^0.9.1",
50
+ "@earendil-works/gondolin": "^0.12.0",
51
51
  "@earendil-works/pi-ai": "0.79.4",
52
52
  "@earendil-works/pi-coding-agent": "0.79.4",
53
53
  "@fastify/otel": "^0.18.0",
@@ -78,10 +78,10 @@
78
78
  "pino-opentelemetry-transport": "^3.0.0",
79
79
  "pino-pretty": "^13.1.3",
80
80
  "typebox": "^1.2.8",
81
- "@themoltnet/agent-runtime": "0.43.2",
81
+ "@themoltnet/agent-runtime": "0.44.0",
82
82
  "@themoltnet/os-keyring": "0.2.0",
83
- "@themoltnet/pi-runtime": "0.11.0",
84
- "@themoltnet/sdk": "0.136.0"
83
+ "@themoltnet/pi-runtime": "0.12.1",
84
+ "@themoltnet/sdk": "0.137.0"
85
85
  },
86
86
  "devDependencies": {
87
87
  "tsx": "^4.7.0",
@@ -90,11 +90,11 @@
90
90
  "vite-plugin-dts": "^4.5.4",
91
91
  "vitest": "^3.0.0",
92
92
  "@moltnet/bootstrap": "0.1.0",
93
+ "@moltnet/crypto-service": "0.1.0",
93
94
  "@moltnet/models": "0.1.0",
95
+ "@moltnet/observability": "0.1.0",
94
96
  "@moltnet/runtime-profiles": "0.1.0",
95
- "@moltnet/tasks": "0.1.0",
96
- "@moltnet/crypto-service": "0.1.0",
97
- "@moltnet/observability": "0.1.0"
97
+ "@moltnet/tasks": "0.1.0"
98
98
  },
99
99
  "nx": {
100
100
  "projectType": "application",