@tea-agent/loop-agent 0.16.1-beta.1 → 0.16.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/AGENTS.md +4 -8
- package/CHANGELOG.md +59 -16
- package/README.md +76 -299
- package/dist/application/evaluation/alias.js +184 -0
- package/dist/application/evaluation/budget.js +192 -0
- package/dist/application/evaluation/campaign-hash.js +47 -0
- package/dist/application/evaluation/campaign-matrix.js +372 -0
- package/dist/application/evaluation/campaign-scorecard.js +135 -0
- package/dist/application/evaluation/campaign.js +370 -0
- package/dist/application/evaluation/candidate.js +23 -6
- package/dist/application/evaluation/corpus-hash.js +38 -0
- package/dist/application/evaluation/corpus.js +56 -0
- package/dist/application/evaluation/experiment.js +294 -0
- package/dist/application/evaluation/ignition.js +198 -0
- package/dist/application/evaluation/integrity-audit.js +162 -0
- package/dist/application/evaluation/outer-loop.js +132 -0
- package/dist/application/evaluation/pi-cell-executor.js +39 -0
- package/dist/application/evaluation/private-verifier.js +46 -0
- package/dist/application/evaluation/promotion-policy.js +151 -0
- package/dist/application/evaluation/proposer.js +98 -0
- package/dist/application/evaluation/types.js +522 -0
- package/dist/cli/command-definitions.js +19 -3
- package/dist/commands/dag-reconcile-run.js +3 -116
- package/dist/commands/eval.js +1176 -13
- package/dist/commands/init.js +7 -1
- package/dist/executors/dag-pi-executor.js +4 -44
- package/dist/executors/shell-executor.js +1 -1
- package/dist/infrastructure/evaluation/alias-store.js +199 -0
- package/dist/infrastructure/evaluation/campaign-store.js +154 -0
- package/dist/infrastructure/evaluation/corpus-store.js +181 -0
- package/dist/infrastructure/evaluation/experiment-store.js +124 -0
- package/dist/infrastructure/evaluation/ignition-store.js +82 -0
- package/dist/infrastructure/evaluation/private-verifier-store.js +145 -0
- package/dist/infrastructure/evaluation/proposer-store.js +78 -0
- package/dist/records/promotion.js +3 -1
- package/dist/worker/cli.js +83 -0
- package/dist/worker/delivery/git-transaction.js +75 -0
- package/dist/worker/delivery/verification-bundle.js +13 -2
- package/dist/worker/feature/review.js +3 -2
- package/dist/worker/observe/static/dag-helpers.js +0 -62
- package/dist/worker/observe/static/styles.css +18 -55
- package/dist/worker/observe/static/views/dag.js +13 -5
- package/dist/worker/outcomes/adapters.js +4 -1
- package/dist/worker/outcomes/declared-artifacts.js +103 -0
- package/dist/worker/outcomes/evidence-tokens.js +29 -0
- package/dist/worker/outcomes/gate.js +10 -11
- package/dist/worker/outcomes/projector.js +30 -4
- package/dist/worker/outcomes/types.js +3 -0
- package/dist/worker/pool/reconcile.js +285 -0
- package/dist/worker/run-task/run-task.js +81 -4
- package/dist/worker/runner/run-ready.js +25 -2
- package/dist/worker/task-graph/ready-planner.js +14 -8
- package/dist/worker/task-graph/task-graph-schema.js +5 -3
- package/dist/workflows/dag/budget-enforcement.js +67 -0
- package/dist/workflows/dag/context-policy.js +137 -0
- package/dist/workflows/dag/failure-routing.js +7 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +0 -77
- package/dist/workflows/dag/init-hybrid.js +33 -53
- package/dist/workflows/dag/knowledge-curator.js +3 -0
- package/dist/workflows/dag/node-execution.js +11 -4
- package/dist/workflows/dag/prompt.js +1 -1
- package/dist/workflows/dag/reconcile-run.js +121 -0
- package/dist/workflows/dag/report.js +12 -0
- package/dist/workflows/dag/runner.js +43 -16
- package/dist/workflows/dag/skill-snapshot.js +11 -7
- package/dist/workflows/dag/types.js +18 -1
- package/dist/workflows/dag/validate.js +15 -1
- package/docs/README.md +3 -1
- package/docs/architecture/runtime-boundaries.md +3 -2
- package/docs/init-surface.manifest.json +4 -0
- package/docs/local-development-environment.md +52 -0
- package/docs/templates/agent-dag.schema.json +0 -5
- package/docs/templates/agent-dag.supervised-implementation.json +23 -4
- package/docs/templates/branch-merge-report.md +14 -0
- package/docs/templates/evaluation/campaign-budget-v1.json +12 -0
- package/docs/templates/evaluation/campaign-dogfood-v0.json +24 -0
- package/docs/templates/evaluation/campaign-evidence-v1.json +44 -0
- package/docs/templates/evaluation/context-policy-baseline-v1.json +17 -0
- package/docs/templates/evaluation/context-policy-role-specialized-v1.json +28 -0
- package/docs/templates/evaluation/corpus-dogfood-v0.manifest.json +118 -0
- package/docs/templates/evaluation/matrix-dag-dry-run-v1.json +21 -0
- package/docs/templates/evaluation/matrix-fixture-v1.json +10 -0
- package/docs/templates/evaluation/private-verifier-dogfood-v0.json +16 -0
- package/docs/templates/product-line/AGENTS.md +1 -0
- package/docs/templates/product-line/README.md +17 -0
- package/docs/templates/product-line/acceptance.yaml +9 -0
- package/docs/templates/product-line/feature.yaml +11 -0
- package/docs/templates/product-line/task-graph.yaml +8 -0
- package/docs/templates/product-line/task.yaml +4 -0
- package/package.json +2 -1
- package/skills/frontend-implementation/references/node-contracts.md +3 -3
- package/skills/loop-agent/references/command-reference.md +5 -0
- package/skills/loop-agent/references/hybrid-dag.md +3 -3
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { readdir, readFile } from "node:fs/promises";
|
|
2
2
|
import { hostname } from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { isHardBudgetBreached, resolveEffectiveMaxConcurrent, } from "../../application/evaluation/budget.js";
|
|
4
5
|
import { readCandidateRecord } from "../../infrastructure/evaluation/candidate-store.js";
|
|
5
6
|
import { CANONICAL_TASK_ID_PATTERN, formatLocalCompactDate, } from "../../task/runtime.js";
|
|
7
|
+
import { assertFrozenBudget, initRunBudgetLedger, preflightBudgetOrBreach, recordFinishedNodeBudget, writeBudgetLedgerArtifacts, } from "./budget-enforcement.js";
|
|
6
8
|
import { getDagRunDir, isTerminalDagRunStatus, locateDagRun, readHumanApprovalArtifact, requireActiveDagRun, } from "./lifecycle.js";
|
|
7
9
|
import { moveToCompletedRunDir, moveToPausedRunDir, prepareActiveRunDir, writeRunSpec, writeRunState, } from "./run-store.js";
|
|
8
10
|
import { createDagNodeExecutor } from "./executor-registry.js";
|
|
@@ -120,7 +122,7 @@ export function createInitialRunState(spec, opts, ranks, runId = opts.runId ?? "
|
|
|
120
122
|
: {}),
|
|
121
123
|
};
|
|
122
124
|
}
|
|
123
|
-
|
|
125
|
+
const state = {
|
|
124
126
|
version: 1,
|
|
125
127
|
title: spec.title,
|
|
126
128
|
runId,
|
|
@@ -147,6 +149,8 @@ export function createInitialRunState(spec, opts, ranks, runId = opts.runId ?? "
|
|
|
147
149
|
}
|
|
148
150
|
: {}),
|
|
149
151
|
};
|
|
152
|
+
initRunBudgetLedger(state, spec.budget);
|
|
153
|
+
return state;
|
|
150
154
|
}
|
|
151
155
|
export function assertFrozenEvaluationBinding(spec, state) {
|
|
152
156
|
const declared = spec.evaluation;
|
|
@@ -183,7 +187,7 @@ export async function runDag(spec, opts) {
|
|
|
183
187
|
controllerVersion: runningIdentity.packageVersion,
|
|
184
188
|
});
|
|
185
189
|
const { ranks } = topoSortToRanks(spec);
|
|
186
|
-
const maxConcurrent = Math.max(1, opts.maxConcurrent ?? 4);
|
|
190
|
+
const { maxConcurrent } = resolveEffectiveMaxConcurrent(Math.max(1, opts.maxConcurrent ?? 4), spec.budget);
|
|
187
191
|
let runId = opts.runId;
|
|
188
192
|
if (runId) {
|
|
189
193
|
for (const warning of validateDagRunIdFormat(runId).warnings) {
|
|
@@ -268,6 +272,7 @@ export async function resumeDagRun(opts) {
|
|
|
268
272
|
throw new Error(`dag run ${opts.runId} is already terminal (status=${state.status})`);
|
|
269
273
|
}
|
|
270
274
|
assertFrozenEvaluationBinding(spec, state);
|
|
275
|
+
assertFrozenBudget(spec.budget, state.budget, state.runId);
|
|
271
276
|
// Runtime contract + controller identity must be re-verified before executing
|
|
272
277
|
// any remaining node on resume; drift fails closed.
|
|
273
278
|
const runningIdentity = resolveRunningControllerIdentity();
|
|
@@ -311,7 +316,7 @@ export async function resumeDagRun(opts) {
|
|
|
311
316
|
catch (error) {
|
|
312
317
|
throw new Error(`skill snapshot validation failed on resume: ${error instanceof Error ? error.message : String(error)}`);
|
|
313
318
|
}
|
|
314
|
-
const maxConcurrent = Math.max(1, opts.maxConcurrent ?? 4);
|
|
319
|
+
const { maxConcurrent } = resolveEffectiveMaxConcurrent(Math.max(1, opts.maxConcurrent ?? 4), spec.budget);
|
|
315
320
|
state.status = "running";
|
|
316
321
|
const resumedAt = new Date().toISOString();
|
|
317
322
|
state.runner = {
|
|
@@ -410,23 +415,43 @@ async function executeDagCheckpoint(input) {
|
|
|
410
415
|
tasksById,
|
|
411
416
|
meta: { runDir, runId: state.runId, spec },
|
|
412
417
|
}),
|
|
413
|
-
executeScheduledNode: (nodeId, executeNode, onPause) =>
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
state
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
418
|
+
executeScheduledNode: async (nodeId, executeNode, onPause) => {
|
|
419
|
+
if (isHardBudgetBreached(state.budgetLedger))
|
|
420
|
+
return;
|
|
421
|
+
const preBreach = preflightBudgetOrBreach(state);
|
|
422
|
+
if (preBreach && isHardBudgetBreached(state.budgetLedger)) {
|
|
423
|
+
await writeBudgetLedgerArtifacts(runDir, state.budgetLedger);
|
|
424
|
+
await persistState();
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
await executeDagNode({
|
|
428
|
+
nodeId,
|
|
429
|
+
tasksById,
|
|
430
|
+
state,
|
|
431
|
+
spec,
|
|
432
|
+
cwd,
|
|
433
|
+
runDir,
|
|
434
|
+
executeNode,
|
|
435
|
+
executeDynamicNode,
|
|
436
|
+
observer: input.observer,
|
|
437
|
+
persistState,
|
|
438
|
+
onPause,
|
|
439
|
+
});
|
|
440
|
+
const node = state.nodes[nodeId];
|
|
441
|
+
if (node &&
|
|
442
|
+
(node.status === "FINISHED" || node.status === "ERROR")) {
|
|
443
|
+
recordFinishedNodeBudget(state, node);
|
|
444
|
+
await writeBudgetLedgerArtifacts(runDir, state.budgetLedger);
|
|
445
|
+
await persistState();
|
|
446
|
+
}
|
|
447
|
+
},
|
|
426
448
|
});
|
|
427
449
|
if (pausedByNodeId) {
|
|
428
450
|
break;
|
|
429
451
|
}
|
|
452
|
+
if (isHardBudgetBreached(state.budgetLedger)) {
|
|
453
|
+
break;
|
|
454
|
+
}
|
|
430
455
|
const convergenceDecision = await runConvergencePassController({
|
|
431
456
|
spec,
|
|
432
457
|
state,
|
|
@@ -446,6 +471,7 @@ async function executeDagCheckpoint(input) {
|
|
|
446
471
|
}
|
|
447
472
|
state.finishedAt = new Date().toISOString();
|
|
448
473
|
const runDirBeforeTransfer = runDir;
|
|
474
|
+
await writeBudgetLedgerArtifacts(runDir, state.budgetLedger);
|
|
449
475
|
if (pausedByNodeId) {
|
|
450
476
|
state.status = "paused";
|
|
451
477
|
await persistState();
|
|
@@ -466,6 +492,7 @@ async function executeDagCheckpoint(input) {
|
|
|
466
492
|
if (state.convergence) {
|
|
467
493
|
relocateConvergenceArtifactPaths(state.convergence, runDirBeforeTransfer, runDir);
|
|
468
494
|
}
|
|
495
|
+
await writeBudgetLedgerArtifacts(runDir, state.budgetLedger);
|
|
469
496
|
await persistState({ allowCompletedFactsWrite: true });
|
|
470
497
|
return {
|
|
471
498
|
title: spec.title,
|
|
@@ -3,9 +3,9 @@ import { readFile } from "node:fs/promises";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import { writeTextAtomic } from "../../infrastructure/harness/atomic-write.js";
|
|
6
|
+
import { resolveContextPolicy } from "./context-policy.js";
|
|
6
7
|
import { buildDagNodePromptEnvelope } from "./prompt.js";
|
|
7
|
-
import {
|
|
8
|
-
import { resolveDagNodeSkills } from "./skills.js";
|
|
8
|
+
import { resolveDagSkillInstructions, } from "./skill-instructions.js";
|
|
9
9
|
export const SKILL_SNAPSHOT_SCHEMA_VERSION = 1;
|
|
10
10
|
export const SKILL_SNAPSHOT_RESOLVER_VERSION = 1;
|
|
11
11
|
export const SKILL_SNAPSHOT_REL_PATH = ".runtime/skill-snapshot.json";
|
|
@@ -117,11 +117,13 @@ function profileId(input) {
|
|
|
117
117
|
export function buildDagSkillSnapshotProfileRequest(spec, task) {
|
|
118
118
|
if (task.executor !== "pi")
|
|
119
119
|
return undefined;
|
|
120
|
+
const policy = resolveContextPolicy(spec);
|
|
121
|
+
const budget = policy.resolveSkillInstructionBudget(task);
|
|
120
122
|
const request = {
|
|
121
|
-
skills: [...
|
|
122
|
-
includeLearnedPatterns:
|
|
123
|
-
perSkillMaxChars:
|
|
124
|
-
totalMaxChars:
|
|
123
|
+
skills: [...policy.resolveSkills(spec, task)],
|
|
124
|
+
includeLearnedPatterns: budget.includeLearnedPatterns,
|
|
125
|
+
perSkillMaxChars: budget.perSkillMaxChars,
|
|
126
|
+
totalMaxChars: budget.totalMaxChars,
|
|
125
127
|
};
|
|
126
128
|
return { id: profileId(request), ...request };
|
|
127
129
|
}
|
|
@@ -511,7 +513,8 @@ function stripPromptText(instruction) {
|
|
|
511
513
|
return metadata;
|
|
512
514
|
}
|
|
513
515
|
export function buildNodePromptFromSnapshot(input) {
|
|
514
|
-
const
|
|
516
|
+
const policy = resolveContextPolicy(input.spec);
|
|
517
|
+
const skillNames = policy.resolveSkills(input.spec, input.task);
|
|
515
518
|
const resolvedSkillInstructions = resolveNodeSkillsFromSnapshot(input.snapshot, input.spec, input.task);
|
|
516
519
|
return {
|
|
517
520
|
prompt: buildDagNodePromptEnvelope({
|
|
@@ -520,6 +523,7 @@ export function buildNodePromptFromSnapshot(input) {
|
|
|
520
523
|
upstream: input.upstream,
|
|
521
524
|
resolvedSkills: skillNames,
|
|
522
525
|
resolvedSkillInstructions,
|
|
526
|
+
maxUpstreamChars: policy.resolveMaxUpstreamChars(input.task),
|
|
523
527
|
}),
|
|
524
528
|
resolvedSkills: resolvedSkillInstructions.map(stripPromptText),
|
|
525
529
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { campaignBudgetSchema, } from "../../application/evaluation/budget.js";
|
|
2
3
|
import { assertDagPromptSourceRule } from "./prompt-source.js";
|
|
3
4
|
import { dagRetryPolicySchema } from "./retry-policy.js";
|
|
4
5
|
export const dagComplexitySchema = z.enum(["HIGH", "MED", "LOW"]);
|
|
@@ -105,12 +106,18 @@ export const dagWritePolicySchema = z.enum([
|
|
|
105
106
|
"exclusive",
|
|
106
107
|
"none",
|
|
107
108
|
]);
|
|
109
|
+
export const contextPolicyIdSchema = z.enum([
|
|
110
|
+
"baseline-v1",
|
|
111
|
+
"role-specialized-v1",
|
|
112
|
+
]);
|
|
108
113
|
export const dagDefaultsSchema = z
|
|
109
114
|
.object({
|
|
110
115
|
executor: dagNodeExecutorSchema.optional(),
|
|
111
116
|
model: z.string().optional(),
|
|
112
117
|
piBackend: z.enum(["sdk-first", "cli-only"]).optional(),
|
|
113
118
|
contextProfile: z.string().optional(),
|
|
119
|
+
/** Eval Lab ContextPolicy seam; default baseline-v1 when omitted. */
|
|
120
|
+
contextPolicyId: contextPolicyIdSchema.optional(),
|
|
114
121
|
skills: z.array(z.string()).optional(),
|
|
115
122
|
writePolicy: dagWritePolicySchema.optional(),
|
|
116
123
|
})
|
|
@@ -267,7 +274,6 @@ export const dagTaskSchema = z.object({
|
|
|
267
274
|
shell: dagShellConfigSchema.optional(),
|
|
268
275
|
static: dagStaticConfigSchema.optional(),
|
|
269
276
|
outputContract: z.string().optional(),
|
|
270
|
-
firstProtocolLine: z.string().min(1).optional(),
|
|
271
277
|
allowedPaths: z.array(z.string()).optional().default([]),
|
|
272
278
|
forbiddenPaths: z.array(z.string()).optional().default([]),
|
|
273
279
|
decisionGate: dagDecisionGateSchema.optional(),
|
|
@@ -301,6 +307,8 @@ export const dagEvaluationBindingSchema = z
|
|
|
301
307
|
taskRef: z.string().min(1).optional(),
|
|
302
308
|
})
|
|
303
309
|
.strict();
|
|
310
|
+
/** Eval Lab Campaign Budget (W3.4); enforced by runner ledger when mode=hard. */
|
|
311
|
+
export const dagBudgetSchema = campaignBudgetSchema;
|
|
304
312
|
export const dagSourceBindingSchema = z.object({
|
|
305
313
|
schemaVersion: z.literal(1),
|
|
306
314
|
taskId: z.string().min(1),
|
|
@@ -321,6 +329,8 @@ export const dagSpecSchema = z
|
|
|
321
329
|
title: z.string().min(1),
|
|
322
330
|
runtimeContract: dagRuntimeContractSchema.optional(),
|
|
323
331
|
evaluation: dagEvaluationBindingSchema.optional(),
|
|
332
|
+
/** Optional hard/record-only budget; requires version 3. */
|
|
333
|
+
budget: dagBudgetSchema.optional(),
|
|
324
334
|
sourceBinding: dagSourceBindingSchema.optional(),
|
|
325
335
|
outputLanguage: dagOutputLanguageSchema.optional(),
|
|
326
336
|
objective: z.string().optional(),
|
|
@@ -341,6 +351,13 @@ export const dagSpecSchema = z
|
|
|
341
351
|
path: ["evaluation"],
|
|
342
352
|
});
|
|
343
353
|
}
|
|
354
|
+
if (spec.budget && spec.version !== 3) {
|
|
355
|
+
ctx.addIssue({
|
|
356
|
+
code: z.ZodIssueCode.custom,
|
|
357
|
+
message: "budget requires DagSpec version 3",
|
|
358
|
+
path: ["budget"],
|
|
359
|
+
});
|
|
360
|
+
}
|
|
344
361
|
if (spec.runtimeContract && spec.version !== 3) {
|
|
345
362
|
ctx.addIssue({
|
|
346
363
|
code: z.ZodIssueCode.custom,
|
|
@@ -229,7 +229,10 @@ function validateRepairArtifactGateConfig(task, spec, issues) {
|
|
|
229
229
|
candidate.role === "reviewer" &&
|
|
230
230
|
!candidate.decisionGate?.enabled &&
|
|
231
231
|
candidate.writePolicy === "read-only" &&
|
|
232
|
-
(candidate.outputContract?.includes("VERDICT:") ?? false)
|
|
232
|
+
(candidate.outputContract?.includes("VERDICT:") ?? false) &&
|
|
233
|
+
// Format recovery / write-set format-repair nodes also emit VERDICT but
|
|
234
|
+
// are not the primary content review after hard verification.
|
|
235
|
+
!/(?:-format-repair-pi|-verdict-recovery-pi)$/.test(candidate.id));
|
|
233
236
|
if (reviewCandidates.length !== 1) {
|
|
234
237
|
issues.push({
|
|
235
238
|
type: "invalid-repair-artifact-gate-config",
|
|
@@ -343,6 +346,17 @@ function validateTaskWritePolicy(task, spec, issues) {
|
|
|
343
346
|
return;
|
|
344
347
|
}
|
|
345
348
|
for (const entry of writeSet) {
|
|
349
|
+
const normalizedEntry = entry.trim().replace(/\\/g, "/").replace(/^\.\//, "");
|
|
350
|
+
if (writePolicy === "exclusive" &&
|
|
351
|
+
(normalizedEntry === "**" ||
|
|
352
|
+
normalizedEntry === "./**" ||
|
|
353
|
+
normalizedEntry === "*")) {
|
|
354
|
+
issues.push({
|
|
355
|
+
type: "write-set-unbounded",
|
|
356
|
+
message: `task ${task.id} writeSet entry "${entry}" is unbounded; exclusive writers must use concrete paths or limited globs`,
|
|
357
|
+
});
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
346
360
|
if (forbiddenPaths.length > 0 &&
|
|
347
361
|
writeSetEntryHitsForbidden(entry, forbiddenPaths)) {
|
|
348
362
|
issues.push({
|
package/docs/README.md
CHANGED
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
- `design/archive/2026-07-14-loop-agent-self-update-notifier.md` — loop-agent CLI 自更新提醒设计(已实现;历史设计说明)
|
|
14
14
|
- `development-principles.md` — 仓库开发原则
|
|
15
15
|
- `github-collaboration.md` — 内部研发人员的轻量 GitHub 协作指南:短分支、简短 PR、CI 与 Squash Merge
|
|
16
|
+
- `local-development-environment.md` — Cursor Cloud 等特定本地开发环境的已知问题与排障方法
|
|
17
|
+
- `branch-merge-guideline.md` — 分支合并标准流程:快速/标准/深度模式、功能保留、冲突解析、init/package 审计与 source-SHA 报告
|
|
16
18
|
- `architecture/runtime-boundaries.md` — runtime 层边界与依赖方向
|
|
17
19
|
- `architecture/README.md` — 架构文档目录索引与阅读路径
|
|
18
20
|
- `architecture/system-overview.md` — loop-agent / agent-worker / 治理层 / 外部系统全景
|
|
@@ -84,7 +86,7 @@
|
|
|
84
86
|
- `templates/product-line/` — 可投影的 Feature/Task/QA/Links 产品线包;配合 `agent-worker task validate-feature` 做 docs CI
|
|
85
87
|
- `templates/production-readiness-checklist.md` — 低/中风险单仓库 DAG readiness 检查清单
|
|
86
88
|
- `templates/init-evolution-review.md` — 初始化能力演化审查报告模板
|
|
87
|
-
- `templates/branch-merge-report.md` —
|
|
89
|
+
- `templates/branch-merge-report.md` — 跨分支合并的模式选择、功能保留、冲突解析、source drift、init/update 与 package surface 审计模板
|
|
88
90
|
- `templates/adr.md` — 架构决策记录(ADR)
|
|
89
91
|
|
|
90
92
|
## 维护
|
|
@@ -85,14 +85,15 @@ Governance (scripts/check-*.sh, src/governance/)
|
|
|
85
85
|
- **职责**:TaskSpec 校验与物化、Task Pool batch/retry/morning report under `.harness/task-pool/`、失败路由,以及只读 Observe 事件/快照/UI;实际 DAG 执行通过 `LoopAgentClient` 启动已发布的 `loop-agent` 子进程。自 0.8.0 起该目录是唯一受支持的 Task Pool runtime root,旧路径不读取、不迁移、不合并、不重映射。
|
|
86
86
|
- **controller identity**:写入型 Feature/batch/Task/final verification 在任何目标仓库或 Task Pool 状态写入前解析并冻结 schemaVersion 1 identity,包括 package name/version、绝对 entry/real entry、直接可执行 launch spec、binary SHA-256 和覆盖 `package.json`、`bin/**`、`dist/**`、`.agents/skills/**` 的 portable fingerprint。expected version/fingerprint 不匹配时 fail-fast;后续 spawn 不重新查询 PATH。
|
|
87
87
|
- **证据传播**:canonical Worker record、Task Pool run、batch/Feature、QA/final evidence 可选携带同一 identity;只有所有相关层都省略 identity 时才按 legacy evidence 接受,部分缺失或锚点不一致会拒绝。
|
|
88
|
-
-
|
|
88
|
+
- **Worker-aware recovery**:`agent-worker task reconcile --action abandon` 由 Worker 持有 Worker run ↔ DAG run association,再调用 DAG-kernel reconcile(`src/workflows/dag/reconcile-run.ts`)并把 Task Pool 投影为 `Failed` + recovery evidence。低层 `loop-agent dag reconcile-run` 只维护 DAG lifecycle,不搜索/修改 Task Pool。应急入口 `pool mark-failed` 同属 Worker,禁止覆盖 `Done`。
|
|
89
|
+
- **允许依赖**:TaskSpec、Task Pool、observability、Node filesystem/path、明确的 shared/task contract,以及只读/共享的 `src/workflows/dag/**` reconcile/lifecycle helpers;它不是第二套 executor 或 DAG kernel。
|
|
89
90
|
- **禁止**:in-process import `src/cli/**`、`src/commands/**` 或 `src/application/**`。
|
|
90
91
|
|
|
91
92
|
### Infrastructure / Store
|
|
92
93
|
|
|
93
94
|
- **位置**:`src/infrastructure/harness/**`(按计划逐步引入);过渡期部分逻辑仍在 `src/workflows/dag/lifecycle.ts`、`src/records/**`。
|
|
94
95
|
- **职责**:`.harness/tasks`、`.harness/dag-runs`、`.harness/runs`、loop state 的集中读写;completed run facts 只读约束。DAG run 自有的 `.runtime/skill-snapshot.json` 随 lifecycle 目录整体迁移,state 只保存相对 ref 和 hash,不保存 active/paused/completed 绝对路径。
|
|
95
|
-
- **DAG recovery mutation**:`dag reconcile-run` 是显式 operator 边界;默认仅检查,只有给出 action + reason 且 runner 已证明停止时才能保存原始快照、写 terminal reconciliation facts 并迁移 lifecycle。Observe、status 和 doctor 始终只读。
|
|
96
|
+
- **DAG recovery mutation**:`dag reconcile-run` 是显式 operator 边界;默认仅检查,只有给出 action + reason 且 runner 已证明停止时才能保存原始快照、写 terminal reconciliation facts 并迁移 lifecycle。Observe、status 和 doctor 始终只读。Task Pool 侧的 abandon/Failed 投影由 `agent-worker task reconcile` 持有,不得让 DAG kernel 反向依赖 Worker。
|
|
96
97
|
- **禁止**:把 raw path mutation 扩散给 runner、loop action 或 command handler。
|
|
97
98
|
|
|
98
99
|
### Governance
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"docs/harness-methodology-tdd.md",
|
|
12
12
|
"docs/harness-methodology-verification.md",
|
|
13
13
|
"docs/harness-methodology-debugging.md",
|
|
14
|
+
"docs/local-development-environment.md",
|
|
14
15
|
"docs/init-surface.manifest.json",
|
|
15
16
|
"docs/architecture/runtime-boundaries.md",
|
|
16
17
|
"docs/architecture/README.md",
|
|
@@ -39,6 +40,7 @@
|
|
|
39
40
|
"docs/templates/product-line/test-plan.md",
|
|
40
41
|
"docs/templates/product-line/task-graph.yaml",
|
|
41
42
|
"docs/templates/product-line/task.yaml",
|
|
43
|
+
"docs/templates/product-line/feature.yaml",
|
|
42
44
|
"docs/templates/product-line/closeout.yaml",
|
|
43
45
|
"docs/templates/product-line/links.md",
|
|
44
46
|
"scripts/kb-bootstrap-init-skeleton.sh",
|
|
@@ -106,6 +108,7 @@
|
|
|
106
108
|
"docs/templates/product-line/test-plan.md",
|
|
107
109
|
"docs/templates/product-line/task-graph.yaml",
|
|
108
110
|
"docs/templates/product-line/task.yaml",
|
|
111
|
+
"docs/templates/product-line/feature.yaml",
|
|
109
112
|
"docs/templates/product-line/closeout.yaml",
|
|
110
113
|
"docs/templates/product-line/links.md",
|
|
111
114
|
"scripts/check-skill-entry.sh",
|
|
@@ -175,6 +178,7 @@
|
|
|
175
178
|
"docs/templates/product-line/test-plan.md": "copied",
|
|
176
179
|
"docs/templates/product-line/task-graph.yaml": "copied",
|
|
177
180
|
"docs/templates/product-line/task.yaml": "copied",
|
|
181
|
+
"docs/templates/product-line/feature.yaml": "copied",
|
|
178
182
|
"docs/templates/product-line/closeout.yaml": "copied",
|
|
179
183
|
"docs/templates/product-line/links.md": "copied",
|
|
180
184
|
"scripts/check-skill-entry.sh": "generated",
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# 本地开发环境排障
|
|
2
|
+
|
|
3
|
+
本文记录仅与特定开发环境有关、但可能反复影响验证结果的问题。它不是通用工程规则;标准验证命令仍以 [`verification-matrix.md`](verification-matrix.md) 为准。
|
|
4
|
+
|
|
5
|
+
## Cursor Cloud
|
|
6
|
+
|
|
7
|
+
Cursor Cloud VM 当前有两个需要特别注意的环境问题。
|
|
8
|
+
|
|
9
|
+
### Node.js 版本
|
|
10
|
+
|
|
11
|
+
VM 默认 `node`(`/exec-daemon/node`)可能是 v22.14.0,但可选依赖 `@earendil-works/pi-ai` / `@earendil-works/pi-coding-agent` 要求 Node.js `>=22.19.0`。版本过低时,`npm install` / `npm ci` 可能跳过这些依赖,随后 `npm run typecheck` 或 `npm run build` 会报告:
|
|
12
|
+
|
|
13
|
+
```text
|
|
14
|
+
Cannot find module '@earendil-works/...'
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
在 Cursor Cloud 中执行安装或验证前,先切换到已配置的 Node.js 22:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
nvm use 22
|
|
21
|
+
node --version
|
|
22
|
+
npm ci
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
当前 VM 预装版本为 v22.22.2;应以 `node --version` 的实际输出为准,不依赖交互式 shell 的默认 PATH。
|
|
26
|
+
|
|
27
|
+
### Git 提交签名导致测试超时
|
|
28
|
+
|
|
29
|
+
Cursor Cloud 的全局 Git 配置可能通过 `cursor-git-ssh-keygen` 为每次提交签名(`gpg.format=ssh`、`commit.gpgsign=true`)。该 helper 可能阻塞 7–30 秒,使会在临时仓库中频繁提交的测试(尤其 `test/worker/delivery/**`、`test/worker/feature/**`)触发 15/30 秒超时。
|
|
30
|
+
|
|
31
|
+
运行 `npm test` 或其他 Git 密集型测试前,可仅对子进程关闭提交签名:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
export GIT_CONFIG_COUNT=1
|
|
35
|
+
export GIT_CONFIG_KEY_0=commit.gpgsign
|
|
36
|
+
export GIT_CONFIG_VALUE_0=false
|
|
37
|
+
npm test
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
也可以只在当前目标仓库设置:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
git config commit.gpgsign false
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
该设置只用于本地开发和测试稳定性,不应写入仓库代码、模板或发布包配置。
|
|
47
|
+
|
|
48
|
+
## 维护规则
|
|
49
|
+
|
|
50
|
+
- 仅把可复现、会反复影响开发或验证的环境问题写入本文。
|
|
51
|
+
- 通用且跨环境成立的要求应写入开发原则或验证矩阵,不应堆在本页。
|
|
52
|
+
- 问题消失或基础镜像修复后,应删除过期说明,避免把临时环境状态长期固化为项目规则。
|
|
@@ -366,11 +366,6 @@
|
|
|
366
366
|
"type": "string",
|
|
367
367
|
"minLength": 1
|
|
368
368
|
},
|
|
369
|
-
"firstProtocolLine": {
|
|
370
|
-
"type": "string",
|
|
371
|
-
"minLength": 1,
|
|
372
|
-
"description": "Optional protocol prefix whose first matching Pi assistant-output line is promoted to the canonical first line. Missing matches are not synthesized."
|
|
373
|
-
},
|
|
374
369
|
"allowedPaths": {
|
|
375
370
|
"type": "array",
|
|
376
371
|
"items": { "type": "string", "minLength": 1 },
|
|
@@ -542,11 +542,30 @@
|
|
|
542
542
|
"subtask_prompt_markdown": "./agent-dag-review-verdict.prompt.md"
|
|
543
543
|
},
|
|
544
544
|
{
|
|
545
|
-
"id": "review-
|
|
545
|
+
"id": "review-verdict-recovery-pi",
|
|
546
546
|
"depends_on": [
|
|
547
547
|
"review-pi"
|
|
548
548
|
],
|
|
549
549
|
"complexity": "LOW",
|
|
550
|
+
"executor": "pi",
|
|
551
|
+
"role": "reviewer",
|
|
552
|
+
"writePolicy": "read-only",
|
|
553
|
+
"allowedPaths": [
|
|
554
|
+
"**"
|
|
555
|
+
],
|
|
556
|
+
"forbiddenPaths": [
|
|
557
|
+
".harness/**",
|
|
558
|
+
"artifacts/**"
|
|
559
|
+
],
|
|
560
|
+
"outputContract": "Plain Markdown whose first non-empty line is exactly VERDICT: pass or VERDICT: request-revision, followed by the original review findings without substantive changes. No file writes.",
|
|
561
|
+
"subtask_prompt": "Normalize the output format of review-pi; this is the single read-only format-recovery attempt for the review verdict protocol. Preserve an existing valid VERDICT exactly. Do not invent VERDICT: pass from natural-language approval. If ambiguous, emit VERDICT: request-revision. Do not re-review code or edit files."
|
|
562
|
+
},
|
|
563
|
+
{
|
|
564
|
+
"id": "review-gate-shell",
|
|
565
|
+
"depends_on": [
|
|
566
|
+
"review-verdict-recovery-pi"
|
|
567
|
+
],
|
|
568
|
+
"complexity": "LOW",
|
|
550
569
|
"executor": "shell",
|
|
551
570
|
"role": "verifier",
|
|
552
571
|
"writePolicy": "read-only",
|
|
@@ -557,11 +576,11 @@
|
|
|
557
576
|
".harness/**",
|
|
558
577
|
"artifacts/**"
|
|
559
578
|
],
|
|
560
|
-
"outputContract": "Deterministic review verdict gate: exit 0 only when review-pi first non-empty assistant output line is pass.",
|
|
561
|
-
"subtask_prompt": "Deterministic gate: block decision gate unless review-pi emitted VERDICT: pass.",
|
|
579
|
+
"outputContract": "Deterministic review verdict gate: exit 0 only when review-verdict-recovery-pi first non-empty assistant output line is pass.",
|
|
580
|
+
"subtask_prompt": "Deterministic gate: block decision gate unless review-verdict-recovery-pi emitted VERDICT: pass.",
|
|
562
581
|
"shell": {
|
|
563
582
|
"verdictGate": {
|
|
564
|
-
"fromNodeId": "review-pi",
|
|
583
|
+
"fromNodeId": "review-verdict-recovery-pi",
|
|
565
584
|
"accept": [
|
|
566
585
|
"VERDICT: pass"
|
|
567
586
|
],
|
|
@@ -6,6 +6,8 @@ Target before merge: `<full SHA>`
|
|
|
6
6
|
Source: `<source-ref>@<full SHA>`
|
|
7
7
|
Merge base: `<full SHA>`
|
|
8
8
|
Merge commit: `<full SHA or follow-up commit reference>`
|
|
9
|
+
Merge mode: `quick | standard | deep`
|
|
10
|
+
Mode rationale: `<why this mode is sufficient>`
|
|
9
11
|
|
|
10
12
|
## Naming Convention
|
|
11
13
|
|
|
@@ -18,6 +20,12 @@ Use `YYYY-MM-DD-origin-<source>-into-<target>-<source-short-sha>.md` under `docs
|
|
|
18
20
|
- Predicted conflicts: `<count and files>`.
|
|
19
21
|
- Baseline verification: `<commands and results>`.
|
|
20
22
|
|
|
23
|
+
## Final Source Drift Check
|
|
24
|
+
|
|
25
|
+
- Final fetch: `<command and timestamp>`.
|
|
26
|
+
- Final source tip: `<full SHA>`.
|
|
27
|
+
- Source advanced during work: `<no | yes, with re-audit details>`.
|
|
28
|
+
|
|
21
29
|
## Incoming Capabilities
|
|
22
30
|
|
|
23
31
|
| Capability | Key files / contracts | Expected user or runtime result | Verification |
|
|
@@ -80,6 +88,12 @@ Record overlapping or high-risk files that Git merged without conflict. These ar
|
|
|
80
88
|
|
|
81
89
|
Add focused tests and real init/update/browser/CLI smokes required by the merged capabilities.
|
|
82
90
|
|
|
91
|
+
## Mode Completion Check
|
|
92
|
+
|
|
93
|
+
- Required gates for selected mode: `<list>`.
|
|
94
|
+
- Passed gates: `<list>`.
|
|
95
|
+
- Any mode upgrade during execution: `<no | from quick/standard to standard/deep, with reason>`.
|
|
96
|
+
|
|
83
97
|
## Residual Risk
|
|
84
98
|
|
|
85
99
|
- `<unverified external environment, performance, migration, or compatibility risk>`
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"campaignId": "camp-dogfood-context-policy-ab",
|
|
4
|
+
"createdAt": "2026-07-19T00:00:00.000Z",
|
|
5
|
+
"status": "prepared",
|
|
6
|
+
"policyVersion": "promotion-policy-v1",
|
|
7
|
+
"incumbentCandidateId": "REPLACE_INCUMBENT_CANDIDATE_ID",
|
|
8
|
+
"challengerCandidateIds": ["REPLACE_CHALLENGER_CANDIDATE_ID"],
|
|
9
|
+
"corpusId": "corpus-dogfood-v0",
|
|
10
|
+
"corpusHash": "REPLACE_AFTER_CORPUS_REGISTER",
|
|
11
|
+
"privateVerifierId": "pv-dogfood-v0",
|
|
12
|
+
"privateVerifierManifestHash": "REPLACE_AFTER_PRIVATE_VERIFIER_REGISTER",
|
|
13
|
+
"budget": {
|
|
14
|
+
"schemaVersion": 1,
|
|
15
|
+
"mode": "hard",
|
|
16
|
+
"limits": {
|
|
17
|
+
"maxExecutorCalls": 500,
|
|
18
|
+
"maxWallTimeMs": 86400000,
|
|
19
|
+
"maxRepairPasses": 50,
|
|
20
|
+
"maxConcurrency": 2
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"notes": "M4 dogfood: single-variable Context Policy A/B. Replace candidate/corpus/privateVerifier hashes after register. held_out cells are promotion_gate_only."
|
|
24
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"campaignId": "campaign-dogfood-v0",
|
|
4
|
+
"challengerCandidateId": "cand-challenger-example",
|
|
5
|
+
"cells": [
|
|
6
|
+
{
|
|
7
|
+
"candidateId": "cand-baseline-example",
|
|
8
|
+
"taskRef": "synthetic/private-1",
|
|
9
|
+
"seed": 1,
|
|
10
|
+
"split": "private",
|
|
11
|
+
"verifyPassed": false
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"candidateId": "cand-challenger-example",
|
|
15
|
+
"taskRef": "synthetic/private-1",
|
|
16
|
+
"seed": 1,
|
|
17
|
+
"split": "private",
|
|
18
|
+
"verifyPassed": true
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"candidateId": "cand-baseline-example",
|
|
22
|
+
"taskRef": "synthetic/heldout-1",
|
|
23
|
+
"seed": 1,
|
|
24
|
+
"split": "held_out",
|
|
25
|
+
"verifyPassed": true
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"candidateId": "cand-challenger-example",
|
|
29
|
+
"taskRef": "synthetic/heldout-1",
|
|
30
|
+
"seed": 1,
|
|
31
|
+
"split": "held_out",
|
|
32
|
+
"verifyPassed": true
|
|
33
|
+
}
|
|
34
|
+
],
|
|
35
|
+
"diffs": [],
|
|
36
|
+
"verifyCommands": {
|
|
37
|
+
"incumbent": ["npm test", "npm run typecheck"],
|
|
38
|
+
"challenger": ["npm test", "npm run typecheck"]
|
|
39
|
+
},
|
|
40
|
+
"budgetStatus": {
|
|
41
|
+
"incumbent": "ok",
|
|
42
|
+
"challenger": "ok"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"policyId": "baseline-v1",
|
|
4
|
+
"description": "Current DAG context assembly: shared upstream char budget, role skill defaults, learned patterns only for implementer.",
|
|
5
|
+
"knobs": {
|
|
6
|
+
"maxUpstreamCharsByRole": {
|
|
7
|
+
"default": 2000
|
|
8
|
+
},
|
|
9
|
+
"includeLearnedPatternsRoles": ["implementer"],
|
|
10
|
+
"perSkillMaxCharsByRole": {
|
|
11
|
+
"default": 3000
|
|
12
|
+
},
|
|
13
|
+
"totalMaxCharsByRole": {
|
|
14
|
+
"default": 12000
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"policyId": "role-specialized-v1",
|
|
4
|
+
"description": "Role-specialized upstream and skill-instruction budgets; skill names still resolve via baseline merge order.",
|
|
5
|
+
"knobs": {
|
|
6
|
+
"maxUpstreamCharsByRole": {
|
|
7
|
+
"default": 2000,
|
|
8
|
+
"scout": 1200,
|
|
9
|
+
"reviewer": 1200,
|
|
10
|
+
"implementer": 3000,
|
|
11
|
+
"verifier": 1600,
|
|
12
|
+
"closeout": 1600,
|
|
13
|
+
"planner": 2000,
|
|
14
|
+
"supervisor": 2000
|
|
15
|
+
},
|
|
16
|
+
"includeLearnedPatternsRoles": ["implementer", "closeout"],
|
|
17
|
+
"perSkillMaxCharsByRole": {
|
|
18
|
+
"default": 3000,
|
|
19
|
+
"scout": 2500,
|
|
20
|
+
"implementer": 3500
|
|
21
|
+
},
|
|
22
|
+
"totalMaxCharsByRole": {
|
|
23
|
+
"default": 12000,
|
|
24
|
+
"scout": 10000,
|
|
25
|
+
"implementer": 14000
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|