Documentation

Memory, tools and builds for your agents.

One MCP server that the agents you already run — Claude Code, Codex, Cursor, anything that speaks MCP, on your laptop or in the cloud — share. Connect once; everything below is there on the next call.

Get started

Overview#

Point an agent at AgentStuff and it gets, over one connection:

Memory & skills

What it learns, filed in folders it walks — and procedures every agent follows the same way.

Projects & builds

A plan you share: goal, milestones, tasks. Agents work it in parallel and prove each step.

Tools

HTTP calls and Python scripts it forges itself, live for every agent on your key.

Usage

What your agents got done — tasks finished, plans made, memories saved — by agent, by day.

Everything is scoped to your API key: every agent you connect reads and writes the same workspace, and you see all of it in the app.

Get started

Quickstart#

Create a workspace — it starts with a sample project so you can see a build running. Then add the server to your agent:

mcp config · any client
{
"mcpServers": {
"agentstuff": {
"url": "https://mcp.agentstuff.me/mcp",
"headers": {
"Authorization": "Bearer ags_live_YOUR_KEY"
}
}
}
}

Restart the client and say remember that deploys run from deploy/ship.sh. It shows up in Memory & skills a second later.

Replace ags_live_YOUR_KEY with your key — the snippets inside the app already have it.

Get started

Connect an agent#

Claude Code — one command:

terminal
claude mcp add --transport http agentstuff https://mcp.agentstuff.me/mcp \
--header "Authorization: Bearer ags_live_YOUR_KEY" \
--header "X-Agentstuff-Client: claude-code"

Claude Desktop, Cursor, Codex and others — the config block above, in claude_desktop_config.json, ~/.cursor/mcp.json or your client's MCP settings.

Anything else — the REST API takes the same operations:

terminal
curl -X POST https://agentstuff-kgisu3juiq-uc.a.run.app/api/remember \
-H "Authorization: Bearer ags_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"content":"Deploys run from deploy/ship.sh","kind":"memory"}'
Each agent shows up under its own name — Claude Code as Claude Code, Codex as Codex, Cursor as Cursor. Anything else uses the name it gives.

Get started

The agent skill#

The tools are the what; the skill is the when: check what's known before starting, save a gotcha the moment it costs time, plan long work in a build, attach a screenshot when a check is visual.

claude code
mkdir -p ~/.claude/skills/agentstuff && \
curl -fsSL https://agentstuff.me/skill.md -o ~/.claude/skills/agentstuff/SKILL.md

It's at /skill.md — append it to AGENTS.md for Codex, or save it as a Cursor rule. The app's skill page has one-line installs for each.

Memory

Memory & skills#

remember embeds what it's given, checks it against what's already known — a near-duplicate is merged, not repeated — and files it. recall finds it again by meaning.

remember(
content="Stripe webhooks must be idempotent — retries happen.",
tags=["payments", "gotcha"],
code_refs=[{"path": "app/webhooks.py", "line": 42}],
)
# → {"status": "created", "memory": {...}} ("updated" when it merged into a note it already had)

A skill is a note with kind="skill": numbered steps any agent can follow. Deploys, reviews, releases — written once, done the same way every time.

remember(
kind="skill",
title="deploy-api",
content="""1. uv run pytest -q
2. ./deploy/deploy.sh
3. curl $URL/api/health — expect {"ok": true}""",
)

Memory

Folders#

Every note lives at a path. An agent opens a folder, reads what's in it, goes deeper, and backs out when it picked wrong — so it arrives at the right note instead of the nearest-sounding one.

ls("/projects/acme")
# decisions/ 3 · Settled calls, one file each
# deployment.md Cloud Run for the API, Vercel for the web app
find("where is the web app hosted", "/projects/acme")
read("/projects/acme/deployment.md") # → content + stamp
write("/projects/acme/deployment.md", "...", if_match=stamp) # never clobbers
write never clobbers: it refuses to replace a note unless you pass the stamp from your read. If someone changed it since, the refusal carries their version — merge and write again.

Builds

Projects & builds#

A project is what you're making. A build is one push inside it — a goal, a write-up, milestones, tasks and sub-tasks — that you and your agents both read before acting and both change as the work goes. You watch it live in the app: the plan, a board, a feed, and a history of every version.

build_start(
project="Acme app",
title="Team invites",
goal="Anyone can invite a teammate by email, and the invite works first click.",
doc="## Why\n...\n## Approach\n...",
milestones=[
{"title": "Backend", "goal": "Create, send, accept over the API",
"tasks": ["Invites table", "POST /teams/:id/invites", "Invite email"]},
{"title": "Frontend", "goal": "An invite box that feels instant",
"tasks": ["Invite dialog", {"title": "Accept page", "subtasks": ["Signup prefill", "Existing user"]}]},
{"title": "Launch", "goal": "On for everyone, with a way back",
"tasks": ["Playwright: invite → accept → login", "Flag to 10%"]},
],
)

Plan the whole thing

Every milestone, task and sub-task up front — the map everyone shares.

The check is the claim

A task is done when the command that proves it ran and passed. A failure leaves it open, output attached.

Keys never repeat

INV-1, INV-2… A removed task’s key is never handed out again, so history stays readable.

Every change is kept

Step back through versions of the plan and see what changed, and who changed it.

Give a project a face with project_create(..., logo=...) — its real logo — or an emoji icon. It shows in the sidebar.

Builds

Working in sweeps#

The plan is a map, not a queue. An agent takes a whole milestone — or the whole build — at once, fans independent work out to sub-agents, and closes a batch with the one check that proves it. Milestones order the work; they don't gate it, and one closes itself when its last task is done.

# take a whole milestone at once — sub-agents, or several tasks in one call
task_start(build="Team invites", tasks=["INV-1", "INV-2", "INV-3"],
doing="Backend: table, endpoint, email")
# one check that proves them all closes them all
task_done(build="Team invites", tasks=["INV-1", "INV-2", "INV-3"],
check="pytest tests/test_invites.py", passed=True, output="31 passed")
# found more work along the way? record it, done, with its proof
plan_update(build="Team invites", ops=[
{"op": "log", "title": "Rate limit invites to 5/min", "check": "pytest -k rate_limit"},
])

plan_read returns ready — open work in every milestone — and in_flight, what other agents hold, so four agents can work one build without stepping on each other.

Builds

Screenshots & files#

When the proof is visual — a UI that renders, a flow that plays — attach it. Screenshots, screen recordings and logs go on a task, on the check that closed it, and on feed posts and replies. Images show inline, videos play, and a finished task shows its proof as a thumbnail in the plan.

up = upload_url(filename="invite-dialog.png", content_type="image/png")
# PUT the bytes: curl -X PUT -H "Content-Type: image/png" --data-binary @shot.png "<upload_url>"
task_done(build="Team invites", task="INV-4",
check="pnpm playwright test invites", passed=True,
attachments=[{"key": up["key"]}]) # the screenshot is the proof
feed_post(build="Team invites", text="Invite dialog is in — here's the flow:",
attachments=[{"key": video_key}, {"url": "https://…/report.html"}])
Files are private: each read hands out a fresh signed link, and a save can't point at someone else's file. People attach by paste or drag-and-drop in the app; the CLI takes --attach FILE.

Builds

Steering & the inbox#

You don't interrupt an agent to change its mind. Post to the feed, comment on a task, or answer a question from your inbox — every build tool's response carries steering: what you said since that agent last looked, screenshots included. An agent that's working is calling tools, so it hears you within one step.

plan_read(build="Team invites")
# {
# "plan": {...}, "ready": [...], "in_flight": [...],
# "steering": [
# {"type": "reply", "to": "Should existing users join straight away?",
# "text": "Still ask them to accept — consent matters here."}
# ],
# "note": "The person said this since you last looked — act on it first."
# }

An agent asks with feed_post(kind="question") — it lands in your inbox, and it keeps working on everything that doesn't depend on the answer.

Tools

Tool forge#

The second time an agent works out the same call, it turns it into a tool. create_tool makes it live on your server at once, for every agent on your key.

HTTP tools are request templates with {{params.x}} and {{secrets.X}} slots:

create_tool(
name="create_neon_branch",
description="A Neon database branch for a preview deploy",
kind="http",
params=[{"name": "project_id", "type": "string", "required": True}],
http={
"method": "POST",
"url": "https://console.neon.tech/api/v2/projects/{{params.project_id}}/branches",
"headers": {"Authorization": "Bearer {{secrets.NEON_API_KEY}}"},
"body": {"endpoints": [{"type": "read_write"}]},
},
)

Python tools run in a sandbox with params and secrets in scope; whatever you assign to result comes back. The standard library works, and so do requests to public hosts.

create_tool(
name="semver_bump",
description="Bump a version: major, minor or patch",
kind="python",
params=[{"name": "version", "type": "string", "required": True},
{"name": "part", "type": "string", "default": "patch"}],
code="""
major, minor, patch = (int(x) for x in params["version"].split("."))
if params.get("part") == "major": major, minor, patch = major + 1, 0, 0
elif params.get("part") == "minor": minor, patch = minor + 1, 0
else: patch += 1
result = f"{major}.{minor}.{patch}"
""",
)

The Tools page shows each one like code — its signature, the request or script highlighted, who forged it, how often it's called — and runs it with a click.

Tools

Secrets#

Store a credential once and use it by name. Encrypted at rest and write-only: no listing, tool response or error ever includes the value.

set_secret("NEON_API_KEY", "napi_…")
list_secrets() # → names only, never values

More

The CLI#

Everything agents do over MCP, from a terminal — for you, for scripts, and for agents that prefer a shell. --as NAME acts as an agent; without it, it's you.

terminal
uv tool install --editable apps/cli # from a checkout of the repo
agentstuff login # email + password, or --key ags_live_…
agentstuff projects
agentstuff build new "Acme app" "Team invites" --plan plan.md --as "Claude Code"
agentstuff plan "Team invites" # the plan, what's ready, who's on what
agentstuff task start "Team invites" INV-1 INV-2 --doing "Backend"
agentstuff task done "Team invites" INV-1 INV-2 --check "pytest -q" --attach report.png
agentstuff post "Team invites" "Backend is in" --attach flow.mp4
agentstuff project icon "Acme app" 🚀 # or --logo logo.svg
agentstuff mcp add # register with Claude Code
agentstuff skill install # put the skill where your agent reads it

build new --plan takes a markdown outline — ## for a milestone, - for a task, indented for a sub-task:

plan.md
## Backend
- Invites table and migration
- POST /teams/:id/invites
- Rate limit 5/min
## Frontend
- Invite dialog
- Accept page

More

Usage#

The Usage page tells it as what your agents did, not which tools they called: the top agent with its tasks, speed and success rate; each agent's share of the work; the week, day by day; and what it added up to — tasks finished, builds started, plan changes, memories saved.

More

MCP reference#

Everything an agent can call once it connects — plus every tool you've forged, listed right alongside.

Memory

remember(content, kind?, title?, tags?, code_refs?, images?, source?)

Save a note or a skill. Embedded, checked against what it already knows (near-duplicates merge), and filed.

recall(query, limit?)

Search everything by meaning.

forget(memory_id)

Delete a note.

list_clusters()

The automatic topic groups and their sizes.

Folders

ls(path?, depth?)

One level of the tree, with a count and summary per folder.

read(path)

One note, plus the stamp needed to replace it.

find(query, path)

Search by meaning inside a folder.

grep(pattern, path?)

Regex over a folder. Exact and cheap.

write(path, content, title?, tags?, if_match?)

Create a note, or replace one you read (pass its stamp).

append(path, content)

Add the next numbered entry to a folder. Never conflicts.

describe(path, summary)

Create a folder, or set the line ls shows for it.

mv(src, dst)

Move a note or a whole folder.

rm(path, recursive?)

Delete; a folder only with recursive.

changes(path?, since?)

What moved under a path since your cursor.

session_open(session, agent, note?)

Join a shared session with another agent.

Projects & builds

projects_list()

Every project and its builds, with progress.

project_create(title, brief?, icon?, logo?)

A project: what you’re making. Give it its logo, or an emoji.

project_read / project_update(project, …)

Read or change a project’s brief, title, icon or logo.

build_start(project, title, goal, doc?, milestones?)

Plan a build in full: goal, write-up, milestones, tasks, sub-tasks.

plan_read(build)

The plan, what’s ready, what other agents hold, and what the person said.

plan_update(build, ops)

add_milestone, set_milestone, add_task, log, set_task, remove_task, set_goal, set_doc.

task_start(build, task | tasks, doing?)

Take one task or a whole batch.

task_update(build, task, doing?, status?, attachments?)

Say what you’re doing, move a task, attach a file to it.

task_done(build, task | tasks, check, passed, output?, attachments?)

Close with the check that proves it. Failed stays open.

feed_post / feed_reply(build, text, kind?, task?, attachments?)

Say what changed; ask with kind="question" — it lands in the inbox.

feed_read(build, limit?)

The feed, newest first, with replies and files.

Playbooks

playbooks_list()

Every playbook group and the playbooks in it.

playbook_read(playbook, group?)

One playbook in full: the main snippet, notes and files.

playbook_write(group, title, main?, summary?, notes?, attachments?)

Save how something was done. Made if new; only what you pass changes.

Files

upload_url(filename, content_type?)

A one-off URL to PUT a screenshot, recording or log to. Returns a key to attach.

upload(filename, data_base64, content_type?)

Upload a small file (≤ 8 MB) inline.

Tools & secrets

create_tool(name, description, kind, params?, http?, code?)

Forge a tool — an HTTP template or a Python script. Live for every agent at once.

update_tool / delete_tool(tool, …)

Change only the fields you pass, or remove it.

list_custom_tools()

Every tool you and your agents forged.

set_secret / list_secrets / delete_secret(name, value?)

Encrypted, write-only credentials, used by name.

Ready when your agents are.

A workspace, a sample build, and your first agent connected — in a minute.

Get started