@tduyng/pi-session
Sessions name themselves, and a turn killed by a usage limit resumes when the limit resets.
Package details
Install @tduyng/pi-session from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:@tduyng/pi-session- Package
@tduyng/pi-session- Version
0.0.5- Published
- Nov 10, 2026
- Downloads
- 840/mo · 643/wk
- Author
- tienduy-nguyen
- License
- Apache-2.0
- Types
- extension
- Size
- 63.8 KB
- Dependencies
- 1 dependency · 2 peers
Pi manifest JSON
{
"extensions": [
"./src/auto-name.ts",
"./src/autoresume.ts",
"./src/peers.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
@tduyng/pi-session
Three gaps in session handling, all filled from what is already on disk. A session names itself from the first user message. A turn killed by a usage limit resumes when the limit resets. A session that starts while another one is working in the same directory says so, because the two will otherwise rewrite each other's files.
Nothing is configured beyond one environment variable, and no tool is registered.
Two sessions in one directory
A live pi appends to its session file as it works, so a file written in the last five minutes is a session that is probably open. On session_start the directory is scanned for those, this session's own file is left out, and a hidden message names how many are there and how recently the newest one wrote. It reaches the model once, on the first turn, and costs about forty tokens.
The message asks for git status before a rewrite and for leaving a change it did not make alone, which is the failure this exists for: two sessions in one repository editing the same file, each unaware of the other.
A file whose last entry is the name pi-session wrote at agent_end belongs to a run that has finished, so it is left out: a one-shot run that ended a minute ago is not a peer. That check reads the last 8KB of the file.
It also names up to three files the peer has been writing or editing, read from the last 256KB of its session file, so the warning points at the file that is likely to collide rather than at the repository as a whole. A path outside the directory is left out, since a file there cannot collide with this one. A peer with no such edit in that tail is not reported at all: a subagent, a review, or a session that only reads cannot overwrite anything, and it is reported later if it starts editing.
A session that starts while this one is already running is reported too, once, from the same scan on turn_end. The peer's file is remembered, so a directory with one other session open does not repeat the warning every turn.
PI_PEER_WINDOW_MINUTES sets the window, five minutes by default, and 0 turns the notice off.
Sending by hand
/peer on its own names the sessions live in this directory. /peer <name or id> <message> sends one, and says whether it landed. The same mailbox the tool uses carries it, so the recipient reads it at its next turn, and the command costs nothing until it is typed.
Messaging that session
The same scan arms a peer tool while another session is live in the directory, so a session that is alone pays nothing for it. It has three actions. list names the live sessions and the name pi-session gave each one. send delivers a note and returns. ask delivers a question and waits up to ten minutes for the answer, which comes back as the tool result. send and ask take the name list shows, and with one live peer they can leave it out: four recorded calls asked a question with no name and were refused.
Mail is a file under .pi/peers/mail/<session id>/, written to a temp name and renamed into place, so two writers never own the same path and a reader never sees a half-written message. Claiming a message deletes its file, and only one unlink of a path can succeed, so two readers never both take it. A message lives 24 hours and a mailbox holds 256, dropping the oldest, and a read sweeps the temp file a crashed write left, so an abandoned mailbox does not grow.
A session collects its mail on session_start and turn_end, and an idle one looks every five seconds, because a question has to reach a session that is sitting still or the asker waits for a turn that may never come. PI_PEER_POLL_SECONDS sets that interval and 0 turns it off. A session that collected its mail and then finds itself idle hands it to pi as an extension message, so it is in the transcript and the next request sees it. wake: false marks a note as information: it waits for the next turn instead of starting one, while a question always wakes its recipient. A plain answer completes the question it answers, because the delivered text names the sender and the action, and the ask sits in a ten-minute window that matches the asker's own wait. A late answer is not lost: it arrives as an ordinary message.
Naming
A new session starts unnamed, so a picker fills with 2026-09-01 14:22 rows and the session wanted is the one that cannot be identified.
On agent_end the extension reads the first user message and uses it: first line, a leading / or # stripped, whitespace collapsed, 60 characters at most, cut at a word boundary.
A week of timestamps becomes fix retry backoff, review auth diff, migrate schema.
An expanded skill uses its argument when there is one, otherwise the skill name. A /skill:name task command uses task. A name set with /name always wins, and naming runs last and swallows its own errors, because it is cosmetic and must never block a turn.
Resuming after a quota reset
When a provider quota kills a turn, the error usually carries a reset time. The parser accepts the shapes that appear in practice: epoch, ISO, try again in 2h 15m, resets 11:30pm (Europe/Paris), retry-after: 3600.
For a claude-bridge session with no time in the error, it asks the Anthropic OAuth usage endpoint and takes the latest reset among the exhausted windows. The usage API wins over the error text, since the error is a snapshot and the API is the current state.
The resume is a real user message on the same session: "The usage limit has reset. Continue the previous task from where it stopped." Nothing is replayed and nothing is stored.
A scheduled resume adds a 5 second safety delay past the reset and caps at 24 hours. Further out than that it warns and schedules nothing. Any user turn, session shutdown or new session cancels it, so a timer never fires over work already resumed by hand. That last case is deliberate: after a reload the schedule is gone rather than firing into a context the old session owned, and /autoresume status is how you find out it is no longer pending.
/autoresume status, including the pending resume time
/autoresume on
/autoresume off
/autoresume cancel
The quota lookup reads the Claude Code OAuth credential from the macOS keychain item Claude Code-credentials, or from ~/.claude/.credentials.json. Nothing else is read, and no value is logged.
Install
pi install npm:@tduyng/pi-session
Local checkout: pi install /path/to/dotfiles/pi/agent/extensions/pi-session
Naming needs nothing. The quota lookup needs network access to api.anthropic.com and either keychain access (macOS may prompt once) or the credentials file.
Limits
- Names are cut at 60 characters on a word boundary.
- The resume polls its timer every 10 seconds, so it can fire up to 10 seconds late.
- The quota lookup is Anthropic only. Other providers get the error-text path.
- A provider that changes its error wording gets no resume.