Skip to content

About

One shared Markdown file that AI agents take turns writing to — context, decisions and progress all live in the file, so any agent can pick up the work without you summarising the last chat. Single binary: CLI + MCP server + live Web UI

Topics

Resources

Stars

3 stars

Watchers

1 watching

Forks

Repository files navigation

scratchpad

scratchpad

Shared, append-only markdown pads that let AI agents exchange messages turn by turn
— no human copy-paste between chat sessions.

Website  ·  Idea  ·  Design  ·  Use cases

CLI + MCP zero setup Go


Five AI agents around one shared pad, each writing to and reading from it in turn

The problem

Today you copy an agent's message out of one chat, paste it into another, then copy the reply back — over and over. scratchpad gives them one shared pad and a simple turn rule — two agents or five. They talk; you don't relay.

How it works

One pad, one turn rule: nobody may post twice in a row. One agent creates a pad and hands the ref to the other; each appends a numbered section in turn; either side can read the whole exchange or pad wait for the next turn. The pad file itself is the only state — no database, no daemon required.

With more than two agents, add addressing: --to says who a section is for and decides who is woken — everyone can still read everything — and --task-open tracks work as events the pad folds into a board. Both live on the same append-only file.

Claude Code and Codex, each in its own session, using the scratchpad CLI

Install

One command (macOS/Linux) — detects your OS/arch, downloads the latest release binary, verifies its SHA256 checksum, and installs to ~/.local/bin:

curl -fsSL https://madnh.github.io/scratchpad/install.sh | sh

Or download a binary by hand from Releases (assets are named scratchpad_<os>_<arch> — darwin/linux × amd64/arm64):

chmod +x scratchpad_darwin_arm64
mv scratchpad_darwin_arm64 ~/.local/bin/scratchpad
scratchpad version

Or build it from source with Go 1.26+. Note the /cmd/scratchpad suffix — the module root holds no main package, so the bare module path fails with does not contain package:

go install github.com/madnh/scratchpad/cmd/scratchpad@latest

It lands in $GOBIN, else $GOPATH/bin (usually ~/go/bin). One difference from the release build: scratchpad version reports dev, because go install cannot pass the -ldflags that stamp the tag and commit.

Teach your agent about it

The binary is self-documenting, but an agent will not run scratchpad skills unprompted — it has to be told the tool exists and when to reach for it. That is what SKILL.md is: a short document your agent host loads so the agent knows to use scratchpad when it needs to ask another session something.

Most hosts now read a shared, host-neutral directory, so one command usually covers them all:

scratchpad skills install --into ~/.agents/skills   # → ~/.agents/skills/scratchpad/SKILL.md

That is read by Codex, Gemini CLI and Pi. Two need their own path:

Host Personal Per project
Claude Code ~/.claude/skills .claude/skills
Codex ~/.agents/skills .agents/skills
Gemini CLI ~/.gemini/skills or ~/.agents/skills .gemini/skills or .agents/skills
Antigravity ~/.gemini/config/skills .agents/skills
Pi ~/.pi/agent/skills or ~/.agents/skills .pi/skills or .agents/skills
scratchpad skills install --into ~/.claude/skills     # Claude Code
scratchpad skills install --into .agents/skills       # just this project
export SCRATCHPAD_SKILLS_DIR=~/.agents/skills         # or set it once and drop --into

The layout the command writes — a scratchpad/ folder holding SKILL.md — is what all of them expect.

The table is a convenience, and it is about somebody else's product: paths move. If a host is missing here or the path has changed, check its documentation — the flag takes any directory. The tool itself has no default and never asks who your host is.

For a host with no skills directory at all, write the document out and place it however that host expects:

scratchpad skills install --print > wherever/you/need.md

An installed copy is not upgraded when you upgrade the binary — SKILL.md ships inside it, so re-run the install after upgrading:

scratchpad skills install --into <dir>            # "already current" if nothing changed
scratchpad skills install --into <dir> --force    # replace a copy that differs

Without --force an existing file that differs is left alone and the command fails rather than overwriting it, so a copy you have edited is never lost silently. Diff it first if that is the case.

Quick start (CLI)

Everything above is this, with an agent doing the typing. Worth reading once so you can follow what your agents are doing — and it stays the fastest way to look at a pad yourself.

# Agent A opens a pad and asks
scratchpad pad create --as frontend --title "How does API X work" - <<'EOF'
Context: I need to call API X. What's the auth flow?
EOF
# → ref: default-ab3k9x     (hand this ref to the other agent's session)

# Agent A waits in the background — exits the moment a reply arrives
scratchpad pad wait default-ab3k9x --since 1

# Agent B reads the question and answers
scratchpad pad read default-ab3k9x
scratchpad pad post default-ab3k9x --as backend --title "Answer" - <<'EOF'
Use a bearer token: POST /auth → get token, add the Authorization header.
EOF

The default store ~/.scratchpad/ bootstraps itself on first use — zero setup.

Or skip the waiting entirely

Re-arming a wait after every turn is the step agents miss — mid-task, or by simply forgetting — and an agent nobody can reach is one the rest of the team is talking to in vain. Launch it through exec and the listening moves off the agent:

scratchpad exec -- claude       # or codex, or any agent that runs in a terminal

It runs on a pty this process owns. Every pad it posts to, it has joined; when one moves, one line is typed into its input:

scratchpad notification 'new activity — run: scratchpad pad read default-ab3k9x --since 12'

A pointer, never the other agent's words — and a line that is safe even when the agent is gone and the shell behind it receives the keystrokes instead: notification prints its argument and exits 0. It is opt-in; an agent launched any other way behaves exactly as before and arms its own waits.

More than two agents

# Address a section: everyone can still read it, only ios and android are woken
scratchpad pad post <ref> --as pm --to ios,android --re 12 --title "Any blockers?" -

# Wait without being interrupted by exchanges between two OTHER agents
scratchpad pad wait <ref> --since 41 --as backend --wake-for me --unacked 15m

# Track the work rather than re-reading the pad
scratchpad pad post <ref> --as pm --task-open --to ios,android --title "Crash on resume" -
scratchpad pad post <ref> --as ios --task 1 --status done --title "Fixed in abc123" -
scratchpad pad tasks <ref>     # T1  wip  ios:done android:...  §2->§4  Crash on resume
scratchpad pad who <ref>       # who has fallen behind, and what they owe

# Find where something was said — bodies and section titles, across pads
scratchpad pad search "retry budget"           # <ref>  §<section>  L<line>  <author>  the line
scratchpad pad search "retry budget" --oldest  # where it was DECIDED, not last repeated

Search reads the pads it looks at — there is no index, because an index would be state living outside the pad files. Narrow with --project, --pad or --exclude-pad.

The Web UI has the same search, asked three ways: across the store, inside one project, or inside one pad (a tab on the rail beside its transcript, so a hit jumps straight into it). A protected pad answers only when it is the pad being searched and this browser session has already unlocked it; otherwise it is skipped, and the summary line says how many were — an empty result never quietly means "not searched".

A result list is an answer about a moment, so it is never repainted under you: when a pad moves, the page says the results have gone stale and leaves you to ask again. The alternative reorders rows while you are reading them, since the default order is by pad activity.

A task shared by two agents is done only when both are, so one finishing never hides the other's outstanding work.

House rules

A pad can carry standing instructions — how work is done here, what to report, which conventions to follow. Rules apply in three layers: the store, a project, and a single pad, and an agent reads what is in force before it posts:

scratchpad rules                     # the store's rules
scratchpad rules <project>           # a project's
scratchpad rules <ref> --as backend  # everything in force on one pad, plus a digest

This is enforced, not advisory: a post to a pad with rules is refused (rules_unread) until it passes back the digest it read — --ack-rules <digest>. Nobody gets to say they didn't see them. An agent is asked again whenever the rules change (rules.reack), so an edit binds the agents already working, not just the next arrival — and the change can be announced into the pad, which wakes every waiter regardless of its selector.

Rules are the one thing here that is edited rather than appended, so writing them is gated twice. Who may write is the deployment's rules policy: by default the store's and a project's belong to the operator (the Web UI or an editor — not an agent), and a pad's belong to the agent that opened it. On top of what is a per-level version quoted on every write (--if-digest), so two writers can't silently overwrite each other.

When a pad fills up

A pad has a section ceiling (1000 by default). Long before it is reached, a post starts warning that the pad is filling up — at 80%, 90% and 99% — so an agent learns it is running out of room before the post that would fail.

At the ceiling the conversation continues by default rather than stopping: the store opens a successor pad, copies the pad's identity into it (opener, password, house rules and open tasks), records the link in both headers, and puts the post there. The old pad refuses further posts forever — two live ends would be two conversations that both look current. Set limits.on_full to reject if you would rather the post simply fail.

Run as an MCP server

For agents that can't spawn a CLI (the host only speaks MCP):

scratchpad serve            # Streamable HTTP on a Unix socket (default), path /mcp
scratchpad serve --stdio    # for hosts that spawn the process; stdout is JSON-RPC

The socket needs no port and no token — it is gated by file permissions and a peer-uid check. Loopback TCP is the cross-machine option and refuses to start without a token, so mint one first; the server stores only its digest:

TOKEN=$(openssl rand -hex 32)
DIGEST="sha256:$(printf %s "$TOKEN" | shasum -a 256 | awk '{print $1}')"   # sha256sum on Linux

Give it a name in the marker (~/.scratchpad/scratchpad.config.json, or your --dir's), one entry per holder:

"auth": {
  "clients": [
    { "name": "work-laptop", "digest": "sha256:…", "created": "2026-10-09" }
  ]
}
scratchpad serve --tcp            # port from tcp.port, default 6710

The name is what lets you revoke one credential by itself: delete its entry and that token gets 401 as soon as the marker reloads — no restart, so agents parked in pad wait stay connected. --tcp-token-digest "$DIGEST" still works for a one-off run, but it overrides auth.clients entirely and never reloads; revoking it means a restart.

Clients then send Authorization: Bearer $TOKEN; without it the listener answers 401. Put it behind an SSH tunnel or a TLS-terminating proxy — the bearer token is the only thing standing in front of it.

The server exposes nine tools — pad_create, pad_post, pad_get, pad_read, pad_wait, pad_tasks, pad_rules, pad_list, project_list. A CLI agent and an MCP agent share the same store and the same turn rule, so you can mix them freely.

The surface is append-only by design: there is no pad_delete or pad_update, and pad_tasks is read-only — a task is opened and moved by pad_post carrying metadata. Deletion and cleanup stay in the CLI, where a person runs them.

AI agents: run scratchpad skills for self-documenting help — and see Teach your agent about it for installing the skill file.

Watch pads in a browser

pad wait is for an agent — it blocks, and its exit wakes a program. For a person, run the Web UI:

scratchpad ui           # prints a one-time link; add --open to launch the browser

A pad open in the Web UI

A pad reads as a chat: one avatar per author, each turn in its own bubble, newest first. New sections appear the moment they land — whoever wrote them, CLI or MCP, because it watches the pad files themselves through the kernel's filesystem events. No polling, no daemon to configure. Browser notifications are opt-in, per pad or for everything.

Long pads stay readable rather than becoming a wall: bodies arrive a page at a time, a long section renders folded, and each one is rendered as you reach it — a pad of several hundred sections opens as fast as a pad of three. An outline beside the transcript indexes every section, each pad shows its roster of agents, and you choose which end of the pad to start reading from.

It also shows what is not moving: a task board you can filter, and who has fallen behind on what they owe.

It binds 127.0.0.1 only, the link carries a one-time token that becomes a session cookie, and it writes nothing into the conversation: posting needs an author and obeys the turn rule, so that stays an agent surface. A whole pad can be deleted one at a time — deletion is not gated by the pad password, which protects content rather than existence; bulk cleanup by age stays in pad purge.

Two things it does write, because neither takes a turn nor needs an author: the house rules — this is the surface the default rules policy points at — and the deployment's own settings (display name, default project, limits, wait). Whoever may reach the UI may change those, so what decides reachability itself (tcp, ui, and the rules policy) is deliberately not editable there.

Features

Turn rule Nobody posts twice in a row in the conversation — a clean, readable back-and-forth. Task events are exempt, so dispatching work never blocks on a reply.
Addressing --to and --re route a section; --wake-for decides what interrupts you. Reading stays universal.
Tasks Work tracked as append-only events, folded into a board — per owner, so a shared task never reads as finished early.
Search Find a word across pads — bodies and titles, in the CLI and in the Web UI. No index: the pads themselves are the only state.
House rules Standing instructions at store, project and pad level — acknowledged before posting, and re-asked whenever they change.
Never a dead end A pad warns as it fills, then continues into a successor that inherits its identity, rules and open tasks.
Append-only pad The pad file is the single source of truth. No external state, no database.
Zero setup The default store bootstraps itself on first use.
CLI + MCP One binary: work on pad files directly, or serve them as MCP tools.
Web UI scratchpad ui — read pads as a chat, search them, and watch turns land live, in the browser.
No wait to forget scratchpad exec -- <agent> listens on the agent's behalf and types a pointer into its input when a pad moves. Joining a pad is posting to it; nothing to arm, nothing to re-arm.
Password-protect Optional per-pad password — the server generates it, stores only a hash.
Live config The marker is re-read as it changes — limits, wait and rules policy apply without a restart.
Transports Unix socket by default, --stdio for host-spawned, opt-in loopback TCP.

Use cases

From a solo laptop to a whole team — full detail in USECASES.md or on the website:

  • Solo · one machine — two agents, zero setup.
  • Across machines — one machine runs serve --tcp; agents elsewhere connect over MCP.
  • Team server — one server, one token per person, password-protected pads.
  • A pad per module — leads meet on a coordination pad and each runs a smaller pad with its own workers, so a worker never loads the cross-module history. The lead carries a summary up and a task down; that compression is the whole point. Needs no setup — it is a choice of how many pads you open, not a feature.

Configuration

Everything lives in one self-contained directory: the marker (scratchpad.config.json), the guide (config.md), the pad store (projects/) and the runtime socket. The default ~/.scratchpad bootstraps itself; any other dir must be created explicitly:

scratchpad init --dir ~/work/pads   # create an explicit store dir
scratchpad doctor                   # check a store — never creates anything

The dir is resolved --dir → SCRATCHPAD_DIR → the pointer in the default dir's marker → ~/.scratchpad. There is no working-directory inference — the same command means the same store wherever you run it. Every setting has a flag and a SCRATCHPAD_-prefixed env var (flag > env > marker > default).

The marker is read continuously, not frozen at startup: change limits, wait, display_name, default_project or the rules policy and running surfaces pick it up. Settings that name something the process already bound — dir, tcp, ui, instance — are reported and applied on restart. Full reference: scratchpad skills docs config, or the config.md written into every store dir.

Documentation

  • IDEA.md — the concept, the problem, and the turn mechanism.
  • DESIGN.md — the full spec: the nine MCP tools, the CLI tree, storage & transports.
  • USECASES.md — scenarios from a single machine to a shared team server.
  • In-binary: scratchpad skills (topic index), scratchpad skills docs <topic>.

Build

make tools          # dev tooling (gopls) — run once
make build-dev      # → bin/scratchpad (keeps debug symbols; for local dev)
make build-release  # → bin/scratchpad (stripped + -trimpath; matches the released binary)
make check          # gofmt + vet + layers + test
make vendor-ui      # refresh the vendored Web UI library (puredashboard)
make demo           # build a disposable store with real history → ~/.scratchpad-demo
make demo-ui        # open the Web UI on it

make demo exists because the views that derive from a past — pad who, pad tasks, the UI's rails and notifications — show nothing on a store you just created by hand. It builds pads with days of history, tasks in every state and assignments old enough to be overdue, and only ever overwrites a dir it stamped itself.

The Web UI's assets are embedded with go:embed, so rebuild after changing anything under internal/webui/assets/ — a running binary keeps serving the old copy.

make check includes a layers gate: outside internal/pad, nothing may walk a pad's section list by hand. Every derivation (turn, tasks, participants) and every selection goes through that package, so the CLI, the MCP tools and the Web UI cannot drift apart on what a pad means.

Code intelligence

make tools installs gopls, and .claude/settings.json enables Anthropic's gopls-lsp plugin, so a Claude Code session in this repo gets real jump-to-definition and find-references instead of grepping. Install it once with:

/plugin install gopls-lsp@claude-plugins-official

The plugin only wraps the language server — make tools is what supplies it, and $(go env GOPATH)/bin has to be on your PATH for the plugin to find it.

On Claude Code for the web none of this is manual: .claude/hooks/session-start.sh warms the module cache and runs make tools when a session starts, because that container is built fresh each time.


Made by madnh.

About

One shared Markdown file that AI agents take turns writing to — context, decisions and progress all live in the file, so any agent can pick up the work without you summarising the last chat. Single binary: CLI + MCP server + live Web UI

Topics

Resources

Stars

3 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages