agent-orchestrator-kit 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/README.md +13 -5
- package/bin/agent-orchestrator.js +200 -59
- package/bin/session-client.js +1 -1
- package/bin/spend-collect.js +197 -2
- package/package.json +1 -1
- package/templates/.agents/rules/session-handoff.mdc +2 -2
- package/templates/.agents/skills/agent-orchestration/SKILL.md +3 -3
- package/templates/.agents/subagents/session-handoff.md +2 -2
- package/templates/.agents/subagents/spec-archiver.md +1 -1
- package/templates/scripts/cursor-spend-collect.cjs +416 -36
- package/templates/scripts/cursor-spend-hook.cjs +8 -2
package/bin/spend-collect.js
CHANGED
|
@@ -20,6 +20,53 @@ function addNullable(a, b) {
|
|
|
20
20
|
return (a ?? 0) + (b ?? 0);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
export function cursorSpendFingerprint(row) {
|
|
24
|
+
if (!row || typeof row !== 'object') return null;
|
|
25
|
+
const input = numOrNull(row.inputTokens);
|
|
26
|
+
const output = numOrNull(row.outputTokens);
|
|
27
|
+
if (input == null && output == null) return null;
|
|
28
|
+
const model = String(row.model || row.modelId || '');
|
|
29
|
+
const cache = numOrNull(row.cacheReadTokens) ?? 0;
|
|
30
|
+
return `${model}|${input ?? 0}|${output ?? 0}|${cache}`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function preferCursorSource(previous, next) {
|
|
34
|
+
if (!previous) return next;
|
|
35
|
+
if (!next) return previous;
|
|
36
|
+
if (next.event === 'stop' && previous.event !== 'stop') return next;
|
|
37
|
+
if (previous.event === 'stop' && next.event !== 'stop') return previous;
|
|
38
|
+
const prevAt = Date.parse(previous.at);
|
|
39
|
+
const nextAt = Date.parse(next.at);
|
|
40
|
+
if (Number.isFinite(nextAt) && Number.isFinite(prevAt) && nextAt !== prevAt) {
|
|
41
|
+
return nextAt > prevAt ? next : previous;
|
|
42
|
+
}
|
|
43
|
+
return (next.totalTokens ?? 0) >= (previous.totalTokens ?? 0) ? next : previous;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function stripCursorCollectMeta(record) {
|
|
47
|
+
if (!record || typeof record !== 'object') return record;
|
|
48
|
+
const { event, ...rest } = record;
|
|
49
|
+
return rest;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function dedupeCursorSources(sources) {
|
|
53
|
+
const best = new Map();
|
|
54
|
+
const rest = [];
|
|
55
|
+
for (const src of sources || []) {
|
|
56
|
+
if (!src || src.platform !== 'cursor') {
|
|
57
|
+
rest.push(src);
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
const fp = cursorSpendFingerprint(src);
|
|
61
|
+
if (!fp) {
|
|
62
|
+
rest.push(src);
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
best.set(fp, preferCursorSource(best.get(fp), src));
|
|
66
|
+
}
|
|
67
|
+
return [...rest, ...[...best.values()].map(stripCursorCollectMeta)];
|
|
68
|
+
}
|
|
69
|
+
|
|
23
70
|
function emptyPlatform(source = 'none') {
|
|
24
71
|
return {
|
|
25
72
|
inputTokens: null,
|
|
@@ -501,7 +548,130 @@ function collectAmpCli(ctx) {
|
|
|
501
548
|
|
|
502
549
|
export const CURSOR_USAGE_FILE_REL = join('.agents', 'spend', 'cursor-usage.jsonl');
|
|
503
550
|
|
|
504
|
-
function
|
|
551
|
+
function loadCursorUsageById(cwd) {
|
|
552
|
+
const filePath = join(cwd, CURSOR_USAGE_FILE_REL);
|
|
553
|
+
const bestById = new Map();
|
|
554
|
+
if (!existsSync(filePath)) return bestById;
|
|
555
|
+
let text;
|
|
556
|
+
try {
|
|
557
|
+
text = readFileSync(filePath, 'utf-8');
|
|
558
|
+
} catch {
|
|
559
|
+
return bestById;
|
|
560
|
+
}
|
|
561
|
+
for (const line of text.split('\n')) {
|
|
562
|
+
if (!line.trim()) continue;
|
|
563
|
+
let row;
|
|
564
|
+
try {
|
|
565
|
+
row = JSON.parse(line);
|
|
566
|
+
} catch {
|
|
567
|
+
continue;
|
|
568
|
+
}
|
|
569
|
+
if (!row || typeof row !== 'object') continue;
|
|
570
|
+
const id = row.id == null || row.id === '' ? null : String(row.id);
|
|
571
|
+
if (!id) continue;
|
|
572
|
+
const inputTokens = numOrNull(row.inputTokens);
|
|
573
|
+
const outputTokens = numOrNull(row.outputTokens);
|
|
574
|
+
if (inputTokens == null && outputTokens == null) continue;
|
|
575
|
+
const totalTokens = (inputTokens ?? 0) + (outputTokens ?? 0);
|
|
576
|
+
const previous = bestById.get(id);
|
|
577
|
+
const previousTotal = previous
|
|
578
|
+
? (numOrNull(previous.inputTokens) ?? 0) + (numOrNull(previous.outputTokens) ?? 0)
|
|
579
|
+
: -1;
|
|
580
|
+
if (!previous || totalTokens >= previousTotal) bestById.set(id, row);
|
|
581
|
+
}
|
|
582
|
+
return bestById;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
export function attachCursorEstimates(sources, cwd) {
|
|
586
|
+
const byId = loadCursorUsageById(cwd);
|
|
587
|
+
let changed = false;
|
|
588
|
+
for (const src of sources || []) {
|
|
589
|
+
if (!src || src.platform !== 'cursor') continue;
|
|
590
|
+
const row = src.id ? byId.get(String(src.id)) : null;
|
|
591
|
+
if (row) {
|
|
592
|
+
const cache = numOrNull(row.cacheReadTokens);
|
|
593
|
+
if (src.cacheReadTokens == null && cache != null) {
|
|
594
|
+
src.cacheReadTokens = cache;
|
|
595
|
+
changed = true;
|
|
596
|
+
}
|
|
597
|
+
if (!src.model && (row.model || row.modelId)) {
|
|
598
|
+
src.model = row.model || row.modelId;
|
|
599
|
+
changed = true;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
const described = describeCursorCostEstimate({
|
|
603
|
+
model: src.model,
|
|
604
|
+
inputTokens: src.inputTokens,
|
|
605
|
+
outputTokens: src.outputTokens,
|
|
606
|
+
cacheReadTokens: src.cacheReadTokens,
|
|
607
|
+
totalTokens: src.totalTokens,
|
|
608
|
+
});
|
|
609
|
+
if (!described) continue;
|
|
610
|
+
if (src.costUsdEstimated !== described.usd) {
|
|
611
|
+
src.costUsdEstimated = described.usd;
|
|
612
|
+
changed = true;
|
|
613
|
+
}
|
|
614
|
+
if (src.costSource !== described.costSource) {
|
|
615
|
+
src.costSource = described.costSource;
|
|
616
|
+
changed = true;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
return changed;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
export function enrichMetricsCursorEstimates(metrics, cwd) {
|
|
623
|
+
let changed = false;
|
|
624
|
+
for (const session of metrics.sessions || []) {
|
|
625
|
+
const deduped = dedupeCursorSources(session.sources || []);
|
|
626
|
+
if (deduped.length !== (session.sources || []).length) {
|
|
627
|
+
session.sources = deduped;
|
|
628
|
+
changed = true;
|
|
629
|
+
} else {
|
|
630
|
+
session.sources = deduped;
|
|
631
|
+
}
|
|
632
|
+
if (attachCursorEstimates(session.sources || [], cwd)) changed = true;
|
|
633
|
+
let estimated = null;
|
|
634
|
+
for (const src of session.sources || []) {
|
|
635
|
+
estimated = addNullable(estimated, numOrNull(src.costUsdEstimated));
|
|
636
|
+
}
|
|
637
|
+
if (estimated != null && session.costUsdEstimated !== estimated) {
|
|
638
|
+
session.costUsdEstimated = estimated;
|
|
639
|
+
changed = true;
|
|
640
|
+
}
|
|
641
|
+
if (!session.spendSource || session.spendSource === 'adapter' || session.spendSource === 'unreported') {
|
|
642
|
+
let inputTokens = null;
|
|
643
|
+
let outputTokens = null;
|
|
644
|
+
let totalTokens = null;
|
|
645
|
+
for (const src of session.sources || []) {
|
|
646
|
+
inputTokens = addNullable(inputTokens, numOrNull(src.inputTokens));
|
|
647
|
+
outputTokens = addNullable(outputTokens, numOrNull(src.outputTokens));
|
|
648
|
+
totalTokens = addNullable(totalTokens, numOrNull(src.totalTokens));
|
|
649
|
+
}
|
|
650
|
+
if (session.inputTokens !== inputTokens) {
|
|
651
|
+
session.inputTokens = inputTokens;
|
|
652
|
+
changed = true;
|
|
653
|
+
}
|
|
654
|
+
if (session.outputTokens !== outputTokens) {
|
|
655
|
+
session.outputTokens = outputTokens;
|
|
656
|
+
changed = true;
|
|
657
|
+
}
|
|
658
|
+
if (session.totalTokens !== totalTokens) {
|
|
659
|
+
session.totalTokens = totalTokens;
|
|
660
|
+
changed = true;
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
if (
|
|
664
|
+
session.spendSource === 'unreported'
|
|
665
|
+
&& (session.inputTokens != null || session.totalTokens != null || (session.sources || []).length)
|
|
666
|
+
) {
|
|
667
|
+
session.spendSource = 'adapter';
|
|
668
|
+
changed = true;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
return changed;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
function collectCursor({ cwd, windowStart, windowEnd, existing, existingSources, notes, env, cursorConversationId }) {
|
|
505
675
|
const filePath = join(cwd, CURSOR_USAGE_FILE_REL);
|
|
506
676
|
if (!existsSync(filePath)) {
|
|
507
677
|
notes.push('cursor: usage file missing (spend hook not installed or no turns recorded yet)');
|
|
@@ -514,8 +684,16 @@ function collectCursor({ cwd, windowStart, windowEnd, existing, notes }) {
|
|
|
514
684
|
notes.push('cursor: cannot read usage file');
|
|
515
685
|
return [];
|
|
516
686
|
}
|
|
687
|
+
const filterId = String((env && env.CURSOR_CONVERSATION_ID) || cursorConversationId || '').trim();
|
|
688
|
+
const existingFingerprints = new Set();
|
|
689
|
+
for (const src of existingSources || []) {
|
|
690
|
+
const fp = cursorSpendFingerprint(src);
|
|
691
|
+
if (fp) existingFingerprints.add(fp);
|
|
692
|
+
}
|
|
517
693
|
// stop / afterAgentResponse / loop follow-ups may write the same generation_id
|
|
518
694
|
// several times with cumulative turn totals; keep the largest record per id.
|
|
695
|
+
// Cursor sometimes omits generation_id, so stop + afterAgentResponse get two
|
|
696
|
+
// ids for one turn — collapse those by token fingerprint.
|
|
519
697
|
const bestById = new Map();
|
|
520
698
|
for (const line of text.split('\n')) {
|
|
521
699
|
if (!line.trim()) continue;
|
|
@@ -529,10 +707,18 @@ function collectCursor({ cwd, windowStart, windowEnd, existing, notes }) {
|
|
|
529
707
|
const id = row.id == null || row.id === '' ? null : String(row.id);
|
|
530
708
|
if (!id) continue;
|
|
531
709
|
if (existing.has(id)) continue;
|
|
710
|
+
if (filterId) {
|
|
711
|
+
const rowConversationId = row.conversationId == null || row.conversationId === ''
|
|
712
|
+
? ''
|
|
713
|
+
: String(row.conversationId).trim();
|
|
714
|
+
if (rowConversationId !== filterId) continue;
|
|
715
|
+
}
|
|
532
716
|
if (!inWindow(row.at, windowStart, windowEnd)) continue;
|
|
533
717
|
const inputTokens = numOrNull(row.inputTokens);
|
|
534
718
|
const outputTokens = numOrNull(row.outputTokens);
|
|
535
719
|
if (inputTokens == null && outputTokens == null) continue;
|
|
720
|
+
const fp = cursorSpendFingerprint(row);
|
|
721
|
+
if (fp && existingFingerprints.has(fp)) continue;
|
|
536
722
|
const model = row.model || row.modelId;
|
|
537
723
|
const cacheReadTokens = numOrNull(row.cacheReadTokens);
|
|
538
724
|
const described = describeCursorCostEstimate({
|
|
@@ -554,12 +740,18 @@ function collectCursor({ cwd, windowStart, windowEnd, existing, notes }) {
|
|
|
554
740
|
costUsdEstimated: described?.usd ?? null,
|
|
555
741
|
costSource: described?.costSource ?? null,
|
|
556
742
|
});
|
|
743
|
+
record.event = row.event || null;
|
|
557
744
|
const previous = bestById.get(id);
|
|
558
745
|
if (!previous || (record.totalTokens ?? 0) >= (previous.totalTokens ?? 0)) {
|
|
559
746
|
bestById.set(id, record);
|
|
560
747
|
}
|
|
561
748
|
}
|
|
562
|
-
|
|
749
|
+
const bestByFingerprint = new Map();
|
|
750
|
+
for (const record of bestById.values()) {
|
|
751
|
+
const fp = cursorSpendFingerprint(record) || record.id;
|
|
752
|
+
bestByFingerprint.set(fp, preferCursorSource(bestByFingerprint.get(fp), record));
|
|
753
|
+
}
|
|
754
|
+
return [...bestByFingerprint.values()].map(stripCursorCollectMeta);
|
|
563
755
|
}
|
|
564
756
|
|
|
565
757
|
function aggregate(sources) {
|
|
@@ -622,15 +814,18 @@ export function collectSpend(options = {}) {
|
|
|
622
814
|
? options.platforms.filter((name) => PLATFORMS.includes(name))
|
|
623
815
|
: PLATFORMS;
|
|
624
816
|
const run = (name) => !wanted.length || wanted.includes(name);
|
|
817
|
+
const existingSources = Array.isArray(options.existingSources) ? options.existingSources : [];
|
|
625
818
|
const ctx = {
|
|
626
819
|
cwd,
|
|
627
820
|
windowStart,
|
|
628
821
|
windowEnd,
|
|
629
822
|
existing,
|
|
823
|
+
existingSources,
|
|
630
824
|
env,
|
|
631
825
|
homedir,
|
|
632
826
|
notes,
|
|
633
827
|
ampThreadId: options.ampThreadId,
|
|
828
|
+
cursorConversationId: options.cursorConversationId,
|
|
634
829
|
exportAmpThread: options.exportAmpThread,
|
|
635
830
|
listAmpThreads: options.listAmpThreads,
|
|
636
831
|
usageAmpThread: options.usageAmpThread,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-orchestrator-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Universal AI agent orchestration kit for Cursor, Claude Code, and Amp Code — spec-driven OpenSpec pipeline, conductor subagents, durable session handoff, factory gates and MCP setup, cloud-agent handoff, and optional local Figma PAT setup",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-agent",
|
|
@@ -20,8 +20,8 @@ Agents (local or cloud) write session artifacts only to git-tracked paths — ne
|
|
|
20
20
|
|
|
21
21
|
## Session Exit (order)
|
|
22
22
|
1. The parent writes `openspec/changes/<name>/handoff.md` itself: Closed role, Change, Done, Decisions, Blocked, Next command, Next role, Attach, Subagents to spawn, Constraints, Runtime, Metrics.
|
|
23
|
-
2. Fill `## Metrics` before running persist. Required keys: `platform`, `model`, `input_tokens`, `output_tokens`, `cost_usd`, `amp_credits`, `spend_source`. Use `unknown`
|
|
24
|
-
3. `npx agent-orchestrator-kit handoff <name> --model <llm-product-id>` — require exit 0 (appends non-empty Decisions into append-only `openspec/changes/<name>/decisions.md`, upserts absolute-path Memory JSON, records the session into `openspec/changes/<name>/metrics.json`, prints the expanded prompt on stdout). `--model` is the LLM product id of this chat (`claude-opus-5`, `claude-fable-5`, `gpt-5.6-sol`, `cursor-grok-4.6`, `accounts/fireworks/models/glm-5p2`) — NEVER pass a Closed role, a subagent name, or an Amp **mode** (`low`, `medium`, `high`, `ultra`) as `--model`. The parent SHOULD still pass `--model`. The parent MUST NOT guess tokens. Persist collects spend for the client locked at restore (Amp: `amp threads export` + local threads; Cursor: hook file; Claude: JSONL). `--input-tokens` / `--output-tokens` / `--total-tokens` / `--cost-usd` override session-level totals only and do not wipe platform maps; they do not rewrite `## Metrics`. Optional `--platform cursor|claude|amp` or `AOK_PLATFORM`. Optional `--collect` runs all three adapters, not only the locked client. The same `npx agent-orchestrator-kit handoff <name>` works in Cursor, Claude Code, and Amp. `decisions.md` is the git canon of change decisions; Memory `Decision:*` is a file→Memory mirror only. Cloud sessions pass `--runtime cloud` (or set `AOK_RUNTIME=cloud` / `AOK_AGENT_ID` in the cloud-agent environment).
|
|
23
|
+
2. Fill `## Metrics` before running persist. Required keys: `platform`, `model`, `input_tokens`, `output_tokens`, `cost_usd`, `amp_credits`, `spend_source`. Use `unknown` for unknown numbers — never invent `0`. Do not set `spend_source: self-report` when tokens are `unknown`. `--model` / `model` is the LLM product id (example `cursor-grok-4.6-xhigh-fast`); family `cursor-grok-4.6` is only a fallback; the CLI takes the product id from hook sources when they exist; Closed role MAY have a sentence after `—`; metrics stores the canonical token. `metrics.json` records what the CLI resolved.
|
|
24
|
+
3. `npx agent-orchestrator-kit handoff <name> --model <llm-product-id>` — require exit 0 (appends non-empty Decisions into append-only `openspec/changes/<name>/decisions.md`, upserts absolute-path Memory JSON, records the session into `openspec/changes/<name>/metrics.json`, prints the expanded prompt on stdout). `--model` is the LLM product id of this chat (`claude-opus-5`, `claude-fable-5`, `gpt-5.6-sol`, `cursor-grok-4.6-xhigh-fast`, `accounts/fireworks/models/glm-5p2`) — NEVER pass a Closed role, a subagent name, or an Amp **mode** (`low`, `medium`, `high`, `ultra`) as `--model`. The parent SHOULD still pass `--model`. The parent MUST NOT guess tokens. Persist collects spend for the client locked at restore (Amp: `amp threads export` + local threads; Cursor: hook file; Claude: JSONL). `--input-tokens` / `--output-tokens` / `--total-tokens` / `--cost-usd` override session-level totals only and do not wipe platform maps; they do not rewrite `## Metrics`. Optional `--platform cursor|claude|amp` or `AOK_PLATFORM`. Optional `--collect` runs all three adapters, not only the locked client. The same `npx agent-orchestrator-kit handoff <name>` works in Cursor, Claude Code, and Amp. `decisions.md` is the git canon of change decisions; Memory `Decision:*` is a file→Memory mirror only. Cloud sessions pass `--runtime cloud` (or set `AOK_RUNTIME=cloud` / `AOK_AGENT_ID` in the cloud-agent environment).
|
|
25
25
|
4. Spawn `session-handoff` in persist mode ONLY if step 3 failed (Amp: isolated `subagent-session-handoff`). Fallback, never routine.
|
|
26
26
|
5. Memory MCP is an optional mirror: if tools are available, update `Change:<name>`, `Handoff:<name>`, `Decision:*` in one call; unavailability never blocks closing.
|
|
27
27
|
6. Paste CLI stdout as one fenced block. First line `/opsx:…`. Body uses `project.agent_language`. Self-contained (Done/Decisions/Blocked/spawn/HARD STOP). No banner.
|
|
@@ -146,8 +146,8 @@ Archive is one deterministic CLI call — `npx agent-orchestrator-kit archive <n
|
|
|
146
146
|
|
|
147
147
|
**End of each session (HARD STOP — you are NOT done):**
|
|
148
148
|
1. Write `openspec/changes/<name>/handoff.md` in the parent using the template below, including `## Metrics`.
|
|
149
|
-
2. Fill `## Metrics` (`platform`, `model`, `input_tokens`, `output_tokens`, `cost_usd`, `amp_credits`, `spend_source`) before persist. Use `unknown`
|
|
150
|
-
3. Run `npx agent-orchestrator-kit handoff <name> --model <llm-product-id>` and require exit 0. `--model` is the LLM product id of this chat (`claude-opus-5`, `claude-fable-5`, `gpt-5.6-sol`, `cursor-grok-4.6`) — NEVER pass a Closed role (`Architect`, `Implementer`, `Explorer`) or a subagent name (`spec-architect`, `session-handoff`) as `--model`. The parent SHOULD still pass `--model`. The parent MUST NOT guess tokens. `--input-tokens` / `--output-tokens` / `--total-tokens` / `--cost-usd` override session-level totals only and do not wipe platform maps or rewrite `## Metrics`. Optional `--platform cursor|claude|amp` or `AOK_PLATFORM`. Optional `--collect` also runs local spend adapters. The same command works in Cursor, Claude Code, and Amp and MUST NOT require Cursor SDK, a Claude `/cost` parser, or an Amp billing API as a required step. The CLI appends non-empty Decisions into append-only `openspec/changes/<name>/decisions.md` (the git canon), upserts Memory JSON with an absolute path (`Decision:*` is a file→Memory mirror only), and prints the expanded self-contained prompt on stdout. Spawn `session-handoff` in persist mode ONLY if this CLI step failed.
|
|
149
|
+
2. Fill `## Metrics` (`platform`, `model`, `input_tokens`, `output_tokens`, `cost_usd`, `amp_credits`, `spend_source`) before persist. Use `unknown` for unknown numbers — never invent `0`. Do not set `spend_source: self-report` when tokens are `unknown`. `--model` / `model` is the LLM product id (example `cursor-grok-4.6-xhigh-fast`); family `cursor-grok-4.6` is only a fallback; the CLI takes the product id from hook sources when they exist; Closed role MAY have a sentence after `—`; metrics stores the canonical token.
|
|
150
|
+
3. Run `npx agent-orchestrator-kit handoff <name> --model <llm-product-id>` and require exit 0. `--model` is the LLM product id of this chat (`claude-opus-5`, `claude-fable-5`, `gpt-5.6-sol`, `cursor-grok-4.6-xhigh-fast`) — NEVER pass a Closed role (`Architect`, `Implementer`, `Explorer`) or a subagent name (`spec-architect`, `session-handoff`) as `--model`. The parent SHOULD still pass `--model`. The parent MUST NOT guess tokens. `--input-tokens` / `--output-tokens` / `--total-tokens` / `--cost-usd` override session-level totals only and do not wipe platform maps or rewrite `## Metrics`. Optional `--platform cursor|claude|amp` or `AOK_PLATFORM`. Optional `--collect` also runs local spend adapters. The same command works in Cursor, Claude Code, and Amp and MUST NOT require Cursor SDK, a Claude `/cost` parser, or an Amp billing API as a required step. The CLI appends non-empty Decisions into append-only `openspec/changes/<name>/decisions.md` (the git canon), upserts Memory JSON with an absolute path (`Decision:*` is a file→Memory mirror only), and prints the expanded self-contained prompt on stdout. Spawn `session-handoff` in persist mode ONLY if this CLI step failed.
|
|
151
151
|
4. If Memory MCP tools are available, mirror `Change:<name>`, `Handoff:<name>`, and new `Decision:<topic>` entities in one call — optional; its absence never blocks closing.
|
|
152
152
|
5. Paste the CLI stdout as one fenced next-session prompt. First line is `/opsx:<next> <name>`; body uses `project.agent_language`; keep Done/Decisions/Blocked/spawn/HARD STOP complete. No banner. Do not emit a thin “read Memory” stub.
|
|
153
153
|
6. Do not start the next phase in this chat. If apply, include build/lint status in the persisted Done section.
|
|
@@ -228,7 +228,7 @@ The Prompt section is overwritten by `npx agent-orchestrator-kit handoff <name>`
|
|
|
228
228
|
|
|
229
229
|
Before specialist work, the parent MUST restore context in order: honor the pasted `/opsx:*` command; run `npx agent-orchestrator-kit handoff --restore` (the CLI briefing is canonical — no separate Memory MCP read step); if the CLI failed, read `openspec/changes/<name>/handoff.md`; spawn `session-handoff` in restore mode ONLY when both failed. Missing Memory MCP never blocks a session. With one active change, free-form “continue” uses `Handoff.next_command` instead of asking for the phase. Amp spawns any needed subagent as an isolated `subagent-*` skill.
|
|
230
230
|
|
|
231
|
-
Before declaring a session closed, the parent MUST, in order: (1) write `openspec/changes/<name>/handoff.md` itself including `## Metrics` (keys `platform`, `model`, `input_tokens`, `output_tokens`, `cost_usd`, `amp_credits`, `spend_source
|
|
231
|
+
Before declaring a session closed, the parent MUST, in order: (1) write `openspec/changes/<name>/handoff.md` itself including `## Metrics` (keys `platform`, `model`, `input_tokens`, `output_tokens`, `cost_usd`, `amp_credits`, `spend_source`). Use `unknown` for unknown numbers — never invent `0`. Do not set `spend_source: self-report` when tokens are `unknown`. `--model` / `model` is the LLM product id (example `cursor-grok-4.6-xhigh-fast`); family `cursor-grok-4.6` is only a fallback; the CLI takes the product id from hook sources when they exist; Closed role MAY have a sentence after `—`; metrics stores the canonical token. (2) run `npx agent-orchestrator-kit handoff <name> --model <llm-product-id>` (exit 0) — NEVER pass a Closed role or subagent name as `--model`; the parent SHOULD still pass `--model` and MUST NOT guess tokens; spend flags override session totals only and do not rewrite `## Metrics`; optional `--platform`; optional `--collect` for local adapters; the same CLI works in Cursor, Claude Code, and Amp and MUST NOT require Cursor SDK, Claude `/cost`, or Amp billing as a required step; this CLI appends `decisions.md` and mirrors `Decision:*` file→Memory; spawn `session-handoff` persist ONLY if this CLI step failed, (3) paste the CLI stdout prompt whose first line is `/opsx:<next> <name>`. Memory MCP mirroring is an optional single call. Never write Memory back into `decisions.md`. The prompt has no `NEXT_SESSION_PROMPT` label, uses `project.agent_language`, and MUST be self-contained (Done, Decisions, Blocked, attach, spawn, HARD STOP) so the next thread can run if Memory MCP is ignored. Never start the next phase in the current chat. Write session artifacts only to git-tracked paths (never `/tmp`, never gitignored caches). If runtime is cloud: after persist, commit → push → `npx agent-orchestrator-kit handoff <name> --cloud-check` with exit 0; closing without that is an incomplete handoff.
|
|
232
232
|
|
|
233
233
|
| Entity | Required fields |
|
|
234
234
|
|--------|-----------------|
|
|
@@ -22,8 +22,8 @@ Use when the parent's restore failed (CLI restore and handoff.md both unavailabl
|
|
|
22
22
|
Use when the parent's persist failed (`npx agent-orchestrator-kit handoff <name>` did not exit 0). A session is not closed until persist succeeds.
|
|
23
23
|
|
|
24
24
|
1. Write or update `openspec/changes/<name>/handoff.md` with every required section: Closed role, Change, Done, Decisions, Blocked, Next command, Next role, Attach, Subagents to spawn, Constraints, Runtime, Metrics.
|
|
25
|
-
2. Fill `## Metrics` (`platform`, `model`, `input_tokens`, `output_tokens`, `cost_usd`, `amp_credits`, `spend_source`) before persist. Use `unknown` when
|
|
26
|
-
3. Run `npx agent-orchestrator-kit handoff <name> --model <llm-product-id>` and require exit 0. `--model` is the LLM product id of this chat (`claude-opus-5`, `claude-fable-5`, `gpt-5.6-sol`, `cursor-grok-4.6`) — NEVER pass a Closed role (`Architect`, `Implementer`, `Explorer`) or a subagent name (`spec-architect`, `session-handoff`) as `--model`. The parent SHOULD still pass `--model`. The parent MUST NOT guess tokens. `--input-tokens` / `--output-tokens` / `--total-tokens` / `--cost-usd` override session-level totals only and do not wipe platform maps. Optional `--platform cursor|claude|amp` or `AOK_PLATFORM`. Optional `--collect` also runs local spend adapters. The same command works in Cursor, Claude Code, and Amp and MUST NOT require Cursor SDK, a Claude `/cost` parser, or an Amp billing API as a required step. This appends non-empty Decisions into append-only `openspec/changes/<name>/decisions.md` (git canon), upserts `.cursor/memory.json` using an absolute path (`Decision:*` mirrors that file, never the reverse), and prints the expanded next-session prompt on stdout. Cloud sessions pass `--runtime cloud` (or `AOK_RUNTIME` / `AOK_AGENT_ID`).
|
|
25
|
+
2. Fill `## Metrics` (`platform`, `model`, `input_tokens`, `output_tokens`, `cost_usd`, `amp_credits`, `spend_source`) before persist. Use `unknown` for unknown numbers — never invent `0`. Do not set `spend_source: self-report` when tokens are `unknown`. `--model` / `model` is the LLM product id (example `cursor-grok-4.6-xhigh-fast`); family `cursor-grok-4.6` is only a fallback; the CLI takes the product id from hook sources when they exist; Closed role MAY have a sentence after `—`; metrics stores the canonical token. The CLI does not overwrite `## Metrics` with resolved values; `metrics.json` records what landed.
|
|
26
|
+
3. Run `npx agent-orchestrator-kit handoff <name> --model <llm-product-id>` and require exit 0. `--model` is the LLM product id of this chat (`claude-opus-5`, `claude-fable-5`, `gpt-5.6-sol`, `cursor-grok-4.6-xhigh-fast`) — NEVER pass a Closed role (`Architect`, `Implementer`, `Explorer`) or a subagent name (`spec-architect`, `session-handoff`) as `--model`. The parent SHOULD still pass `--model`. The parent MUST NOT guess tokens. `--input-tokens` / `--output-tokens` / `--total-tokens` / `--cost-usd` override session-level totals only and do not wipe platform maps. Optional `--platform cursor|claude|amp` or `AOK_PLATFORM`. Optional `--collect` also runs local spend adapters. The same command works in Cursor, Claude Code, and Amp and MUST NOT require Cursor SDK, a Claude `/cost` parser, or an Amp billing API as a required step. This appends non-empty Decisions into append-only `openspec/changes/<name>/decisions.md` (git canon), upserts `.cursor/memory.json` using an absolute path (`Decision:*` mirrors that file, never the reverse), and prints the expanded next-session prompt on stdout. Cloud sessions pass `--runtime cloud` (or `AOK_RUNTIME` / `AOK_AGENT_ID`).
|
|
27
27
|
4. If Memory MCP tools are available, also create/update `Change:<name>`, `Handoff:<name>`, and each `Decision:<topic>` to match `decisions.md`. MCP failure is not a blocker after the CLI succeeds.
|
|
28
28
|
5. Put the CLI stdout prompt (first line `/opsx:…`) into **Next prompt** unchanged. Do not shorten it. Do not add a banner.
|
|
29
29
|
6. If runtime is cloud: after persist, commit and push `openspec/changes/<name>/`, then `npx agent-orchestrator-kit handoff <name> --cloud-check` (exit 0 required). Closing without this is an incomplete handoff. The CLI never runs `git commit` / `git push`.
|
|
@@ -9,7 +9,7 @@ Workflow:
|
|
|
9
9
|
|
|
10
10
|
1. Read `.agents/orchestrator.yaml`, the complete change, review verdict, task state, and verification/merge evidence supplied by the conductor.
|
|
11
11
|
2. Refuse to archive unless required review is approved, all tasks are complete, and the configured merge/CI gate is satisfied.
|
|
12
|
-
3. Fill `## Metrics` in the change `handoff.md` only when reporting Archiver-specific numbers
|
|
12
|
+
3. Fill `## Metrics` in the change `handoff.md` only when reporting Archiver-specific numbers. Use `unknown` for unknown numbers — never invent `0`. Do not set `spend_source: self-report` when tokens are `unknown`. `--model` / `model` is the LLM product id (example `cursor-grok-4.6-xhigh-fast`); family `cursor-grok-4.6` is only a fallback; the CLI takes the product id from hook sources when they exist; Closed role MAY have a sentence after `—`; metrics stores the canonical token. Do not copy the previous apply session. The CLI auto-collects the locked client into the Archiver session.
|
|
13
13
|
4. Run `npx agent-orchestrator-kit archive <name>` so delta requirements are merged into main specs, the change moves to the dated archive path, and stdout prints the change-wide metrics summary (by phase / by platform / by model).
|
|
14
14
|
5. Run strict validation after the move and report the resulting archive path and modified main specs.
|
|
15
15
|
|