atris 3.57.3 → 3.58.5
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 +12 -1
- package/atris/skills/clawhub/member-runtime/SKILL.md +9 -9
- package/atris/skills/create-member/SKILL.md +16 -13
- package/atris/skills/design/SKILL.md +27 -4
- package/atris/skills/engines/SKILL.md +57 -13
- package/atris/skills/x-search/SKILL.md +13 -5
- package/atris/skills/youtube/SKILL.md +39 -8
- package/atris/team/brainstormer/MEMBER.md +2 -2
- package/atris/team/executor/MEMBER.md +3 -3
- package/atris/team/launcher/MEMBER.md +4 -4
- package/atris/team/navigator/MEMBER.md +3 -3
- package/atris/team/researcher/MEMBER.md +2 -2
- package/atris/team/validator/MEMBER.md +1 -1
- package/atris.md +14 -0
- package/ax +157 -24
- package/bin/atris.js +79 -31
- package/commands/aeo.js +4 -4
- package/commands/agents.js +1 -1
- package/commands/align.js +12 -12
- package/commands/analytics.js +3 -3
- package/commands/app.js +5 -5
- package/commands/ask.js +175 -0
- package/commands/auth.js +7 -7
- package/commands/autoland.js +29 -5
- package/commands/autopilot-front.js +6 -6
- package/commands/autopilot.js +90 -90
- package/commands/avail.js +5 -5
- package/commands/bench.js +63 -1
- package/commands/browse.js +2 -2
- package/commands/business.js +12 -12
- package/commands/card.js +2 -2
- package/commands/clean.js +13 -13
- package/commands/close.js +297 -15
- package/commands/compile.js +13 -13
- package/commands/computer.js +94 -27
- package/commands/console.js +5 -5
- package/commands/decide.js +1 -1
- package/commands/deck.js +9 -9
- package/commands/drive.js +4 -4
- package/commands/engine.js +173 -19
- package/commands/errors.js +3 -3
- package/commands/experiments.js +18 -1
- package/commands/feed.js +2 -2
- package/commands/feedback.js +4 -4
- package/commands/fleet.js +12 -12
- package/commands/game.js +1 -1
- package/commands/guide.js +49 -0
- package/commands/improve.js +19 -17
- package/commands/init.js +67 -19
- package/commands/interview.js +7 -7
- package/commands/land.js +40 -15
- package/commands/learn.js +12 -12
- package/commands/lesson.js +2 -2
- package/commands/log.js +1 -1
- package/commands/loops.js +4 -4
- package/commands/member.js +75 -50
- package/commands/mission.js +97 -98
- package/commands/one-lap.js +1 -1
- package/commands/pack.js +74 -8
- package/commands/plugin.js +6 -6
- package/commands/probe.js +9 -9
- package/commands/pull.js +35 -35
- package/commands/pulse.js +1 -1
- package/commands/push.js +21 -21
- package/commands/rainmaker.js +1 -1
- package/commands/recap.js +7 -7
- package/commands/reel.js +2 -2
- package/commands/release.js +3 -3
- package/commands/report.js +1 -1
- package/commands/review.js +5 -5
- package/commands/revisions.js +1 -1
- package/commands/run.js +1 -1
- package/commands/scout.js +3 -3
- package/commands/security-review.js +7 -7
- package/commands/serve.js +9 -9
- package/commands/setup.js +1 -1
- package/commands/sign.js +4 -4
- package/commands/signup.js +4 -4
- package/commands/site.js +8 -3
- package/commands/skill.js +6 -6
- package/commands/slop.js +18 -18
- package/commands/social.js +3 -3
- package/commands/soul.js +14 -14
- package/commands/spaceship.js +1 -1
- package/commands/status.js +2 -2
- package/commands/strings.js +13 -13
- package/commands/study.js +4 -4
- package/commands/sync.js +8 -8
- package/commands/task.js +50 -25
- package/commands/teach.js +2 -2
- package/commands/team.js +1 -1
- package/commands/theme.js +2 -2
- package/commands/tree.js +34 -0
- package/commands/truth.js +7 -7
- package/commands/verify.js +12 -12
- package/commands/visualize.js +2 -2
- package/commands/workflow.js +97 -40
- package/commands/worktree.js +57 -1
- package/commands/write.js +9 -9
- package/commands/x-search.js +304 -15
- package/commands/xp.js +6 -3
- package/commands/youtube.js +916 -65
- package/lib/accept-verify-gate.js +3 -3
- package/lib/activity-stream.js +1 -1
- package/lib/apply-gate.js +13 -0
- package/lib/auto-accept-certified.js +28 -12
- package/lib/autoland.js +6 -6
- package/lib/bench/engines.js +59 -8
- package/lib/bench/report.js +1 -1
- package/lib/bench/runner.js +204 -5
- package/lib/bench/tree-render.js +76 -0
- package/lib/card.js +1 -1
- package/lib/chat-log-scan.js +1 -1
- package/lib/claude-boot-block.js +2 -0
- package/lib/deck-compose.js +1 -1
- package/lib/deck-history.js +2 -2
- package/lib/deck-layout.js +1 -1
- package/lib/deck-review.js +5 -5
- package/lib/deck-schema.js +1 -1
- package/lib/default-verifier.js +1 -1
- package/lib/developer-api.js +3 -0
- package/lib/engine-ask.js +66 -9
- package/lib/engine-registry.js +15 -2
- package/lib/file-ops.js +1 -1
- package/lib/fleet.js +71 -35
- package/lib/html-render.js +1 -1
- package/lib/intents.js +172 -0
- package/lib/journal.js +1 -1
- package/lib/known-commands.js +2 -2
- package/lib/land-green.js +364 -0
- package/lib/lesson-contradiction.js +2 -2
- package/lib/lesson-preflight.js +1 -1
- package/lib/manifest.js +5 -5
- package/lib/member-alive.js +9 -5
- package/lib/memory-view.js +3 -3
- package/lib/mission-protected-lane.js +1 -1
- package/lib/mission-root.js +2 -2
- package/lib/orb-context.js +10 -10
- package/lib/pack-capabilities.js +861 -3
- package/lib/permission-grants.js +2 -2
- package/lib/policy-lessons.js +6 -6
- package/lib/pulse.js +8 -8
- package/lib/receipt-evidence.js +1 -1
- package/lib/reel.js +1 -1
- package/lib/review-integrity.js +1 -1
- package/lib/runner-command.js +35 -5
- package/lib/scorecard.js +4 -4
- package/lib/security-scan.js +5 -5
- package/lib/self-drive.js +1 -1
- package/lib/site-publish.js +372 -0
- package/lib/site.js +1 -1
- package/lib/slides-deck.js +2 -2
- package/lib/state-detection.js +2 -2
- package/lib/sync-telemetry.js +2 -2
- package/lib/task-db.js +17 -10
- package/lib/task-explanation.js +1 -1
- package/lib/task-proof.js +2 -2
- package/lib/task-receipt.js +3 -3
- package/lib/theme.js +12 -12
- package/lib/todo-fallback.js +3 -3
- package/lib/todo-sections.js +1 -1
- package/lib/tree-hash.js +79 -0
- package/lib/usage.js +1 -1
- package/lib/wish-delegate.js +1 -1
- package/lib/workspace-safety.js +4 -4
- package/lib/workspace-scaffold.js +3 -3
- package/package.json +1 -1
- package/scripts/det/concierge-bakeoff.sh +26 -0
- package/scripts/det/ytquote-repair.js +0 -1
- package/scripts/det/ytrail-race.js +0 -5
- package/scripts/member-operate.mjs +52 -14
- package/scripts/outbound-artifact-gate.js +0 -3
- package/utils/auth.js +0 -3
- package/utils/config.js +0 -1
- package/scripts/det/checklist-score.js +0 -191
package/commands/mission.js
CHANGED
|
@@ -90,6 +90,7 @@ const {
|
|
|
90
90
|
const { resolveWorkspaceRoot, redirectToWorkspaceRoot } = require('../lib/mission-root');
|
|
91
91
|
const { isFreshWorkspace, speakFirstMinute } = require('../lib/first-minute');
|
|
92
92
|
const { readJson, writeJson } = require('../lib/json-file');
|
|
93
|
+
const { treeHashFor } = require('../lib/tree-hash');
|
|
93
94
|
const {
|
|
94
95
|
normalizeHumanAsks,
|
|
95
96
|
openHumanAsks,
|
|
@@ -453,7 +454,7 @@ function exitMissionError(message, code = 1, asJson = false) {
|
|
|
453
454
|
}
|
|
454
455
|
|
|
455
456
|
// Write-time warnings judge only what a machine can truly judge: identifiers,
|
|
456
|
-
// flags, and task ids. Whether a why is present is a judgment call
|
|
457
|
+
// flags, and task ids. Whether a why is present is a judgment call, the tick
|
|
457
458
|
// prompt demands it, the review pass judges it. A warning that cries wolf on
|
|
458
459
|
// plain sentences teaches agents to ignore it (golden-path papercut). The
|
|
459
460
|
// strict operatorReady bar stays at the digest surface, where under-showing
|
|
@@ -978,11 +979,6 @@ function loadMissionMap(root = process.cwd()) {
|
|
|
978
979
|
return map;
|
|
979
980
|
}
|
|
980
981
|
|
|
981
|
-
function hasLocalMissionState(root = process.cwd()) {
|
|
982
|
-
return readJsonLines(statePaths(root).missionsJsonl)
|
|
983
|
-
.some((mission) => mission && mission.id && mission.cloud !== true);
|
|
984
|
-
}
|
|
985
|
-
|
|
986
982
|
const LIVE_MISSION_TICK_MS = 24 * 60 * 60 * 1000;
|
|
987
983
|
|
|
988
984
|
function isLiveInFlightMission(mission, now = Date.now(), root = process.cwd()) {
|
|
@@ -1024,19 +1020,15 @@ function speakMissionDoor(args = [], root = resolveWorkspaceRoot()) {
|
|
|
1024
1020
|
if (live) {
|
|
1025
1021
|
return statusMission(asJson ? [live.id, '--json'] : [live.id]);
|
|
1026
1022
|
}
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
fresh: isFreshWorkspace(root),
|
|
1031
|
-
asJson,
|
|
1032
|
-
});
|
|
1033
|
-
process.exitCode = code;
|
|
1034
|
-
return code;
|
|
1035
|
-
}
|
|
1036
|
-
return require('./human-missions').currentMissionCommand(args, {
|
|
1023
|
+
// Nothing running in this folder: same two spoken lines as bare atris.
|
|
1024
|
+
// A finished or account-global mission from another room is not the live card.
|
|
1025
|
+
const code = speakFirstMinute({
|
|
1037
1026
|
root,
|
|
1038
|
-
|
|
1027
|
+
fresh: isFreshWorkspace(root),
|
|
1028
|
+
asJson,
|
|
1039
1029
|
});
|
|
1030
|
+
process.exitCode = code;
|
|
1031
|
+
return code;
|
|
1040
1032
|
}
|
|
1041
1033
|
|
|
1042
1034
|
function terminalNextAction(status) {
|
|
@@ -1212,7 +1204,7 @@ function listWorktreeRollupMissions(root = process.cwd()) {
|
|
|
1212
1204
|
// run/tick/show. This is the same local+rollup, deduped-by-id, list-sorted
|
|
1213
1205
|
// set `mission status`/`list` renders, so id/slug/suffix lookups search
|
|
1214
1206
|
// exactly what list showed. n stays exactly as each mission's home workspace
|
|
1215
|
-
// assigned it (loadMissionMap owns that)
|
|
1207
|
+
// assigned it (loadMissionMap owns that), this index does NOT renumber,
|
|
1216
1208
|
// because n must stay a stable, durable handle across saves (see
|
|
1217
1209
|
// mission-number-stability.test.js); numeric-n resolution stays scoped to
|
|
1218
1210
|
// the local root, where n is guaranteed unique.
|
|
@@ -1275,7 +1267,7 @@ function missionNumberDisplayIndex(missions, canonicalMissions = missions) {
|
|
|
1275
1267
|
const MISSION_REF_SUFFIX_MIN_LENGTH = 6;
|
|
1276
1268
|
|
|
1277
1269
|
// A ref that "looks like" an id/suffix/number is a single token with no
|
|
1278
|
-
// whitespace matching a hex/id/numeric shape. Those must resolve or error
|
|
1270
|
+
// whitespace matching a hex/id/numeric shape. Those must resolve or error,
|
|
1279
1271
|
// never silently fall through to starting a new mission (a mistyped id/n
|
|
1280
1272
|
// creating a junk mission is the worst failure mode here).
|
|
1281
1273
|
function missionRefLooksLikeHandle(ref) {
|
|
@@ -1411,10 +1403,10 @@ function exitMissingMission(ref, code = 1, asJson = false, root = process.cwd())
|
|
|
1411
1403
|
} else if (candidates.length) {
|
|
1412
1404
|
console.error('Nearest candidates:');
|
|
1413
1405
|
for (const candidate of candidates) {
|
|
1414
|
-
console.error(` #${candidate.n} ${candidate.id} (${candidate.status})
|
|
1406
|
+
console.error(` #${candidate.n} ${candidate.id} (${candidate.status}), ${candidate.objective}`);
|
|
1415
1407
|
}
|
|
1416
1408
|
} else {
|
|
1417
|
-
console.error('Mission ids are workspace-local
|
|
1409
|
+
console.error('Mission ids are workspace-local, run this from the workspace that created the mission.');
|
|
1418
1410
|
}
|
|
1419
1411
|
}
|
|
1420
1412
|
process.exit(code);
|
|
@@ -1444,7 +1436,7 @@ function resolveMission(ref, root = process.cwd()) {
|
|
|
1444
1436
|
return chosen;
|
|
1445
1437
|
}
|
|
1446
1438
|
if (matches.length) return matches[0];
|
|
1447
|
-
// No mission has this n. An all-digit ref can still be a hash suffix
|
|
1439
|
+
// No mission has this n. An all-digit ref can still be a hash suffix,
|
|
1448
1440
|
// missionId()'s 8-hex-char suffix is purely numeric ~2.4% of the time
|
|
1449
1441
|
// ((10/16)^8), and returning null here made those missions unresolvable
|
|
1450
1442
|
// by suffix. Fall through to slug/suffix resolution instead.
|
|
@@ -1509,7 +1501,7 @@ function saveMission(mission, root = process.cwd(), eventType = 'mission_updated
|
|
|
1509
1501
|
});
|
|
1510
1502
|
appendJsonLine(paths.missionsJsonl, next);
|
|
1511
1503
|
// Self-maintenance: once history outweighs live missions 4:1 the ledger is
|
|
1512
|
-
// rewritten to one row per mission (latest state, first display number
|
|
1504
|
+
// rewritten to one row per mission (latest state, first display number,
|
|
1513
1505
|
// exactly what loadMissionMap reconstructs). Every status/tick path parses
|
|
1514
1506
|
// this file several times, so unbounded snapshot history taxes everything.
|
|
1515
1507
|
try { compactMissionLedger(paths.missionsJsonl); } catch {}
|
|
@@ -1789,7 +1781,7 @@ function missionLandingStepSummary(summary) {
|
|
|
1789
1781
|
const withoutProofTail = withoutInternalId.replace(/;\s+(?=(?:PR\s+\d+|(?:node|npm|git)\b)).*$/i, '').trim();
|
|
1790
1782
|
const plainVerified = missionPlainVerifiedSummary(withoutProofTail);
|
|
1791
1783
|
if (plainVerified) return plainVerified;
|
|
1792
|
-
const beforeInlineProof = withoutProofTail.split(/\s+(
|
|
1784
|
+
const beforeInlineProof = withoutProofTail.split(/\s+(?:\u2014|-)\s+verified by\b/i)[0] || withoutProofTail;
|
|
1793
1785
|
const beforeChecks = (beforeInlineProof.split(/\s+(?:checks?|verified|proof):\s+/i)[0] || beforeInlineProof)
|
|
1794
1786
|
.replace(/,\s*(?:verifier|proof|checks?)\s+(?:still|pending)[.!]?$/i, '')
|
|
1795
1787
|
.trim();
|
|
@@ -2104,26 +2096,38 @@ function normalizeMissionReceiptResult(mission, result, receiptPath = '') {
|
|
|
2104
2096
|
|
|
2105
2097
|
function missionRunStartNextLine(mission, nextCommand, warnings = []) {
|
|
2106
2098
|
const missingVerifier = warnings.some((warning) => warning && warning.code === 'missing_verifier');
|
|
2107
|
-
if (missingVerifier) return 'Add a
|
|
2099
|
+
if (missingVerifier) return 'Add a completion check, then start the first piece of work.';
|
|
2108
2100
|
if (isCodexGoalMission(mission) && !codexNativeGoalAck(mission)) {
|
|
2109
|
-
return 'Start the
|
|
2101
|
+
return 'Start the first piece of work in this chat.';
|
|
2110
2102
|
}
|
|
2111
|
-
if (/attach-task/.test(nextCommand || '')) return '
|
|
2112
|
-
return '
|
|
2103
|
+
if (/attach-task/.test(nextCommand || '')) return 'Start the first piece of work.';
|
|
2104
|
+
return 'Keep working on the next useful result.';
|
|
2105
|
+
}
|
|
2106
|
+
|
|
2107
|
+
function missionRunCheckPlanText(command) {
|
|
2108
|
+
const check = String(command || '').trim();
|
|
2109
|
+
if (!check) return 'No completion check is ready yet.';
|
|
2110
|
+
if (/^git\s+diff\s+--check\b/i.test(check)) {
|
|
2111
|
+
return 'Atris will check the changed files for formatting problems before the result is ready.';
|
|
2112
|
+
}
|
|
2113
|
+
if (/\bnode\s+--test\b/i.test(check)) {
|
|
2114
|
+
return 'Atris will run the relevant behavior checks before the result is ready.';
|
|
2115
|
+
}
|
|
2116
|
+
if (/^test\s+-s\s+\S+/i.test(check)) {
|
|
2117
|
+
return 'Atris will confirm the saved result exists and is not empty.';
|
|
2118
|
+
}
|
|
2119
|
+
return 'A completion check is ready and will run before the result is ready.';
|
|
2113
2120
|
}
|
|
2114
2121
|
|
|
2115
2122
|
function missionRunTakeoffLines(mission, { warnings = [], nextCommand = '' } = {}) {
|
|
2116
|
-
const checked = mission.verifier
|
|
2117
|
-
? `Verifier configured: ${mission.verifier}.`
|
|
2118
|
-
: 'No verifier was recorded for this mission.';
|
|
2119
2123
|
return [
|
|
2120
|
-
'
|
|
2124
|
+
'Mission started.',
|
|
2121
2125
|
` Goal: ${mission.objective}`,
|
|
2122
|
-
` Done when: ${mission.stop_condition || 'the
|
|
2126
|
+
` Done when: ${mission.stop_condition || 'the result is checked and ready to review'}.`,
|
|
2123
2127
|
...(missionBudgetLine(mission) ? [` Budget: ${missionBudgetLine(mission)}`] : []),
|
|
2124
2128
|
...missionGoalChainLines(mission),
|
|
2125
|
-
'
|
|
2126
|
-
`
|
|
2129
|
+
' Saved: Atris will remember this mission and its progress.',
|
|
2130
|
+
` How it will be checked: ${missionRunCheckPlanText(mission.verifier)}`,
|
|
2127
2131
|
` Next: ${missionRunStartNextLine(mission, nextCommand, warnings)}`,
|
|
2128
2132
|
];
|
|
2129
2133
|
}
|
|
@@ -2185,16 +2189,9 @@ function selectMissionRunUsefulTarget(rawObjective, root = process.cwd()) {
|
|
|
2185
2189
|
}
|
|
2186
2190
|
}
|
|
2187
2191
|
|
|
2188
|
-
function
|
|
2189
|
-
const clean = String(text || '').replace(/\s+/g, ' ').trim();
|
|
2190
|
-
if (clean.length <= max) return clean;
|
|
2191
|
-
return `${clean.slice(0, max - 3).replace(/\s+\S*$/, '').trimEnd()}...`;
|
|
2192
|
-
}
|
|
2193
|
-
|
|
2194
|
-
function missionRunPreflightObjective(rawObjective, room, owner) {
|
|
2195
|
-
const name = room?.name || 'Mission Room';
|
|
2192
|
+
function missionRunPreflightObjective(rawObjective, room) {
|
|
2196
2193
|
const task = room?.task_plan_preview?.task || room?.truth_snapshot || rawObjective;
|
|
2197
|
-
return
|
|
2194
|
+
return String(task || rawObjective || '').replace(/\s+/g, ' ').trim();
|
|
2198
2195
|
}
|
|
2199
2196
|
|
|
2200
2197
|
function missionRunTrustedObjective(rawObjective, room, target) {
|
|
@@ -2266,7 +2263,7 @@ function buildMissionRunRoomPreflight(rawObjective, args = [], options = {}) {
|
|
|
2266
2263
|
const written = writeMissionRoomReceipt(room, { root });
|
|
2267
2264
|
const shapedObjective = trustedRun
|
|
2268
2265
|
? missionRunTrustedObjective(rawObjective, written.room, selectedTarget)
|
|
2269
|
-
: missionRunPreflightObjective(rawObjective, written.room
|
|
2266
|
+
: missionRunPreflightObjective(rawObjective, written.room);
|
|
2270
2267
|
const shaping = guardMissionRunShaping(rawObjective, shapedObjective, selectedTarget);
|
|
2271
2268
|
const acceptedTarget = shaping.selectedTarget;
|
|
2272
2269
|
const taskSpineRequired = !acceptedTarget && (explicitPreflight || signalPreflight);
|
|
@@ -4031,7 +4028,7 @@ function inheritedWorktreeBase(cwd) {
|
|
|
4031
4028
|
// Dedup gate: the same objective + owner already active anywhere in the
|
|
4032
4029
|
// workspace family (this store or any worktree's) is reused, never cloned.
|
|
4033
4030
|
// Born 2026-07-02: an hourly alive loop spawned six identical auto-improver
|
|
4034
|
-
// missions in six fresh worktrees in one day
|
|
4031
|
+
// missions in six fresh worktrees in one day, pure token burn. --duplicate
|
|
4035
4032
|
// is the explicit escape hatch.
|
|
4036
4033
|
const TWIN_ACTIVE_STATUSES = new Set(['planning', 'ready', 'running']);
|
|
4037
4034
|
|
|
@@ -4078,7 +4075,7 @@ function startMission(args, options = {}) {
|
|
|
4078
4075
|
printJsonOrText(
|
|
4079
4076
|
{ ok: true, action: 'mission_recovered', recovered: true, mission: existing, note: 'objective looked like a mission id; recovered the existing mission instead of creating a new one' },
|
|
4080
4077
|
[
|
|
4081
|
-
`That's a mission id, not an objective
|
|
4078
|
+
`That's a mission id, not an objective, recovered ${existing.id} (${existing.status}).`,
|
|
4082
4079
|
`Resume: atris mission run ${existing.id}`,
|
|
4083
4080
|
],
|
|
4084
4081
|
asJson,
|
|
@@ -4128,7 +4125,7 @@ function startMission(args, options = {}) {
|
|
|
4128
4125
|
{ ok: true, action: 'mission_reused', reused: true, mission: twin, note: 'an active mission with this objective and owner already exists; resumed instead of cloning (pass --duplicate to force a second one)' },
|
|
4129
4126
|
[
|
|
4130
4127
|
`Already active: ${twin.id} (${twin.status})`,
|
|
4131
|
-
`Same objective, same owner
|
|
4128
|
+
`Same objective, same owner, reusing it instead of starting a clone.`,
|
|
4132
4129
|
`Resume: atris mission run ${twin.id}`,
|
|
4133
4130
|
`Really want a second one: re-run with --duplicate`,
|
|
4134
4131
|
],
|
|
@@ -4149,7 +4146,7 @@ function startMission(args, options = {}) {
|
|
|
4149
4146
|
}
|
|
4150
4147
|
// --worktree: bind the mission to its own isolated checkout. We chdir before
|
|
4151
4148
|
// any state writes so the mission record, baseline sidecar, receipts, and
|
|
4152
|
-
// member files all land inside the worktree
|
|
4149
|
+
// member files all land inside the worktree, ticks run there, and the main
|
|
4153
4150
|
// checkout's dirt never reaches the mission baseline.
|
|
4154
4151
|
if (hasFlag(args, '--worktree')) {
|
|
4155
4152
|
let created;
|
|
@@ -4277,14 +4274,15 @@ async function startMissionFromRunObjective(objective, args) {
|
|
|
4277
4274
|
landRun ? DEFAULT_LONG_RUN_VERIFIER : inferRunObjectiveVerifier(missionObjective)
|
|
4278
4275
|
|| inferRunObjectiveVerifier(rawObjective)
|
|
4279
4276
|
|| (missionRunPreflight?.trusted_run ? DEFAULT_LONG_RUN_VERIFIER : '')
|
|
4280
|
-
|| (inferredLoop.wantsLongRun ? DEFAULT_LONG_RUN_VERIFIER : '')
|
|
4277
|
+
|| (inferredLoop.wantsLongRun ? DEFAULT_LONG_RUN_VERIFIER : '')
|
|
4278
|
+
|| (hasFlag(args, '--no-verify') ? '' : resolveDefaultVerifier(process.cwd(), { allowBroadSuite: false })),
|
|
4281
4279
|
);
|
|
4282
4280
|
const stopCondition = readFlag(
|
|
4283
4281
|
args,
|
|
4284
4282
|
'--stop',
|
|
4285
4283
|
budgetStopCondition(budgetContract) || (inferredLoop.wantsLongRun
|
|
4286
4284
|
? `run for ${inferredLoop.requestedHours || 'the requested overnight window'} hour${inferredLoop.requestedHours === 1 ? '' : 's'}, or stop when proof is ready`
|
|
4287
|
-
: (verifier ? '
|
|
4285
|
+
: (verifier ? 'the first result is checked and ready to review' : 'the first result is saved and ready to review')),
|
|
4288
4286
|
);
|
|
4289
4287
|
const startArgs = [
|
|
4290
4288
|
missionObjective,
|
|
@@ -5092,7 +5090,7 @@ function missionRunsReceiptIndex(root = process.cwd()) {
|
|
|
5092
5090
|
}
|
|
5093
5091
|
|
|
5094
5092
|
// Roll every ran tick for a mission into a verification-debt tally. `unchecked`
|
|
5095
|
-
// counts ran ticks that recorded no verifier result at all
|
|
5093
|
+
// counts ran ticks that recorded no verifier result at all, the "tick recorded
|
|
5096
5094
|
// but nothing was checked" red the report and now.md rollup surface and count
|
|
5097
5095
|
// against the mission.
|
|
5098
5096
|
function missionVerificationDebt(mission, root = process.cwd()) {
|
|
@@ -5120,7 +5118,7 @@ function missionVerificationDebt(mission, root = process.cwd()) {
|
|
|
5120
5118
|
// when every ran tick was checked, so clean missions stay quiet.
|
|
5121
5119
|
function missionVerificationDebtLine(debt) {
|
|
5122
5120
|
if (!debt || !debt.unchecked) return '';
|
|
5123
|
-
return `unverified ticks: ${debt.unchecked} of ${debt.ran} ran no check
|
|
5121
|
+
return `unverified ticks: ${debt.unchecked} of ${debt.ran} ran no check ⚠, treat those increments as unproven`;
|
|
5124
5122
|
}
|
|
5125
5123
|
|
|
5126
5124
|
function missionReportTimeline(mission, root = process.cwd(), limit = 6) {
|
|
@@ -6035,7 +6033,7 @@ function doctorMission(args) {
|
|
|
6035
6033
|
if (findings.length) process.exitCode = 1;
|
|
6036
6034
|
}
|
|
6037
6035
|
|
|
6038
|
-
// `atris mission watch [id]
|
|
6036
|
+
// `atris mission watch [id]`, read-only live heartbeat. Prints a line per tick as it
|
|
6039
6037
|
// lands so a human (or any terminal) can see the loop is alive without rerunning status.
|
|
6040
6038
|
function watchMission(args) {
|
|
6041
6039
|
if (hasFlag(args, '--help') || hasFlag(args, '-h') || String(args[0] || '').trim() === 'help') {
|
|
@@ -6061,7 +6059,7 @@ function watchMission(args) {
|
|
|
6061
6059
|
const fingerprint = (mission) => [mission.status, mission.last_tick_at, mission.last_tick_index, mission.receipt_path].join('|');
|
|
6062
6060
|
const tickNote = (mission) => {
|
|
6063
6061
|
const heartbeat = missionHeartbeatLines(mission).map((line) => line.trim()).join(', ');
|
|
6064
|
-
return `${heartbeat || `state: ${mission.status}`}${mission.receipt_path ?
|
|
6062
|
+
return `${heartbeat || `state: ${mission.status}`}${mission.receipt_path ? `, proof: ${mission.receipt_path}` : ''}`;
|
|
6065
6063
|
};
|
|
6066
6064
|
const seen = new Map();
|
|
6067
6065
|
let lastIdleAt = Date.now();
|
|
@@ -6069,7 +6067,7 @@ function watchMission(args) {
|
|
|
6069
6067
|
const poll = () => {
|
|
6070
6068
|
const targets = loadTargets();
|
|
6071
6069
|
if (!targets.length && !seen.size) {
|
|
6072
|
-
emitOnce('no active missions yet
|
|
6070
|
+
emitOnce('no active missions yet, waiting');
|
|
6073
6071
|
}
|
|
6074
6072
|
let changed = false;
|
|
6075
6073
|
for (const mission of targets) {
|
|
@@ -6110,6 +6108,7 @@ function writeReceipt(mission, result, root = process.cwd()) {
|
|
|
6110
6108
|
const finalResult = normalizeMissionReceiptResult(mission, result, relativeReceiptPath);
|
|
6111
6109
|
const receipt = {
|
|
6112
6110
|
schema: 'atris.mission_receipt.v1',
|
|
6111
|
+
tree_hash: treeHashFor(root),
|
|
6113
6112
|
mission_id: mission.id,
|
|
6114
6113
|
objective: mission.objective,
|
|
6115
6114
|
owner: mission.owner,
|
|
@@ -6358,7 +6357,7 @@ function loadMissionWorktreeBaseline(missionId, root = process.cwd()) {
|
|
|
6358
6357
|
// Closed missions no longer tick, so the sidecar is dead weight; prune it and
|
|
6359
6358
|
// fold a compact audit summary into the mission record (full path lists stay
|
|
6360
6359
|
// out of missions.jsonl, which re-appends the whole record on every save).
|
|
6361
|
-
// Paused missions keep their sidecar
|
|
6360
|
+
// Paused missions keep their sidecar, resume ticks still subtract it.
|
|
6362
6361
|
function pruneMissionWorktreeBaseline(mission, root = process.cwd()) {
|
|
6363
6362
|
const baseline = loadMissionWorktreeBaseline(mission.id, root);
|
|
6364
6363
|
try { fs.rmSync(missionBaselinePath(mission.id, root), { force: true }); } catch {}
|
|
@@ -6389,7 +6388,7 @@ function worktreeReceipt(before, after, { verifier = '', baseline = null } = {})
|
|
|
6389
6388
|
// tick-start snapshot for legacy missions). Only paths dirtied beyond that
|
|
6390
6389
|
// baseline count toward the unverified signal. Loop exhaust the mission
|
|
6391
6390
|
// writes about itself (state plane, receipts, rendered status) is not work
|
|
6392
|
-
// product, so it never counts
|
|
6391
|
+
// product, so it never counts, otherwise every multi-tick mission in a repo
|
|
6393
6392
|
// that doesn't gitignore those dirs would flag its own bookkeeping.
|
|
6394
6393
|
const baselinePaths = baseline
|
|
6395
6394
|
? new Set(baseline.paths)
|
|
@@ -6418,7 +6417,7 @@ function worktreeReceipt(before, after, { verifier = '', baseline = null } = {})
|
|
|
6418
6417
|
}
|
|
6419
6418
|
|
|
6420
6419
|
// ---------------------------------------------------------------------------
|
|
6421
|
-
// `atris mission run <id
|
|
6420
|
+
// `atris mission run <id>`, bounded local headless loop. v0.1.
|
|
6422
6421
|
// Spawns `claude -p --resume <session>` per tick. Honors cadence, active-hours,
|
|
6423
6422
|
// rate-limit info, and a flock per mission. `max-ticks` bounds total attempts;
|
|
6424
6423
|
// `ran_ticks` separately reports ticks that actually made progress.
|
|
@@ -7820,19 +7819,19 @@ function consecutiveVerifierFails(ticks) {
|
|
|
7820
7819
|
}
|
|
7821
7820
|
|
|
7822
7821
|
// BCK-1324: a "holding tick" is a tick that reports status=ran/reason=tick-ok
|
|
7823
|
-
// (so it never trips the error-streak breakers) but left no structural trace
|
|
7822
|
+
// (so it never trips the error-streak breakers) but left no structural trace,
|
|
7824
7823
|
// no new or cleared dirty files beyond whatever was already dirty at tick
|
|
7825
7824
|
// start, and no fresh verifier pass. Claude's own summary text ("holding
|
|
7826
7825
|
// tick, no drift") is not the signal: agents self-label busywork as progress
|
|
7827
7826
|
// constantly. The worktree diff and verifier result are ground truth.
|
|
7828
7827
|
function tickMadeProgress(tick) {
|
|
7829
|
-
if (!tick || tick.status !== 'ran') return true; // errors/skips aren't "idle"
|
|
7828
|
+
if (!tick || tick.status !== 'ran') return true; // errors/skips aren't "idle", other breakers own those
|
|
7830
7829
|
const wt = tick.worktree;
|
|
7831
7830
|
if (wt && wt.available) {
|
|
7832
7831
|
if ((wt.new_dirty_count || 0) > 0) return true;
|
|
7833
7832
|
if ((wt.cleared_dirty_count || 0) > 0) return true;
|
|
7834
7833
|
} else {
|
|
7835
|
-
// No worktree signal available (e.g. git unavailable)
|
|
7834
|
+
// No worktree signal available (e.g. git unavailable), don't punish a
|
|
7836
7835
|
// tick we have no evidence against.
|
|
7837
7836
|
return true;
|
|
7838
7837
|
}
|
|
@@ -7842,7 +7841,7 @@ function tickMadeProgress(tick) {
|
|
|
7842
7841
|
|
|
7843
7842
|
// Count the trailing run of ticks (most recent first) that made no progress
|
|
7844
7843
|
// per tickMadeProgress. A single progressing tick anywhere in the run resets
|
|
7845
|
-
// this to 0
|
|
7844
|
+
// this to 0, only the tail streak matters.
|
|
7846
7845
|
function consecutiveNoProgressTicks(ticks) {
|
|
7847
7846
|
let n = 0;
|
|
7848
7847
|
for (let i = ticks.length - 1; i >= 0; i--) {
|
|
@@ -7863,7 +7862,7 @@ function missionJudgmentPlainText(value) {
|
|
|
7863
7862
|
.replace(/\b[0-9A-HJKMNP-TV-Z]{26}\b/gi, ' ')
|
|
7864
7863
|
.replace(/\b[0-9a-f]{12,40}\b/gi, ' ')
|
|
7865
7864
|
.replace(/\b[A-Z][A-Z0-9]{1,9}-\d+\b/g, ' ')
|
|
7866
|
-
.replace(/[
|
|
7865
|
+
.replace(/[\u2014–]/g, ' ')
|
|
7867
7866
|
.replace(/[*_~#>]+/g, ' ')
|
|
7868
7867
|
.replace(/\s+/g, ' ')
|
|
7869
7868
|
.replace(/^[\s:;,.-]+|[\s:;,.-]+$/g, '')
|
|
@@ -8211,7 +8210,7 @@ function acquireMissionLock(missionId, root = process.cwd(), options = {}) {
|
|
|
8211
8210
|
const holderKnown = Number.isInteger(holderPid) && holderPid > 0;
|
|
8212
8211
|
// A lock is created empty (openSync 'wx') and its pid record is written a
|
|
8213
8212
|
// moment later. A racing waiter that reads the file inside that window sees
|
|
8214
|
-
// no pid
|
|
8213
|
+
// no pid, do NOT treat that as a dead owner, or two processes both "own"
|
|
8215
8214
|
// the lock and clobber each other's writes. Only steal when the holder pid
|
|
8216
8215
|
// is known-dead, or when a pidless lock has sat abandoned past the tiny
|
|
8217
8216
|
// creation window (guards against a crash between openSync and writeSync).
|
|
@@ -8443,7 +8442,7 @@ function buildTickPrompt(mission, tickIndex, maxTicks, frozen, pings = []) {
|
|
|
8443
8442
|
`**Lane:** ${frozen.lane}`,
|
|
8444
8443
|
`**Cadence:** ${mission.cadence}`,
|
|
8445
8444
|
`**Stop condition:** ${mission.stop_condition || 'human marks complete'}`,
|
|
8446
|
-
`**Verifier (frozen):** ${frozen.verifier || '(none
|
|
8445
|
+
`**Verifier (frozen):** ${frozen.verifier || '(none, receipt only)'}`,
|
|
8447
8446
|
`**Last status:** ${mission.status}`,
|
|
8448
8447
|
`**Last tick:** ${mission.last_tick_at || 'never'}`,
|
|
8449
8448
|
...missionBudgetPromptLines(mission),
|
|
@@ -8451,7 +8450,7 @@ function buildTickPrompt(mission, tickIndex, maxTicks, frozen, pings = []) {
|
|
|
8451
8450
|
``,
|
|
8452
8451
|
`## Your task`,
|
|
8453
8452
|
`Do ONE increment of work toward the stop condition. ONE. No more.`,
|
|
8454
|
-
`- You are the member "${mission.owner}". Read atris/team/${mission.owner}/MEMBER.md (and SOUL.md if present) before acting
|
|
8453
|
+
`- You are the member "${mission.owner}". Read atris/team/${mission.owner}/MEMBER.md (and SOUL.md if present) before acting, work in that identity, inside its scope and stop rules. After your work, append what you did and what you learned to atris/team/${mission.owner}/logs/<today's date>.md.`,
|
|
8455
8454
|
`- FIRST: inspect current mission/task state before acting. Read the relevant files, run \`atris mission status ${mission.id}\`, \`git status\`, or \`atris task list\` as needed so you know what's already done.`,
|
|
8456
8455
|
`- Pick the smallest concrete action that moves the mission forward.`,
|
|
8457
8456
|
`- Before acting, state your single next move in one sentence.`,
|
|
@@ -8482,14 +8481,14 @@ function buildTickPrompt(mission, tickIndex, maxTicks, frozen, pings = []) {
|
|
|
8482
8481
|
`- Do NOT start new missions, modify other missions, or expand scope.`,
|
|
8483
8482
|
`- Do NOT run destructive commands without strong evidence they're correct.`,
|
|
8484
8483
|
``,
|
|
8485
|
-
`When done, output a short receipt. The summary's first line must name what changed and how it was verified. Then include: (1) the exact files edited / commands run / artifacts produced
|
|
8484
|
+
`When done, output a short receipt. The summary's first line must name what changed and how it was verified. Then include: (1) the exact files edited / commands run / artifacts produced, name them, (1b) one verify command a reviewer can rerun to check the work, (2) the metric of progress, (3) what the next tick should pick up. End the receipt with one line naming the layer this tick touched: \`layer: identity|beliefs|capabilities|behaviors|environment\` (final line, the harness parses it).`,
|
|
8486
8485
|
];
|
|
8487
8486
|
if (mission.task_ids?.length) {
|
|
8488
8487
|
lines.push('', `## Task ids`, mission.task_ids.map((t) => `- ${t}`).join('\n'));
|
|
8489
8488
|
}
|
|
8490
8489
|
const humanAsks = openHumanAsks(mission.human_asks);
|
|
8491
8490
|
if (humanAsks.length) {
|
|
8492
|
-
lines.push('', `## Human asks (don't act on these
|
|
8491
|
+
lines.push('', `## Human asks (don't act on these, surface them)`, humanAsks.map((ask) => `- ${ask.text}`).join('\n'));
|
|
8493
8492
|
}
|
|
8494
8493
|
return lines.join('\n');
|
|
8495
8494
|
}
|
|
@@ -8524,16 +8523,16 @@ function missionPauseNextAction(pauseReason, missionId, deadModel = null, lastEr
|
|
|
8524
8523
|
return 'stopped: the worker kept reporting the same thing 3 times in a row';
|
|
8525
8524
|
}
|
|
8526
8525
|
if (pauseReason === 'model-unavailable' && deadModel) {
|
|
8527
|
-
return `model "${deadModel}" is unavailable
|
|
8526
|
+
return `model "${deadModel}" is unavailable, set a live model (mission.model, ATRIS_RUNNER_MODEL, or legacy ATRIS_CLAUDE_MODEL), then: atris mission run ${missionId}`;
|
|
8528
8527
|
}
|
|
8529
8528
|
if (typeof pauseReason === 'string' && pauseReason.startsWith('repeated-error:')) {
|
|
8530
8529
|
const reason = pauseReason.slice('repeated-error:'.length);
|
|
8531
|
-
return `tick kept failing with "${reason}"
|
|
8530
|
+
return `tick kept failing with "${reason}", inspect the last receipt, fix the cause, then: atris mission run ${missionId}`;
|
|
8532
8531
|
}
|
|
8533
8532
|
// Single-tick cron runs pause via max-ticks-reached on the very first errored tick.
|
|
8534
8533
|
// A bare "resume" there just re-errors; point the operator at the cause instead.
|
|
8535
8534
|
if (pauseReason === 'max-ticks-reached' && lastErrorReason) {
|
|
8536
|
-
return `hit the tick budget while erroring ("${lastErrorReason}")
|
|
8535
|
+
return `hit the tick budget while erroring ("${lastErrorReason}"), inspect the last receipt before resuming: atris mission run ${missionId}`;
|
|
8537
8536
|
}
|
|
8538
8537
|
return `resume with: atris mission run ${missionId}`;
|
|
8539
8538
|
}
|
|
@@ -9090,7 +9089,7 @@ async function parseAndValidateMissionRunPhase(args) {
|
|
|
9090
9089
|
}
|
|
9091
9090
|
// --fleet: staff every idle capable engine on the board's claimable
|
|
9092
9091
|
// safe-lane tasks, build in parallel worktrees, land serially. Humble flag,
|
|
9093
|
-
// full loop
|
|
9092
|
+
// full loop, see lib/fleet.js. --dry-run previews the staffing only.
|
|
9094
9093
|
if (hasFlag(args, '--fleet')) {
|
|
9095
9094
|
const { runFleetFlight } = require('../lib/fleet');
|
|
9096
9095
|
const slots = Math.max(1, Number(readFlag(args, '--slots', '')) || 3);
|
|
@@ -9211,7 +9210,7 @@ async function resolveMissionRunPhase(context) {
|
|
|
9211
9210
|
// BCK-1319: a bare single token that looks like an id/suffix/number (no
|
|
9212
9211
|
// whitespace, hex/id/numeric shape) is a mistyped or stale mission handle,
|
|
9213
9212
|
// not a new objective. Silently starting a fresh mission from it is the
|
|
9214
|
-
// worst failure mode
|
|
9213
|
+
// worst failure mode, it buries the mission the operator meant to run.
|
|
9215
9214
|
// Only a genuine multi-word (or non-handle-shaped) ref keeps the
|
|
9216
9215
|
// start-a-new-mission shortcut.
|
|
9217
9216
|
if (!mission && ref && missionRefLooksLikeHandle(ref)) {
|
|
@@ -9392,7 +9391,7 @@ async function executeMissionRunTicksPhase(context) {
|
|
|
9392
9391
|
const effectiveMaxTicks = (cadenceSeconds === 0 && !hasExplicitTickBudget) ? 1 : maxTicks;
|
|
9393
9392
|
|
|
9394
9393
|
// Session setup: only Claude-backed workers need a persisted session id.
|
|
9395
|
-
// atris2 turns are stateless per tick
|
|
9394
|
+
// atris2 turns are stateless per tick, continuity lives on disk (logs, receipts, now.md).
|
|
9396
9395
|
if (!skipWorker && !atris2Runner && !drillRunner && !sessionId && !pendingSessionId) {
|
|
9397
9396
|
pendingSessionId = crypto.randomUUID();
|
|
9398
9397
|
mission = saveMission({ ...mission, pending_session_id: pendingSessionId }, cwd, 'mission_session_pending', { session_id: pendingSessionId }).mission;
|
|
@@ -9473,7 +9472,7 @@ async function executeMissionRunTicksPhase(context) {
|
|
|
9473
9472
|
// the auto runner has already resolved this tick to a *different* ready engine
|
|
9474
9473
|
// (the walled one is now credit_out and excluded from resolution), the run-wide
|
|
9475
9474
|
// cooldown no longer applies. Clear it so the fresh engine runs now instead of
|
|
9476
|
-
// the loop sleeping through
|
|
9475
|
+
// the loop sleeping through, or pausing on `rate-limit-exceeded-wall` for, a
|
|
9477
9476
|
// wall this engine never hit.
|
|
9478
9477
|
if (lastRateLimit && rateLimitedEngineId && tickEngineId && tickEngineId !== rateLimitedEngineId) {
|
|
9479
9478
|
result.engine_swapped_from = rateLimitedEngineId;
|
|
@@ -9672,8 +9671,8 @@ async function executeMissionRunTicksPhase(context) {
|
|
|
9672
9671
|
// held, so the next resume fails with "already in use". A session
|
|
9673
9672
|
// that was cleaned up between ticks fails the resume the other way,
|
|
9674
9673
|
// with "No conversation found with session ID". Both mean the stored
|
|
9675
|
-
// id is dead. Session continuity is disposable
|
|
9676
|
-
// on disk (receipts, logs, now.md)
|
|
9674
|
+
// id is dead. Session continuity is disposable, mission state lives
|
|
9675
|
+
// on disk (receipts, logs, now.md), so rotate to a fresh id instead
|
|
9677
9676
|
// of grinding the repeated-error breaker on a stale session.
|
|
9678
9677
|
const staleSession = /session id .* is already in use/i.test(claudeResult.stderr || '')
|
|
9679
9678
|
|| /no conversation found with session id/i.test(claudeResult.stderr || '');
|
|
@@ -9701,7 +9700,7 @@ async function executeMissionRunTicksPhase(context) {
|
|
|
9701
9700
|
break;
|
|
9702
9701
|
}
|
|
9703
9702
|
} else if (sessionId && claudeResult.sessionIds.length > 0 && !claudeResult.sessionIds.includes(sessionId)) {
|
|
9704
|
-
// session_id mismatch on a resumed session
|
|
9703
|
+
// session_id mismatch on a resumed session, abort run
|
|
9705
9704
|
pauseReason = 'session-id-mismatch';
|
|
9706
9705
|
break;
|
|
9707
9706
|
}
|
|
@@ -9750,7 +9749,7 @@ async function executeMissionRunTicksPhase(context) {
|
|
|
9750
9749
|
|| worktreeReceipt(tickWorktreeBefore, gitWorktreeSnapshot(cwd), { verifier: frozen.verifier, baseline: runWorktreeBaseline });
|
|
9751
9750
|
|
|
9752
9751
|
// Layer classification needs the receipt text AND the worktree receipt, so it
|
|
9753
|
-
// runs here
|
|
9752
|
+
// runs here, after both exist, covering the claude and atris2 branches alike.
|
|
9754
9753
|
if (!(cachedStep && cachedStep.tick && cachedStep.tick.layer)) {
|
|
9755
9754
|
const tickReceiptText = result.atris2?.receipt_text || result.claude?.receipt_text || result.drill?.receipt_text || '';
|
|
9756
9755
|
const layerInfo = extractLayerFromReceiptText(tickReceiptText, tickWorktree?.new_since_baseline_sample);
|
|
@@ -9779,7 +9778,7 @@ async function executeMissionRunTicksPhase(context) {
|
|
|
9779
9778
|
const fullBudgetMode = budgetRemainingSeconds > 0;
|
|
9780
9779
|
// Worker may call `mission complete` mid-tick. Re-read disk so we do not
|
|
9781
9780
|
// demote terminal status back to ready (always-on + verifier-pass used to
|
|
9782
|
-
// reopen closed rooms
|
|
9781
|
+
// reopen closed rooms, mission 32 complete@19:23:33 → ready@19:23:48).
|
|
9783
9782
|
const latestOnDisk = loadMissionMap(cwd).get(mission.id) || mission;
|
|
9784
9783
|
const alreadyTerminal = TERMINAL_STATUSES.has(String(latestOnDisk.status || ''));
|
|
9785
9784
|
let newStatus = (verifierResult?.passed && mission.always_on) ? 'ready' :
|
|
@@ -9872,7 +9871,7 @@ async function executeMissionRunTicksPhase(context) {
|
|
|
9872
9871
|
ranTicks++;
|
|
9873
9872
|
backoffAttempt = 0;
|
|
9874
9873
|
} else if (result.status === 'errored' && result.reason !== 'claude-session-busy') {
|
|
9875
|
-
// A rotated session is already healed
|
|
9874
|
+
// A rotated session is already healed, the next tick starts on a
|
|
9876
9875
|
// fresh id, so backing off just burns wall clock. If rotation itself
|
|
9877
9876
|
// keeps failing, the repeated-error breaker below still stops the run.
|
|
9878
9877
|
backoffAttempt++;
|
|
@@ -9896,7 +9895,7 @@ async function executeMissionRunTicksPhase(context) {
|
|
|
9896
9895
|
if (consecutiveIdenticalSummaryTicks(ticks) >= 3) { pauseReason = 'stuck-repeating'; break; }
|
|
9897
9896
|
// A retired/inaccessible model is deterministic: the id is fixed for the run, so
|
|
9898
9897
|
// every remaining tick (and every future cron firing) fails identically. Backoff
|
|
9899
|
-
// only slows the bleeding. Stop on first detection and surface the dead id
|
|
9898
|
+
// only slows the bleeding. Stop on first detection and surface the dead id,
|
|
9900
9899
|
// CLI-245 named this failure; this stops the loop from grinding on it forever.
|
|
9901
9900
|
if (result.status === 'errored' && result.reason === 'model-unavailable') { pauseReason = 'model-unavailable'; break; }
|
|
9902
9901
|
// Any OTHER error that recurs identically (claude-timeout, atris2-error, claude-error)
|
|
@@ -10063,12 +10062,12 @@ function completeMissionRunPhase(context) {
|
|
|
10063
10062
|
}
|
|
10064
10063
|
if (context.handled) return;
|
|
10065
10064
|
|
|
10066
|
-
// BCK-1324: no-progress is a clean, honest stop
|
|
10065
|
+
// BCK-1324: no-progress is a clean, honest stop, the run did what it
|
|
10067
10066
|
// could and correctly recognized there was nothing left to do. It is NOT
|
|
10068
10067
|
// a failure/blocker: pausing it (resumable, retried by cron/self-drive)
|
|
10069
10068
|
// or dispatching handleMissionBlocker (files a fleet task, dispatches an
|
|
10070
10069
|
// engine) would recreate the exact busywork loop this guard exists to
|
|
10071
|
-
// stop. Give it its own branch, shaped like stopMission()'s stop path
|
|
10070
|
+
// stop. Give it its own branch, shaped like stopMission()'s stop path,
|
|
10072
10071
|
// status=stopped, a receipt, no escalation, no blocker.
|
|
10073
10072
|
if (pauseReason === 'no-progress') {
|
|
10074
10073
|
const stoppedAt = stampIso();
|
|
@@ -10086,7 +10085,7 @@ function completeMissionRunPhase(context) {
|
|
|
10086
10085
|
stop_reason: noProgressReason,
|
|
10087
10086
|
receipt_path: noProgressReceipt || mission.receipt_path || null,
|
|
10088
10087
|
worktree_baseline: baselineSummary || mission.worktree_baseline || null,
|
|
10089
|
-
next_action: 'mission stopped: no progress
|
|
10088
|
+
next_action: 'mission stopped: no progress, inspect the last few receipts, then start a fresh mission or resume with new context',
|
|
10090
10089
|
}, cwd, 'mission_run_stopped_no_progress', {
|
|
10091
10090
|
reason: noProgressReason,
|
|
10092
10091
|
idle_ticks: idleCount,
|
|
@@ -10115,7 +10114,7 @@ function completeMissionRunPhase(context) {
|
|
|
10115
10114
|
}).mission;
|
|
10116
10115
|
}
|
|
10117
10116
|
|
|
10118
|
-
// 'no-progress' is a clean stop the run diagnosed itself
|
|
10117
|
+
// 'no-progress' is a clean stop the run diagnosed itself, never route it
|
|
10119
10118
|
// through handleMissionBlocker (that would file a blocker task and
|
|
10120
10119
|
// dispatch an engine to "fix" a mission that correctly stopped itself).
|
|
10121
10120
|
const blockerReason = ['no-progress', 'budget-exhausted'].includes(pauseReason)
|
|
@@ -10235,7 +10234,7 @@ function tickMission(args) {
|
|
|
10235
10234
|
}
|
|
10236
10235
|
|
|
10237
10236
|
try {
|
|
10238
|
-
// Re-read inside the lock
|
|
10237
|
+
// Re-read inside the lock, the initial resolveMission ran before we held it.
|
|
10239
10238
|
mission = resolveMission(mission.id) || mission;
|
|
10240
10239
|
|
|
10241
10240
|
if (['complete', 'stopped'].includes(mission.status)) {
|
|
@@ -10250,7 +10249,7 @@ function tickMission(args) {
|
|
|
10250
10249
|
// budget window from now BEFORE the budget check below reads it. Without this,
|
|
10251
10250
|
// a spend-full-budget mission whose original window already elapsed, resumed
|
|
10252
10251
|
// via `mission tick` rather than `mission run`, would compute zero remaining
|
|
10253
|
-
// budget on this very tick and land immediately
|
|
10252
|
+
// budget on this very tick and land immediately, the footgun `mission run`
|
|
10254
10253
|
// already closes. Stamping resumed_at here makes both resume paths agree.
|
|
10255
10254
|
mission = saveMission({
|
|
10256
10255
|
...mission,
|
|
@@ -10495,7 +10494,7 @@ function receiptShowsPass(receipt) {
|
|
|
10495
10494
|
|| result?.tick?.verifier_passed === true;
|
|
10496
10495
|
}
|
|
10497
10496
|
|
|
10498
|
-
// Terminal gate: a verifier mission may only complete on real evidence
|
|
10497
|
+
// Terminal gate: a verifier mission may only complete on real evidence, a
|
|
10499
10498
|
// passing receipt belonging to this mission, or durable state showing the
|
|
10500
10499
|
// verifier passed. Mirrors the task plane's proof-only accept guard so the
|
|
10501
10500
|
// final transition consumes the receipts instead of trusting free text.
|
|
@@ -10667,7 +10666,7 @@ function stopMission(args) {
|
|
|
10667
10666
|
}
|
|
10668
10667
|
|
|
10669
10668
|
// A mission parked in paused/planning/ready and untouched for a week is
|
|
10670
|
-
// abandoned in practice
|
|
10669
|
+
// abandoned in practice, nobody resumes it, and each one is a line the
|
|
10671
10670
|
// operator re-reads forever. The daily autoland tick expires them to
|
|
10672
10671
|
// stopped with a revive hint; running missions and anything touched
|
|
10673
10672
|
// recently are never aged out, and a tick on an expired id revives it.
|
|
@@ -10709,7 +10708,7 @@ function expireStaleMissions(root = process.cwd(), { idleDays = MISSION_IDLE_EXP
|
|
|
10709
10708
|
for (const mission of listMissions(root)) {
|
|
10710
10709
|
const status = String(mission.status || '').toLowerCase();
|
|
10711
10710
|
if (!statuses.has(status)) continue;
|
|
10712
|
-
// updated_at and paused_at are machine-polluted
|
|
10711
|
+
// updated_at and paused_at are machine-polluted, status renders and
|
|
10713
10712
|
// goal controllers re-save parked missions daily, so a mission nobody
|
|
10714
10713
|
// has run since May reads as "touched today". Real activity is the
|
|
10715
10714
|
// last tick (or creation, for missions that never ran).
|
|
@@ -10746,7 +10745,7 @@ function expireStaleMissions(root = process.cwd(), { idleDays = MISSION_IDLE_EXP
|
|
|
10746
10745
|
}
|
|
10747
10746
|
|
|
10748
10747
|
// Zombie-mission reap: a mission left paused past a short leash (48h default)
|
|
10749
|
-
// is dead in practice long before the 7-day general idle expiry ever fires
|
|
10748
|
+
// is dead in practice long before the 7-day general idle expiry ever fires,
|
|
10750
10749
|
// nobody is coming back to it inside a session, and it just sits on
|
|
10751
10750
|
// `mission list` as noise. Narrower than expireStaleMissions on purpose: only
|
|
10752
10751
|
// `paused`, never planning/ready, so the weekly cadence for those is unchanged.
|
|
@@ -11199,7 +11198,7 @@ function classifyPathsByLayer(paths) {
|
|
|
11199
11198
|
return winnerLayer ? { layer: winnerLayer, source: 'fallback' } : { layer: null, source: 'unknown' };
|
|
11200
11199
|
}
|
|
11201
11200
|
|
|
11202
|
-
// `atris mission layers
|
|
11201
|
+
// `atris mission layers`, per-layer growth curve across tick receipts. The member
|
|
11203
11202
|
// proof standard says: if every tick is one layer and none touch the others, the
|
|
11204
11203
|
// loop is doing work but not getting smarter. This makes that check one command.
|
|
11205
11204
|
function layersMission(args) {
|
|
@@ -11260,7 +11259,7 @@ function layersMission(args) {
|
|
|
11260
11259
|
...LAYERS.map((l) => ` ${l.padEnd(12)} ${String(byLayer[l]).padStart(3)}${byLayer[l] ? ' ' + '█'.repeat(Math.min(byLayer[l], 40)) : ''}`),
|
|
11261
11260
|
...(untagged ? [` untagged ${String(untagged).padStart(3)} (pre-layer receipts or missing tag)`] : []),
|
|
11262
11261
|
` provenance: explicit ${bySource.explicit}, explicit-inline ${bySource['explicit-inline']}, fallback ${bySource.fallback}`,
|
|
11263
|
-
...(skewed ? [` rebalance: ${Math.round((byLayer[dominant] / tagged) * 100)}% of tagged ticks are "${dominant}"
|
|
11262
|
+
...(skewed ? [` rebalance: ${Math.round((byLayer[dominant] / tagged) * 100)}% of tagged ticks are "${dominant}", the proof standard wants the other layers moving too`] : []),
|
|
11264
11263
|
];
|
|
11265
11264
|
printJsonOrText({ ok: true, since: sinceRaw || null, total, tagged, untagged, by_layer: byLayer, by_source: bySource, dominant: tagged ? dominant : null, skewed }, lines, asJson);
|
|
11266
11265
|
}
|
|
@@ -11349,7 +11348,7 @@ function findMissionAcrossWorktrees(ref, root = process.cwd()) {
|
|
|
11349
11348
|
return null;
|
|
11350
11349
|
}
|
|
11351
11350
|
|
|
11352
|
-
// atris mission ping <id> "<message>"
|
|
11351
|
+
// atris mission ping <id> "<message>", leave a note the mission's next tick
|
|
11353
11352
|
// reads (and consumes) as operator direction. This is how you talk to an
|
|
11354
11353
|
// always-on member mid-run without stopping it.
|
|
11355
11354
|
function inspectMission(args) {
|
|
@@ -11428,7 +11427,7 @@ function pingMission(args, opts = {}) {
|
|
|
11428
11427
|
if (asJson) {
|
|
11429
11428
|
console.log(JSON.stringify({ ok: true, action: 'mission_ping', mission_id: saved.id, pending_pings: pending, ping }));
|
|
11430
11429
|
} else {
|
|
11431
|
-
console.log(`pinged ${saved.id}
|
|
11430
|
+
console.log(`pinged ${saved.id}, the next tick reads it (${pending} unread).`);
|
|
11432
11431
|
}
|
|
11433
11432
|
}
|
|
11434
11433
|
return saved;
|
|
@@ -11578,7 +11577,7 @@ function missionCommand(args) {
|
|
|
11578
11577
|
if (first && !first.startsWith('-')) {
|
|
11579
11578
|
// Shortcut forms: a mission id, or an objective (quoted or spread
|
|
11580
11579
|
// across argv: `atris mission fix the issue`). Two shapes are
|
|
11581
|
-
// mistyped verbs, not objectives
|
|
11580
|
+
// mistyped verbs, not objectives, a later arg naming a mission id
|
|
11582
11581
|
// (`mission say <id> ...`), and a single bare word with no objective
|
|
11583
11582
|
// after it. Creating a mission from those silently is worse than erroring.
|
|
11584
11583
|
const positionals = args.filter((value) => !String(value).startsWith('-'));
|