fix(story-mode): a second opinion is a line in the next panel, not a panel of its own

From review round 9: "Claude asks a stronger AI model for a second
opinion" stood as a whole panel on almost every page, and the reader
never learns what it said (the answer is encrypted). An advisor call now
rides on the panel of the step after it (or the last one, when nothing
follows), counted in `secondOpinion`: a small line under the caption,
"after asking a stronger AI model for a second opinion" ("then asks…"
when it came last), and "· after 1 second opinion" in the Director's
cut. Pages lose a panel of noise and keep room for the work.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Codeman maintainer committed 2026-10-18 15:49:45 +02:00
1 parent 555efef53d
commit a80638308b
17 files changed
+137 -240

No files matched your search

@@ -51,24 +51,13 @@
{
"kind": "backup",
"ok": null,
"story": "Claude asks a stronger AI model for a second opinion, but gets no answer.",
"director": "Advisor · a second opinion",
"story": "Claude calls in 2 helpers.",
"director": "Agent · general-purpose · 6 calls inside · after 1 second opinion",
"balloon": null,
"tests": null,
"roundsOk": null,
"printed": null,
"events": 1
},
{
"kind": "backup",
"ok": null,
"story": "Claude sends 1 helper off to work in the background.",
"director": "Agent · general-purpose · 6 calls inside",
"balloon": null,
"tests": null,
"roundsOk": null,
"printed": null,
"events": 1
"events": 2
},
{
"kind": "fix",
@@ -37,27 +37,16 @@
"printed": null,
"events": 2
},
{
"kind": "backup",
"ok": null,
"story": "Claude asks a stronger AI model for a second opinion.",
"director": "Advisor · a second opinion",
"balloon": null,
"tests": null,
"roundsOk": null,
"printed": null,
"events": 1
},
{
"kind": "fix",
"ok": null,
"story": "Claude writes 2 new files.",
"director": "Write ×2 · mathx.py +11 · test_mathx.py +26",
"director": "Write ×2 · mathx.py +11 · test_mathx.py +26 · after 1 second opinion",
"balloon": null,
"tests": null,
"roundsOk": null,
"printed": null,
"events": 2
"events": 3
},
{
"kind": "check",
@@ -186,38 +175,16 @@
"printed": null,
"events": 1
},
{
"kind": "plan",
"ok": null,
"story": "Claude switches to plan mode to think it through.",
"director": "EnterPlanMode",
"balloon": null,
"tests": null,
"roundsOk": null,
"printed": null,
"events": 1
},
{
"kind": "backup",
"ok": null,
"story": "Claude asks a stronger AI model for a second opinion.",
"director": "Advisor · a second opinion",
"balloon": null,
"tests": null,
"roundsOk": null,
"printed": null,
"events": 1
},
{
"kind": "plan",
"ok": null,
"story": "Claude writes a plan: 3 steps.",
"director": "Write · plan file · 3 steps",
"director": "EnterPlanMode · Write · plan file · 3 steps · after 1 second opinion",
"balloon": null,
"tests": null,
"roundsOk": null,
"printed": null,
"events": 1
"events": 3
},
{
"kind": "ask",
+8 -30
View File
@@ -27,37 +27,26 @@
"events": 1
},
{
"kind": "montage",
"kind": "investigate",
"ok": null,
"story": "Claude gets started: runs 3 commands.",
"director": "Bash ×3",
"story": "Claude looks around the project.",
"director": "Bash ×2 · ls -la && git log --oneline -5 2>&1; git status; python3 --…",
"balloon": null,
"tests": null,
"roundsOk": null,
"printed": null,
"events": 3
},
{
"kind": "backup",
"ok": null,
"story": "Claude asks a stronger AI model for a second opinion.",
"director": "Advisor · a second opinion",
"balloon": null,
"tests": null,
"roundsOk": null,
"printed": null,
"events": 1
"events": 2
},
{
"kind": "run",
"ok": null,
"story": "Claude works in the terminal.",
"director": "Bash · mkdir -p /work/cases/story-r6-a/tests",
"director": "Bash ×2 · python3 -c \"import calc_stats\" 2>&1 | tail -1; ls pytest.in… · after 1 second opinion",
"balloon": null,
"tests": null,
"roundsOk": null,
"printed": null,
"events": 1
"events": 3
},
{
"kind": "fix",
@@ -125,23 +114,12 @@
"kind": "save",
"ok": true,
"story": "Claude saves the work: \"implement calc_stats mean and median\".",
"director": "git commit -q -F -",
"director": "git commit -q -F - · then 1 second opinion",
"balloon": null,
"tests": null,
"roundsOk": null,
"printed": null,
"events": 1
},
{
"kind": "backup",
"ok": null,
"story": "Claude asks a stronger AI model for a second opinion.",
"director": "Advisor · a second opinion",
"balloon": null,
"tests": null,
"roundsOk": null,
"printed": null,
"events": 1
"events": 2
},
{
"kind": "complete",
@@ -37,22 +37,11 @@
"printed": null,
"events": 2
},
{
"kind": "backup",
"ok": null,
"story": "Claude asks a stronger AI model for a second opinion.",
"director": "Advisor · a second opinion",
"balloon": null,
"tests": null,
"roundsOk": null,
"printed": null,
"events": 1
},
{
"kind": "loop",
"ok": true,
"story": "The first run fails on purpose, as you asked; the second passes.",
"director": "2 checks · 3 edits · +35 −1 · last: ok",
"director": "2 checks · 3 edits · +35 −1 · last: ok · after 1 second opinion",
"balloon": "All 4 pass.",
"tests": {
"passed": 4,
@@ -63,7 +52,7 @@
true
],
"printed": null,
"events": 5
"events": 6
},
{
"kind": "run",
@@ -27,37 +27,41 @@
"events": 1
},
{
"kind": "montage",
"kind": "investigate",
"ok": null,
"story": "Claude gets started: runs 4 commands.",
"director": "Bash ×4",
"story": "Claude looks around the project.",
"director": "Bash ×2 · ls -la && git status 2>&1 | head; git log --oneline 2>&1 |… · 1 error",
"balloon": null,
"tests": null,
"roundsOk": null,
"printed": null,
"events": 4
"events": 2
},
{
"kind": "backup",
"kind": "run",
"ok": null,
"story": "Claude asks a stronger AI model for a second opinion.",
"director": "Advisor · a second opinion",
"story": "Claude works in the terminal.",
"director": "Bash ×2 · for t in ruff flake8 pyflakes pycodestyle pylint mypy black…",
"balloon": null,
"tests": null,
"roundsOk": null,
"printed": null,
"events": 1
"printed": [
"name rc=1",
"…@…",
"email rc=0"
],
"events": 2
},
{
"kind": "fix",
"ok": null,
"story": "Claude writes 3 files.",
"director": "Bash · pyproject.toml +11 · .gitignore +11 · __init__.py +3",
"director": "Bash · pyproject.toml +11 · .gitignore +11 · __init__.py +3 · after 1 second opinion",
"balloon": null,
"tests": null,
"roundsOk": null,
"printed": null,
"events": 1
"events": 2
},
{
"kind": "mission",
@@ -131,7 +135,7 @@
"kind": "check",
"ok": true,
"story": "The tests pass.",
"director": "python3 -m pytest -q -p no:cacheprovider 2>&1 → ok · 63 passed",
"director": "python3 -m pytest -q -p no:cacheprovider 2>&1 → ok · 63 passed · then 1 second opinion",
"balloon": null,
"tests": {
"passed": 63,
@@ -139,18 +143,7 @@
},
"roundsOk": null,
"printed": null,
"events": 1
},
{
"kind": "backup",
"ok": null,
"story": "Claude asks a stronger AI model for a second opinion.",
"director": "Advisor · a second opinion",
"balloon": null,
"tests": null,
"roundsOk": null,
"printed": null,
"events": 1
"events": 2
},
{
"kind": "complete",
@@ -37,22 +37,11 @@
"printed": null,
"events": 2
},
{
"kind": "backup",
"ok": null,
"story": "Claude asks a stronger AI model for a second opinion.",
"director": "Advisor · a second opinion",
"balloon": null,
"tests": null,
"roundsOk": null,
"printed": null,
"events": 1
},
{
"kind": "loop",
"ok": true,
"story": "Fix and test, 2 rounds: a test fails and Claude fixes it, then a layout check fails and Claude fixes that; everything passes. Claude saves the work in 4 commits along the way.",
"director": "3 checks · 5 edits · +218 −1 · last: ok",
"director": "3 checks · 5 edits · +218 −1 · last: ok · after 1 second opinion",
"balloon": "All 19 tests pass, ruff check is clean, and the output matches real wc column for column.",
"tests": {
"passed": 19,
@@ -64,7 +53,7 @@
true
],
"printed": null,
"events": 7
"events": 8
},
{
"kind": "save",
@@ -77,22 +66,11 @@
"printed": null,
"events": 1
},
{
"kind": "backup",
"ok": null,
"story": "Claude asks a stronger AI model for a second opinion.",
"director": "Advisor · a second opinion",
"balloon": null,
"tests": null,
"roundsOk": null,
"printed": null,
"events": 1
},
{
"kind": "run",
"ok": null,
"story": "Claude works in the terminal.",
"director": "Bash · python3 wcount.py nope nope2; echo \"exit=$?\"; wc nope nope2…",
"director": "Bash · python3 wcount.py nope nope2; echo \"exit=$?\"; wc nope nope2… · after 1 second opinion",
"balloon": null,
"tests": null,
"roundsOk": null,
@@ -101,7 +79,7 @@
"wcount: nope2: No such file or directory",
"0 0 0 total"
],
"events": 1
"events": 2
},
{
"kind": "complete",
@@ -48,22 +48,11 @@
"printed": null,
"events": 2
},
{
"kind": "backup",
"ok": null,
"story": "Claude asks a stronger AI model for a second opinion.",
"director": "Advisor · a second opinion",
"balloon": null,
"tests": null,
"roundsOk": null,
"printed": null,
"events": 1
},
{
"kind": "loop",
"ok": true,
"story": "Test first: the first run fails as planned, the second passes.",
"director": "2 checks · 4 edits · +151 −1 · last: ok",
"director": "2 checks · 4 edits · +151 −1 · last: ok · after 1 second opinion",
"balloon": "All 19 test cases pass (13 test functions), and ruff check and ruff format both come back clean.",
"tests": {
"passed": 19,
@@ -74,7 +63,7 @@
true
],
"printed": null,
"events": 6
"events": 7
},
{
"kind": "fix",
@@ -117,7 +106,7 @@
"kind": "check",
"ok": true,
"story": "The tests pass.",
"director": "python3 -m pytest -q -p no:cacheprovider) → ok · 19 passed",
"director": "python3 -m pytest -q -p no:cacheprovider) → ok · 19 passed · then 1 second opinion",
"balloon": null,
"tests": {
"passed": 19,
@@ -125,18 +114,7 @@
},
"roundsOk": null,
"printed": null,
"events": 1
},
{
"kind": "backup",
"ok": null,
"story": "Claude asks a stronger AI model for a second opinion.",
"director": "Advisor · a second opinion",
"balloon": null,
"tests": null,
"roundsOk": null,
"printed": null,
"events": 1
"events": 2
},
{
"kind": "complete",
@@ -37,27 +37,16 @@
"printed": null,
"events": 2
},
{
"kind": "backup",
"ok": null,
"story": "Claude asks a stronger AI model for a second opinion.",
"director": "Advisor · a second opinion",
"balloon": null,
"tests": null,
"roundsOk": null,
"printed": null,
"events": 1
},
{
"kind": "save",
"ok": true,
"story": "Claude writes 2 files and saves the work: \"add package.json and index.js with add and slugify\".",
"director": "git commit -q -m \"feat: add package.json and index.js with… · writes 2 files",
"director": "git commit -q -m \"feat: add package.json and index.js with… · writes 2 files · after 1 second opinion",
"balloon": null,
"tests": null,
"roundsOk": null,
"printed": null,
"events": 1
"events": 2
},
{
"kind": "backup",
@@ -262,22 +251,11 @@
"printed": null,
"events": 1
},
{
"kind": "backup",
"ok": null,
"story": "Claude asks a stronger AI model for a second opinion.",
"director": "Advisor · a second opinion",
"balloon": null,
"tests": null,
"roundsOk": null,
"printed": null,
"events": 1
},
{
"kind": "check",
"ok": true,
"story": "Claude writes 4 files; the tests pass.",
"director": "npm test 2>&1 → ok · 8 passed · writes 4 files",
"director": "npm test 2>&1 → ok · 8 passed · writes 4 files · after 1 second opinion",
"balloon": "All 8 tests pass, the README examples give the same outputs, and the barrel exports only add and slugify.",
"tests": {
"passed": 8,
@@ -285,7 +263,7 @@
},
"roundsOk": null,
"printed": null,
"events": 1
"events": 2
},
{
"kind": "save",
@@ -48,27 +48,16 @@
"printed": null,
"events": 2
},
{
"kind": "backup",
"ok": null,
"story": "Claude asks a stronger AI model for a second opinion.",
"director": "Advisor · a second opinion",
"balloon": null,
"tests": null,
"roundsOk": null,
"printed": null,
"events": 1
},
{
"kind": "fix",
"ok": null,
"story": "Claude writes 4 new files.",
"director": "Write ×4 · csvstat.py +115 · sample.csv +6 · test_csvstat.py +129",
"director": "Write ×4 · csvstat.py +115 · sample.csv +6 · test_csvstat.py +129 · after 1 second opinion",
"balloon": null,
"tests": null,
"roundsOk": null,
"printed": null,
"events": 4
"events": 5
},
{
"kind": "check",
+4 -26
View File
@@ -51,27 +51,16 @@
],
"events": 1
},
{
"kind": "backup",
"ok": null,
"story": "Claude asks a stronger AI model for a second opinion.",
"director": "Advisor · a second opinion",
"balloon": null,
"tests": null,
"roundsOk": null,
"printed": null,
"events": 1
},
{
"kind": "fix",
"ok": null,
"story": "Claude writes 3 new files.",
"director": "Write ×3 · timer.js +42 · timer.test.mjs +74 · index.html +120",
"director": "Write ×3 · timer.js +42 · timer.test.mjs +74 · index.html +120 · after 1 second opinion",
"balloon": null,
"tests": null,
"roundsOk": null,
"printed": null,
"events": 3
"events": 4
},
{
"kind": "check",
@@ -91,23 +80,12 @@
"kind": "save",
"ok": true,
"story": "Claude saves the work: \"add pomodoro timer with tests\".",
"director": "git commit -q -m \"feat: add pomodoro timer with tests\" -m \"…",
"director": "git commit -q -m \"feat: add pomodoro timer with tests\" -m \"… · then 1 second opinion",
"balloon": null,
"tests": null,
"roundsOk": null,
"printed": null,
"events": 1
},
{
"kind": "backup",
"ok": null,
"story": "Claude asks a stronger AI model for a second opinion.",
"director": "Advisor · a second opinion",
"balloon": null,
"tests": null,
"roundsOk": null,
"printed": null,
"events": 1
"events": 2
},
{
"kind": "complete",
+20
View File
@@ -866,6 +866,26 @@ describe('story-mode.js overlay', () => {
);
});
it("a second opinion is a line in the next panel's caption, not a panel of its own", async () => {
const advisor = {
type: 'assistant',
timestamp: '2026-10-18T09:41:20.000Z',
message: {
content: [
{ type: 'server_tool_use', id: 'srv1', name: 'advisor', input: {} },
{ type: 'advisor_tool_result', tool_use_id: 'srv1', content: { type: 'advisor_redacted_result' } },
],
},
};
boot(storyOf([prompt('fix it'), advisor, ...edit('e1', '/work/demo-shop/a.py', ['+a = 1']), end(1000)]));
app.openStoryMode(SID);
await settle();
expect($$('.story-panel').map((p) => p.dataset.kind)).toEqual(['mission', 'fix', 'complete']);
const note = $('.story-panel[data-kind="fix"] .story-cap-note')!;
expect(note.hidden).toBe(false);
expect(note.textContent).toBe('after asking a stronger AI model for a second opinion');
});
it('the final panel counts the commits of the whole turn, and says when the work was uploaded', async () => {
boot(
storyOf([
+4 -2
View File
@@ -1256,8 +1256,10 @@ describe('buildStoryPage', () => {
},
end(1000),
]);
const backup = page.panels.find((p) => p.kind === 'backup')!;
expect(backup.caption.story).toBe('Claude asks a stronger AI model for a second opinion.');
// Not a step of its own: it rides on the panel before it, here the mission (nothing follows).
expect(page.panels.map((p) => p.kind)).toEqual(['mission', 'complete']);
expect(page.panels[0]).toMatchObject({ secondOpinion: 1 });
expect(page.panels[0].caption.director).toMatch(/then 1 second opinion$/);
expect(page.events[1]).toMatchObject({ tool: 'Advisor', ok: true, helper: { name: 'advisor' } });
expect(page.events[1].helper!.result).toMatch(/encrypted/);
expect(page.helpers).toBe(0);