pi-pattern-retry
Pi extension: keep agent sessions alive across provider rate-limits, quota exhaustion, and transient auth failures by re-injecting continuation messages on an escalating retry schedule.
Package details
Install pi-pattern-retry from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:pi-pattern-retry- Package
pi-pattern-retry- Version
0.1.0- Published
- May 20, 2026
- Downloads
- not available
- Author
- buihongduc132
- License
- MIT
- Types
- extension
- Size
- 60.1 KB
- Dependencies
- 0 dependencies · 1 peer
Pi manifest JSON
{
"extensions": [
"./index.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
pi-pattern-retry
Keep your Pi Agent session alive across provider rate-limits, quota exhaustion, and transient auth failures. pi-pattern-retry watches provider responses and agent errors, matches them against configurable patterns (regex / substring), and re-injects a continuation message at escalating intervals — 30s → 1m → 5m → 15m → 30m → 1h → 4h → 5h → 12h → 24h by default — so the agent picks up where it left off.
Designed alongside todo-enforcer: same retry-scheduler idea, separate package, ships independently.
Install
pi install npm:pi-pattern-retry
Then restart your Pi session. The plugin ships with sensible defaults — no config file is required to get started.
What it does
Two trigger sources feed one matcher:
| Event | What it inspects |
|---|---|
after_provider_response |
HTTP status — catches 401 / 403 / 429 / 5xx |
agent_end |
last assistant message's errorMessage — catches transport text |
A match schedules one timer per (sessionId, patternName). When the timer fires:
- If the conversation has progressed (branch grew, excluding our own injections), reset attempt counter and stop.
- Otherwise inject
pi.sendUserMessage(<your template>)so the agent retries. - Advance to the next delay in the schedule. When the schedule is exhausted, the pattern stays inert until
/pattern-retry-reset.
Duplicate triggers (e.g. both events firing for the same failure) are debounced — the already-live timer wins.
Default patterns
Shipped with three patterns pre-seeded. Override or replace via config file.
| Name | Trigger | Schedule (seconds) |
|---|---|---|
llm-429-quota |
HTTP 429 |
30, 60, 300, 900, 1800, 3600, 14400, 18000, 43200, 86400 (10 steps) |
llm-401-403-auth |
HTTP 401 / 403 |
60, 300, 1800 (3 steps) |
litellm-weekly-limit |
errorMessage contains "Weekly/Monthly Limit Exhausted" |
3600, 14400, 43200, 86400 |
Configuration
Global file: ~/.pattern-retry.json. Project override: <cwd>/.pattern-retry.json. Project keys override global; arrays replace, they do not merge.
{
"enabled": true,
"patterns": [
{
"name": "llm-429-quota",
"match": { "source": "providerStatus", "kind": "regex", "value": "^429$" },
"scheduleSec": [30, 60, 300, 900, 1800, 3600, 14400, 18000, 43200, 86400],
"message": "Provider returned {{status}} (attempt {{attempt}}/{{maxAttempts}}). Retrying after {{lastDelaySec}}s.",
"stopOnProgress": true,
"notify": ["webhook", "dashboard"]
},
{
"name": "litellm-weekly-limit",
"match": {
"source": "errorMessage",
"kind": "contains",
"value": "Weekly/Monthly Limit Exhausted"
},
"scheduleSec": [3600, 14400, 43200, 86400],
"message": "Hit weekly LLM quota. Resuming attempt {{attempt}}."
}
],
"notifyTargets": {
"webhook": { "enabled": false, "url": "", "headers": {}, "timeoutMs": 10000 },
"dashboard": { "enabled": false, "endpoint": "" }
}
}
Match semantics
kind: "contains"— case-sensitive substring match.kind: "regex"— compiled withnew RegExp(value); no implicit flags. For case-insensitive, encode flags in the pattern itself.source: "providerStatus"— matched against the decimal status string (e.g."429").source: "errorMessage"— matched against the assistant message'serrorMessagestring. If absent/empty, the pattern does not match.
A malformed regex degrades to a logged warning — other patterns continue to work.
Template variables
| Variable | Value |
|---|---|
{{status}} |
HTTP status from providerStatus, or 0 |
{{statusText}} |
Human label (e.g. "Too Many Requests") |
{{reason}} |
Match-source snippet |
{{patternName}} |
The matched pattern's name |
{{attempt}} |
1-indexed current attempt |
{{maxAttempts}} |
scheduleSec.length |
{{lastDelaySec}} |
Delay that elapsed before this fire |
{{nextDelaySec}} |
Next delay if any, else 0 |
Missing variables interpolate to empty string.
Commands
/pattern-retry-status— show each pattern'sattempt/max,exhausted, andscheduledflags./pattern-retry-reset [patternName|all]— cancel timer(s) and reset attempt counter./pattern-retry-toggle— flip the globalenabledflag./pattern-retry-log [N]— tail the lastNlines (default 50, max 500) of the central log file straight into the session.
Troubleshooting / Logs
Every meaningful decision is appended to a single central log file:
~/.pi/logs/extensions/pattern-retry.log
This is the shared convention across all pi extensions — written via the rotated, size-capped (25 MB, 80% trim) createPluginLogger. Each line is:
<iso-timestamp> [pattern-retry] <LEVEL> <event> <json-details>
Events you can grep for:
| Event | Means |
|---|---|
session-start |
Session bound to pattern-retry; sessionId + cwd recorded |
config-loaded / config-load failed |
Result of merging ~/.pattern-retry.json + project override |
trigger:matched |
A provider/error trigger arrived; lists matched pattern names |
trigger:skipped-disabled / trigger:skipped-no-session |
Trigger dropped — explains why |
schedule:armed |
Timer set; attempt, delaySec, trigger metadata |
schedule:skip-idempotent |
Duplicate trigger dropped because a live timer already exists |
schedule:skip-exhausted / schedule:exhausted |
All attempts used up |
timer:elapsed |
Scheduled delay elapsed; about to fire |
fire:injected |
Continuation message sent via pi.sendUserMessage |
fire:reset-on-progress |
Agent advanced before timer fired → attempt counter reset, no send |
cancel:pattern / cancel:session |
/pattern-retry-reset or session shutdown |
command:toggle / command:reset / command:reset-all |
Audit trail for user commands |
notify:<event>:<target> |
A configured notify target fired (V1: log-only) |
When something looks wrong:
tail -f ~/.pi/logs/extensions/pattern-retry.log
# or from inside a session:
/pattern-retry-log 200
The file is the source of truth — every fire, skip, reset, and exhaustion is there with the sessionId and pattern name attached, so you can reconstruct exactly what happened and why.
Notify hooks (V1: no-op)
Each pattern may carry notify?: ("webhook" | "dashboard")[]. V1 logs one line per enabled target — real HTTP/WebSocket senders ship in V2 (see flow/plans/pattern-retry-future.md upstream).
The notifyHooks() signature is the public contract — V2 swaps the implementation without changing callers.
How it differs from todo-enforcer
| Plugin | Trigger | Use case |
|---|---|---|
todo-enforcer |
Idle agent with pending todos | Nudge the agent to keep working on its list |
pi-pattern-retry |
Provider error / quota signal | Resume after an external rate-limit or outage |
They share the retry-scheduler pattern (copy-adapted), not code — install one without the other.
License
MIT