amsd-pipeline-workflow 0.1.6 → 0.1.8
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 +42 -1
- package/driver/session-cost.js +26 -5
- package/generator/generate-agents.js +10 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ Three steps, in this order, into one install:
|
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
12
|
npx amsd-pipeline@2.0.80 --dest <dir> --ref v2.0.80 --no-docker # 1. the pipeline, as always
|
|
13
|
-
npx amsd-pipeline-shared@<version> install --into <dir> # 2. the shared gateway, reviewer, messages
|
|
13
|
+
npx amsd-pipeline-shared@<version> install --into <dir> # 2. the shared gateway, reviewer, messages, pre-launch
|
|
14
14
|
npx amsd-pipeline-workflow@<version> install --into <dir> [--codeline-cache <cache>] # 3. the workflows
|
|
15
15
|
```
|
|
16
16
|
|
|
@@ -20,7 +20,20 @@ into the same project, so the pipeline reuses them instead of minting and genera
|
|
|
20
20
|
launch whether each is current. Reinstalling replaces `claude-workflows/`. Without step 2 the workflows still run, but
|
|
21
21
|
messaging is off (said at generation) and the gateway's hub keeps 2.0.80's defect B56 (a failed call reads as success).
|
|
22
22
|
|
|
23
|
+
Then seed the codeline the ticket's work belongs to with the pipeline's own restore, so the launch starts from its
|
|
24
|
+
recorded agents and reviewed prompts (a fresh install's project prompts are not the ticket's):
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
EPAM_PROJECT_CONFIG_DIR=<dir>/orchestrations/projects/<project> \
|
|
28
|
+
bash -c '. <dir>/orchestrations/scripts/lib/prompt-variant.sh; codeline_minted_restore <codeline>'
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
A codeline cache built with `tools/cache-from-checkpoint.js` carries both of the builder's layers: the prompt set and, with
|
|
32
|
+
`--entries`, the source run's per-prompt entries (only where the entry is the cached prompt), plus `--marker`, the source
|
|
33
|
+
run's own completion marker. A template change then rebuilds only that template's prompt.
|
|
34
|
+
|
|
23
35
|
Needs: Node 20+, the `claude` CLI, `unshare` (util-linux) and `systemd-run --user` (the memory bound, `bin/bounded`).
|
|
36
|
+
ClickHouse is not used: Langfuse runs on postgres and redis only.
|
|
24
37
|
|
|
25
38
|
## What every agent call gets
|
|
26
39
|
|
|
@@ -44,6 +57,19 @@ sent as one generation through the pipeline's own emitter.
|
|
|
44
57
|
|
|
45
58
|
## Live runs: the run note and the abort
|
|
46
59
|
|
|
60
|
+
Before any leg, the launch runs the **shared pre-launch** (amsd-pipeline-shared `lib/prelaunch.js`, the same one every bash
|
|
61
|
+
launcher runs): the pipeline's pre-flight, Langfuse, seed, cache, cost, alone, memory, no-timeout and the 12-item checklist.
|
|
62
|
+
Any red refuses the launch before a model call (`<run>/prelaunch-leg-<n>/prelaunch.json`). A live launch states the seeded
|
|
63
|
+
codeline and, if it accepts some prompts being rebuilt, how many:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
node tools/rehearse.js --install <install> --out <run dir> --live yes --tag <n> --start live --stop-at pause-1 \
|
|
67
|
+
--seed-codeline <codeline> --expect-scope <codeline> [--max-rebuild <n>]
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
`--expect-scope` stops the leg the moment ingest scopes the story to any other set of codelines (`tools/scope-watch.js`). A
|
|
71
|
+
leg has no time limit. Right before launching, check that no other memory-heavy job is running on the machine.
|
|
72
|
+
|
|
47
73
|
`tools/rehearse.js --live yes` (and `--start live`, `--leg 2|3`, `--tag`) keeps one note per run in the operator's vault
|
|
48
74
|
(`pipeline/live/<run>.md`, `tools/live-tracker.js`), refreshed every 2 minutes while a leg runs: legs, cost by stage,
|
|
49
75
|
the Langfuse session id (proven by a launch trace read back before each leg), the lanes, the messages, and the six
|
|
@@ -54,3 +80,18 @@ At £0, `--plant writer,reviewer` makes those agents give no answer once per lan
|
|
|
54
80
|
## Version
|
|
55
81
|
|
|
56
82
|
`amsd-pipeline-workflow version`. `package.json` `amsdPipeline.tested` names the pipeline release it was tested against.
|
|
83
|
+
|
|
84
|
+
## The critical agent transitions as `claude plugin eval` cases
|
|
85
|
+
|
|
86
|
+
`evals/` holds the critical agent transitions of the pipeline as a [`claude plugin eval`](https://code.claude.com/docs/en/plugin-evals)
|
|
87
|
+
suite: discovery's repository descriptions, the classifier's scope, role assignment for every story/codeline pair, the
|
|
88
|
+
self-heal analyst's corrective, and the Writer's reviewer (approves run 9's real fix; rejects the same change with a planted
|
|
89
|
+
accessibility defect — prompts rendered by `team-lead-review.sh` itself, `tools/capture-review-prompt.sh`). Each case is the transition's real rendered prompt recorded from a run (`evals/source/`, exported
|
|
90
|
+
from Langfuse), handed verbatim to that seam's own agent definition, with graders on its answer. Build the plugin from a run's
|
|
91
|
+
generated agents, then run it (every run is a real model call):
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
node tools/build-eval-plugin.js --agents <run>/w1/gen/agents # writes evals/plugin/
|
|
95
|
+
claude plugin validate evals/plugin # free: structure only
|
|
96
|
+
claude plugin eval evals/plugin --ablation none # paid: 6 cases x 3 runs
|
|
97
|
+
```
|
package/driver/session-cost.js
CHANGED
|
@@ -96,13 +96,34 @@ const sum = (t) => TYPES.reduce((a, k) => a + (t[k] || 0), 0);
|
|
|
96
96
|
/** modelUsage's entry for a transcript's model: the same name, or one a prefix of the other (a dated id). */
|
|
97
97
|
const usageFor = (modelUsage, model) => modelUsage[model] || Object.entries(modelUsage).find(([k]) => model.startsWith(k) || k.startsWith(model))?.[1] || null;
|
|
98
98
|
|
|
99
|
-
/**
|
|
100
|
-
|
|
99
|
+
/**
|
|
100
|
+
* A call's cost from its own tokens and the pipeline's price table (config/model-pricing.json, or MODEL_PRICING_FILE), for
|
|
101
|
+
* a call its session reported no cost for — a leg killed before its session answered (LIVE-AMSD-2974-8/9, 2026-10-19:
|
|
102
|
+
* every call booked $0). null when the table does not price the model: unknown stays unknown, never a guessed figure.
|
|
103
|
+
* Cache writes and reads at the table's own rates, else the vendor's standard multiples of the input price (1.25x, 0.1x).
|
|
104
|
+
*/
|
|
105
|
+
function priceFromTable(prices, model, t) {
|
|
106
|
+
const p = prices[model] || Object.entries(prices).find(([k]) => model.startsWith(k) || k.startsWith(model))?.[1];
|
|
107
|
+
if (!p || p.inputPerMillion == null || p.outputPerMillion == null) return null;
|
|
108
|
+
const write = p.cacheWrite5mPerMillion != null ? p.cacheWrite5mPerMillion : p.inputPerMillion * 1.25;
|
|
109
|
+
const read = p.cacheReadPerMillion != null ? p.cacheReadPerMillion : p.inputPerMillion * 0.1;
|
|
110
|
+
return ((t.in || 0) * p.inputPerMillion + (t.cacheCreate || 0) * write + (t.cacheRead || 0) * read + (t.out || 0) * p.outputPerMillion) / 1e6;
|
|
111
|
+
}
|
|
112
|
+
function priceTable(env) {
|
|
113
|
+
const f = (env && env.MODEL_PRICING_FILE) || (env && env.SCRIPT_DIR ? path.join(env.SCRIPT_DIR, '..', 'config', 'model-pricing.json') : '');
|
|
114
|
+
try { return (JSON.parse(fs.readFileSync(f, 'utf8')) || {}).models || {}; } catch { return {}; }
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Each entry's share of its model's session cost, by its tokens; a model the session did not report, priced from the table. */
|
|
118
|
+
function allocate(entries, modelUsage, prices = {}) {
|
|
101
119
|
const totals = {};
|
|
102
120
|
for (const e of entries) { const key = usageFor(modelUsage, e.model) ? e.model : null; if (key) totals[key] = (totals[key] || 0) + sum(e.tokens); }
|
|
103
121
|
return entries.map((e) => {
|
|
104
122
|
const mu = usageFor(modelUsage, e.model);
|
|
105
|
-
if (!mu)
|
|
123
|
+
if (!mu) {
|
|
124
|
+
const est = priceFromTable(prices, e.model, e.tokens);
|
|
125
|
+
return est == null ? { ...e, costUsd: 0, costUnknown: true } : { ...e, costUsd: est, costUnknown: false, pricedFromTable: true };
|
|
126
|
+
}
|
|
106
127
|
const w = totals[e.model] ? sum(e.tokens) / totals[e.model] : 0;
|
|
107
128
|
return { ...e, costUsd: (mu.costUSD || 0) * w, costUnknown: false };
|
|
108
129
|
});
|
|
@@ -128,7 +149,7 @@ function recordSession(env, session, { stage, storyId, phase, projectsDir, log }
|
|
|
128
149
|
const rows = [];
|
|
129
150
|
try {
|
|
130
151
|
if (entries && entries.length) {
|
|
131
|
-
for (const e of allocate(entries, modelUsage)) {
|
|
152
|
+
for (const e of allocate(entries, modelUsage, priceTable(env))) {
|
|
132
153
|
rows.push(appendLedgerRecord({ ledgerFile: ledger, agent: e.agent, storyId, phase, model: e.model,
|
|
133
154
|
cost: { costUsd: e.costUsd, tokensIn: e.tokens.in, tokensOut: e.tokens.out, tokensCached: e.tokens.cacheRead, tokensCacheCreate: e.tokens.cacheCreate,
|
|
134
155
|
costIsEstimate: true, costUnknown: e.costUnknown } }));
|
|
@@ -156,4 +177,4 @@ function recordSession(env, session, { stage, storyId, phase, projectsDir, log }
|
|
|
156
177
|
}
|
|
157
178
|
}
|
|
158
179
|
|
|
159
|
-
module.exports = { recordSession, sessionEntries, allocate, transcriptTokens, callContent };
|
|
180
|
+
module.exports = { recordSession, sessionEntries, allocate, transcriptTokens, callContent, priceFromTable };
|
|
@@ -55,8 +55,14 @@ const STAGE_PHASES = [
|
|
|
55
55
|
{ title: 'Role assignment', step: 'agents', seams: ['role-assigner'] }, // :1080–1087
|
|
56
56
|
{ title: 'Project prompts', step: 'agents', seams: ['prompt-builder', 'prompt-review', 'prompt-claim-adjudicator'] }, // :1159–1406
|
|
57
57
|
];
|
|
58
|
-
//
|
|
59
|
-
|
|
58
|
+
// ASKED FROM INSIDE ANY SEAM'S CALL, never a phase of its own: the pipeline's self-heal — the analyst that diagnoses a
|
|
59
|
+
// failed attempt and the synthesiser that turns its episodes into KB constraints (content-retry.js, self-heal.js,
|
|
60
|
+
// agent-attempt-analyst.sh, kb-synthesizer.js). Read from the registry by what those seams produce, not listed by name:
|
|
61
|
+
// the list named only the analyst, so the synthesiser was "out of the pipeline's order" and self-heal could not finish
|
|
62
|
+
// inside the stage (LIVE-AMSD-2974-7, 2026-10-19).
|
|
63
|
+
const SELF_HEAL_PRODUCES = ['diagnosis', 'kb-constraint'];
|
|
64
|
+
const anyPhaseSeams = (registry, agentMap) => Object.keys(registry.profiles || {})
|
|
65
|
+
.filter((s) => SELF_HEAL_PRODUCES.includes((registry.profiles[s] || {}).produces) && agentMap[s]).sort();
|
|
60
66
|
const PAUSE_STEP = 'pause-1';
|
|
61
67
|
const HOOK = path.resolve(__dirname, '..', 'hooks', 'agents-step.js');
|
|
62
68
|
const q = (x) => `'${String(x).replace(/'/g, "'\\''")}'`;
|
|
@@ -219,7 +225,7 @@ function main(a) {
|
|
|
219
225
|
const features = JSON.parse(fs.readFileSync(path.join(__dirname, 'features.json'), 'utf8')).features;
|
|
220
226
|
const plan = {
|
|
221
227
|
source: `${a.project} run ${a.run} (Codeline, Agents; issues ${issuesFrom.file || `rebuilt from run ${issuesFrom.recordedRun}`})`,
|
|
222
|
-
steps: STEPS, pauseStep: PAUSE_STEP, agents: agentMap, runners, si, stagePhases: STAGE_PHASES, anyPhaseSeams:
|
|
228
|
+
steps: STEPS, pauseStep: PAUSE_STEP, agents: agentMap, runners, si, stagePhases: STAGE_PHASES, anyPhaseSeams: anyPhaseSeams(registry, agentMap),
|
|
223
229
|
// The 1,000-agent limit of one workflow run (decision 15), less the step runners.
|
|
224
230
|
maxCalls: 900,
|
|
225
231
|
features,
|
|
@@ -238,7 +244,7 @@ function main(a) {
|
|
|
238
244
|
return manifest;
|
|
239
245
|
}
|
|
240
246
|
|
|
241
|
-
module.exports = { main, STEPS, STAGE_PHASES,
|
|
247
|
+
module.exports = { main, STEPS, STAGE_PHASES, SELF_HEAL_PRODUCES, anyPhaseSeams };
|
|
242
248
|
if (require.main === module) {
|
|
243
249
|
try { const m = main(args(process.argv.slice(2))); process.stdout.write(`${JSON.stringify({ agents: m.agents.length })}\n`); } catch (e) { process.stderr.write(`${e.message}\n`); process.exit(1); }
|
|
244
250
|
}
|
package/package.json
CHANGED