---
slug: "danypops-pi-scribe"
source_type: "readme"
source_url: "https://cdn.jsdelivr.net/gh/dpopsuev/scribe@main/README.md"
repo: "https://github.com/dpopsuev/scribe"
source_file: "README.md"
branch: "main"
---
<p align="center">
  <img src="assets/scribe_logo.png" alt="Scribe" width="200">
</p>

# Scribe

Work graph for AI agents. Scribe is a structured artifact store with native DAG support that lets AI coding assistants plan, track, and recall work across sessions -- beyond the limits of a single context window.

## Quick Start

### Container (recommended)

```bash
# podman or docker
podman run -d --name scribe \
  -p 8080:8080 \
  -v scribe-data:/data \
  quay.io/dpopsuev/scribe:v1.5.2
```

### Binary

```bash
go install github.com/dpopsuev/scribe/cmd/scribe@latest
scribe serve                   # stdio (Cursor, Claude Desktop)
scribe serve --transport http  # Streamable HTTP on :8080
```

### MCP Configuration

**Cursor / Claude Desktop (stdio -- local binary):**

```json
{
  "mcpServers": {
    "scribe": {
      "command": "scribe",
      "args": ["serve"]
    }
  }
}
```

**Cursor / Claude Desktop (HTTP -- container):**

```json
{
  "mcpServers": {
    "scribe": {
      "url": "http://localhost:8080/?workspace=origami"
    }
  }
}
```

### Agent runtime attachment (HTTP)

Prefer Streamable HTTP so multiple agents share one Scribe process. Stdio is fine for single-client local use.

```bash
scribe serve --transport http --addr :8080
```

| Concern | How |
|---|---|
| Liveness | `GET /healthz` |
| Scope | `?workspace=<scope>` (comma-separated for multi-scope) |
| Auth | `SCRIBE_AUTH_TOKEN` → Bearer on MCP routes; `/healthz` open |
| Ingest | `POST /api/v1/ingest` (NDJSON; used by code-intel scanners) |

**Claim ≈ assignee:** `artifact(action=claim, …)` — TTL lease in `Extra.claim`. See `help(query=claim)`.

**Message stream:** `message_add` / `message_list` (+ `comment_*` aliases) and `cursor_mark` / `cursor_get`. See `help(query=message)`.

**Librarian:** mesh compaction operators (`librarian`, `librarian_pass`). Opt-in ticker: `SCRIBE_LIBRARIAN_INTERVAL=24h` (Go duration or hours; empty/`0` = off). See `help(query=librarian)`.

**Sessions:** `agent.session` / `agent.turn` when `record_session` is on — not the same as MCP HTTP session IDs.

## Workflow

Scribe is designed for natural language. You talk to your agent, and it builds the work graph behind the scenes.

> **You:** I want authentication and rate limiting for v1.0.
>
> **Agent:** There's already a spec for rate limiting. I've created a spec for auth. What's your goal for today?

> **You:** I need to reinforce security — we found a token leak vulnerability.
>
> **Agent:** I've run an audit and created a goal for Security Reinforcement. Filed `BUG-1: Token leak`. Would you like a campaign as well?

> **You:** Yes.
>
> **Agent:** Campaign ready. Here's the audit and brief — 3 tasks, spec for the auth fix, bug for the leak, topo sort shows the execution order. Auth fix first, then rate limiting, then leak patch.

> **You:** Execute the campaign.
>
> **Agent:** *(works through tasks)* Security Reinforcement campaign complete. 3 tasks closed, code committed, all tests green.

## The Problem

LLM context windows are finite. A coding agent can hold ~100K tokens in working memory. When a session ends, everything it learned -- goals, decisions, dependencies, progress -- evaporates.

This creates three failure modes:

1. **Amnesia.** The agent re-discovers the same codebase from scratch every session.
2. **Drift.** Multi-session work loses coherence because there's no shared record of what was decided and why.
3. **Fragmentation.** Plans scattered across chat logs, markdown files, and issue trackers can't be queried or traversed as a graph.

Scribe solves this by giving agents a structured, persistent memory they can read and write through MCP tools -- a place to store campaigns, goals, specs, tasks, bugs, and their relationships in a queryable DAG.

## Core Concepts

| Concept | What it is |
|---|---|
| **Artifact** | The universal record. Everything is an artifact with a kind, status, scope, and auto-generated ID (e.g. `TASK-2026-042`). |
| **Kind** | The type of artifact. Canonical kinds: `goal`, `task`, `spec`, `bug`, `campaign`, `template`, `need`, `ref`, `doc`, `decision`, `config`, `mirror`. Enforced by schema validation. |
| **Task** | The primary unit of work. A task carries a goal statement, design sections, and dependency edges. Tasks **implement** specs and bugs. |
| **Spec** | A specification: the *what* and *why*. Defines acceptance criteria. Tasks implement specs. |
| **Bug** | A defect record. Like a spec, a bug is resolved by a task that implements it. |
| **Goal** | The north-star artifact for a scope. Setting a goal auto-creates a root delivery artifact and archives any previous goal. |
| **Status** | Lifecycle state: `draft` &rarr; `active` &rarr; `complete` / `dismissed`. Also: `current` (goals), `retired`, `archived`. |
| **Scope** | The project or repository an artifact belongs to (e.g. `locus`, `origami`). Enables multi-project planning from a single Scribe instance. |
| **Section** | A named text block attached to an artifact. Use for design notes, mermaid diagrams, acceptance criteria, or any structured content. |
| **Campaign** | Cross-project mission container. Groups goals, specs, and tasks under a theme (e.g. "v1 stabilization"). |
| **Template** | Meta-artifact (artifact about artifacts). Defines required sections and guidance for a kind. Artifacts link to templates via `satisfies`. |
| **Need** | A requirement or capability gap. Justifies goals and specs. |
| **Config** | Runtime configuration artifact. Sections are key-value pairs with cascading resolution (scoped > global). |
| **Edge** | A directed relationship: `parent_of`, `depends_on`, `follows`, `justifies`, `implements`, `documents`, `satisfies`. Edges form a DAG that agents can traverse. |

### Artifact Relationships

```mermaid
graph LR
    subgraph "Organizes Work"
        CAMPAIGN["campaign"]
        GOAL["goal"]
    end

    subgraph "Defines Work"
        SPEC["spec"]
        BUG["bug"]
        NEED["need"]
    end

    subgraph "Does Work"
        TASK["task"]
    end

    TEMPLATE["template"]

    CAMPAIGN -- parent_of --> GOAL
    GOAL -- parent_of --> TASK
    GOAL -- parent_of --> SPEC
    GOAL -- parent_of --> BUG
    TASK -- implements --> SPEC
    TASK -- implements --> BUG
    TASK -- depends_on --> TASK
    TASK -. follows .-> TASK
    NEED -. justifies .-> SPEC
    CAMPAIGN -. satisfies .-> TEMPLATE
    GOAL -. satisfies .-> TEMPLATE
    TASK -. satisfies .-> TEMPLATE
    SPEC -. satisfies .-> TEMPLATE
    BUG -. satisfies .-> TEMPLATE
```

**Campaigns** are mission containers that group goals. **Goals** are north-star artifacts that parent specs, tasks, and bugs. **Specs** and **bugs** define *what* needs to happen. **Tasks** implement specs and resolve bugs. `depends_on` edges enforce execution order; `follows` edges suggest ROI order. The `detect` admin tool warns when a task has no spec/bug link, or when a spec/bug has no implementing task.

### Example Artifact Graph

```mermaid
graph TD
    CMP["SCR-CMP-1\nv1.0 Campaign\n(active)"]

    GOAL["SCR-GOL-1\nv1.0 Release\n(current)"]
    CMP -->|parent_of| GOAL

    SPEC["SCR-SPC-1\nAuth Spec\n(complete)"]
    GOAL -->|parent_of| SPEC

    BUG["SCR-BUG-1\nToken Leak\n(open)"]
    GOAL -->|parent_of| BUG

    TSK1["SCR-TSK-1\nImplement Auth\n(complete)"]
    TSK1 -.->|implements| SPEC
    GOAL -->|parent_of| TSK1

    TSK2["SCR-TSK-2\nRate Limiting\n(active)"]
    TSK2 -.->|depends_on| TSK1
    GOAL -->|parent_of| TSK2

    TSK3["SCR-TSK-3\nFix Token Leak\n(draft)"]
    TSK3 -.->|implements| BUG
    GOAL -->|parent_of| TSK3
```

Solid arrows are `parent_of` edges (tree structure). Dashed arrows are `implements`, `depends_on`, or `follows` edges (semantics). The agent uses `graph next` to find the highest-priority unblocked task whose dependencies are all complete.

## Architecture

> Diagrams generated by [Locus](https://github.com/dpopsuev/locus) (`locus diagram --theme natural`). Components colored by health: green = healthy, yellow = sick (fan-in >= 3, churn >= 8), red = fatal.

### Dependency Graph

> `locus diagram /path/to/scribe --type dependency --theme natural`

```mermaid
%%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#F7FAFC', 'primaryTextColor': '#2D3748', 'primaryBorderColor': '#4A90D9', 'lineColor': '#4A90D9', 'background': '#FFFFFF', 'fontSize': '14px'}}}%%
graph TD
    classDef boundary fill:#FFFFFF,stroke:#718096,color:#2D3748
    classDef component fill:#F7FAFC,stroke:#4A90D9,color:#2D3748
    classDef edge stroke:#4A90D9
    classDef entry fill:#4A90D9,stroke:#4A90D9,color:#FFFFFF
    classDef fatal fill:#E53E3E,stroke:#E53E3E,color:#FFFFFF
    classDef healthy fill:#38A169,stroke:#38A169,color:#FFFFFF
    classDef sick fill:#D69E2E,stroke:#D69E2E,color:#FFFFFF
    classDef violation_edge stroke:#E53E3E
    cmd_scribe["cmd/scribe [churn:12]"]:::entry
    config["config [churn:6]"]:::healthy
    directive["directive [churn:6]"]:::healthy
    keygen["keygen [churn:2]"]:::healthy
    lifecycle["lifecycle [churn:7]"]:::healthy
    mcp["mcp [churn:20]"]:::healthy
    mcpclient["mcpclient [churn:1]"]:::healthy
    model["model [churn:9]"]:::sick
    protocol["protocol [churn:14]"]:::sick
    render["render [churn:1]"]:::healthy
    store["store [churn:13]"]:::sick
    web["web [churn:6]"]:::healthy
    cmd_scribe -->|"13"| config
    cmd_scribe -->|"7"| directive
    cmd_scribe -->|"2"| mcp
    cmd_scribe -->|"6"| mcpclient
    cmd_scribe -->|"18"| model
    cmd_scribe -->|"90"| protocol
    cmd_scribe -->|"3"| render
    cmd_scribe -->|"6"| store
    cmd_scribe -->|"1"| web
    config -->|"2"| model
    config -->|"5"| protocol
    config -->|"1"| store
    lifecycle -->|"6"| model
    lifecycle -->|"6"| store
    mcp -->|"8"| directive
    mcp -->|"6"| mcpclient
    mcp -->|"9"| model
    mcp -->|"88"| protocol
    mcp -->|"3"| render
    mcp -->|"1"| store
    protocol -->|"1"| keygen
    protocol -->|"5"| lifecycle
    protocol -->|"57"| model
    protocol -->|"21"| store
    render -->|"24"| model
    store -->|"40"| model
    web -->|"2"| model
    web -->|"12"| protocol
```

### Layer Diagram

> `locus diagram /path/to/scribe --type layers --theme natural`

```mermaid
%%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#F7FAFC', 'primaryTextColor': '#2D3748', 'primaryBorderColor': '#4A90D9', 'lineColor': '#4A90D9', 'background': '#FFFFFF', 'fontSize': '14px'}}}%%
block-beta
    columns 1
    block:layer_0["Layer 0"]
        cmd_scribe["cmd/scribe"]
    end
    columns 3
    block:layer_1["Layer 1"]
        config["config"]
        mcp["mcp"]
        web["web"]
    end
    columns 4
    block:layer_2["Layer 2"]
        directive["directive"]
        mcpclient["mcpclient"]
        protocol["protocol"]
        render["render"]
    end
    columns 2
    block:layer_3["Layer 3"]
        keygen["keygen"]
        lifecycle["lifecycle"]
    end
    columns 1
    block:layer_4["Layer 4"]
        store["store"]
    end
    columns 1
    block:layer_5["Layer 5"]
        model["model"]
    end
```

### Packages

| Package | LOC | Churn | Role |
|---|---|---|---|
| `cmd/scribe` | 1880 | 19 | CLI entry point. Every MCP tool has a CLI equivalent. Capsule and seed-dir commands. |
| `mcp` | 3215 | 44 | MCP server. Handlers for artifact, graph, and admin tools. Bulk ops, diff, top-N, impact. |
| `protocol` | 4939 | 38 | All business logic. Capsule export/import, seed, config resolution, template conformance. |
| `model` | 1048 | 25 | Data model: `Artifact`, `Section`, `Edge`, `Filter`, `Schema`, `IDConfig`. |
| `store` | 2656 | 30 | Persistence interface + SQLite. Snapshots with pluggable backend. |
| `render` | 281 | 2 | Markdown and table formatters for CLI and MCP output. |
| `config` | 281 | 12 | Configuration loading, workspace definitions, seed_dir. |
| `directive` | 119 | 6 | MCP tool registry and input validation. |
| `keygen` | 214 | 2 | Auto-generated ID sequences per artifact kind. |
| `web` | 371 | 7 | Web UI for artifact browsing and sprint boards. |

### Storage

Single SQLite database (CGo-free via `modernc.org/sqlite`). Tables:

- **artifacts** -- UID primary key (crypto/rand hex), human-readable ID as unique index with auto-rename on collision. JSON for arrays/maps.
- **edges** -- directed graph: `(from, to, relation)` with a unique constraint.
- **sequences** / **scoped_sequences** -- auto-increment counters with collision checks.

Snapshots: automatic periodic backups with pluggable backend (local filesystem, future S3). Restore with pre-restore backup via `admin snapshot restore`.

Default location: `~/.scribe/scribe.sqlite` (binary) or `/data/scribe.sqlite` (container).

### Data Model

Every artifact carries:

- **Identity:** auto-generated ID (`PREFIX-YYYY-SEQ`), kind, scope
- **Content:** title, goal statement, named sections (arbitrary text blocks)
- **Graph:** parent, depends_on edges, typed links (justifies, implements, documents)
- **Lifecycle:** status, priority, sprint assignment, labels, timestamps
- **Extension:** `extra` map for domain-specific key-value pairs (reminders, custom fields)

The vocabulary is enforced: unknown kinds are rejected with a hint to register them via `scribe vocab add`. Unknown fields go into `extra`.

## MCP Tools

| Tool | Description |
|---|---|
| `artifact` | Create, read, update, and manage work artifacts. Actions: `create`, `batch_create`, `clone`, `get` (bulk via ids, section_filter), `list` (compact fields, count, group_by, top-N ranking), `set`, `update` (patch map), `archive`, `attach_section`, `get_section`, `detach_section`, `diff`. Template auto-linking with cascading resolution. |
| `graph` | Navigate and modify artifact relationships. Actions: `tree`, `briefing`, `topo_sort`, `link`, `unlink`, `bulk_link`, `bulk_unlink`, `move` (re-parent), `replace` (swap target), `impact` (downstream analysis). Relations: parent_of, depends_on, follows, justifies, implements, documents, satisfies. |
| `admin` | System administration and monitoring. Actions: `motd`, `changelog`, `dashboard`, `snapshot` (create/list/diff/restore), `set_goal`, `vacuum`, `detect`, `lint`, `check`, `seed`, `transfer_scope` (with dry_run), `set_scope_labels`, `list_scope_labels`. |

## Configuration

Scribe works with zero configuration. For customization, create a `scribe.yaml`:

```yaml
# scribe.yaml
db: ~/.scribe/scribe.sqlite
transport: stdio
addr: ":8080"

scopes:
  - myproject

vocabulary:
  kinds:
    - goal
    - sprint
    - task
    - spec
    - bug
    # add your own via: scribe vocab add <kind>

schema:
  kinds:
    goal:          { prefix: GOAL }
    sprint:        { prefix: SPR }
    task:          { prefix: TASK }
    spec:          { prefix: SPE }
    bug:           { prefix: BUG }
    campaign:      { prefix: CON }
    template:      { prefix: TPL }
    need:          { prefix: NED }

  statuses:
    - draft
    - active
    - current
    - complete
    - dismissed
    - archived

  guards:
    archived_readonly: true
    completion_requires_children_complete: true
    auto_archive_goal_on_justify_complete: true
    delete_requires_archived: true
    auto_complete_parent_on_children_terminal: true
    auto_activate_next_draft_sprint: true

workspaces:
  origami: [origami, asterisk, achilles]
  sidecar: [scribe, locus, lex, limes]
```

**Resolution order:** `--config` flag > `$SCRIBE_CONFIG` > `./scribe.yaml` > `$SCRIBE_ROOT/scribe.yaml` > `~/.scribe/scribe.yaml` > built-in defaults.

**Override chain:** CLI flags > environment variables > config file > defaults.

**Workspace connections (HTTP):** Add `?workspace=origami` to the MCP URL to scope a connection to specific scopes.

For containers, mount a config file at `/data/scribe.yaml`:

```bash
podman run -d --name scribe \
  -p 8080:8080 \
  -v scribe-data:/data \
  -v ./scribe.yaml:/data/scribe.yaml \
  quay.io/dpopsuev/scribe:v1.5.2
```

## Environment Variables

| Variable | Default | Description |
|---|---|---|
| `SCRIBE_ROOT` | `~/.scribe` | Storage root; sets default DB and config paths |
| `SCRIBE_DB` | `$SCRIBE_ROOT/scribe.sqlite` | Database path (overrides `SCRIBE_ROOT`) |
| `SCRIBE_TRANSPORT` | `stdio` | Transport: `stdio` or `http` |
| `SCRIBE_ADDR` | `:8080` | Listen address (HTTP transport only) |
| `SCRIBE_CONFIG` | `./scribe.yaml` or `$SCRIBE_ROOT/scribe.yaml` | Path to config file (first found wins) |
| `SCRIBE_LOG_LEVEL` | `info` | Log level: `debug`, `info`, `warn`, `error`. JSON to stderr. |
| `SCRIBE_WORKSPACE` | — | Workspace name for stdio transport (resolves scopes from config). |
| `SCRIBE_AUTH_TOKEN` | — | If set, MCP HTTP routes require `Authorization: Bearer …`. `/healthz` stays open. |
| `SCRIBE_MCP_STATELESS` | `true` | HTTP: accept stale Mcp-Session-Id after restart. Set `0` for sticky sessions. |

## License

MIT
