Skip to content

Sky: a haiku that was not written leaves the one before showing - #680

Merged
lydakis merged 6 commits into
mainfrom
claude/wave1-haiku
Nov 5, 2026
Merged

lydakis merged 6 commits into
mainfrom
claude/wave1-haiku

Conversation

@lydakis

@lydakis lydakis commented Nov 5, 2026

Copy link
Copy Markdown
Owner

Wave 1 item 9 (D1, #669): an empty haiku left Sky blank.

Cause

Kept::claim cleared the kept haiku (lines, at_ms) when an agent took the title, before its copy wrote anything. If the copy's reply was empty, refused, failed or ran long, write returned None and nothing else touched the file. haiku.json then held "lines": []: every sky that opened showed no haiku (it needs three lines), and the holder could not claim again for the rest of its hold.

Change

  • The claim is kept apart from the haiku shown. Kept.claim is (by, since_ms) and the hold the cap and the "once a hold" rule count. by/lines/at_ms/since_ms are the last haiku written, and only publish changes them.
  • A failed write is a typed failure. haiku::write returns Result<[String; 3], Failed>, with Failed::Unreached(detail) (daemon or fork), Failed::Unanswered(detail) (still running past the wait, failed, or cut short) and Failed::NotAHaiku (an empty reply among them). The sky records it under unwritten: {by, at_ms, why, detail} in haiku.json, but only while that claim is still the current one.
  • Never a haiku with no lines. clean and publish take [String; 3], so a haiku of the wrong length can't be built.
  • The claim stays spent. A copy that keeps failing costs no more than the day's cap, and the same hold is not tried again. The busiest agent takes the slot again at the next change of title.
  • Old files repair themselves on read. A file an earlier claim had emptied shows the last of the day's past haikus, and the claim it held stays spent.
  • Heard::Read boxes Kept (clippy large_enum_variant).

Restart and replay

haiku.json gains claim and unwritten. Older files load as described above. Replays read past, which never held an empty haiku.

Tests (fail before, pass after)

  • a_claim_that_writes_no_haiku_leaves_the_one_before_showing: a writes a haiku, b claims and fails with NotAHaiku, and every load still shows a's haiku. The count stays spent, b is not tried again for the same hold, and a late failure for a claim that has been taken over records nothing.
  • a_file_whose_claim_cleared_the_haiku_shows_the_last_one_written: a 0.8.0 file with "lines": [] shows the last past haiku.
  • clean("") and whitespace-only replies are not haikus.
  • cargo test -p sonny, clippy and fmt pass.

There is no change on screen when a haiku is written, so there are no screenshots. The only visible effect is that the previous haiku stays up where there used to be a blank. FEATURES.md /sky row updated.


Generated by Claude Code

A claim cleared the kept haiku before the copy wrote one, so a reply that
was empty, refused or failed saved `"lines": []`: every sky opened blank,
and the same holder could not claim again during its hold. The claim is
now kept apart from the haiku shown; a write that fails is a typed
`Failed` (unreached, unanswered, not a haiku) recorded under `unwritten`,
and only three lines can be published. A file an earlier claim had
emptied shows the last of the day's haikus.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jkdGG873rfqV9CdHz7KrE
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 5, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-05T02:07:43.178557Z eaf1c07 New commits
🔒 Security Review ✅ Completed 2026-11-05T01:19:40.504742Z 2e30209 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2e30209778

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread sonny/src/haiku.rs Resolved
Comment thread sonny/src/haiku.rs Resolved
…e claim

The 0.8.0 `haiku.json` (no `claim`, a cleared haiku) is rewritten once by
a registered `upgrade::once` migration, retiring at 0.10.0, so the reader
knows only the current form. A sky opened while a claim's haiku is
missing restores that claim's holder and hold, not the last writer's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jkdGG873rfqV9CdHz7KrE

lydakis commented Nov 5, 2026

Copy link
Copy Markdown
Owner Author

CI rust failed on mux::tests::a_view_that_opens_reads_where_the_windows_sit_now (left: (None, Some(2)), right: (Some(1), None)). This PR doesn't touch mux.rs. The test sends a close-and-open job to the worker and then reads the cached places, expecting the job not to have run yet. Here the worker ran it first, so this is a timing race in the test itself. It passes 5 of 5 runs locally on this head. I can't re-run CI from here, and fixing that test is outside this PR. The next push here will run it again.


Generated by Claude Code

@lydakis lydakis mentioned this pull request Nov 5, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 071f7790c5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread sonny/src/haiku.rs Outdated
let Some(_lock) = store.lock() else {
return false;
};
let mut all = store.all();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reject unreadable or malformed haiku files before migrating

If an existing haiku.json cannot be read or parsed, Store::all() silently returns an empty map. This migration then overwrites the file with {} and records haiku-claims as complete, permanently discarding every daemon's poems and claim counts merely by opening a sky. Read and validate the migration input fallibly, returning false without rewriting or marking completion when conversion is impossible. The root migration guidance explicitly requires rejecting unconvertible data rather than silently resetting it.

AGENTS.md reference: AGENTS.md:L27-L30

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Real. Fixed in the commit before eaf1c07: upgrade now reads haiku.json strictly under the lock. If it can't be read or isn't a JSON object, the migration returns false without writing and without recording itself done, so the next sky tries again. The test a_file_that_cannot_be_read_is_left_as_it_is_and_upgraded_once_it_can checks that a truncated file is left byte for byte, and is upgraded once it reads.


Generated by Claude Code

Comment thread sonny/src/sky.rs
self.title.rule = rule;
}
self.heed(kept, now);
self.heed(*kept, now);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Restore the claim holder when returning from replay

If A's displayed poem is 20 minutes old and B claimed the title but failed one minute ago, returning from replay replaces B with A as holder, allowing a busy challenger to spend another claim during B's ten-minute hold. Unlike the corrected View::with initialization, View::replay(None) clears shown, and the next Heard::Read calls heed, which still restores the title from kept.by and kept.since_ms rather than kept.claim. This replay-to-live path is fresh evidence distinct from the previously fixed opening path. Update heed to synchronize the claim holder independently of the displayed poem.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Real. Fixed in the same commit: heed no longer takes the title from the haiku shown. The view keeps the last claim it heard (claimed). A read whose kept.claim differs moves the title to kept.holder(now, HOUR) (set directly if the agent is here, or as heir otherwise), whatever haiku is new or shown. A replay doesn't touch the title, so coming back keeps B. The test a_haiku_another_sky_wrote_shows_here_and_its_writer_holds_the_title now has another sky claim and write nothing, then checks that B holds the title with A's haiku still up, both on the next read and after shown is reset as replay(None) resets it.


Generated by Claude Code

…ku file is not upgraded

A read of haiku.json moved the title only with a new haiku, and from the
haiku's writer, so a sky back from a replay handed the title back to the
writer of the haiku shown over the claim that wrote nothing. Now a claim
that differs from the last one heard moves the title, whatever is shown.

The haiku-claims migration read the file leniently and wrote an empty one
over a file it could not parse, recording itself done. Now it leaves such
a file as it is and tries again at the next sky.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jkdGG873rfqV9CdHz7KrE
lydakis pushed a commit that referenced this pull request Nov 5, 2026
`a_view_that_opens_reads_where_the_windows_sit_now` sent a job that closes
and opens windows, then read the places kept from before it. The worker
thread could run that job and refresh the kept places first, so the test
failed now and then (seen on #675 and #680). A job ahead of it now holds
the worker until the read is done, as the naming test beside it does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jkdGG873rfqV9CdHz7KrE
(cherry picked from commit f38b3c0)
lydakis added a commit that referenced this pull request Nov 5, 2026
`a_view_that_opens_reads_where_the_windows_sit_now` sent a job that closes
and opens windows, then read the places kept from before it. The worker
thread could run that job and refresh the kept places first, so the test
failed now and then (seen on #675 and #680). A job ahead of it now holds
the worker until the read is done, as the naming test beside it does.


Claude-Session: https://claude.ai/code/session_013jkdGG873rfqV9CdHz7KrE

Co-authored-by: Claude <noreply@anthropic.com>
@lydakis
lydakis merged commit 7b2c345 into main Nov 5, 2026
10 checks passed
@lydakis
lydakis deleted the claude/wave1-haiku branch November 5, 2026 02:27
lydakis added a commit that referenced this pull request Nov 5, 2026
…e until work resumes (#675)

* Sonny tool host: a call that ran is never run again because its result was not recorded

The host dropped a call from its active map whatever came of handing in
its result, so when the daemon did not take the result (a lost
connection, a store that could not write), the next sweep found the call
still waiting and ran it again: a second write, message or task.

A call answered is now kept with its result until the daemon takes it or
says the call wants none (answered already, out of time, its turn over),
handed in again every two seconds, and never run again meanwhile. The
host's own state is in memory: a host that restarts between running a
call and recording it still runs it again, which the execution records
of the next release cover.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jkdGG873rfqV9CdHz7KrE

* Sonny tool host: a pause stops what Sonny's calls run, and starts none until work resumes

A pause parks every turn and stops the daemon's own processes, but the
tool host heard only of calls started and turns finished: an exec script
ran on after a pause and could make further MCP calls, ones that need no
approval among them.

The host now hears the pause: what each call still runs is stopped, so
a script makes no call after it (one already made may have had effects,
which the daemon's daemon_paused result tells its agent), and no waiting
call starts while it holds, also for a host that starts or connects
again while paused. Resuming lets them start.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jkdGG873rfqV9CdHz7KrE

* Sonny tool host: a result the daemon did not take wakes the host to hand it in again

A call that ended after a sweep, whose result was then lost, waited for the
30 s sweep. Its task now wakes the serving loop. The declared-tools row
records the guarantee and its limit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jkdGG873rfqV9CdHz7KrE

* Sonny tool host: a listing read across a stop starts none of its calls

A pause (or a turn's end) that stopped a call while the waiting calls were
listed left that call on the listing with no job to say it ran, so the
sweep started it again after the resume. The host counts its stops, and a
sweep whose listing a stop came during admits nothing from it and looks
again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jkdGG873rfqV9CdHz7KrE

* Sonny tool host: only a stop that let a call go makes a listing stale

Every turn's end counted, so on a busy store a sweep could keep finding
its listing stale.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jkdGG873rfqV9CdHz7KrE

* Tests: the approvals test change belongs to the judge handover branch, not this one

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jkdGG873rfqV9CdHz7KrE

* Sonny tool host: every pause invalidates a listing read across it

A sweep admitted none from a listing read across a pause only when the
pause stopped a running call. With nothing running yet, a call named on
an earlier page could start after the resume though the daemon had
already answered it `daemon_paused`. Each pause now counts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jkdGG873rfqV9CdHz7KrE

* Tool host: a call a pause stops is answered, and losing the events stops calls too

A call the host stopped for a pause was forgotten, so a turn the daemon had
parked on it (past the answer hold) listed it again after the resume and it
ran twice. Now a stopped call keeps an answer saying why (`daemon_paused`)
and is handed in like any result. Losing the connection that reads the
events stops the calls too (`events_lost`) and holds until the next
greeting, since a pause while it is down reaches no event.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jkdGG873rfqV9CdHz7KrE

* Sonny tests: hold the mux worker until the kept places are read

`a_view_that_opens_reads_where_the_windows_sit_now` sent a job that closes
and opens windows, then read the places kept from before it. The worker
thread could run that job and refresh the kept places first, so the test
failed now and then (seen on #675 and #680). A job ahead of it now holds
the worker until the read is done, as the naming test beside it does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jkdGG873rfqV9CdHz7KrE
(cherry picked from commit f38b3c0)

---------

Co-authored-by: Claude <noreply@anthropic.com>
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.

2 participants