Skip to content

The worker is asked for a handover before the context window fills, by a provider mod the policy switches

The worker is asked for a handover before the context window fills, by a provider mod the policy switches

majordomus://adr/adr-0123proposed sessionhandoverprovider

it references

  • put_in_force share/mods/claude-code/majordomus-handover/hooks/register.ts (file)
  • put_in_force adr-0015
  • put_in_force adr-0052
  • put_in_force docs/claims/session-lifecycle.md (document)
  • put_in_force lib/capture.sh (implementation)
  • put_in_force test/cases/1016_the_worker_is_asked_before_compaction.sh (test)

referenced by

123. The worker is asked for a handover before the context window fills, by a provider mod the policy switches

Context

The session lifecycle records what a compaction is about to discard. PreCompact derives a checkpoint, and SessionEnd derives a continuation record. Both are derived, and the reason is stated where they are written: the worker is not asked anything at that moment and could not answer if it were. The provider fires PreCompact when the window is already full, and the writing turn a handover needs (reading git, running check, composing three sections) no longer fits.

So the record the next worker resumes from is assembled from the task record, the ledger and git. It does not carry what only the worker knew: which hypothesis was being tested, which command was about to run, what the last failure meant. That is the part a compaction loses.

The worker can be asked earlier, while there is still room. Nothing in the lifecycle can do the asking. No provider event fires as the window fills. The fill is measured inside the conversation, on every response, and only code running there sees it. Claude Code exposes that measurement to a mod (session.measure) and lets the mod queue a prompt ($.prompt.submit). It loads a mod by itself from a folder inside the repository (.claude/skills/<name>). A shim answers an event, and this is not an event, so the request cannot be a shim.

Decision

capture install writes a provider mod beside the shims. The mod is share/mods/claude-code/majordomus-handover, installed to .claude/skills/majordomus-handover. It is driven by a table in lib/capture.sh (MJ_CAPTURE_MODS), one line per mod: the provider, the name, the folder and the policy key.

The episode boundary stays where ADR 0015 put it, in the provider's hooks. The mod opens and closes nothing and replaces no derived record.

The mod does one thing. In a repository it can see is supervised (.ai/manifest.yaml at the session's root), it asks the worker for a majordomus handover once the window crosses a line. The line is a fixed headroom short of the window or a share of it, whichever comes first, because the writing turn costs a fixed amount and not a share. Both are constants of the mod (HEADROOM_TOKENS and MAX_FILL in its hooks module), and this record states neither figure. The request names the three required sections, says to read the live state first, and says to stop afterwards. It is asked once per window. It re-arms when the fill falls below half the line, after a compaction or a /clear. It is not asked at all when the worker already wrote a handover in that window. The prompt is queued and starts a turn of its own once the session is idle. A turn that runs into compaction before then is the case PreCompact already covers, so the mod adds no fallback of its own.

The installed folder follows the shims' ownership rules. Its hooks module carries the Written by majordomus capture install marker. A folder without the marker is somebody's own and is left as it is. One with the marker is rewritten when the distribution's copy differs, so an upgrade is repaired by the command that installed it. The provider lays type declarations beside a mod it loaded (.claude-plugin/types/). They are not compared and not tracked. The distribution's test (hooks/register.test.ts, run by claude plugin test) is not installed.

session.handover_on_fill is the switch, declared in the policy schema, the skeleton and the allow-list, and only true turns it on. The mod writes into the adopter's own .claude/ tree, and an absent key is not consent to that. That is the reading F15 gave the keys that commit and push. The skeleton declares true, so a repository that runs init opts in knowingly. An older policy without the key gets nothing, and so does one that cannot be read. capture install converges on the switch in both directions: anything but true removes a folder the tool wrote, and leaves one it did not write. A mod the policy turned off that the provider still loads would be a declaration nothing enforces.

Consequences

  • A Claude Code worker in a supervised repository is asked for its own account of the live state while it can still give one. The derived records at compaction and at the end stay as they were. The mod adds a record and replaces none.
  • The request costs a turn. One handover per window was the measure chosen, not one per threshold crossing within a window.
  • The mod API is marked early access by its vendor and may change between releases. The distribution's own test runs against the engine (claude plugin test), and case 1016 proves the half the repository owns: written, idempotent, repaired, switched and never taken from its owner. No case runs the mod inside a live provider session. The load was verified by hand once, with a headless run that logged hooks module majordomus-handover@skills-dir loaded … events: session.measure,tool.call.
  • capture status does not yet report the mod's state. The prompt and session aspects have five states each. A mod aspect would be the third, and it is left for when a second mod or a second provider gives it a shape.

Alternatives rejected

  • A hook on PreCompact that asks the worker. It fires too late for the turn to fit, which is why it derives in the first place.
  • The mod derives a handover itself on session.compact. That duplicates the PreCompact checkpoint the lifecycle already writes, under a second owner.
  • A share of the window alone. A share leaves room in proportion to the window, and the writing turn needs the same room on any window.
  • A user-level install under ~/.claude as the product's form. The mod gates itself on .ai/manifest.yaml, so it would stay silent elsewhere. But no repository's policy would declare it, capture install could not converge on it, and one person's machine is not an adopter's. Such a copy is a person's own, and the marker rules leave it alone. It also loads beside a repository's copy, so the two ask twice.

Where this comes from

canonical file
.ai/repo/adrs/0123-the-worker-is-asked-for-a-handover-before-the-window-fills.md
identity
adr-0123
also served as
MCP resource majordomus://adr/adr-0123 · majordomus entity show adr/adr-0123 · GET /api/v1/entity?uri=majordomus://adr/adr-0123
in the Cockpit
/cockpit/objects/adr/adr-0123

This page is generated from the file above by scripts/generate-site-data; editing the page changes nothing, editing the file changes every surface. The relations above are edges of docs/generated/graph.json, which the executable writes from the same index.

Searches the titles and descriptions of every page this site publishes.