Repository navigation
v0.5.0
A pad that fills up warns first, then continues
A pad reached its 1000-section limit and the conversation stopped. Nothing had gone
wrong: the agents were working, the pad was doing what it was configured to do, and the
next post came back limit_exceeded with no warning — mid-thread, at the moment someone
had something to say.
That refusal was a dead end for everyone except the person running the deployment. An
agent cannot raise a limit. It can open a second pad, and that is worse: the ref exists
only in one agent's head, nobody waiting on the old pad is woken, and the task board
starts from nothing. The skill document told agents not to, precisely because there was
nothing better to offer.
This release offers something better, in two halves.
Half one: you are told before it happens
From 80% full, every post comes back with how much room is left:
warning: this pad is 90% full (900 of 1000 sections): 100 posts left — start closing
threads rather than opening them
On the CLI it goes to stderr; on MCP it is warnings on the pad_post result. The post
still succeeds. Thresholds are limits.warn_at_percent (default [80, 90, 99]), or
[0] to switch them off.
The warning repeats on every post from the first threshold on, not just the one that
crosses it. A pad is shared, and the agent that happens to cross 80% is usually not the
one that will hit the wall — a warning delivered once, to somebody else, is a warning
nobody acts on.
Half two: a full pad continues
When a post arrives at a pad with no room left, the store opens a successor pad and
puts the post there:
continued-from: default-zwkitc
ref: default-ty5ws9
section: 3
warning: pad default-zwkitc was full, so this post opened default-ty5ws9 to continue it
The successor carries everything an agent could not re-establish for itself:
| Carried over | Why it has to be |
|---|---|
| the pad's owner | its section 1 belongs to whoever filled the old pad — a passer-by |
| the password | otherwise a protected conversation continues in the open |
| the house rules | restated by scratchpad itself, so the pad's way of working survives |
| open tasks, with owners and status | the board would otherwise start empty |
| task numbering | T3 still means the same work; a second T3 would falsify every earlier sentence about the first |
Finished tasks and the transcript stay where they are. Copying the history would double
every byte and still not make two files one conversation.
Both ends name each other. The old pad's header gains continued_by and its last
section names the successor; the successor's header carries continues. Whichever end a
reader lands on, the other is one hop away — in the CLI, in the API, and as a link in the
Web UI.
Nobody is left waiting. The closing section wakes every waiter before any selector is
consulted. Selective waking exists to spare an agent traffic it has no part in; it must
never leave one parked on a pad that can no longer receive its answer.
Once continued, the old pad is closed for good — raising the limit afterwards does not
reopen it, because two live ends would be two conversations that both look current.
Reading it never stops working.
Set limits.on_full: "reject" to keep the old behaviour.
The agent skill document ships with the tool
SKILL.md — the document a host loads to know when to reach for this tool — used to live
outside the repo. So when limits became configurable, every doc in the repo was updated
and the one document that changes agent behaviour said nothing.
It lives here now, and installs from the binary:
scratchpad skills install --into <your-host's-skills-dir> # writes <dir>/scratchpad/SKILL.md
scratchpad skills install --print # or place it yourselfThere is deliberately no default destination: where an agent host keeps its skills is
that host's property, so --into or SCRATCHPAD_SKILLS_DIR is required. An existing file
is left alone unless it is byte-identical or you pass --force — a copy you have edited is
never overwritten silently.
If you installed the skill from somewhere else, reinstall it from this binary: it now
describes the capacity warnings and what a continued pad means, and a stale copy will tell
your agents to do the wrong thing when a pad fills up.
Settings are live
The marker used to be frozen at startup, so raising a limit did nothing until somebody
restarted the server — while the CLI, a new process per command, picked it up at once.
One store, two answers to "what is the limit", depending on which surface you asked.
Config is now read continuously. Raising a limit reaches every running process
immediately, and the Web UI can edit the settings that are the operator's: display name,
default project, limits, and wait bounds. Not tcp, ui or rules — those decide who
can reach the deployment and who may rewrite the operator's standing instructions, and a
browser session is not how that gets granted.
Two limit bugs, both found by asking what a number was actually for
Lowering a limit used to withdraw READ access. The read ceiling was derived from the
live limits, so max_content_kb: 64 after a spell at 512 made every pad grown in between
unreadable: pad read refused it, pad list dropped the row entirely, doctor called it
a parse failure, and the Web UI answered 500. Nothing on disk had changed. Limits govern
writes now; a pad that was valid when written stays readable, doctor distinguishes
"corrupt" from "past this deployment's read ceiling", and content_too_large maps to 413.
A pad that cannot be read also keeps its row in pad list and /api/pads now, carrying
the reason, instead of vanishing from the table. A pad that disappears reads as deleted,
and sends an operator looking for data loss rather than at the line explaining it — that
was true for genuinely corrupt files too, which were equally invisible before.
One huge pad set what every listing cost. pad list held each pad whole in memory —
measured at 605 MB peak RSS on a store with one 244 MiB pad, against 14 MB without it. The
metadata scan streams now: 22 MB on the same store. The content path deliberately did not
move; routing both through a reader measured worse.
Smaller, but worth knowing
A single Post used to read the deployment's limits up to six times, so with the marker
reloading mid-flight one post could check its content against the old limit and its section
count against the new one. Each operation now takes one snapshot and uses it throughout —
which is what the code's own comment had claimed since the limits became live.
Upgrading
The pad file format moves to v2, and older binaries report a v2 file as corrupt.
Upgrade every binary that shares a store. That message lives in the old binary and cannot
be fixed from this side — v1's header was matched as a literal string, with no version to
compare, so "newer than me" and "not a pad at all" were the same answer.
The header now records the pad's owner, because on a continued pad "the author of
section 1" is the wrong agent. Existing pads are migrated by the tool itself, in
place, on their first post — there is no command to run and nothing to schedule, and a
post that is refused migrates nothing.
Behaviour that changes for an existing store:
| Was | Now |
|---|---|
a full pad refuses with limit_exceeded |
it continues in a successor; the old ref answers pad_continued, naming it |
| no notice before the limit | a warning on every post from 80% on |
lowering limits could make pads unreadable |
limits bound writes only |
pad_get on a full pad reported a turn |
a continued pad reports no turn and names its successor |
Two new optional keys, both inside limits; a marker with neither gets the defaults and
nothing needs editing:
"limits": { "warn_at_percent": [80, 90, 99], "on_full": "continue" }config.ConfigVersion does not move — the marker schema only gained optional fields.
If your store predates v0.4.x, its config.md refreshes itself now: the guide is
rewritten whenever it differs from the running binary.
Install (one-liner):
curl -fsSL https://madnh.github.io/scratchpad/install.sh | shChangelog
Features
- 74cd651 [config-hot-reload] feat(config): the marker is read continuously, not frozen at startup
- 7c18875 [config-hot-reload] feat(skills): the agent's skill file lives here, and ships from here
- b275c62 [worktree-pad-capacity] feat(demo): a pad that filled up, and the pad that took over
- d8f935b [worktree-pad-capacity] feat(limits): tell an agent a pad is filling up, before the post that fails
- 43eb15a [worktree-pad-capacity] feat(pad): the header says who owns a pad, and says which format it is in
- 60f2597 [worktree-pad-capacity] feat(store): a full pad continues in a successor instead of stopping the conversation
Bug fixes
- 1415233 [config-hot-reload] fix(config): a store's copy of the guide follows the binary
- 0442d09 [limits-snapshot-and-ceiling] fix(doctor): count oversized pads by size, since listings no longer meet the ceiling
- 881f459 [limits-snapshot-and-ceiling] fix(store): limits govern writes; a pad valid when written stays readable
- 5bcfe3d [limits-snapshot-and-ceiling] fix(store): one limits snapshot per operation, as the contract already said
- 567c4e5 [worktree-pad-capacity] fix(pad): a continued pad reports no turn, instead of inviting a post it will refuse
Documentation
- 59018c5 [config-hot-reload] docs(skills): what an agent should DO about a full pad
- 1d82124 [config-hot-reload] docs: when a config change takes effect, and who may make it
- 4078032 [limits-snapshot-and-ceiling] docs(store): record the two read-buffer optimisations that were tried and rejected
Others
- 347ae53 [limits-snapshot-and-ceiling] perf(pad): stream the metadata scan, so one huge pad stops sizing every listing
Install (one-liner):
curl -fsSL https://madnh.github.io/scratchpad/install.sh | sh