@plainconceptsplatform/workflows 1.6.11 → 1.6.21
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/loops/actions/housekeeping/action.yml +15 -0
- package/loops/actions/merge-train/action.yml +7 -0
- package/loops/actions/merge-train/merge-train.sh +29 -1
- package/loops/actions/report-workflow-errors/action.yml +92 -23
- package/loops/actions/report-workflow-errors/report-policy.cjs +110 -0
- package/loops/actions/verify-route-matrix/verify-route-matrix.sh +198 -0
- package/loops/docs/diagrams.md +8 -4
- package/loops/templates/agentics/agentics-error-report.yml +2 -0
- package/loops/templates/opencode/opencode.ci.json +118 -1
- package/loops/workflows/agent-apply-review.md +12 -1
- package/loops/workflows/agent-audit.md +6 -1
- package/loops/workflows/agent-implement.md +109 -13
- package/loops/workflows/agent-merge-gate.md +6 -1
- package/loops/workflows/agent-refine.md +10 -1
- package/loops/workflows/agent-release.md +6 -1
- package/loops/workflows/agent-triage.md +10 -1
- package/loops/workflows/shared/opencode-ci.md +7 -1
- package/loops/workflows/work-router.yml +22 -2
- package/package.json +1 -1
|
@@ -1788,6 +1788,64 @@ fi
|
|
|
1788
1788
|
# leaving a verdict or an attempt is a loop: the train cannot tell a gate that declined from a gate
|
|
1789
1789
|
# that has not run. Refusals are for what the train never hands over (a fork, a stranger, a pull
|
|
1790
1790
|
# request that is not open), and the train filters those itself.
|
|
1791
|
+
# The implement run is mostly model turns and repeated verification: Pliny-Bot #694 reached its
|
|
1792
|
+
# full verification at minute forty-one after six scoped runs, and #751 spent fifteen of twenty-two
|
|
1793
|
+
# minutes in twelve tiny worker tasks. These sentences are what keep a run near twenty minutes.
|
|
1794
|
+
if worker_installed implement; then
|
|
1795
|
+
if grep -qF 'Run them once, on the final tree.' "$IMPLEMENT_WORKER_MD" && grep -qF 'on both paths, before the commands above.' "$IMPLEMENT_WORKER_MD" && grep -qF 'Ask it for few, coarse tasks' "$IMPLEMENT_WORKER_MD" && grep -qF 'WORK IN FEW TURNS' "$IMPLEMENT_WORKER_MD"; then
|
|
1796
|
+
PASS=$((PASS + 1))
|
|
1797
|
+
else
|
|
1798
|
+
FAIL=$((FAIL + 1))
|
|
1799
|
+
echo "FAIL: the implement prompt must verify once on the final tree, ask for coarse tasks and work in few turns" >&2
|
|
1800
|
+
fi
|
|
1801
|
+
fi
|
|
1802
|
+
# gh-aw leaves OPENCODE_MODEL empty, so every lead ran on opencode.ci.json's one model. Each worker
|
|
1803
|
+
# names its own in AGENT_LEAD_MODEL, the merge step writes it onto ci-workflow-agent, and the model
|
|
1804
|
+
# it names, like every tier model in harness.json, has to be declared in opencode.ci.json or the
|
|
1805
|
+
# run dies at its first turn on "model not found".
|
|
1806
|
+
LEAD_OK=1
|
|
1807
|
+
# A consumer has it at its root; this package keeps the template beside the workflows.
|
|
1808
|
+
CI_JSON="${HERE}/../../../opencode.ci.json"
|
|
1809
|
+
[ -f "$CI_JSON" ] || CI_JSON="${HERE}/../../templates/opencode/opencode.ci.json"
|
|
1810
|
+
for route in refine implement triage apply-review merge-gate audit release; do
|
|
1811
|
+
worker_md="${WORKFLOWS_DIR}/agent-${route}.md"
|
|
1812
|
+
[ -f "$worker_md" ] || continue
|
|
1813
|
+
lead=$(sed -n 's/^ AGENT_LEAD_MODEL: "\(.*\)"$/\1/p' "$worker_md")
|
|
1814
|
+
if [ -z "$lead" ]; then
|
|
1815
|
+
LEAD_OK=0; echo "FAIL: agent-${route} names no AGENT_LEAD_MODEL, so its session runs on the cheap default" >&2; continue
|
|
1816
|
+
fi
|
|
1817
|
+
if [ -f "$CI_JSON" ] && ! jq -e --arg p "${lead%%/*}" --arg m "${lead#*/}" '.providers[$p].models[$m]' "$CI_JSON" > /dev/null; then
|
|
1818
|
+
LEAD_OK=0; echo "FAIL: agent-${route} leads on ${lead}, which opencode.ci.json does not declare" >&2
|
|
1819
|
+
fi
|
|
1820
|
+
done
|
|
1821
|
+
if [ -f "${WORKFLOWS_DIR}/agent-refine.md" ] && grep -q '^ AGENT_LEAD_MODEL: "forge/carina' "${WORKFLOWS_DIR}/agent-refine.md"; then
|
|
1822
|
+
LEAD_OK=0; echo "FAIL: refine must never lead on carina" >&2
|
|
1823
|
+
fi
|
|
1824
|
+
if ! grep -qF 'jq --arg model "$AGENT_LEAD_MODEL"' "${WORKFLOWS_DIR}/shared/opencode-ci.md"; then
|
|
1825
|
+
LEAD_OK=0; echo "FAIL: the CI config merge must write AGENT_LEAD_MODEL onto ci-workflow-agent" >&2
|
|
1826
|
+
fi
|
|
1827
|
+
if worker_installed implement && ! grep -qF 'subagent_type: planner' "$IMPLEMENT_WORKER_MD"; then
|
|
1828
|
+
LEAD_OK=0; echo "FAIL: the implement prompt must run propose and archive in the planner agent" >&2
|
|
1829
|
+
fi
|
|
1830
|
+
if [ "$LEAD_OK" -eq 1 ]; then PASS=$((PASS + 1)); else FAIL=$((FAIL + 1)); fi
|
|
1831
|
+
# The shape of the tests reaches the agent only through these two prompts (check-diff measures size,
|
|
1832
|
+
# never shape), so losing the sentence silently brings the pyramid back.
|
|
1833
|
+
if worker_installed implement; then
|
|
1834
|
+
if grep -qF 'Shape what you test like a trophy' "$IMPLEMENT_WORKER_MD"; then
|
|
1835
|
+
PASS=$((PASS + 1))
|
|
1836
|
+
else
|
|
1837
|
+
FAIL=$((FAIL + 1))
|
|
1838
|
+
echo "FAIL: the implement prompt must state the testing trophy shape" >&2
|
|
1839
|
+
fi
|
|
1840
|
+
fi
|
|
1841
|
+
if worker_installed apply-review; then
|
|
1842
|
+
if grep -qF 'put it at the integration layer' "${WORKFLOWS_DIR}/agent-apply-review.md"; then
|
|
1843
|
+
PASS=$((PASS + 1))
|
|
1844
|
+
else
|
|
1845
|
+
FAIL=$((FAIL + 1))
|
|
1846
|
+
echo "FAIL: the apply-review prompt must say new tests go at the integration layer" >&2
|
|
1847
|
+
fi
|
|
1848
|
+
fi
|
|
1791
1849
|
if worker_installed merge-gate; then
|
|
1792
1850
|
# check-diff exiting 1 is the `fail` the code_rules job reports. Under GitHub's `bash -e` a bare
|
|
1793
1851
|
# assignment from it ends the step, so the report is never recorded and the gate burns its
|
|
@@ -1984,6 +2042,11 @@ if worker_installed merge-gate; then
|
|
|
1984
2042
|
assert "a failure that is main's too is waited out, not repaired" wait "$(move MERGEABLE=MERGEABLE CI_STATUS=completed CI_CONCLUSION=failure SAME_AS_MAIN=true)"
|
|
1985
2043
|
assert "a failure that is not main's still goes to the gate" gate "$(move MERGEABLE=MERGEABLE CI_STATUS=completed CI_CONCLUSION=failure SAME_AS_MAIN=false)"
|
|
1986
2044
|
assert "a conflict is still the gate's even when main is red" gate "$(move MERGEABLE=CONFLICTING CI_STATUS=completed CI_CONCLUSION=failure SAME_AS_MAIN=true)"
|
|
2045
|
+
# The wait for a red base is bounded. Pliny-Bot's main was red from 19:30 and the train waited
|
|
2046
|
+
# on it until morning, saying nothing, while five pull requests sat with their repairs unspent.
|
|
2047
|
+
grep -q 'MAIN_RED_WAIT_HOURS' "$TRAIN_SH" && grep -q 'red_for_hours' "$TRAIN_SH" &&
|
|
2048
|
+
grep -q 'main-red-wait-hours' "${HERE}/../merge-train/action.yml" ||
|
|
2049
|
+
{ FAIL=$((FAIL + 1)); echo "FAIL: the merge train waits on a red base branch with no time limit" >&2; }
|
|
1987
2050
|
assert "green but behind is brought up to date first" update "$(move MERGEABLE=MERGEABLE CI_STATUS=completed CI_CONCLUSION=success BEHIND_BY=2)"
|
|
1988
2051
|
assert "green and current goes to the gate" gate "$(move MERGEABLE=MERGEABLE CI_STATUS=completed CI_CONCLUSION=success BEHIND_BY=0)"
|
|
1989
2052
|
assert "no CI run is passed by" skip "$(move MERGEABLE=MERGEABLE CI_STATUS=none)"
|
|
@@ -2253,6 +2316,55 @@ if [ -f "$ERROR_REPORT_YML" ]; then
|
|
|
2253
2316
|
# above cannot see stops the job instead of being rendered upstream.
|
|
2254
2317
|
er 'not in the reportable field list' 'does not check the finding shape at run time'
|
|
2255
2318
|
|
|
2319
|
+
# 3b. What the report files and what it leaves as a number. Read back over a month, 320 issues:
|
|
2320
|
+
# 59% were cancelled jobs, 80% carried no diagnosis, 47% re-filed a signature seen before, and
|
|
2321
|
+
# every one was closed as not planned. The decision lives in report-policy.cjs so it can be run
|
|
2322
|
+
# here, and the action has to actually use it.
|
|
2323
|
+
POLICY_CJS="${HERE}/../report-workflow-errors/report-policy.cjs"
|
|
2324
|
+
if [ -f "$POLICY_CJS" ]; then
|
|
2325
|
+
policy_js='
|
|
2326
|
+
const p = require(process.argv[1]);
|
|
2327
|
+
const bad = [];
|
|
2328
|
+
const ok = (cond, what) => { if (!cond) bad.push(what); };
|
|
2329
|
+
ok(p.severityOf("runner-lost-mid-job") === "infra", "a runner lost mid-job is infrastructure");
|
|
2330
|
+
ok(p.severityOf("jq-error") === "package-bug", "a jq error is a package bug");
|
|
2331
|
+
ok(p.severityOf("unrecognised") === "unknown", "an unrecognised cause is unknown");
|
|
2332
|
+
const a = { workflow: "w.yml", step: "one", conclusion: "failure", patternId: "gh-api-403" };
|
|
2333
|
+
const b = { ...a, step: "two" };
|
|
2334
|
+
ok(p.signatureOf(a) === p.signatureOf(b), "one specific cause is one signature whatever the step");
|
|
2335
|
+
const u = { ...a, patternId: "unrecognised" };
|
|
2336
|
+
ok(p.signatureOf(u) !== p.signatureOf({ ...u, step: "two" }), "an unrecognised cause is told apart by its step");
|
|
2337
|
+
const f = (patternId, count, severity) => ({ patternId, count, severity });
|
|
2338
|
+
const r = p.select([f("a", 5, "package-bug"), f("b", 9, "infra"), f("c", 1, "unknown"), f("d", 3, "unknown")], { minOccurrences: 2, maxFindings: 5 });
|
|
2339
|
+
ok(r.file.map((x) => x.patternId).join() === "a,d", "infrastructure and one-off findings are held, the rest filed most frequent first");
|
|
2340
|
+
ok(r.held.length === 2, "two findings held");
|
|
2341
|
+
const many = Array.from({ length: 9 }, (_, i) => f("p" + i, 10 - i, "unknown"));
|
|
2342
|
+
ok(p.select(many, { minOccurrences: 2, maxFindings: 5 }).file.length === 5, "a bad night files at most five");
|
|
2343
|
+
const now = Date.parse("2026-10-17T00:00:00Z");
|
|
2344
|
+
ok(p.isQuiet("2026-10-10T00:00:00Z", now, 30) === true, "an issue closed a week ago keeps its signature quiet");
|
|
2345
|
+
ok(p.isQuiet("2026-08-01T00:00:00Z", now, 30) === false, "an issue closed long ago does not");
|
|
2346
|
+
ok(p.isQuiet(null, now, 30) === false, "no closing date is not quiet");
|
|
2347
|
+
ok(p.parseVersion("# Managed by @plainconceptsplatform/workflows@1.2.3. Source: x") === "1.2.3", "the version has no trailing full stop");
|
|
2348
|
+
ok(p.parseVersion("nothing here") === "unknown", "no version reads as unknown");
|
|
2349
|
+
if (bad.length) { console.error(bad.join("\n")); process.exit(1); }
|
|
2350
|
+
'
|
|
2351
|
+
if node -e "$policy_js" "$POLICY_CJS" 2>/tmp/policy-err; then
|
|
2352
|
+
PASS=$((PASS + 1))
|
|
2353
|
+
else
|
|
2354
|
+
ER_OK=0
|
|
2355
|
+
echo "FAIL: report-policy.cjs decides wrongly:" >&2
|
|
2356
|
+
sed 's/^/ /' /tmp/policy-err >&2
|
|
2357
|
+
fi
|
|
2358
|
+
else
|
|
2359
|
+
ER_OK=0
|
|
2360
|
+
echo "FAIL: report-workflow-errors has no report-policy.cjs" >&2
|
|
2361
|
+
fi
|
|
2362
|
+
er 'policy\.select\(ranked' 'does not filter what it files through the policy'
|
|
2363
|
+
er 'quiet\.cancelledNoStep \+= 1; continue;' 'files cancelled jobs that never reached a failing step'
|
|
2364
|
+
er "patternId === 'log-unavailable'\) \{ quiet\.unreadable \+= 1; continue; \}" 'files jobs whose log could not be read'
|
|
2365
|
+
er 'policy\.isQuiet\(' 'does not keep a recently closed signature quiet'
|
|
2366
|
+
er 'listAnnotations' 'does not read the job annotations, where the runner shutdown is written'
|
|
2367
|
+
|
|
2256
2368
|
# 4. No model. A model asked to summarise a failure paraphrases whatever the log held, which
|
|
2257
2369
|
# is the one thing that must not cross the boundary. This job stays deterministic.
|
|
2258
2370
|
if grep -qiE '(engine:|opencode|safe-outputs|OPENAI_API_KEY)' "$ERROR_REPORT_YML"; then
|
|
@@ -2583,6 +2695,92 @@ if worker_installed refine; then
|
|
|
2583
2695
|
PASS=$((PASS + 1))
|
|
2584
2696
|
fi
|
|
2585
2697
|
fi
|
|
2698
|
+
# The implement prompt used to say the safe-output tools are called as `safeoutputs/<tool>(...)`, an
|
|
2699
|
+
# MCP function call. The sandbox mounts them as a shell program on PATH instead, and the agent works
|
|
2700
|
+
# through an `execute` tool whose `tools` object does not hold them, so it searched there, found
|
|
2701
|
+
# nothing and ended Pliny-Bot #694 after seventy-eight minutes and sixteen commits with no pull
|
|
2702
|
+
# request. The prompt must show the shell form the refine prompt already uses.
|
|
2703
|
+
if worker_installed implement; then
|
|
2704
|
+
IMPLEMENT_SO_MD="${WORKFLOWS_DIR}/agent-implement.md"
|
|
2705
|
+
if grep -qF "safeoutputs create_pull_request ." "$IMPLEMENT_SO_MD" && grep -qF "safeoutputs report_incomplete ." "$IMPLEMENT_SO_MD" && ! grep -qF "safeoutputs/create_pull_request(" "$IMPLEMENT_SO_MD"; then
|
|
2706
|
+
PASS=$((PASS + 1))
|
|
2707
|
+
else
|
|
2708
|
+
FAIL=$((FAIL + 1))
|
|
2709
|
+
echo "FAIL: the implement prompt does not show the safe-output tools as shell commands on PATH; an agent that looks for them in the execute tool ends the run with nothing" >&2
|
|
2710
|
+
fi
|
|
2711
|
+
fi
|
|
2712
|
+
# The implement run is not interactive, so a turn that ends in text ends the run. Pliny-Bot #750 moved
|
|
2713
|
+
# `pnpm verify` to the background, said it would be resumed with the result and stopped: eight commits,
|
|
2714
|
+
# no pull request. The prompt must forbid backgrounding and waiting, and name the remedy.
|
|
2715
|
+
if worker_installed implement; then
|
|
2716
|
+
IMPLEMENT_FG_MD="${WORKFLOWS_DIR}/agent-implement.md"
|
|
2717
|
+
if grep -qF "Run every command in the foreground and wait for it in the same turn" "$IMPLEMENT_FG_MD" && grep -qF "never end a turn to wait for it" "$IMPLEMENT_FG_MD" && grep -qF "timeout" "$IMPLEMENT_FG_MD"; then
|
|
2718
|
+
PASS=$((PASS + 1))
|
|
2719
|
+
else
|
|
2720
|
+
FAIL=$((FAIL + 1))
|
|
2721
|
+
echo "FAIL: the implement prompt does not forbid running commands in the background and ending the turn to wait for them" >&2
|
|
2722
|
+
fi
|
|
2723
|
+
fi
|
|
2724
|
+
# The merge train wakes on a finished CI run only. Pliny-Bot had four green pull requests wait an hour
|
|
2725
|
+
# because a repair gate dispatched for red CI kept them marked as having a gate, and nothing called the
|
|
2726
|
+
# train again once it was cancelled. The hourly sweep is now a heartbeat, and a repair gate whose CI has
|
|
2727
|
+
# turned green is cancelled by the train itself.
|
|
2728
|
+
HK_YML="${HERE}/../housekeeping/action.yml"
|
|
2729
|
+
TRAIN_SH_HB="${HERE}/../merge-train/merge-train.sh"
|
|
2730
|
+
if [ -f "$HK_YML" ] && [ -f "$TRAIN_SH_HB" ]; then
|
|
2731
|
+
if grep -qF "operation: 'merge-train'" "$HK_YML" && grep -qF "createWorkflowDispatch" "$HK_YML" && grep -qF "its CI is green" "$TRAIN_SH_HB" && grep -qF 'live_titles=$(printf' "$TRAIN_SH_HB"; then
|
|
2732
|
+
PASS=$((PASS + 1))
|
|
2733
|
+
else
|
|
2734
|
+
FAIL=$((FAIL + 1))
|
|
2735
|
+
echo "FAIL: the merge train has no heartbeat from housekeeping, or does not cancel a repair gate whose CI turned green" >&2
|
|
2736
|
+
fi
|
|
2737
|
+
fi
|
|
2738
|
+
# The implement queue was the issue's key modulo the number of queues, so three independent issues whose
|
|
2739
|
+
# numbers were multiples of three (Pliny-Bot #726, #750 and #694 under #690) shared queue zero and ran in
|
|
2740
|
+
# series behind a three-hour run. The queue is now the key's place among the keys of the issues waiting for
|
|
2741
|
+
# an implement, and the children of one refine still share a key.
|
|
2742
|
+
if grep -qF "implement-queue-" "$ROUTER_YML" 2>/dev/null; then
|
|
2743
|
+
if grep -qF 'place=$index' "$ROUTER_YML" && grep -qF 'queue=$(( place % queues ))' "$ROUTER_YML" && ! grep -qF 'queue=$(( key % queues ))' "$ROUTER_YML"; then
|
|
2744
|
+
PASS=$((PASS + 1))
|
|
2745
|
+
else
|
|
2746
|
+
FAIL=$((FAIL + 1))
|
|
2747
|
+
echo "FAIL: the implement queue is chosen by the issue number modulo the queue count, which puts independent issues in one queue" >&2
|
|
2748
|
+
fi
|
|
2749
|
+
fi
|
|
2750
|
+
# The runner VM kills any job that outlives RUNNER_JOB_TIMEOUT, whatever the workflow allows. The default
|
|
2751
|
+
# of 90 minutes killed three Pliny-Bot implements that the workflow had given 180: no log, no pull request.
|
|
2752
|
+
# The cap in cloud-init has to stay above the longest agent job, or the workflow's own timeout is decoration.
|
|
2753
|
+
CLOUD_INIT_YML="${HERE}/../../../runners/cloud-init.yaml"
|
|
2754
|
+
if [ -f "$CLOUD_INIT_YML" ] && [ -f "${WORKFLOWS_DIR}/agent-implement.md" ]; then
|
|
2755
|
+
cap="$(sed -n 's/^ *RUNNER_JOB_TIMEOUT=\([0-9][0-9]*\)$/\1/p' "$CLOUD_INIT_YML" | tail -1)"
|
|
2756
|
+
longest="$(grep -hoE '^timeout-minutes: [0-9]+' "${WORKFLOWS_DIR}"/agent-*.md | grep -oE '[0-9]+' | sort -n | tail -1)"
|
|
2757
|
+
if [ -n "$cap" ] && [ -n "$longest" ] && [ "$cap" -gt $((longest * 60)) ]; then
|
|
2758
|
+
PASS=$((PASS + 1))
|
|
2759
|
+
else
|
|
2760
|
+
FAIL=$((FAIL + 1))
|
|
2761
|
+
echo "FAIL: the runner job cap (${cap:-unset} s in cloud-init.yaml) is not above the longest agent timeout (${longest:-?} min); a long agent job would be killed by the VM" >&2
|
|
2762
|
+
fi
|
|
2763
|
+
fi
|
|
2764
|
+
# decide-agent-retry reads why an agent job died from the job's annotations, and the annotations API needs
|
|
2765
|
+
# `checks: read`. Without it the read fails quietly (`2>/dev/null || true`), a runner shut down mid-job
|
|
2766
|
+
# (exit code 143) reads as no cause, and the issue is parked for a person instead of being run again:
|
|
2767
|
+
# Pliny-Bot #750 died at 44 minutes with exactly that annotation and was flagged for review.
|
|
2768
|
+
RETRY_PERM_OK=1
|
|
2769
|
+
for worker in "${WORKFLOWS_DIR}"/agent-*.md; do
|
|
2770
|
+
[ -f "$worker" ] || continue
|
|
2771
|
+
grep -q "decide-agent-retry" "$worker" || continue
|
|
2772
|
+
missing="$(awk '
|
|
2773
|
+
function flush() { if (job != "" && uses && !checks) printf "%s ", job }
|
|
2774
|
+
/^ [a-z_]+:$/ { flush(); job = $1; uses = 0; checks = 0; next }
|
|
2775
|
+
/decide-agent-retry/ { uses = 1 }
|
|
2776
|
+
/^ checks: read$/ { checks = 1 }
|
|
2777
|
+
END { flush() }' "$worker")"
|
|
2778
|
+
if [ -n "${missing// /}" ]; then
|
|
2779
|
+
RETRY_PERM_OK=0
|
|
2780
|
+
echo "FAIL: ${worker##*/}: job(s) ${missing}use decide-agent-retry without checks: read, so a runner shutdown reads as no cause" >&2
|
|
2781
|
+
fi
|
|
2782
|
+
done
|
|
2783
|
+
if [ "$RETRY_PERM_OK" -eq 1 ]; then PASS=$((PASS + 1)); else FAIL=$((FAIL + 1)); fi
|
|
2586
2784
|
# The implement prompt tells the agent it is already on its branch and to skip the pc-plan-goal
|
|
2587
2785
|
# branching phase. That is only true if a step before the agent made the branch, and nothing in the
|
|
2588
2786
|
# sandbox can do it later: no git credentials, and the prompt forbids the commands. Without the
|
package/loops/docs/diagrams.md
CHANGED
|
@@ -709,7 +709,7 @@ the repository it runs in. Every consumer is private, so that is the whole desig
|
|
|
709
709
|
|
|
710
710
|
What crosses the boundary is a fixed, enumerable set of facts about workflows **this package ships**:
|
|
711
711
|
their file names, their job and step names, a conclusion, a runner label, a count, and the id of a
|
|
712
|
-
matched entry from a catalogue of sixteen known failure shapes. What never crosses it is free text
|
|
712
|
+
matched entry from a catalogue of sixteen known failure shapes, plus a severity. What never crosses it is free text
|
|
713
713
|
of any kind no log line, no branch name, no issue title, no path, no URL, no number that could be
|
|
714
714
|
looked up. Hence no model: a model asked to summarise a failure paraphrases whatever the log held.
|
|
715
715
|
|
|
@@ -720,8 +720,12 @@ flowchart TB
|
|
|
720
720
|
erOwned -.->|"no: the name could describe<br/>a product or a customer"| erCount("Counted, never inspected")
|
|
721
721
|
erOwned -->|yes| erState{"Failed, or queued<br/>past 45 minutes?"}
|
|
722
722
|
erState -.->|neither| erCount
|
|
723
|
-
erState -->|yes| erClassify("Read the log tail,<br/>match the catalogue,<br/>keep only the matched id")
|
|
724
|
-
erClassify -->
|
|
723
|
+
erState -->|yes| erClassify("Read the log tail and the job annotations,<br/>match the catalogue,<br/>keep only the matched id")
|
|
724
|
+
erClassify --> erNoise{"Cancelled with no failing step,<br/>log unreadable, or an<br/>infrastructure cause?"}
|
|
725
|
+
erNoise -.->|yes| erCount
|
|
726
|
+
erNoise -->|no| erFilter{"Seen at least twice,<br/>within the cap, and not<br/>closed in the last 30 days?"}
|
|
727
|
+
erFilter -.->|no| erCount
|
|
728
|
+
erFilter -->|yes| erBody("Build the report from<br/>package-owned names,<br/>a conclusion and a count")
|
|
725
729
|
erBody --> erScan{"Leak scanner:<br/>repo or owner name, a URL,<br/>an email, a path, a token,<br/>an issue number, a ref?"}
|
|
726
730
|
erScan -.->|"anything matches"| erWithhold(("Withheld<br/>nothing filed, run goes red"))
|
|
727
731
|
erScan -->|clean| erToken{"Upstream token<br/>available?"}
|
|
@@ -737,7 +741,7 @@ flowchart TB
|
|
|
737
741
|
classDef idle fill:#f4f4f6,stroke:#5b5b66,stroke-width:2px,color:#2b2b33
|
|
738
742
|
class erCron start
|
|
739
743
|
class erRuns,erClassify,erBody,erFile,erCount action
|
|
740
|
-
class erOwned,erState,erScan,erToken decision
|
|
744
|
+
class erOwned,erState,erNoise,erFilter,erScan,erToken decision
|
|
741
745
|
class erWithhold failure
|
|
742
746
|
class erDone success
|
|
743
747
|
class erSummary idle
|
|
@@ -69,6 +69,8 @@ jobs:
|
|
|
69
69
|
# upstream with a separate token and writes nothing here.
|
|
70
70
|
contents: read
|
|
71
71
|
actions: read
|
|
72
|
+
# The annotations GitHub writes on a job say why a runner went away; the log does not.
|
|
73
|
+
checks: read
|
|
72
74
|
steps:
|
|
73
75
|
- name: Checkout workflow actions
|
|
74
76
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://opencode.ai/config.json",
|
|
3
|
-
"model": "forge/
|
|
3
|
+
"model": "forge/carina-60b",
|
|
4
4
|
"default_agent": "ci-workflow-agent",
|
|
5
5
|
"agents": {
|
|
6
6
|
"ci-workflow-agent": {
|
|
@@ -44,7 +44,61 @@
|
|
|
44
44
|
"apiKey": "{env:OPENAI_API_KEY}"
|
|
45
45
|
},
|
|
46
46
|
"models": {
|
|
47
|
+
"carina-60b": {
|
|
48
|
+
"name": "Carina 60B",
|
|
49
|
+
"capabilities": {
|
|
50
|
+
"tools": true,
|
|
51
|
+
"input": [
|
|
52
|
+
"text",
|
|
53
|
+
"image"
|
|
54
|
+
],
|
|
55
|
+
"output": [
|
|
56
|
+
"text"
|
|
57
|
+
]
|
|
58
|
+
},
|
|
59
|
+
"limit": {
|
|
60
|
+
"context": 1048576,
|
|
61
|
+
"input": 1048576,
|
|
62
|
+
"output": 131072
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
"quasar-438b": {
|
|
66
|
+
"name": "Quasar 438B",
|
|
67
|
+
"capabilities": {
|
|
68
|
+
"tools": true,
|
|
69
|
+
"input": [
|
|
70
|
+
"text",
|
|
71
|
+
"image"
|
|
72
|
+
],
|
|
73
|
+
"output": [
|
|
74
|
+
"text"
|
|
75
|
+
]
|
|
76
|
+
},
|
|
77
|
+
"limit": {
|
|
78
|
+
"context": 1048576,
|
|
79
|
+
"input": 1048576,
|
|
80
|
+
"output": 131072
|
|
81
|
+
}
|
|
82
|
+
},
|
|
47
83
|
"glm-5-2": {
|
|
84
|
+
"name": "GLM 5.2",
|
|
85
|
+
"capabilities": {
|
|
86
|
+
"tools": true,
|
|
87
|
+
"input": [
|
|
88
|
+
"text",
|
|
89
|
+
"image"
|
|
90
|
+
],
|
|
91
|
+
"output": [
|
|
92
|
+
"text"
|
|
93
|
+
]
|
|
94
|
+
},
|
|
95
|
+
"limit": {
|
|
96
|
+
"context": 1048576,
|
|
97
|
+
"input": 1048576,
|
|
98
|
+
"output": 131072
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
"glm-5-3": {
|
|
48
102
|
"name": "GLM 5.3",
|
|
49
103
|
"capabilities": {
|
|
50
104
|
"tools": true,
|
|
@@ -61,8 +115,53 @@
|
|
|
61
115
|
"input": 1048576,
|
|
62
116
|
"output": 131072
|
|
63
117
|
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
"plainconcepts": {
|
|
122
|
+
"package": "aisdk:@ai-sdk/openai-compatible",
|
|
123
|
+
"settings": {
|
|
124
|
+
"baseURL": "{env:OPENAI_BASE_URL}",
|
|
125
|
+
"apiKey": "{env:OPENAI_API_KEY}"
|
|
126
|
+
},
|
|
127
|
+
"models": {
|
|
128
|
+
"carina-60b": {
|
|
129
|
+
"name": "Carina 60B",
|
|
130
|
+
"capabilities": {
|
|
131
|
+
"tools": true,
|
|
132
|
+
"input": [
|
|
133
|
+
"text",
|
|
134
|
+
"image"
|
|
135
|
+
],
|
|
136
|
+
"output": [
|
|
137
|
+
"text"
|
|
138
|
+
]
|
|
139
|
+
},
|
|
140
|
+
"limit": {
|
|
141
|
+
"context": 1048576,
|
|
142
|
+
"input": 1048576,
|
|
143
|
+
"output": 131072
|
|
144
|
+
}
|
|
64
145
|
},
|
|
65
146
|
"quasar-438b": {
|
|
147
|
+
"name": "Quasar 438B",
|
|
148
|
+
"capabilities": {
|
|
149
|
+
"tools": true,
|
|
150
|
+
"input": [
|
|
151
|
+
"text",
|
|
152
|
+
"image"
|
|
153
|
+
],
|
|
154
|
+
"output": [
|
|
155
|
+
"text"
|
|
156
|
+
]
|
|
157
|
+
},
|
|
158
|
+
"limit": {
|
|
159
|
+
"context": 1048576,
|
|
160
|
+
"input": 1048576,
|
|
161
|
+
"output": 131072
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
"glm-5-2": {
|
|
66
165
|
"name": "GLM 5.2",
|
|
67
166
|
"capabilities": {
|
|
68
167
|
"tools": true,
|
|
@@ -79,6 +178,24 @@
|
|
|
79
178
|
"input": 1048576,
|
|
80
179
|
"output": 131072
|
|
81
180
|
}
|
|
181
|
+
},
|
|
182
|
+
"glm-5-3": {
|
|
183
|
+
"name": "GLM 5.3",
|
|
184
|
+
"capabilities": {
|
|
185
|
+
"tools": true,
|
|
186
|
+
"input": [
|
|
187
|
+
"text",
|
|
188
|
+
"image"
|
|
189
|
+
],
|
|
190
|
+
"output": [
|
|
191
|
+
"text"
|
|
192
|
+
]
|
|
193
|
+
},
|
|
194
|
+
"limit": {
|
|
195
|
+
"context": 1048576,
|
|
196
|
+
"input": 1048576,
|
|
197
|
+
"output": 131072
|
|
198
|
+
}
|
|
82
199
|
}
|
|
83
200
|
}
|
|
84
201
|
}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
# Managed by @plainconceptsplatform/workflows. Source: loops/workflows/agent-apply-review.md. Update with `workflows update --force`; consumer edits may be overwritten.
|
|
3
3
|
env:
|
|
4
|
+
# The model this worker's own session runs on. opencode.ci.json's `model` is the cheap default
|
|
5
|
+
# every agent without its own model falls back to; the lead is where a wrong call costs a run,
|
|
6
|
+
# so it names its model here. `model:` below only satisfies the compiler. A repository may
|
|
7
|
+
# change it to any model its opencode.ci.json declares.
|
|
8
|
+
AGENT_LEAD_MODEL: "forge/quasar-438b"
|
|
4
9
|
# Printed by the prompt as the verification block. Consumers set their own commands.
|
|
5
10
|
VERIFY_COMMANDS: ""
|
|
6
11
|
REPO_RULES: "Apply only actionable outstanding reviewer feedback to the selected bot pull request. Make minimal changes that address each comment. Preserve architecture and do not weaken tests. Run full verification after changes."
|
|
@@ -307,6 +312,10 @@ jobs:
|
|
|
307
312
|
issues: write
|
|
308
313
|
# the retry re-enters through the router, which is a workflow_dispatch
|
|
309
314
|
actions: write
|
|
315
|
+
# decide-agent-retry reads why the agent job died from its annotations, and the annotations API needs this scope.
|
|
316
|
+
# Without it the read fails quietly, a runner shut down mid-job (exit code 143) reads as no cause,
|
|
317
|
+
# and the issue is parked for a person instead of being run again (Pliny-Bot #750).
|
|
318
|
+
checks: read
|
|
310
319
|
steps:
|
|
311
320
|
- name: Checkout workflow actions
|
|
312
321
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
@@ -402,7 +411,7 @@ engine:
|
|
|
402
411
|
env:
|
|
403
412
|
OPENAI_BASE_URL: https://forge.plainconcepts.com/v1
|
|
404
413
|
|
|
405
|
-
model: openai/
|
|
414
|
+
model: openai/quasar-438b
|
|
406
415
|
|
|
407
416
|
max-turns: 300
|
|
408
417
|
max-turn-cache-misses: 3000
|
|
@@ -545,6 +554,8 @@ timeout-minutes: 90
|
|
|
545
554
|
`node .opencode/scripts/check-diff.mjs`.
|
|
546
555
|
It must exit 0: delete the comments and tests it lists. The merge gate runs it too and does not
|
|
547
556
|
merge while it fails.
|
|
557
|
+
When you have to add a test, put it at the integration layer through the public surface, not as a
|
|
558
|
+
unit test of internals; a bug fix gets one regression test.
|
|
548
559
|
|
|
549
560
|
7. Select one review outcome.
|
|
550
561
|
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
# Managed by @plainconceptsplatform/workflows. Source: loops/workflows/agent-audit.md. Update with `workflows update --force`; consumer edits may be overwritten.
|
|
3
3
|
env:
|
|
4
|
+
# The model this worker's own session runs on. opencode.ci.json's `model` is the cheap default
|
|
5
|
+
# every agent without its own model falls back to; the lead is where a wrong call costs a run,
|
|
6
|
+
# so it names its model here. `model:` below only satisfies the compiler. A repository may
|
|
7
|
+
# change it to any model its opencode.ci.json declares.
|
|
8
|
+
AGENT_LEAD_MODEL: "forge/quasar-438b"
|
|
4
9
|
REPO_RULES: "Read-only repository audit. Report only reproducible, actionable defects with evidence. Do not modify files, commit, push, or run write operations."
|
|
5
10
|
# Split out of REPO_RULES, which carried both the read-only discipline above and the list
|
|
6
11
|
# below. All four consuming repositories had customised the list and none could touch it
|
|
@@ -67,7 +72,7 @@ engine:
|
|
|
67
72
|
env:
|
|
68
73
|
OPENAI_BASE_URL: https://forge.plainconcepts.com/v1
|
|
69
74
|
|
|
70
|
-
model: openai/
|
|
75
|
+
model: openai/quasar-438b
|
|
71
76
|
|
|
72
77
|
max-turns: 300
|
|
73
78
|
max-turn-cache-misses: 3000
|