qmQM
§ 00 · Source-level Observatory

qm multiplayer agent harness

QM 不是单个 coding agent,而是一套让 Slack、Web、Cron 和多种 agent runtime 共用的组织级 Agent 工作台。源码里最值得拆的是它怎么把人、scope、harness、sandbox、skills 和凭据串成一个可租约化、可审计的 turn。

★ 7,463
GitHub Stars
1013
TS/JS Files
5d
Created Window
Node
Runtime
Slack/Web/Cron │ ▼ app-turn.ts → orchestrator.ts → harness-router.ts │ │ │ ▼ ▼ ▼ RunStore lease sandbox scope Codex / Claude / OpenCode / Pi
§ 00 · OVERVIEW

它解决什么,凭什么值得拆

QM 的 README 把它称为“multiplayer agent harness for work”,源码对应的是一个组织级 agent control plane:Web turn 入口先在 src/api/app-turn.ts:56 刷新身份并解析 actor,随后在 src/api/app-turn.ts:102 通过项目 roster 版本保护共享上下文,最后把 surface、conversation、runtime 和权限信息拼成 orchestrator 输入。

它值得拆的原因不是 stars 数,而是边界足够复杂:src/core/orchestrator.ts:189 创建的 orchestrator 同时接入 memory、skills、credential broker、security posture、attachments 和 delivery;src/runs/worker.ts:22 又把 turn 变成可租约、可心跳、可释放的后台 run。

▸核心数据

架构模式
多 surface → durable run queue → orchestrator → sandbox/harness adapter。
核心抽象
Scope、Conversation、Run lease、Harness、SandboxHandle、SkillResolution。
工程强度
源码超过千个 TS/JS 文件,覆盖 CLI、插件、Web UI、沙箱和核心服务。
下游依赖
Fastify、Postgres、Slack、OpenAI/Codex、Claude/OpenCode、Fly/AWS 沙箱。

▸源码阅读路线

第一条路线看 runtime:src/harness/harness-router.ts:12 决定 harness/model,src/api/app-turn.ts:122 在 Web turn 中执行 provider 和模型可用性检查,src/runs/worker.ts:59 再把排队时间和 attempt 注入 orchestrator。

第二条路线看隔离:src/core/orchestrator/sandboxes.ts:198 用 resolution layers provision sandbox,src/workspace/workspace-store.ts:16 用 safeJoin 防止 workspace path escape,src/skills/materialize.ts:210 负责把可见 skills 投影进 sandbox。

§ 01 · ARCHITECTURE

运行时路由不是配置开关,而是安全边界

src/harness/harness-router.ts:19 先读取部署批准的 harness 集合,src/harness/harness-router.ts:31 再把 org 默认值和 scope 覆盖合成继承链;这意味着用户临时指定 model/harness 不是自由字符串,而要通过 src/harness/harness-router.ts:48 的 approved + supported 检查。

▸RuntimeChoice 的收敛路径

src/harness/harness-router.ts · L12-54TypeScript
12export function resolveRuntimeChoice(
13  config: Pick<ScopedConfigStore, "getApprovedHarnesses" | "getRuntimeSelection" | "getBaseModel">,
14  orgScopeId: ScopeId,
15  scope: ScopeId,
16  fallback: RuntimeChoice,
17  requested?: Partial<RuntimeChoice>,
18): RuntimeChoice {
19  const approved = config.getApprovedHarnesses() ?? [fallback.harnessId];
20  const orgStored = config.getRuntimeSelection(orgScopeId);
21  const orgLegacy = config.getBaseModel(orgScopeId);
22  const configuredOrg =
23    orgStored && isHarnessId(orgStored.harnessId)
24      ? { harnessId: orgStored.harnessId, modelId: orgStored.modelId }
25      : { harnessId: fallback.harnessId, modelId: orgLegacy ?? fallback.modelId };
26  const firstApproved = approved.find(isHarnessId) ?? fallback.harnessId;
27  const safeFallback =
28    approved.includes(fallback.harnessId) && modelSupportedByHarness(fallback.modelId, fallback.harnessId)
29      ? fallback
30      : { harnessId: firstApproved, modelId: defaultModelForHarness(firstApproved, fallback.modelId) };
31  const org =
32    approved.includes(configuredOrg.harnessId) &&
33    modelSupportedByHarness(configuredOrg.modelId, configuredOrg.harnessId)
34      ? configuredOrg
35      : safeFallback;
36  const scopedStored = scope === orgScopeId ? null : config.getRuntimeSelection(scope);
37  const scopedLegacy = scope === orgScopeId ? null : config.getBaseModel(scope);
38  let inherited = org;
39  if (scopedStored && isHarnessId(scopedStored.harnessId)) {
40    inherited = { harnessId: scopedStored.harnessId, modelId: scopedStored.modelId };
41  } else if (scopedLegacy) {
42    inherited = { harnessId: fallback.harnessId, modelId: scopedLegacy };
43  }
44  const choice =
45    requested?.harnessId || requested?.modelId
46      ? { harnessId: requested.harnessId ?? inherited.harnessId, modelId: requested.modelId ?? inherited.modelId }
47      : inherited;
48  if (!approved.includes(choice.harnessId) || !modelSupportedByHarness(choice.modelId, choice.harnessId)) {
49    if (requested?.harnessId || requested?.modelId)
50      throw new NonRetryableTurnError(`runtime ${choice.harnessId}/${choice.modelId} is not approved`);
51    return org;
52  }
53  return choice;
54}

src/harness/harness-router.ts:44 把 requested runtime 合入 inherited runtime,但 src/harness/harness-router.ts:49 会把未批准的请求变成 NonRetryableTurnError。这个设计让 Web UI 可以暴露模型选择,同时不会绕过组织批准的 harness 白名单。

▸Web turn 入口先裁剪权限,再运行模型

src/api/app-turn.ts · L56-100TypeScript
56    async turn(req: TurnRequest): Promise<TurnResult> {
57      await deps.identity.refresh();
58      const actor: Principal = deps.identity.resolve(req.actor);
59      let projectAudience: Principal[] | undefined;
60      let projectName: string | undefined;
61      let projectVersion: string | undefined;
62      let sessionParticipantIds: string[] | undefined;
63      const conversationRef = req.conversation.channelRef;
64      const projectGroup = req.conversation.kind === "group" && !!conversationRef && isProjectGroupRef(conversationRef);
65      const projectId = projectGroup ? projectIdFromGroupRef(conversationRef) : null;
66
67      if (projectGroup) {
68        if (!deps.identity.isInternal(actor)) {
69          return { status: "refused", reason: "you're not a member of that context" };
70        }
71        if (!projectId) return { status: "refused", reason: "you're not a member of that context" };
72        const project = await deps.projects?.get(projectId);
73        if (
74          !project ||
75          project.orgId !== orgIdOf() ||
76          !deps.identity.isInternal(deps.identity.classify(project.ownerId))
77        ) {
78          return { status: "refused", reason: "you're not a member of that context" };
79        }
80        const activeMemberIds = project.memberIds.filter((memberId) =>
81          deps.identity.isInternal(deps.identity.classify(memberId)),
82        );
83        if (!activeMemberIds.includes(actor.id))
84          return { status: "refused", reason: "you're not a member of that context" };
85        projectAudience = await Promise.all(
86          activeMemberIds.map(async (memberId) => {
87            if (memberId === actor.id) return actor;
88            const principal = deps.identity.classify(memberId);
89            const member = await deps.directory.get(memberId).catch(() => null);
90            return member?.displayName ? { ...principal, displayName: member.displayName } : principal;
91          }),
92        );
93        projectName = project.name;
94        projectVersion = String(project.updatedAt);
95        sessionParticipantIds = [...activeMemberIds];
96      } else if (req.surface === "web" && req.conversation.kind !== "dm") {
97        if (!conversationRef || !(await mayUseSharedScope(req.conversation.kind, conversationRef, actor))) {
98          return { status: "refused", reason: "you're not a member of that context" };
99        }
100      }

src/api/app-turn.ts:67 对 project group 做内部成员校验,src/api/app-turn.ts:80 刷 active member,src/api/app-turn.ts:96 又对非 DM Web conversation 检查 shared scope。这里的路线说明 QM 不是“先让 Agent 决定”,而是在 Agent 前先确定人和上下文是否匹配。

▸模型可用性和 provider keys 一起校验

src/api/app-turn.ts · L122-190TypeScript
122        const org = scopeId("org", orgIdOf());
123        const targetScope =
124          req.conversation.kind === "dm"
125            ? scopeId("personal", actor.id)
126            : scopeId(req.conversation.kind, req.conversation.channelRef ?? threadRef);
127        const fallbackHarness = isHarnessId(deps.harnessId) ? deps.harnessId : "pi";
128        const runtimeFallback = deps.runtimeFallback ?? {
129          harnessId: fallbackHarness,
130          modelId: defaultModelForHarness(fallbackHarness),
131        };
132        let orgRuntime;
133        let configuredRuntime;
134        let runtime;
135        try {
136          orgRuntime = await resolveRuntimeChoiceDurable(deps.config, org, org, runtimeFallback);
137          configuredRuntime =
138            targetScope === org
139              ? orgRuntime
140              : await resolveRuntimeChoiceDurable(deps.config, org, targetScope, runtimeFallback);
141          runtime =
142            req.harness || req.model
143              ? await resolveRuntimeChoiceDurable(deps.config, org, targetScope, runtimeFallback, {
144                  ...(req.harness && isHarnessId(req.harness) ? { harnessId: req.harness } : {}),
145                  ...(req.model ? { modelId: req.model } : {}),
146                })
147              : configuredRuntime;
148        } catch (error) {
149          return { status: "refused", reason: errMessage(error) };
150        }
151        if (req.harness && !isHarnessId(req.harness)) {
152          return { status: "refused", reason: `runtime ${req.harness} is not approved` };
153        }
154        const configuredKeys = deps.providerKeys ??
155          deps.modelProviders ?? { anthropic: false, openai: false, openrouter: false };
156        let providers = deps.modelProviders;
157        if (deps.modelCredentials) {
158          providers = modelProviderAvailabilityFor(
159            runtime.harnessId,
160            configuredKeys,
161            await deps.modelCredentials.availability(),
162          );
163        } else if (deps.providerKeys) {
164          providers = modelProviderAvailabilityFor(runtime.harnessId, configuredKeys);
165        }
166        if (providers && !modelServiceable(runtime.modelId, providers)) {
167          return {
168            status: "refused",
169            reason: "that model isn't available on this deployment (its provider isn't configured)",
170          };
171        }
172        const configuredWebuiModels = await deps.config.getWebuiModelsDurable(org);
173        let enabledWebuiModels: string[] | null = null;
174        if (configuredWebuiModels?.length) {
175          enabledWebuiModels = [...new Set([...configuredWebuiModels, orgRuntime.modelId])];
176        } else if (providers?.openrouter) {
177          enabledWebuiModels = [
178            ...new Set([
179              ...selectableCatalogForHarness(
180                await selectableModelCatalog(deps.modelCredentialFetch),
181                runtime.harnessId,
182              ).map((model) => model.id),
183              ...(orgRuntime.harnessId === runtime.harnessId ? [orgRuntime.modelId] : []),
184            ]),
185          ];
186        }
187        const invalidModelOption =
188          validateWebTurnModelOptions(req, enabledWebuiModels, providers) ??
189          webTurnRuntimeModelRefusal(runtime.modelId, orgRuntime.modelId, configuredWebuiModels);
190        if (invalidModelOption) return { status: "refused", reason: invalidModelOption };

src/api/app-turn.ts:136 先解析 org runtime,src/api/app-turn.ts:141 再解析 target scope runtime,src/api/app-turn.ts:166 用 provider availability 判断模型是否 serviceable。这对企业部署很关键:配置里允许某 harness,不代表每个 provider key 都已经可用。

深度阅读 · runtime router 和 worker lease
§ 02 · WORKER

后台 run 靠租约心跳避免多实例踩踏

QM 的 worker 不是简单 while true。src/runs/worker.ts:22 的 processRun 要求 run 已经持有 lease token,src/runs/worker.ts:29 定时 heartbeat,src/runs/worker.ts:37 统计连续 lease lost,超过 src/runs/worker.ts:38 的阈值才取消本地 turn。

▸Run processing 包含取消信号和最终 attempt

src/runs/worker.ts · L22-82TypeScript
22export async function processRun(deps: ProcessDeps, run: Run, opts?: { background?: boolean }): Promise<TurnResult> {
23  const token = run.leaseToken;
24  if (token === null) throw new Error(`processRun called with an unleased run ${run.id}`);
25  const intervalMs = deps.heartbeatIntervalMs ?? Math.max(1_000, Math.floor(deps.leaseTtlMs / 3));
26  const cancel = new AbortController();
27  let consecutiveLost = 0;
28  let leaseLost = false;
29  const beat = setInterval(() => {
30    void deps.runs
31      .heartbeat(run.id, token, deps.leaseTtlMs)
32      .then((alive) => {
33        if (alive) {
34          consecutiveLost = 0;
35          return;
36        }
37        consecutiveLost += 1;
38        if (consecutiveLost >= LEASE_LOST_CONSECUTIVE && !leaseLost) {
39          leaseLost = true;
40          clearInterval(beat);
41          console.warn(
42            `[worker] run ${run.id} lost its lease after ${consecutiveLost} consecutive beats; cancelling the in-process turn`,
43          );
44          cancel.abort();
45        }
46      })
47      .catch((err: unknown) => {
48        consecutiveLost = 0;
49        console.warn(`[worker] heartbeat failed for run ${run.id} (transient, ignored): ${errMessage(err)}`);
50      });
51  }, intervalMs);
52  let beatStopped = false;
53  const stopBeat = (): void => {
54    if (beatStopped) return;
55    beatStopped = true;
56    clearInterval(beat);
57  };
58  try {
59    const queueMs = run.startedAt !== null ? Math.max(0, run.startedAt - run.createdAt) : undefined;
60    const result = await deps.orchestrator.handleTurn({
61      ...run.request,
62      origin: resolveTurnOrigin(run.request),
63      runId: run.id,
64      attempt: run.attempts,
65      finalAttempt: errorParks(run, deps.runs.maxClaims),
66      background: opts?.background ?? false,
67      cancel: cancel.signal,
68      ...(queueMs !== undefined ? { queueMs } : {}),
69    });
70    stopBeat();
71    await deps.runs.complete(run.id, token, result);
72    return result;
73  } catch (err) {
74    stopBeat();
75    await deps.runs.fail(run.id, token, errMessage(err), {
76      retry: !(err instanceof NonRetryableTurnError),
77    });
78    throw err;
79  } finally {
80    stopBeat();
81  }
82}

src/runs/worker.ts:60 调 orchestrator.handleTurn 时会带 runId、attempt、finalAttempt、background 和 cancel signal;src/runs/worker.ts:71 完成 run,src/runs/worker.ts:75 失败时决定是否 retry。这种结构使长任务、自动化和恢复逻辑可以共用同一条执行语义。

▸Worker loop 有 transient claim backoff

src/runs/worker.ts · L101-187TypeScript
101export function createWorker(deps: WorkerDeps): Worker {
102  const workerId = deps.workerId ?? `w-${randomUUID().slice(0, 8)}`;
103  const pollMs = deps.pollMs ?? 50;
104  let stopped = false;
105  let loopDone: Promise<void> | null = null;
106  let inFlight: { runId: string; leaseToken: string; threadRef: string } | null = null;
107  let releasedLeaseToken: string | null = null;
108  let releasing: Promise<void> | null = null;
109
110  async function loop(): Promise<void> {
111    let claimFailures = 0;
112    while (!stopped) {
113      if (deps.canClaim && !deps.canClaim()) {
114        await sleep(pollMs);
115        continue;
116      }
117      let run: Run | null;
118      try {
119        run = await deps.runs.claim(workerId, deps.leaseTtlMs);
120        claimFailures = 0;
121      } catch (e) {
122        claimFailures += 1;
123        if (claimFailures >= CLAIM_FAIL_CRASH_CONSECUTIVE) throw e;
124        swallow("worker: claim failed (transient, retrying)", e);
125        await sleep(Math.min(pollMs * 2 ** Math.min(claimFailures, 5), 5_000));
126        continue;
127      }
128      if (!run) {
129        await sleep(pollMs);
130        continue;
131      }
132      if (stopped) {
133        if (run.leaseToken !== null)
134          await deps.runs
135            .releaseLease(run.id, run.leaseToken)
136            .catch((e) => swallow("worker: post-stop claim handback failed", e));
137        break;
138      }
139      inFlight =
140        run.leaseToken !== null ? { runId: run.id, leaseToken: run.leaseToken, threadRef: run.sessionId } : null;
141      deps.onClaimed?.();
142      try {
143        await processRun(deps, run, { background: true });
144      } catch (e) {
145        swallow("worker: background run crashed", e);
146      } finally {
147        inFlight = null;
148      }
149    }
150  }
151
152  return {
153    start() {
154      if (loopDone) return;
155      stopped = false;
156      loopDone = loop();
157    },
158    busy() {
159      return inFlight !== null;
160    },
161    releaseInFlight() {
162      const held = inFlight;
163      if (!held || held.leaseToken === releasedLeaseToken) return Promise.resolve();
164      if (releasing) return releasing;
165      releasing = (async () => {
166        try {
167          if (await deps.runs.heartbeat(held.runId, held.leaseToken, deps.leaseTtlMs)) {
168            const session = await deps.sessions.getByThread(held.threadRef);
169            if (session) await deps.sessions.forceReleaseLease(session.id);
170            await deps.runs.releaseLease(held.runId, held.leaseToken);
171          }
172          releasedLeaseToken = held.leaseToken;
173        } catch (e) {
174          swallow("worker: releaseInFlight failed", e);
175        } finally {
176          releasing = null;
177        }
178      })();
179      return releasing;
180    },
181    async stop(drainMs = STOP_DRAIN_MS) {
182      stopped = true;
183      await Promise.race([loopDone, sleep(drainMs, { unref: true })]);
184      loopDone = null;
185    },
186  };
187}

src/runs/worker.ts:119 从 RunStore claim,src/runs/worker.ts:123 只有连续失败到阈值才崩溃,src/runs/worker.ts:161 还提供 releaseInFlight。它适合 blue-green 或多实例部署,因为 in-flight run 可以被显式释放而不是僵在某个进程里。

§ 03 · SANDBOX & SKILLS

沙箱承载凭据、技能和文件系统的交汇点

src/core/orchestrator/sandboxes.ts:60 的 createTurnSandboxes 很像 turn 的硬件抽象层。它拿到 actor、session、resolution layers、connectorEnv、skillScopes、brokeredTools,再把这些材料推到 sandbox handle 上。

▸凭据 wrapper 是运行时生成的

src/core/orchestrator/sandboxes.ts · L93-130TypeScript
93  let ownerAuthCommand: ((command: string) => string) | undefined;
94  if (ownerAuthAvailable) {
95    ownerAuthCommand = (command) => {
96      for (const credentialId of ownerEnvCredentialIds) {
97        deps.auditLog.record({
98          at: Date.now(),
99          principalId: actor.id,
100          action: "keychain.materialize",
101          resource: `${credentialId} (owner-auth command)`,
102          scopeLabel: scopeId,
103        });
104      }
105      const invoked = [...brokerVended.values()].filter(({ tool }) =>
106        new RegExp(`(^|[\\s;&|()])${tool.binary}(?=$|[\\s;&|()])`).test(command),
107      );
108      for (const { tool } of invoked) {
109        deps.auditLog.record({
110          at: Date.now(),
111          principalId: actor.id,
112          action: "credential.materialize",
113          resource: `${tool.service} (ephemeral broker)`,
114          scopeLabel: scopeId,
115        });
116      }
117      const exports = Object.entries(ownerAuthEnv)
118        .map(([key, value]) => `${key}=${shq(value)}`)
119        .join(" ");
120      const wrappers = invoked
121        .map(({ tool, env }) => {
122          const brokerExports = Object.entries(env)
123            .map(([key, value]) => `${key}=${shq(value)}`)
124            .join(" ");
125          return `${tool.binary}() { ${brokerExports} command ${tool.binary} "$@"; }; `;
126        })
127        .join("");
128      return `${exports ? `export ${exports}; ` : ""}${wrappers}${command}`;
129    };
130  }

src/core/orchestrator/sandboxes.ts:97 会对 owner-auth command 记录 keychain materialize 审计,src/core/orchestrator/sandboxes.ts:105 检测命令里调用了哪些 brokered tool,src/core/orchestrator/sandboxes.ts:120 再生成 shell wrapper。这里的重点是凭据不是永久塞进环境,而是按实际调用路径记录和注入。

▸Sandbox provision 带 resolution layers 和 egress token

src/core/orchestrator/sandboxes.ts · L198-260TypeScript
198  const doProvision = async (emit: typeof emitGapWork): Promise<SandboxHandle> => {
199    const provisionStart = Date.now();
200    const handle = await deps.sandbox.provision(resolution.layers, {
201      env: connectorEnv,
202      egress: resolution.egress,
203      ...(egressTokenForTurn ? { egressToken: egressTokenForTurn } : {}),
204      ...(onSandboxStatus ? { onStatus: onSandboxStatus } : {}),
205    });
206    box.pending = handle;
207    emit("provision", provisionStart, Date.now());
208    box.provisionMs = Date.now() - provisionStart;
209    handle.env = { ...handle.env, AGENT_OUTBOX: `${handle.rootDir}/${turnOutboxDir}` };
210    if (deps.keychain) {
211      const deviceFlowStart = Date.now();
212      const restoreOwnerId =
213        input.origin.kind === "automation" && input.origin.useOwnerKeychain && !isolateOwnerKeychain
214          ? actor.id
215          : deviceFlowCredOwner(memoryScopeId, actor.id);
216      const resetGenerations = new Map<string, string>();
217      for (const tool of brokeredTools) {
218        if (cutoverModeOf(tool.service) !== "legacy") continue;
219        const generation = await deps.deviceFlowCutover?.residentResetGeneration(memoryScopeId, tool.service);
220        if (generation) resetGenerations.set(tool.service, generation);
221      }
222      const owned = resetGenerations.size ? await deps.keychain.listByOwner(restoreOwnerId) : [];
223      const resetServices = [...resetGenerations.keys()].filter((service) =>
224        owned.some((record) => record.service === service),
225      );
226      const removeServices = [...new Set([...quarantinedServices, ...resetServices])];
227      if (removeServices.length) {
228        await removeDeviceFlowLogins({
229          sandbox: deps.sandbox,
230          handle,
231          keychain: deps.keychain,
232          ownerId: restoreOwnerId,
233          services: removeServices,
234          canonicalRoots: brokeredTools
235            .filter((tool) => removeServices.includes(tool.service))
236            .flatMap((tool) => tool.roots),
237        });
238      }
239      try {
240        const restoredServices = await materializeDeviceFlowLogins({
241          sandbox: deps.sandbox,
242          handle,
243          keychain: deps.keychain,
244          ownerId: restoreOwnerId,
245          ...(quarantinedServices.length ? { excludeServices: quarantinedServices } : {}),
246        });
247        for (const service of restoredServices) {
248          deps.credentialUsage?.record({
249            slug: `keychain:${service}`,
250            host: "local",
251            status: cutoverModeOf(service) === "prefer_ephemeral" ? "legacy_retained" : "legacy_restored",
252            scopeLabel: scopeId,
253            principalId: actor.id,
254          });
255        }
256        for (const [service, generation] of resetGenerations) {
257          await deps.deviceFlowCutover?.markResidentReset(memoryScopeId, service, generation);
258        }
259      } catch (err) {
260        deps.errors?.record({

src/core/orchestrator/sandboxes.ts:200 把 resolution.layers 传给 sandbox.provision,src/core/orchestrator/sandboxes.ts:202 同步 egress policy,src/core/orchestrator/sandboxes.ts:209 设置 AGENT_OUTBOX。随后 src/core/orchestrator/sandboxes.ts:240 恢复 device-flow logins,形成“scope workspace + credential + outbox”的 turn computer。

▸Skill materialization 用 hash 避免重复铺文件

src/skills/materialize.ts · L44-86TypeScript
44function indexHash(resolved: SkillResolution[]): string {
45  const h = createHash("sha256");
46  const entries = resolved
47    .filter((r) => r.skill)
48    .map((r) => `${r.skill!.manifest.name}\0${renderedBody(r)}`)
49    .sort();
50  for (const e of entries) {
51    h.update(e);
52    h.update("\n");
53  }
54  return h.digest("hex");
55}
56
57function treeHash(resolution: SkillResolution, bundles: SkillBundle[]): string {
58  const m = resolution.skill!.manifest;
59  const files = [...(m.files ?? [])]
60    .map((f) => `${f.path}\0${f.content}\0${f.executable === true ? "1" : "0"}`)
61    .sort()
62    .join("\0");
63  const folded = [...bundles]
64    .map((b) => b.hash)
65    .sort()
66    .join("\0");
67  return createHash("sha256")
68    .update(renderedBody(resolution))
69    .update("\0")
70    .update(files)
71    .update("\0")
72    .update(folded)
73    .digest("hex");
74}
75
76function packRoot(resolution: SkillResolution): string | null {
77  return resolution.skill?.pack ? `${SKILL_PACKS_DIR}/${safeSkillDirName(resolution.skill.pack.packId)}` : null;
78}
79
80function renderedBody(resolution: SkillResolution): string {
81  const body = resolution.skill!.manifest.body;
82  const root = packRoot(resolution);
83  return root
84    ? `${body}\n\n## Pack files\nResolve repository-relative shared-file paths against \`${root}/\`; pack files never overwrite the workspace root.`
85    : body;
86}

src/skills/materialize.ts:44 计算 indexHash,src/skills/materialize.ts:57 计算 treeHash,src/skills/materialize.ts:80 会把 pack root 提示追加到 rendered body。这个细节说明 QM 把 skill 当成可投影、可版本化的文件树,而不是每轮盲目复制目录。

▸Workspace path escape 被最小层拦住

src/workspace/workspace-store.ts · L16-70TypeScript
16function safeJoin(baseDir: string, relPath: string): string {
17  const target = resolve(baseDir, relPath);
18  const rel = relative(baseDir, target);
19  if (rel.startsWith("..") || isAbsolute(rel)) {
20    throw new Error(`path escapes workspace: ${relPath}`);
21  }
22  return target;
23}
24
25export function createLocalWorkspaceStore(rootDir: string): WorkspaceStore {
26  const base = resolve(rootDir, "workspaces");
27
28  function scopeDir(scopeId: ScopeId): string {
29    return join(base, scopeStorageKey(scopeId));
30  }
31
32  const store: WorkspaceStore = {
33    scopeDir,
34    async ensureScope(scopeId) {
35      await mkdir(scopeDir(scopeId), { recursive: true });
36    },
37    async read(scopeId, relPath) {
38      const path = safeJoin(scopeDir(scopeId), relPath);
39      try {
40        return await readFile(path, "utf8");
41      } catch {
42        return null;
43      }
44    },
45    async readBytes(scopeId, relPath) {
46      const path = safeJoin(scopeDir(scopeId), relPath);
47      try {
48        return await readFile(path);
49      } catch {
50        return null;
51      }
52    },
53    async write(scopeId, relPath, data) {
54      const path = safeJoin(scopeDir(scopeId), relPath);
55      await mkdir(dirname(path), { recursive: true });
56      await writeFile(path, data);
57    },
58    async remove(scopeId, relPath) {
59      await rm(safeJoin(scopeDir(scopeId), relPath), { force: true });
60    },
61    async list(scopeId) {
62      try {
63        const entries = await readdir(scopeDir(scopeId), { recursive: true, withFileTypes: true });
64        return entries.filter((e) => e.isFile()).map((e) => join(e.parentPath, e.name));
65      } catch {
66        return [];
67      }
68    },
69  };
70  return store;

src/workspace/workspace-store.ts:16 的 safeJoin 是很小但关键的底座:src/workspace/workspace-store.ts:19 拒绝向上跳出 workspace,src/workspace/workspace-store.ts:53 写入前创建父目录。多人 scope 和 Agent 写文件时,这类小函数比大口号更重要。

深度阅读 · sandbox、skills 与 credential broker
§ 04 · TESTS

测试覆盖的是权限语义,不只是函数返回值

我最喜欢的测试证据在 cron scope shared:test/cron-scope-shared.test.ts:36 验证 scopeShared cron 会以 owner 身份运行但携带成员 audience,test/cron-scope-shared.test.ts:70 验证 owner 离开 managed group 后不能继续读,test/cron-scope-shared.test.ts:100 验证 scopeShared owner 离开当前 roster 会被禁用。

▸Scope shared 的正向测试

test/cron-scope-shared.test.ts · L35-68TypeScript
35describe("runTrigger: scopeShared unions the owner's keychain into the scope run", () => {
36  it("fires AS the owner, flags ownerKeychainUnion, carries the member audience, and delivers to the channel", async () => {
37    const { deps, seen } = captureDeps();
38    const out = await runTrigger(deps, {
39      owner: "U-carol",
40      ownerScopeId: scopeId("channel", "C-eng"),
41      input: "compose",
42      fireKey: "ss1",
43      surface: "cron",
44      runAs: "scopeShared",
45      members,
46      destination: toChannel,
47    });
48    assert.equal(out.ran, true);
49    assert.equal(seen.length, 1);
50    const req = seen[0]!;
51    assert.equal(req.ownerKeychainUnion, true, "the fire flags the owner∪scope keychain union");
52    assert.equal(req.actor.externalId, "U-carol", "runs as the owner, not a substituted scope member");
53    assert.equal(req.conversation.kind, "channel");
54    assert.equal(req.conversation.audience?.length, 2);
55    assert.equal((await deps.deliveries.pending("slack")).length, 1);
56  });
57
58  it("an owner cron does NOT set the union flag", async () => {
59    const { deps, seen } = captureDeps();
60    await runTrigger(deps, {
61      owner: "U-carol",
62      ownerScopeId: scopeId("personal", "U-carol"),
63      input: "x",
64      fireKey: "o1",
65      surface: "cron",
66    });
67    assert.equal(seen[0]!.ownerKeychainUnion, undefined);
68  });

test/cron-scope-shared.test.ts:51 断言 ownerKeychainUnion 被设置,test/cron-scope-shared.test.ts:52 断言 actor 仍是 owner,test/cron-scope-shared.test.ts:55 断言 delivery 入队。这个组合正好覆盖“用 owner 凭据、发到共享 scope”的危险边界。

▸离开 roster 的反向测试

test/cron-scope-shared.test.ts · L70-116TypeScript
70  it("an owner-mode task cannot read a managed group after leaving its current roster", async () => {
71    const { deps, seen } = captureDeps();
72    deps.currentScopeMembers = async () => [members[1]!];
73    const out = await runTrigger(deps, {
74      owner: "U-owner",
75      ownerScopeId: scopeId("group", "web-project-p1"),
76      input: "x",
77      fireKey: "owner-revoked",
78      surface: "cron",
79    });
80    assert.equal(out.authzFailed, false);
81    assert.equal(out.ran, true);
82    assert.match(out.note ?? "", /no longer a member/);
83    assert.equal(seen.length, 0);
84  });
85
86  it("a scopeFloor cron does NOT set the union flag (it runs at the scope floor, not the owner's creds)", async () => {
87    const { deps, seen } = captureDeps();
88    await runTrigger(deps, {
89      owner: "U-carol",
90      ownerScopeId: scopeId("channel", "C-eng"),
91      input: "x",
92      fireKey: "f1",
93      surface: "cron",
94      runAs: "scopeFloor",
95      members,
96    });
97    assert.equal(seen[0]!.ownerKeychainUnion, undefined);
98  });
99
100  it("disables scopeShared when its owner left the current roster", async () => {
101    const { deps, seen } = captureDeps();
102    deps.currentScopeMembers = async () => [members[1]!];
103    const out = await runTrigger(deps, {
104      owner: "U-owner",
105      ownerScopeId: scopeId("channel", "C-eng"),
106      input: "x",
107      fireKey: "ss-revoked",
108      surface: "cron",
109      runAs: "scopeShared",
110      members,
111    });
112    assert.equal(out.authzFailed, true);
113    assert.equal(out.ran, false);
114    assert.match(out.note ?? "", /no longer a current scope member/);
115    assert.equal(seen.length, 0);
116  });

test/cron-scope-shared.test.ts:80 和 test/cron-scope-shared.test.ts:112 分别覆盖 owner-mode 与 scopeShared 的拒绝路径。对一个 multiplayer agent harness 来说,这比单测 prompt formatting 更有价值,因为它证明权限跟成员资格变化联动。

§ 05 · HIGHLIGHTS

技术亮点

▸多 harness 可替换,但不是任意可选

src/harness/harness-router.ts:84 用 adapters map 组合多个 harness,src/harness/harness-router.ts:99 发现 session 切换 harness 时会 reset prior 和 current session。这个设计比单纯 provider switch 更稳,能避免不同 runtime 共享一段脏 session 状态。

▸Run lease 是多实例部署的基本盘

src/runs/worker.ts:167 releaseInFlight 前会再次 heartbeat,src/runs/worker.ts:170 才 releaseLease。这个顺序让 worker 停机、滚动升级和手动释放不会轻易释放别的进程已经接管的 run。

▸Skill 投影考虑 pack 和 stale cleanup

src/skills/materialize.ts:222 处理旧 marker,src/skills/materialize.ts:237 清理不再 active 的 skill name,src/skills/materialize.ts:252 重新 lay 当前 entries。这里能看出作者在处理长期 resident sandbox,而不只是一轮临时容器。

§ 06 · RISKS

风险与缺口

▸复杂度已经接近平台级系统

src/core/orchestrator.ts:129 暴露 OrchestratorInput/Deps,src/core/orchestrator.ts:189 一次创建会绑定大量依赖。好处是能力完整,坏处是新贡献者很难只靠 README 找到行为入口。

▸凭据与 resident sandbox 需要更强运维纪律

src/core/orchestrator/sandboxes.ts:158 有 scrubOwnerAuthHandle,src/core/orchestrator/sandboxes.ts:164 会 removeDeviceFlowLogins,但任何 resident computer 体系都要求部署者理解备份、恢复、隔离和审计边界。

▸适合组织内部,不适合轻量个人玩具

src/api/app-turn.ts:67 到 src/api/app-turn.ts:100 的成员与 scope 检查说明它面向多人组织场景;如果只是单人本地 coding agent,QM 的控制面可能显得太重。

深度阅读 · runtime router深度阅读 · sandbox skills