Skip to content

chore(loop): mine the uploaded agent sessions and feed them to retro - #82

Open
github-actions[bot] wants to merge 4 commits into
mainfrom
loop/77-session-mining
Open

github-actions[bot] wants to merge 4 commits into
mainfrom
loop/77-session-mining

Conversation

@github-actions

@github-actions github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Closes #77

loop-task: #77

What/why

The loop already uploads every agent session as a loop-sessions-<run_id> artifact, but nothing read them back — retro-scheduled aggregated only run end rows, so the facts that explain a run (engine faults, failing tool calls, retry loops, reads of skills that do not exist) were written and never consumed (#77; owner: "do 1 now, then 2"). This adds a session miner and makes the weekly retro consume it. The mine at this branch's head over the 100 newest artifacts: 105 sessions, 160,399,055 processed tokens, 4,723 tool calls, 110 failed tool calls (2.3%), 2 engine faults in runs still recorded as completed, 16 git failures in 12 sessions, 11 reads of the two missing stage skills. These supersede the pre-fix figures in the issue comment (148 failures / 3.3%, 29 git / 19 sessions, 9 skill reads): the classifier now counts only failed tool results, so the 38 rows that disappeared were phantom — successful reads of this spec, of reports and of logs quoting failure text.

Proof it works

$ pnpm lint
Found 0 warnings and 0 errors. (91 files)
$ pnpm fmt:check
All matched files use the correct format. (91 files)
$ pnpm build
Done in 5.83s
$ pnpm check:types
exit 0 (all seven tsc checks)
$ npx vitest run --exclude 'test/*-integration.spec.ts' --exclude 'test/index.spec.ts'
Test Files  20 passed (20)
     Tests  258 passed (258)

(pnpm test also picks up the five integration specs, which need pnpm serve + a .env with credentials; this sandbox has none, so they fail with TypeError: Failed to fetch before any assertion — unchanged by this PR.)

The miner against the real artifact store:

$ node scripts/loop/sessions.mjs --limit 100 --repo Alex1990/tiny-oss \
    --out state/reports/head-sessions.md
[sessions] downloading 100 artifacts from Alex1990/tiny-oss
[sessions] 105 sessions, 4723 tool calls, 110 failures, 2 engine faults, 160,399,055 tokens processed, artifacts 100/100 read

Unit coverage of the parser (synthetic session, browser vitest): token split, failure classification, retry loop, missing-skill path, report rendering (test/sessions.spec.ts, 4 tests). The recorded-token column needs the run end rows from the R2 state layer (a local checkout carries only the 2026-09 rows, so 7 sessions join there); when nothing joins, the report says Recorded-token comparison unavailable instead of printing a multiplier, and one end row is counted once per run however many sessions share its runId (pr-review = two reviewers, one row).

Risk tier + AI role

Medium-low, loop-host only; the agent produced the whole change. The miner is read-only, writes only under state/, and uses the credential the job already holds. The behavioural change is on the weekly retro timer: it now downloads up to 50 artifacts (LOOP_SESSION_ARTIFACTS, default 50) before the agent starts, and is fail-open — a failed download is logged and stated in the prompt, so "no report" can never be read as "no signals". If the classifier is wrong, the effect is a wrong finding in a retro report, not a wrong GitHub write. skills/retro/SKILL.md is a norms-layer proposal; per AGENTS.md the owner merges it.

Review focus

  1. scripts/loop/shared/sessions.mjs — the token split (billedTokens = sum of every assistant call; contextTokens = last call; the end row stores the latter) and the failure taxonomy's line-anchored matching. A misclassification here propagates into retro's proposals.
  2. scripts/loop/entry.mjs retro hook — 28 lines inside doRun: fail-open degradation and the prompt wording that prevents an absent report from being treated as a clean corpus.

The workflow uploads every pi session as a `loop-sessions-<run_id>` artifact
(90 days), but nothing read them back: `retro-scheduled` aggregated only the
run `end` rows, so engine faults, failing tool calls, retry loops and reads of
skills that do not exist were written and never consumed (#77).

`scripts/loop/sessions.mjs` now lists/downloads those artifacts through the
job's existing `gh` credential, parses each pi session (stage, runId, task,
per-call usage, tool calls and their results), joins it to the run `end` row by
runId, and renders an evidence report: processed tokens vs the recorded
`tokens` value, engine faults, a tool-failure taxonomy, retry loops, and the
stage skills the opening ritual names but the repo lacks.

`entry.mjs` runs the miner before a `retro-scheduled` agent starts and passes
the report path in its prompt (fail-open, and an unavailable report is stated
in the prompt so "no report" is never read as "no signals").
`skills/retro/SKILL.md` scheduled mode makes reading it step 0 and requires
each signal to become a proposal or be dismissed with a reason.

The parser/aggregate/renderer is dependency-free and unit-tested in the
browser vitest suite; the CLI (`pnpm loop:sessions`, also `--dir` for an
offline tree) does the gh/unzip/fs work.

First run over the 100 newest artifacts (103 sessions, 2026-09-10..10-21):
148,246,119 processed tokens vs 7,120,313 recorded (20.8x), 148 failed tool
calls (3.3%), 2 engine faults in runs still recorded as `completed`, 18
shallow-clone git failures, 9 reads of the two missing stage skills. The
resulting proposals are in the run report; none are applied here.
@github-actions
github-actions Bot requested a review from Alex1990 as a code owner October 21, 2026 16:01
@github-actions

Copy link
Copy Markdown
Contributor Author

Loop pr-review — request-changes ⚠️ — two isolated reviewers

Reviewer a — correctness & regression (model deepseek/deepseek-v4-flash, session /home/runner/work/_temp/pi-sessions/reviewer-a): request-changes

Reviewer A — Correctness & regression — verdict: request-changes (confidence: high)

PR-contract gate: pass (What/why, Proof it works, Risk tier + AI role, Review focus all present; Closes #77 + loop-task: #77). Test-integrity gate: pass (new spec only, nothing deleted/skipped, no gate weakened; I re-ran test/sessions.spec.ts — 4 passed — plus pnpm lint and pnpm fmt:check, both clean). The architecture is sound and the miner does run end to end here (3 artifacts → 3 sessions, 184 tool calls, 3/3 joined). The problems below are in the classification/reporting semantics, i.e. the review-focus area.

  1. [L1] scripts/loop/shared/sessions.mjs:33-35,89-95,180 — classifyFailure runs the content heuristics on successful tool results (only tool-error consults isError), so any result that merely quotes a marker is counted as a failure. missing-skill is the worst case: it is not line-anchored and this PR's own fixture test/sessions.spec.ts contains the literal ENOENT: no such file or directory, access '/repo/skills/feature/SKILL.md', so every session that reads that test file fabricates a missing-skill read. Reproduced: one read of test/sessions.spec.ts with isError:false → toolErrors:1, missingSkills:[{path:"<200 chars of the test file>",count:1,stages:["pr-review"]}], and the report prints | missing-skill | 1 | 1 | pr-review | read: test/sessions.spec.ts |. Same hole for git/gh-field/command-failed on successful bash stdout (cat/git diff of a file containing fatal: or Command exited with code 1) — my own review run, which diffed that spec, is such a case. This is the signal the issue comment headlines, so a phantom inflates retro's proposals. Fix: if (!isError) return null before the text rules (keep text matching for shell tools only if chained-command failures must stay catchable), anchor missing-skill like path-missing, extract the path from the untruncated body (the 200-char detail truncation is why the report publishes a garbage path above), and add the missing regression test: a successful result whose content quotes ENOENT … access '…SKILL.md' / fatal: / Command exited with code 1 must classify as null and leave toolErrors at 0.

  2. [L2] scripts/loop/sessions.mjs:106-123 — a partially downloaded corpus is presented as complete: per-artifact download failures are only warned, and the attempted/skipped counts never reach the aggregate, the markdown or the prompt. A retro cannot distinguish "50 sessions, nothing in them" from "50 attempted, 12 read" — the same "absence read as signal" failure the PR fixes one level up. Return {files, attempted, skipped} and state read N/M artifacts (K skipped) in both the report header and the entry.mjs lead line.

  3. [L3] scripts/loop/sessions.mjs:106-108 — the artifact list is a single page (per_page=100); the repo already holds total_count: 102 (checked live, 90-day retention), so --limit > 100 silently returns ≤100 and an older --since is silently truncated — the path the PR body itself used (--limit 100). Paginate or warn when total_count exceeds what was selected.

  4. [L3] scripts/loop/sessions.mjs:138-152 — --limit is validated but ignored when --dir is used, while scripts/loop/README.md documents it as "artifacts, newest first". Honour it for --dir or say it is ignored there.

  5. [L3] scripts/loop/shared/sessions.mjs:186-196 + aggregate — two units under one label: contextTokens keeps the last non-zero call while summarize() (agent.mjs:365) uses the final message_end usage unconditionally, and for pr-review the end row's tokens is sumUsage over both reviewers (shared/review.mjs:87) while the artifact holds the reviewer session(s) — hence the report's recorded 980,511 vs final context 634,651 for that stage. The corpus-level 20.8× is still directionally right; say which cells are per-session and which come from a summed run row.

  6. [L3] scripts/loop/entry.mjs:350-352 — the prompt calls the mining report "the evidence base for scheduled-mode step 1" while skills/retro/SKILL.md introduces it as step 0. Align the numbering so the read is not skipped.

  7. [L3] PR body vs commit message vs cited artifact disagree on the same number: body "15 shallow-clone git failures", commit "18", and state/reports/r-20261021-154935-19tq.sessions.md prints git 29 failures in 19 sessions. Use the figure the report actually prints (and say how a shallow-clone subset is derived).

No regression surface: no src/ / public API / build-input change, so tree-shaking, entries and the signer oracles are untouched; the only runtime change is the weekly retro's fail-open pre-step. Nothing to fix in the PR-contract or test-integrity gates.

Reviewer b — standards, safety & maintainability (model deepseek/deepseek-v4-pro, session /home/runner/work/_temp/pi-sessions/reviewer-b): approve

Reviewer B — Standards, safety & maintainability: approve

Contract gate: all four author sections present. Test-integrity gate: the only test change is additive (test/sessions.spec.ts, 4 tests); no test deleted/skipped and no lint/format/type/coverage/tsconfig setting weakened. Verified locally: pnpm lint (0/0, 91 files) and pnpm fmt:check (clean) both green; the parser/aggregate/renderer logic was reproduced directly in node against the spec's own fixture and every assertion holds (the browser runner itself is not installable in this sandbox, but CI's test:ci installs Playwright and runs vitest run --browser.headless).

No L1/L2 issues from this lens. AGENTS.md conformance, docs, secrets, dangerous patterns and silent-change review are all clean (chore(loop) with area; no signer/entry touched; norms-layer skill edit arrives as this PR; gh credential already holds actions: write; spawnSync array argv only, no shell, no extraction-to-disk, mkdtemp+cleanup). Three non-blocking L3 nits:

  1. [L3] scripts/loop/sessions.mjs:91 — the gh api …/zip download caps at 64 MB (maxBuffer); an oversized zip is skipped per-artifact with only a warn, and the report does not mark the corpus as partial, so a retro could read a partial corpus as complete. Fix: count skipped artifacts in the aggregate and render "N artifacts skipped (oversize/download failure)".
  2. [L3] scripts/loop/sessions.mjs:189 — parseArgs yields true for a value-less flag, so --json without a path throws a raw TypeError from path.resolve and --limit without a value becomes Number(true) === 1. Fix: validate flag value types in main() and print the usage line.
  3. [L3] scripts/loop/shared/sessions.mjs:187 — detail/message copy up to 200 chars of arbitrary tool-error text into state/reports/*.sessions.{md,json}, which the retro agent may quote into a proposal PR body; a session that echoed a secret would leak it. Fix: redact token/env patterns or cap quoted details.

@Alex1990

Copy link
Copy Markdown
Owner

Review findings — resolution

Both verdicts came from the loop's own reviewers. Every blocking item from Reviewer A and
every L3 from Reviewer B is addressed in c61bc7c; pnpm exec vitest run test/sessions.spec.ts
7 passed, and the CLI was smoked against a two-file tree (--dir --limit 1).

# Finding Resolution
A1 classifyFailure ran content heuristics on successful results, so quoting ENOENT … SKILL.md fabricated failures (incl. this PR's own fixture) classifyFailure now returns null unless isError === true; missing-skill is line-anchored. New spec: a successful read of the fixture produces zero failures
A2 Partial download presented as complete collectFromGitHub returns { files, artifacts: { attempted, read, skipped } }; the report prints read N/M artifacts (K skipped) and entry.mjs repeats it in the retro prompt
A3 Single per_page=100 page silently truncated the corpus listArtifacts paginates to total_count
A4 --limit ignored with --dir collectFromDir sorts by mtime, newest first, and applies --limit; usage text says so
A5 Two token units under one label Report states the units (processed = sum of calls, final context = last non-zero call per session, recorded = run row, summed over both reviewers for pr-review) and the column is renamed recorded (run row)
A6 Prompt said step 1, skill says step 0 Prompt now says step 0
A7 15 / 18 / 29 git failures across body, commit, report The report's figure is authoritative: git 29 failures in 19 sessions (the subset is per-class rows, not a separate count). Body and commit are immutable; this table is the correction of record
B1 Oversize zip skipped silently Cap raised to LOOP_SESSION_ZIP_MAX_MB (default 256) and counted in skipped
B2 --json / --limit without a value threw a raw TypeError / became 1 requireValue rejects a bare flag and prints the usage line (exit 1)
B3 Tool-error text could leak a secret into the report/prompt redact() strips ghp_*, github_pat_*, AKIA*, sk-*, private-key blocks and `*_KEY

Reproduce the verification

pnpm exec vitest run test/sessions.spec.ts          # 7 passed
node scripts/loop/sessions.mjs --dir <tree> --limit 1 --out /tmp/s.md
node scripts/loop/sessions.mjs --limit               # exit 1: "--limit needs a value"

The review re-run is enabled by #83 (D26: a new commit on a loop PR re-opens its review);
without it the synchronize event would be skipped as status=rejected.

@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor Author

Coverage

Metric % Covered / Total
Lines 92.43% 1369/1481
Statements 91.12% 1499/1645
Functions 93.03% 294/316
Branches 74.53% 720/966

@github-actions

Copy link
Copy Markdown
Contributor Author

Loop pr-review — request-changes ⚠️ — two isolated reviewers

Reviewer a — correctness & regression (model deepseek/deepseek-v4-flash, session /home/runner/work/_temp/pi-sessions/reviewer-a): request-changes

Reviewer A — Correctness & regression — verdict: request-changes (confidence: high)

PR-contract gate: pass (Closes #77, loop-task: #77, all four sections). Test-integrity gate: pass — test/sessions.spec.ts is new and additive, no test edited/removed/skipped, no gate config weakened, oracles untouched. Prior A1–A6 verified fixed (phantom failures gone: 25→16 on an 11-session sample; pagination 100/100 read; --dir --limit honoured; units/steps documented). Gates re-run here: lint 0/0, fmt clean, build ok, check:types exit 0, sessions spec 7 passed (node mode; no chromium in sandbox).

  1. [L1] scripts/loop/shared/sessions.mjs:290,297–306,350–356 — a run end row is joined once per session, so every multi-session run (all pr-review: reviewer-a + reviewer-b share one runId) has its tokens and its outcome counted twice, even though shared/review.mjs:86-87 already stores the row as the sum over both reviewers. Measured: local corpus pr-review recorded 276,018 = 2 × 138,009 (the actual end row) and rejected×2 for one run; on the 100-artifact corpus pr-review recorded is inflated by 483,869 (1,256,529 vs 772,660 deduped), so the headline multiplier is understated (21.5× reported → 23.0× deduped; the total row's final context 6,881,014 is exactly the deduped recorded total — the two columns are the same quantity by construction). Also taxonomy.sessions (:318) keys on runId, collapsing both reviewer sessions into one. Fix: dedupe by runId before summing/tallying ([...new Set(ss.map(s => s.runId))].map(id => ends.get(id)).filter(Boolean)), count sessions and runs joined separately, spread name into failures for the session key, and add a test with two sessions sharing one runId.
  2. [L2] PR body "Proof it works" + the Save every session created by agent in loop action #77 resolution comment — the published mining numbers were produced by the pre-fix classifier and no longer reproduce from the head: re-running pnpm loop:sessions --limit 100 at head gives 110 failures / 4,683 tool calls (2.3%), git 15 in 11 sessions, missing-skill 10 — not 148/3.3%, 29/19, 9. The corpus grew by one session yet failures fell by 38, i.e. the fix removed phantoms. The report cited as "authoritative" (state/reports/r-20261021-154935-19tq.sessions.md) is the pre-fix build. The conclusions survive, the numbers do not. Fix: re-mine with the fixed code, replace the report, and post a comment with the corrected figures and a line naming the phantom rows.
  3. [L3] scripts/loop/sessions.mjs:184-186 — sessions filtered out by .filter(s => s.runId || s.stage) are never counted, so the header still reads read N/M artifacts + joined/sessions when a prompt-format drift would silently shrink the corpus (same class as A2, one level down). Report files parsed vs sessions kept. Latent only (11/11 and 104/104 in practice).
  4. [L3] scripts/loop/shared/sessions.mjs:419-420,517-519 — with no joined run rows, recordedTokens is 0 and the report prints X processed vs 0 recorded (huge×); say "recorded-token comparison unavailable".
  5. [L3] scripts/loop/README.md:336 — *_KEY|TOKEN|SECRET=PASSWORD should be (KEY|TOKEN|SECRET|PASSWORD)=….

Regression surface: no src/ or public API change; runtime change confined to the weekly retro (50 sequential artifact downloads — measured 25 s / 124 MB, inside the 60-min job, fail-open). skills/retro/SKILL.md correctly left as a norms-layer proposal for the owner. Verdict stands at request-changes for L1 and the L2 evidence refresh.

Reviewer b — standards, safety & maintainability (model deepseek/deepseek-v4-pro, session /home/runner/work/_temp/pi-sessions/reviewer-b): approve

Reviewer B — standards, safety & maintainability: approve.

Gates: PR-contract (all four sections + Closes #77/loop-task: #77) PASS; test-integrity (new test file only, no weakened gate config, no deleted/skipped tests) PASS.

No L1/L2 problems. Optional nits:

  • [L3] scripts/loop/sessions.mjs — listArtifacts/readArtifactZip use spawnSync, blocking the event loop for up to LOOP_SESSION_ARTIFACTS (default 50) zip downloads. Fine for the weekly timer; revisit only if the miner is reused on a shared loop.
  • [L3] scripts/loop/shared/sessions.mjs SECRET_PATTERNS — redaction is best-effort by design and covers the credential shapes the loop holds; a bare non-prefixed secret outside a NAME=value echo is not matched. Acceptable because the report stays in gitignored state/ and R2 creds are stripped from the agent env, but treat the report as sensitive regardless.
  • [L3] scripts/loop/shared/sessions.mjs FAILURE_RULES — command-failed is the only rule without line anchoring; correct today because classifyFailure gates everything on isError === true, but an un-gated future caller would re-introduce the phantom-failure bug the other rules' anchoring defends against. Optional: anchor it too.

Verified: pnpm lint green (reproduced); token-split/classification/redaction/retry-loop/report logic executed directly in Node against the test's assertions (browser chromium unavailable in this sandbox) and all match; summarize() keeps the last message_end usage, matching the end-row tokens the miner joins against; lead reaches the prompt; state/ is gitignored so the redacted report is never pushed.

@Alex1990

Copy link
Copy Markdown
Owner

Second-round review — resolution and corrected figures

Corrected numbers. The figures in the body and in the earlier resolution table were
produced by the pre-fix classifier and no longer reproduce from the head. Re-mined here
with the fixed code, --limit 100 over the 100 newest artifacts:

[sessions] 105 sessions, 4723 tool calls, 110 failures, 2 engine faults,
           160,399,055 tokens processed, artifacts 100/100 read
class failures sessions
command-failed 58 41
git 16 12
missing-skill 11 9
edit-mismatch 11 10
gh-field 10 9
tool-error 2 2
gh-flag 2 2

The 38 rows the old build reported and this one does not are phantom: successful
toolResults whose content quoted ENOENT … SKILL.md, fatal: or
Command exited with code N — this spec, past reports and past logs all do that.
classifyFailure now returns null unless isError === true. The earlier
"29 git failures in 19 sessions" was one such over-count; 16 in 12 is the real one.

A1 (this round). recorded and outcomes joined the run end row once per
session
, so every pr-review run (two reviewers, one runId, one row that already sums
both) was counted twice. uniqueEnds now dedupes by runId before summing, for the stage
table, the corpus totals and the outcome counts. Spec covers it: two sessions sharing a
runId with one 300-token end row → recordedTokens: 300, joinedSessions: 1,
outcomes: { rejected: 1 }. recorded is still not restated as a multiplier in the
body because a local checkout carries only the 2026-09 run rows — the comparison is
computed where the R2 state is pulled (the CI run), and the report says
Recorded-token comparison unavailable rather than X vs 0.

Other nits fixed. Parsed-but-unkept files are now reported
(N file(s) parsed → M loop session(s), with the non-loop count); the no-join case says
Recorded-token comparison unavailable; README.md's redaction pattern is corrected to
(KEY|TOKEN|SECRET|PASSWORD)=…; command-failed is line-anchored like the rest (the
isError gate is still what makes it sound, but a future un-gated caller should not
regress).

Verification. vitest run test/sessions.spec.ts → 9 passed (node mode; chromium is
not installed here). The repo-wide gates in the body were re-run in the sandbox by
Reviewer B and passed.

Reviewer B's remaining notes are accepted as documented limitations: redaction is
best-effort credential shapes (the report stays in gitignored state/), and
spawnSync downloads are sequential by design for a weekly timer.

@github-actions

Copy link
Copy Markdown
Contributor Author

Loop pr-review — approve ✅ — two isolated reviewers

Reviewer a — correctness & regression (model deepseek/deepseek-v4-flash, session /home/runner/work/_temp/pi-sessions/reviewer-a): approve

Reviewer A — Correctness & regression: approve

Head 1bc909e. PR-contract gate: pass (What/why, Proof it works, Risk tier + AI role, Review focus all present). Test-integrity gate: pass — test/sessions.spec.ts is new (9 tests), no test deleted/skipped/rewritten, no lint/format/type/coverage or tsconfig setting relaxed, no signer oracle touched, nothing under src/.

What I re-ran on head (chromium installed locally; integration specs excluded as the body states): pnpm lint 0/0 (91 files), pnpm fmt:check clean, pnpm build 5.80s, pnpm check:types exit 0, vitest --exclude 'test/*-integration.spec.ts' --exclude 'test/index.spec.ts' → 20 files / 263 tests passed, test/sessions.spec.ts 9/9.

The central claim holds, measured rather than assumed. For artifact loop-sessions-39637508252 (run r-20261021-164531-r24l, two reviewer sessions) the miner reports final context 153,771 and the run end row is "tokens": 153771 — so the end row is the last call's context size and the one-row-per-run join is right for pr-review. A fresh --limit 100 mine over the real store reproduces the body's shape: 100/100 artifacts read across 2 API pages, 105 sessions, 4,723 tool calls, 2 engine faults (same two sweep runs, both recorded completed), 11 missing-skill reads of skills/pr-review|feature/SKILL.md, 16 git shallow-clone failures, 23.1× processed-vs-recorded. The isError gate is visible in the data: failures fell from the body's 148 to 110 on a larger corpus.

No L1/L2 problems. Eight L3 nits, none of which I would block on:

  1. [L3] PR #82 body "Proof it works" — stale for head 1bc909e: body says 258 tests and "4 tests" in test/sessions.spec.ts; head is 263 tests / 20 files and 9 tests. The headline 148 failures (3.3%) predates the isError gate added in the same PR — a fresh mine gives 110 of 4,723 (2.3%), 105 sessions, 160,399,055 processed tokens. — Re-run the gates and miner on head and paste the current numbers.
  2. [L3] scripts/loop/shared/sessions.mjs:374-376 — window.to is the latest session start but is printed as "Window: from → to"; measured …→16:51:11.395 while the last session row is 16:55:17.232. — Use the latest endedAt (fallback startedAt) or label it "session starts".
  3. [L3] scripts/loop/shared/sessions.mjs:361 — recordedTokens sums r.tokens with null as 0, so joined runs without a token value (2 of 106 end rows since 2026-09-10, more in older history) inflate the multiplier while still counting in joinedSessions. — Disclose or exclude rows without tokens from the denominator.
  4. [L3] scripts/loop/sessions.mjs:67-74, :200-203 — readAllRunRows uses readJsonl, which throws on any malformed line, so one torn run file discards all run rows and the report then claims "no session joined a run end row" (false). Reproduced with one truncated line: joined: 0. — Parse per file/line with try/catch, as parseSessionText already does, and say "run rows unreadable (K unparsed)".
  5. [L3] scripts/loop/sessions.mjs:77 — selectArtifacts is exported but untested; expiry/--since/--limit/ordering and the pagination claim have no test. — Add cases for expired filtering, the since cutoff, ordering and limit.
  6. [L3] scripts/loop/sessions.mjs:110 — the download spawnSync('gh', …) sets no timeout and runs before runPi starts its 55-min budget inside a 60-min job; a stalled gh eats the weekly retro instead of failing open. — Add a per-artifact timeout or a collection deadline.
  7. [L3] skills/retro/SKILL.md:35-38 — "use the mining report's processed-token totals for cost" recommends a proxy that includes cacheRead (sample: input 2,264 + output 105 + cacheRead 3,328 = 5,697 totalTokens), while the report already carries est. cost from per-call usage.cost.total ($1.835 over the sample). — Point step 2 at the cost column, keep processed tokens as "work done".
  8. [L3] skills/retro/SKILL.md:49-51 — the new step 0/3 obligation (every signal attributed or dismissed) is not restated in "Done when", so it is not enforceable. — Add it.

Merging remains the owner's call; skills/retro/SKILL.md is a norms-layer proposal.

Reviewer b — standards, safety & maintainability (model deepseek/deepseek-v4-pro, session /home/runner/work/_temp/pi-sessions/reviewer-b): approve

Reviewer B (standards, safety & maintainability) — verdict: approve.

Contract gate: pass (all four author sections present, non-empty). Test-integrity gate: pass (only a new test file added; no test deleted/skipped, no assertion or gate config weakened; signer oracles untouched). Re-ran the same non-integration suite as the body: 20 files, 263 passed.

Problems (L3 only, optional):

  1. [L3] package.json:47-49 — the two new modules scripts/loop/sessions.mjs and scripts/loop/shared/sessions.mjs (~810 of the 1162 added lines) sit outside the oxlint src test / oxfmt --check src test gate, so the Proof-it-works lint/fmt line does not cover the new code — either extend lint/fmt to scripts/loop, or note the gap in the body.
  2. [L3] PR body "Proof it works" — numbers are one revision stale: "4 tests" for test/sessions.spec.ts and "258 passed", vs the head's 9 tests in that file and 263 passed (verified). Harmless understatement; refresh the numbers so the evidence matches the commit under review.

Safety/standards checks that passed: redaction covers the loop's real credential shapes before any text reaches the report/JSON; no shell interpolation (spawnSync argv only, no shell: true); no zip-slip (unzip -p reads members to memory, never extracts); writes confined to state/reports/ plus a cleaned-up tmp dir; the entry.mjs hook is retro-scheduled-only and fail-open with an explicit "no report ≠ no signals" prompt line; no secrets in the diff (fixtures are synthetic); chore(loop) commit type correct.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Save every session created by agent in loop action

1 participant