feat(traces): truthful recover-then-succeed outcomes + phenotype insight fingerprint (PHNX-3387, PHNX-3327) - #3240
feat(traces): truthful recover-then-succeed outcomes + phenotype insight fingerprint (PHNX-3387, PHNX-3327)#3240muqsitnawaz wants to merge 5 commits into
Conversation
|
CI is green (test ✅, gitleaks ✅). Handing off to the owner/orchestrator for independent non-author review and merge. |
…ght fingerprint (PHNX-3387, PHNX-3327) - sync.ts: derive outcome from final tool step, not errorCount; keep surfacedToolFailures on completed runs. - insights.ts: add phenotype to FailureSignature and computeInsights grouping; existing null-phenotype signatures stay stable. - sync.ts: compute per-session phenotype from SessionDetail and thread it into computeInsights so the index shard stays incremental. - Add recover-then-succeed fixture and update tests/CHANGELOG fragments.
bc832c7 to
56b515c
Compare
|
REQUEST CHANGES Independent non-author review (PR body is empty — no description, no before/after evidence; flagged as a blocker below per repo convention that a user-visible behavior change needs an evidence trail). Both defects below were reproduced with real code execution against the PR branch ( Conformance vs the tickets
BLOCKER 1 — Phenotype dimension only covers this run's incremental batch, silently fragmenting existing failure clusters at the exact "10k+ session scale" the docs claim it doesn't affectFile: // sync.ts:191 — new, empty every syncTraces() call
const phenotypes = new Map<string, FailurePhenotype | null>();
// sync.ts:218 — only set for rows in `limited` (this run's incremental batch:
// new/changed sessions since the watermark, plus retry-worthy failures)
phenotypes.set(row.id, classifyPhenotype(detail));
...
// sync.ts:253 — but buildIndexShard/computeInsights run over `allRows`,
// the FULL historical corpus for this device, not `limited`
const shard = buildIndexShard(allRows, device, owner, prevShard, phenotypes);// insights.ts:173-174
const phenotype = phenotypes?.get(sessionId) ?? null;
const groupKey = `${call.tool}\0${cause}\0${key}\0${phenotype ?? ''}`;
Practical effect: a session synced last week has no entry in this run's Reproduced ( Fix: persist per-session phenotype the same way BLOCKER 2 —
|
Code Review — PHNX-3387 / PHNX-3327Verdict: CHANGES REQUESTED CI is green. The core logic — SHOULD — Phenotype map covers only this sync batch; carryover sessions silently cluster as phenotype-nullFile: // line 191 — phenotypes built only from `limited` (this sync's upload batch)
const phenotypes = new Map<string, FailurePhenotype | null>();
// line 218 — populated only for rows in this sync run
phenotypes.set(row.id, classifyPhenotype(detail));
// line 240–242 — allRows = ALL sessions in the DB, no watermark, no limit
const allRows = db
.prepare('SELECT * FROM sessions WHERE machine = ? OR machine IS NULL')
.all(device) as SyncRow[];
// line 253 — phenotypes passed for allRows, but only covers limited
const shard = buildIndexShard(allRows, device, owner, prevShard, phenotypes);Inside const phenotype = phenotypes?.get(sessionId) ?? null;
const groupKey = `${call.tool}\0${cause}\0${key}\0${phenotype ?? ''}`;
The previous AGENTS.md was explicit: "patterns do not yet carry a phenotype … classifying that needs the full derived trajectory, which is only ever materialized per-session during upload, not cached the way per-session insight facets are." That caveat was correct — and the design principle it named still applies. The new code resolves it for the current batch but silently regresses all carryover sessions to the same state the old code had for everyone. Failure scenario: A user runs Fix options:
Either way, the AGENTS.md comment at line 187–190 should acknowledge the scope: "phenotypes populated for sessions parsed in this sync run; carryover sessions default to null." SHOULD — AGENTS.md removes the scope-gap disclosure without replacing itFile: Old text (deleted): New text: "When it is available" is technically true but silent about what makes it unavailable (carryover sessions from prior syncs). The old wording was precise about the structural constraint. The new wording implies the constraint is resolved when it is only partially resolved. The CHANGELOG entries ( This is not a docs-vs-code divergence (the code does what the docs say for the current batch), but it is a dishonest truncation of a known limitation that the repo's own convention asks docs to maintain honestly. What clears the verdict
Filtered
|
…essions cluster correctly (PHNX-3327) Adds a `session_phenotypes` stamp-validated DB cache (same mtime+size shape as `session_topics`/`session_insights`). After each sync run, freshly-computed phenotypes are persisted; before `buildIndexShard`, the full corpus phenotype map is read from cache and the fresh batch overlaid. Without this, sessions processed in prior incremental syncs had no phenotype entry in the map, landing in the phenotype=null failure cluster instead of the correct one.
Code Review — CHANGES REQUESTEDReviewer: prix/code-reviewer (manual pass, auto-reviewer paused per AGENTS.md #1767) Conformance against ticket goalsPHNX-3387 (truthful recover-then-succeed outcomes): PARTIAL — the outcome derivation is present but introduces a correctness contradiction with PHNX-3327 (phenotype fingerprint in insight clusters + caching): YES — BLOCKER —
|
…rorCount gate in isPrematureCompletion (PHNX-3387) deriveRunOutcome now requires a non-human-facing ok step strictly after the last error to call a run 'completed' — matching the recoveredAfterErrors predicate used across phenotype.ts. A session ending on AskUserQuestion(ok) after a Bash(error) was incorrectly counted as recovered. isPrematureCompletion drops the `errorCount > 0` short-circuit: `outcome === 'completed'` already guarantees errors were causally recovered from, so keying prematurity off errorCount mislabeled every recover-then-succeed run as premature.
Re-review — PHNX-3387 + PHNX-3327 (commit f1dbe67)Reviewer: prix/code-reviewer (manual pass, auto-reviewer paused per AGENTS.md #1767) Conformance vs the two blockers raised in the previous REQUEST CHANGESBLOCKER 1 — Phenotype map covered only the incremental batchStatus: RESOLVED.
The fragmentation scenario from the prior review (day-1 sessions missing phenotype in day-2 builds) is closed. The BLOCKER 2 —
|
|
Triggering CI re-run by close/reopen |
|
Closing: work landed on main via commit 16f7749 (feat(traces): truthful recover-then-succeed outcome + persisted phenotype fingerprint). That commit covers PHNX-3387 (deriveRunOutcome via recoveredAfterErrors + HUMAN_FACING_TOOLS), PHNX-3327 (session_phenotypes cache), and the isPrematureCompletion errorCount gate removal — everything this PR added, with the deriveRunOutcome implementation using the superior work-signature matching from recoveredAfterErrors. PR superseded. |
feat(traces): truthful recover-then-succeed outcomes + phenotype insight fingerprint (PHNX-3387, PHNX-3327)
What changed
cli/src/lib/traces/sync.ts—buildSessionDetail()now derivesmeta.outcomefrom the final tool step, noterrorCount. A run that hits tool failures but recovers and finishes with a successful final tool step is reported ascompleted, whilesurfacedToolFailuresstill lists every failed step so the Evals console can honestly surface "green run with hidden tool failures."cli/src/lib/traces/insights.ts—FailureSignaturegains aphenotypedimension andcomputeInsights()groups by(tool, cause, normalized-error, phenotype). Existing patterns with no phenotype remain stable.cli/src/lib/traces/sync.ts—buildIndexShard()computes each session's phenotype from the sameSessionDetailit already uploads, then threads aphenotypesmap intocomputeInsights()so the index shard stays incremental and never re-parses transcripts at scale.recover-then-succeed.jsonl, updatedsync.test.tsandinsights.test.ts, and refreshedrich-index.json.cli/AGENTS.mdand queued.changelog/next/PHNX-3387.md+.changelog/next/PHNX-3327.md.Before
sync.tsderived outcome fromerrorCount:A session with one failed
bun testcall followed by a successful recovery haderrorCount = 1, so it was mislabelederroredandsurfacedToolFailuresonly existed when outcome was alreadyerrored— the "green run with hidden failures" case could never fire honestly.After
Running the recover-then-succeed fixture:
The test asserts the run is now
completedwhile still exposing the recovered-from failure:Verification
bun run test -- src/lib/traces/sync.test.ts src/lib/traces/insights.test.ts src/lib/traces/phenotype.test.ts→ 37 passedbun run test -- scripts/gen-changelog.test.ts→ 5 passed (CHANGELOG aggregate in sync)Relates to PHNX-3328, PHNX-3300.