Skip to content

Retire a conversation idle for retire_after - #73

Merged
wu-sheng merged 1 commit into
mainfrom
retention-retire-after
Oct 17, 2026
Merged

wu-sheng merged 1 commit into
mainfrom
retention-retire-after

Conversation

@wu-sheng

Copy link
Copy Markdown
Member

This is the second level of retention. storage.retention.retire_after, -1 and off by default, is how long a conversation may be idle before everything of it is removed, its metadata too, and it is retired. A retired conversation is never collected again, even when its runtime resumes it, so the setting should be longer than the runtime keeps its own history. The writers already pass a retired session over (#72). This change retires sessions, and documents both.

What changes

  • Configuration. retire_after sits beside metadata_only_after, on the same clock: the header time of the newest landed file. It is at least metadata_only_after, which must be set too. With both equal, a conversation is retired with no metadata-only step first. Either rule broken is a configuration error. Only both settings off keeps everything. asz.yaml spells it out.
  • When. At the end of a pass, after the send and before level 1. A session idle for retire_after is never given level 1. It is retired once its chain covers its files, its metrics are derived when the pipeline derives them, and no removal of its files is in progress. That is asked under the session's locks. Sending is not waited for.
  • How. The steps keep a crash or a power loss from losing anything, or landing the session again:
    1. _retired/<session-id>, an empty file, is written and synced, the errors checked, before anything is deleted. Every attempt that deletes anything writes it again.
    2. Each metrics request the receiver does not have is written again from its receipt and synced, a request already there included.
    3. The session's directory and its conversation's are renamed under _removed/ and deleted there. Both directories of each rename are synced, the errors checked, before anything moved is deleted. storage.RemoveDirDurable does this; the scenario remover keeps RemoveDir.
    4. The requests the receiver has are deleted. The root, _conversations/, _removed/ and the spool are synced on every attempt before the state forgets the session.
    5. push.state and metrics.state forget the session. The provider's table marks its lines retired. _langsmith/open.json is not changed.
  • Owner. A session's owner line stays in push.state while a request of it waits in the spool, so the request goes to that owner only.
  • Recovery. Every pass finishes a retirement left unfinished before anything lands, whatever the settings say then. asz parse and asz push do the same first.
  • What a person sees. The pass line counts retired=. The startup says what each level does:
    retention: a conversation idle for 2160h0m0s is retired: everything of it is removed, and it is never collected again, even when its runtime resumes it
    
    The page drops a retired conversation from memory, also one another process retired.
  • Docs. asz.yaml, the configuration page, the storage root (layout, provider states, Retirement, What travels, A scenario root), the command-line page, the scenario guide, the Claude Code, Kiro and LangSmith pages, the changelog (also for Write nothing of a retired session again #72), and CLAUDE.md's invariants.

Tests

  • tests/chain:
    • every scenario retired whole, then its sources offered again with no cursor: nothing is written or sent;
    • the clock: level 1 then level 2, both on the first pass with the levels apart and equal, record times and file times that say otherwise, sources gone;
    • what a retirement waits for: not parsed, not derived, a removal in progress, busy;
    • a stop at each of its 12 points, then a recovery with both settings off;
    • a failed sync at each of the 12 places it is checked, in the pass and in the retry, with a power loss that brought back a deleted request or the directories;
    • a waiting request written again from its receipt and replaced read-only, and one deleted before its write;
    • receipts that are empty or cannot be read;
    • waiting requests and their owner: never sent, another owner, rejected in part, a save that failed after the send;
    • no level 1 for a retired session;
    • the page at each point of a retirement.
  • tests: a LangChain conversation retired, its open.json unchanged, and a later turn not collected.
  • internal/view: the list drops a conversation another process retired.
  • cmd/asz: a pass retires and counts it, the page drops it, recovery is counted, asz parse and asz push finish a retirement first, and the startup lines.
  • internal/config: the two levels together.

Each new guard was proven by reverting it. make test, the JSON v1 step (GOEXPERIMENT=nojsonv2), make scenarios, lint, and the Windows build and vet pass.

@wu-sheng wu-sheng added this to the 0.6.0 milestone Oct 17, 2026
storage.retention.retire_after is the second level of retention. A
conversation idle that long has everything of it removed, its metadata
too, and is retired: it is never collected again, even when its runtime
resumes it. It is read on the same clock as metadata_only_after, the
header time of the newest landed file, and is at least as long. It needs
metadata_only_after set, and equal to it a conversation is retired with
no metadata-only step first. Either rule broken is a configuration error.
Only both settings off keeps everything.

A pass retires at its end, after the send, before level 1, and gives no
level 1 to an idle or a retired session. A session is retired once its
chain covers its files, its metrics are derived when the pipeline
derives them, and no removal of its files is in progress, asked under
its locks. The steps keep a crash or a power loss from losing anything,
or landing the session again:

- The marker _retired/<session>, an empty file, is written and synced,
  the errors checked, before anything is deleted. Every attempt that
  deletes anything writes it again.
- Each metrics request the receiver does not have is written again from
  its receipt and synced, a request already there included.
- The session's directory and its conversation's are renamed under
  _removed/ and deleted. Both directories of each rename are synced, the
  errors checked, before anything moved is deleted.
- The requests the receiver has are deleted. The root, _conversations,
  _removed and the spool are synced on every attempt before push.state
  and metrics.state forget the session, and the provider's table marks
  its lines retired.

A session's owner stays in push.state while a request of it waits in the
spool, so the request goes to that owner only. Every pass finishes a
retirement left unfinished before anything lands, whatever the settings
say then, and asz parse and asz push do the same first. The pass line
counts retired=, the startup says what each level does, and the page
drops a retired conversation from memory, also one another process
retired.
@wu-sheng
wu-sheng force-pushed the retention-retire-after branch from 5092e97 to 2c196f2 Compare October 17, 2026 13:08
@wu-sheng
wu-sheng merged commit 06c5ab2 into main Oct 17, 2026
24 checks passed
@wu-sheng
wu-sheng deleted the retention-retire-after branch October 17, 2026 13:22
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.

1 participant