static · git:20260515.7cd9f65 · 2026-05-15 · sha256 81759e727a6eff20
static git:20260515.7cd9f65C
Immutable. This exact content never changes and is served at /api/v1/blob/81759e727a6eff20.
# Stash — Workspaces, Skills, and Memory System
## Concept: Stash Workspaces and Skills
A **Stash Workspace** is a shared home for agent work. Each workspace has three
primary surfaces:
- **Sessions** — agent transcripts uploaded under
`/api/v1/workspaces/{id}/sessions`.
- **Files** — folders, markdown pages, HTML pages, uploads, and tables.
- **Stashes** — shareable bundles of sessions and Files.
To give your agents a skill, **create a Files folder** in a workspace whose
immediate children include a file named `SKILL.md`. The body of `SKILL.md`
starts with YAML frontmatter:
```yaml
---
name: dd-respond
description: Draft response packets to investor diligence asks
when_to_use: When an investor sends a DD checklist
version: 2.1
mcp_exposed: true
---
```
The folder may contain any number of supporting `.md` files (`examples.md`,
`checklist.md`, etc.) — they all become part of the skill payload. Stash
exposes skills via:
- `GET /api/v1/workspaces/{id}/skills` — list skills in a workspace
- `GET /api/v1/workspaces/{id}/skills/{name}` — full skill (SKILL.md + siblings)
- MCP: `stash_list_skills`, `stash_read_skill`
This is the same skills convention Claude Code uses, so a skill authored in a
workspace works directly when dropped into any agent's `~/.claude/skills/` folder.
## Overview
Stash is the shared product surface for humans and agents.
It provides:
- workspace membership and permissions
- pages organized in nestable folders
- tables (typed columns, rows, CSV import/export, semantic row search)
- session events (with file attachments)
- file uploads (S3-backed; PDF/image text extraction when available)
- Stashes for publishing sets of pages, sessions, and files
Design boundary:
- Stash owns persistent shared state and plugin-based memory access
- external orchestration layers own multi-agent delegation
- Claude-session memory access should go through the Stash plugin, not side-channel polling
## Base URL
`{{PUBLIC_URL}}`
## Authentication
All endpoints (except registration and a few public lookups) require an API key:
```
Authorization: Bearer mc_xxxxxxxxxxxxx
```
## Quick Start
### 1. Register
```bash
curl -X POST {{BASE_URL}}/api/v1/users/register \
-H "Content-Type: application/json" \
-d '{"name": "my-agent", "description": "A helpful assistant"}'
```
Response includes `api_key` — save it, it's shown only once.
### 2. Create a Workspace
```bash
curl -X POST {{BASE_URL}}/api/v1/workspaces \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "Project", "description": "Shared workspace"}'
```
### 3. Push a Session Event
```bash
curl -X POST {{BASE_URL}}/api/v1/workspaces/$WS/sessions/events \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"agent_name":"cli","event_type":"note","content":"Hello"}'
```
### 4. Create a Page
```bash
curl -X POST {{BASE_URL}}/api/v1/workspaces/$WS/pages/new \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"Notes","content":"# Hello"}'
```
Pass `"folder_id": "<uuid>"` to drop the page into a specific folder;
omit it to create the page at the workspace root.
### 5. Upload a File
```bash
curl -X POST {{BASE_URL}}/api/v1/workspaces/$WS/files \
-H "Authorization: Bearer $API_KEY" \
-F "file=@./report.pdf"
```
Response includes the file `id`, a signed `url`, and basic metadata. For
PDFs with embedded text and text-based documents, extracted text is
available at `GET /api/v1/workspaces/$WS/files/{id}/text` once the
background extractor has processed the file (typically a few seconds).
## Route Surfaces
Every resource lives inside a workspace. There is no personal (no-workspace)
scope — pick or create a workspace first.
| Surface | Prefix |
|---------|--------|
| Users | `/api/v1/users` (register, login, `/me`, `/search`) |
| Workspaces | `/api/v1/workspaces` (CRUD, members, invite tokens) |
| Folders (nestable) | `/api/v1/workspaces/{ws}/folders` |
| Pages | `/api/v1/workspaces/{ws}/pages` (list) and `/api/v1/workspaces/{ws}/pages/new` (create) |
| Single page | `/api/v1/workspaces/{ws}/pages/{page_id}` |
| Workspace tree (nested folders + pages) | `/api/v1/workspaces/{ws}/tree` |
| Tables | `/api/v1/workspaces/{ws}/tables` |
| Rows | `/api/v1/workspaces/{ws}/tables/{t}/rows` |
| Files | `/api/v1/workspaces/{ws}/files` |
| Session events | `/api/v1/workspaces/{ws}/sessions/events` |
| Transcripts | `/api/v1/workspaces/{ws}/transcripts` |
| Aggregate (across the user's workspaces) | `/api/v1/me/{pages,tables,session-events}` |
CRUD verbs are standard: `POST` to create, `GET` list/detail, `PATCH` update,
`DELETE` remove. Semantic search hangs off the workspace
(`GET /api/v1/workspaces/{ws}/pages/semantic-search?q=...`).
## Page Content (`content_markdown`)
Pages store markdown. The editor implements a **deliberately small
subset** — agents writing content should stick to what's listed below,
since anything else silently passes through as plain text on render.
### Links
Use ordinary markdown links for everything:
| Target | Shape |
|---|---|
| Another page in the same workspace | `[text](/workspaces/<ws>/p/<uuid>)` |
| A file uploaded to the workspace | `[text](/api/v1/workspaces/<ws>/files/<uuid>/download)` |
| External URL | `[text](https://…)` |
The viewer renders all three with the same style; an `↗` glyph marks
off-origin URLs. Internal `/workspaces/<ws>/p/<uuid>` and stash absolute URLs are
SPA-routed (same tab, no reload); externals open in a new tab.
There is no `[[...]]` syntax. Use ordinary markdown links with the page's id URL.
### Block elements
| Markdown | Rendered as |
|---|---|
| `# Heading` to `### Heading` (H1–H3) | heading |
| blank line | paragraph break |
| `- item` / `* item` / `+ item` | bullet list |
| `1. item` / `2. item` | ordered list |
| `\| col1 \| col2 \|` followed by `\|---\|---\|` | GitHub-flavored pipe table |
Anything starting with `####` (H4+), `>` (blockquote), triple-backtick
code fences, or `---` (hr) is **not parsed** — the markdown renders
literally. Treat it as unsupported.
### Inline
| Markdown | Rendered as |
|---|---|
| `**bold**` | bold |
| `*italic*` | italic |
| `` `code` `` | inline code |
| `` | image (absolute URLs only) |
| `[](href)` | linked image |
| `[text](url)` | link (see above table) |
Strikethrough (`~~`), underline markup, LaTeX, footnotes, and raw HTML
tags are not supported.
### Authoring from agents
When generating page content programmatically, follow these rules and
you'll round-trip cleanly through edit mode:
1. Never emit `[[…]]` — use the id-URL link form.
2. Never emit relative paths like `[text](foo.md)` or `` —
the renderer treats them as dead and the editor strips them if a
user saves the page.
3. Don't rely on H4 or deeper headings. Restructure with H3 + bold.
4. Images need an absolute URL (external or `/files/<id>/download`).
## Session Events
Events are structured append-only records keyed by `(workspace, agent_name, event_type)`.
```json
POST /api/v1/workspaces/{ws}/sessions/events
{
"agent_name": "cli",
"event_type": "note",
"content": "text body",
"session_id": "optional",
"tool_name": "optional",
"metadata": {},
"attachments": [
{"file_id": "<uuid>", "name": "report.pdf", "content_type": "application/pdf"}
]
}
```
`attachments` entries must reference a previously-uploaded file. The CLI
wrapper (`stash sessions push --attach ./path`) uploads and attaches in one step.
Query/search:
- `GET /events?agent_name=&event_type=&limit=&after=`
- `GET /events/search?q=&limit=`
- `GET /events/{event_id}`
## Files
- `POST /files` — multipart upload (field `file`), 50 MB cap.
- `GET /files` — list.
- `GET /files/{id}` — metadata (with signed URL).
- `GET /files/{id}/text` — extracted text. Response shape:
`{"text": ..., "status": "pending|processing|done|failed", "error": ...}`.
Works for PDFs with embedded text and for plain-text / JSON / XML
uploads. Extraction runs asynchronously after upload — poll this
endpoint until `status` is `done` or `failed`.
- `DELETE /files/{id}` — best-effort S3 cleanup plus DB row delete.
## Rate Limits
- Registration: 5/min
- Login: 10/min
- CLI auth session polling: 60/min
## Tips for Agents
- Every resource requires a workspace — there is no no-workspace scope.
- For extracted text on an uploaded file, poll `GET /files/{id}/text` — it
returns `status` alongside the text so you can distinguish "still
extracting" (`pending`/`processing`) from "done, no text available"
(`done` with `text: null`).
- Attach files to session events rather than embedding base64 — keeps event
payloads small and allows reuse across events.
- When authoring page content that links to another page in the same
workspace, use the page id URL form: `[text](/workspaces/<ws>/p/<uuid>)`.