# ProductBrain LLM Operations Guide

This guide teaches any LLM how to read, understand, and modify a ProductBrain project. After reading this, you should be able to generate a well-formed brain from a codebase or artifact.

## What is ProductBrain?

A logic-driven product planning tool that synthesizes Opportunity Solution Trees and User Story Mapping. The core insight: a single dataset rendered as either a **tree** (strategy view) or a **matrix** (execution view).

**It is NOT:**
- A Jira clone (no time estimates, no velocity)
- A user story repository (jobs are verifiable checkpoints, not "As a user...")
- A customer interview tool (logic-driven validation, not interview-driven)

## The Hierarchy

Four levels in the OST tree, plus a standalone Task type:

```
Goal (green)
 -- Need (blue)
     -- Approach (amber)
         -- Job (gray)

Task (standalone, no parent)
```

| Level | Question it Answers | Required Fields | Optional Tags |
|-------|---------------------|-----------------|---------------|
| **Goal** | What's the business outcome? | label | goalStatus, description |
| **Need** | What must happen for the goal to succeed? | label, parentId (goal) | description |
| **Approach** | What's our scoped bet? | label, parentId (need), **measure** | **kano**, **size**, **approachStatus**, description, notes |
| **Job** | What can you verify from the deployed product? | label, parentId (approach) | **maturity**, **iteration**, status, description, notes |
| **Task** | Ad-hoc work outside the tree? | label | **iteration**, status, description |

### Parent-Child Rules

- **Goals** have no parent (root nodes)
- **Needs** must have a Goal parent
- **Approaches** must have a Need parent
- **Jobs** must have an Approach parent
- **Tasks** are standalone (`parentId: null`) — for bugs, polish, and work discovered during testing that doesn't fit the hierarchy. Tasks appear in the **Misc column** of matrix view.

### Type-Specific Field Rules

Fields are validated per node type. Setting a field on the wrong type is rejected:

| Field | Valid on |
|-------|----------|
| `iteration` | job, task |
| `maturity` | job |
| `kano` | approach |
| `size` | approach |
| `measure` | approach |
| `approachStatus` | approach |
| `goalStatus` | goal |

Approaches do **not** carry an iteration. Their presence in the matrix is determined by whether any of their child jobs are assigned to the selected iteration.

## Tags and Their Meaning

> For the full model rationale see `docs/model-hierarchy.md`.

### Kano Classification (on Approaches)

Describes the **market maturity tier** of this approach — what satisfaction level is this bet targeting? The same need can have multiple approaches at different Kano tiers (e.g., manual windows is must-have, power windows is performance, auto-close on rain is delighter).

| Tag | Meaning | Example |
|-----|---------|---------|
| `must-have` | Table stakes. Causes dissatisfaction if absent, no delight if present. | Basic Stripe checkout, ToS page |
| `performance` | More is better, linear satisfaction. | Self-serve billing portal, annual plan |
| `delighter` | Unexpected, disproportionate satisfaction. | AI-maintained tree integrity, proactive coaching |

### Approach Status

Lifecycle phase of the approach — distinct from job completion:

| Status | Meaning |
|--------|---------|
| `development` | Jobs are being built |
| `validation` | Shipped to users, being tested against the measure |
| `resolved` | The bet paid off, approach delivered its value |
| `retired` | The bet failed or was superseded |

### Maturity (on Jobs)

The quality bar for this job — **MVP or releasable**. Set at planning time.

| Tag | Meaning |
|-----|---------|
| `mvp` | It works, it's testable, the approach can be validated. Rough edges acceptable. |
| `releasable` | Polished enough to stay. You're not embarrassed when a stranger uses it. |

**Never use maturity to indicate completion.** Use `status: "done"` instead.

### Status (on Jobs and Tasks)

Tracks whether work has been completed. Orthogonal to maturity.

- `status: "done"` — work is complete
- Absent — work is not done

To mark a job done, set `data.status = "done"`. To mark it not done, remove `status` from `data`.

### Approach Sizing (on Approaches)

Relative weight of the bet — NOT time estimates:

| Size | Feel |
|------|------|
| `skateboard` | Trivial, just do it |
| `vespa` | Small but real, low risk |
| `car` | Substantial, common investment level |
| `truck` | Large, complex, needs coordination |
| `antonov` | Decompose immediately — this is a program, not a bet |

### Goal Status

Lifecycle state for goals:

| Status | Meaning |
|--------|---------|
| `focus` | Actively working on this (default) |
| `later` | Parked, not currently a priority |
| `done` | Completed, kept for reference |

## Iterations

Iterations are horizontal delivery slices across approaches. They are not sprints (no fixed time box) and not releases (no deployment coupling). They are thematic groupings of work that should ship together.

- **System iterations** (e.g. "Later") are non-deletable. Later is the inbox — all unassigned work defaults here. Later is hidden from the matrix unless explicitly selected.
- **Done iterations** are hidden from the UI and matrix view.
- **Current iteration** = the earliest active non-system iteration by `sort_order`. When the user says "add to the current iteration" or "assign to the current iteration", query iterations and pick this one — don't ask.
- A need does **not** need to be completely met before moving on. Ship the must-have approach, soak it, then progress higher-value work elsewhere.

```bash
# List all iterations
curl -s "$API_URL/iterations?projectId=$PROJECT_ID" \
  -H "Authorization: Bearer $API_KEY" | jq '.iterations'

# Active iterations only
curl -s "$API_URL/iterations?projectId=$PROJECT_ID&status=active" \
  -H "Authorization: Bearer $API_KEY" | jq '.iterations'

# Get the current iteration (first active non-system by sort order)
curl -s "$API_URL/iterations?projectId=$PROJECT_ID&current=true" \
  -H "Authorization: Bearer $API_KEY" | jq '.iteration'
```

## The Notes Field

**Critical for LLM-to-LLM context transfer.**

- `description` = human-facing summary. Keep it scannable (1-3 sentences).
- `notes` = extended context. Rationale, alternatives considered, edge cases, discussion history.

When making decisions or capturing tradeoffs, **write to notes liberally**. Future LLM sessions read notes to understand *why* something exists, not just *what* it is.

```json
{
  "label": "WebSocket bidirectional sync",
  "description": "Real-time sync between agent and UI without polling.",
  "notes": "## Decision (Feb 2026)\nChose WebSocket over polling for latency.\n\n## Alternatives Considered\n- Long polling: simpler but higher latency\n- SSE: one-way only, doesn't support agent->server"
}
```

## Data Storage

ProductBrain uses **Supabase** (Postgres) as the source of truth. Data is stored in four tables:

| Table | Purpose |
|-------|---------|
| `projects` | Project metadata (name, overview, summary) |
| `nodes` | All tree nodes and tasks |
| `iterations` | Iteration definitions with sort order |
| `changelog` | Append-only audit log of all mutations |

Realtime sync uses Supabase Realtime (postgres_changes) for cross-client updates.

## Node ID Conventions

IDs are generated atomically by the mutation API when adding nodes — you never need to generate IDs manually.

Format: `{type}-{sequential number}` (e.g., `goal-1`, `need-15`, `approach-23`, `job-109`, `task-8`).

## API

ProductBrain exposes a REST API for LLM access. Writes go through `POST /api/mutate`. Reads go through `GET /api/search` (semantic) or `GET /api/nodes` (bulk). Auth via `Bearer` API key.

### Setup

```bash
export API_KEY="<your API key>"
export API_URL="https://productbrain.com/api"
```

The API key is per-user — generate one from the app's API Key modal or obtain it from your environment's secret store.

### List Projects

```bash
curl -s "$API_URL/projects" \
  -H "Authorization: Bearer $API_KEY" | jq '.projects'
```

Response: `{ "projects": [{ "id": "example-abc123", "name": "My Project" }, ...] }`

```bash
export PROJECT_ID="<project id from above>"
```

### Search Nodes

Semantic search over all nodes. Every node has a vector embedding — search matches by meaning, not exact text.

```bash
curl -s "$API_URL/search?q=barcode+scanning&projectId=$PROJECT_ID" \
  -H "Authorization: Bearer $API_KEY" | jq '.results'
```

Returns ranked candidates with similarity scores and ancestor paths (the full lineage: goal → need → approach). Use this to:
- **Find existing work** before creating a node — avoids duplicates
- **Resolve placement** — which parent does a new node belong under?
- **Look up a node** when you know the concept but not the ID

If results are ambiguous, drill into a candidate: `GET /api/tree?nodeId=<id>&direction=context` returns its ancestors, siblings, and children.

**When to use search vs bulk read:**

| I need to... | Use |
|---|---|
| Find a node by concept or description | `GET /api/search?q=...` |
| Check if work already exists before creating | `GET /api/search?q=...` |
| Find the right parent for a new node | `GET /api/search?q=...` |
| List all jobs in an iteration | `GET /api/nodes?type=job&iteration=X` |
| Export the full tree | `GET /api/nodes` |
| Count nodes by type | `GET /api/nodes?type=X` |

The first three are the most common operations. Use search.

### Read Nodes

Bulk read with optional filters. Returns all matching nodes without ranking.

```bash
# All nodes
curl -s "$API_URL/nodes?projectId=$PROJECT_ID" \
  -H "Authorization: Bearer $API_KEY" | jq '.nodes'

# Goals only
curl -s "$API_URL/nodes?projectId=$PROJECT_ID&type=goal" \
  -H "Authorization: Bearer $API_KEY" | jq '.nodes'

# Jobs in a specific iteration
curl -s "$API_URL/nodes?projectId=$PROJECT_ID&type=job&iteration=Cloud" \
  -H "Authorization: Bearer $API_KEY" | jq '.nodes'
```

Iterations are read via `GET /api/iterations` — see the Iterations section above.

### Add Node

The API handles ID generation and user_id lookup automatically.

```bash
curl -X POST "$API_URL/mutate" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "add",
    "projectId": "'"$PROJECT_ID"'",
    "node": {
      "type": "job",
      "parentId": "approach-1",
      "data": {
        "label": "Add upload progress indicator",
        "maturity": "releasable",
        "iteration": "MVP"
      }
    }
  }'
# Returns: { "success": true, "node": { "id": "job-199", "type": "job", ... } }
```

### Add Task

```bash
curl -X POST "$API_URL/mutate" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "add",
    "projectId": "'"$PROJECT_ID"'",
    "node": {
      "type": "task",
      "data": {
        "label": "Fix tooltip overlap on small screens",
        "iteration": "Cloud"
      }
    }
  }'
```

### Update Node

Uses PATCH semantics — only include the fields you want to change. Existing data fields are preserved (merged with the provided fields).

```bash
curl -X POST "$API_URL/mutate" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "update",
    "projectId": "'"$PROJECT_ID"'",
    "nodeId": "job-45",
    "data": { "label": "Updated label", "status": "done" }
  }'
```

### Delete Node (Hard Delete)

```bash
curl -X POST "$API_URL/mutate" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "delete",
    "projectId": "'"$PROJECT_ID"'",
    "nodeId": "job-99"
  }'
```

Deletes are permanent. The changelog table retains an audit trail of what was deleted and its state before deletion. There is no undo.

### Batch Mutations

Validate-all-then-apply semantics — if any mutation fails validation, the entire batch is rejected.

```bash
curl -X POST "$API_URL/mutate" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "batch",
    "projectId": "'"$PROJECT_ID"'",
    "mutations": [
      {
        "action": "add",
        "node": {
          "type": "job",
          "parentId": "approach-1",
          "data": { "label": "First job", "maturity": "minimal", "iteration": "MVP" }
        }
      },
      {
        "action": "update",
        "nodeId": "job-100",
        "data": { "status": "done" }
      }
    ]
  }'
```

### Iteration CRUD

Iterations are managed through the mutation API, same as nodes.

```bash
# Create an iteration
curl -X POST "$API_URL/mutate" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "addIteration",
    "projectId": "'"$PROJECT_ID"'",
    "iteration": { "name": "Model refinement" }
  }'

# Update an iteration (PATCH semantics)
curl -X POST "$API_URL/mutate" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "updateIteration",
    "projectId": "'"$PROJECT_ID"'",
    "iterationName": "Model refinement",
    "data": { "status": "done" }
  }'
# Marking done auto-sets completedAt. Fields: name, status, gitTags, sortOrder, completedAt.

# Delete an iteration (system iterations cannot be deleted)
curl -X POST "$API_URL/mutate" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "deleteIteration",
    "projectId": "'"$PROJECT_ID"'",
    "iterationName": "Model refinement"
  }'

# Reorder iterations
curl -X POST "$API_URL/mutate" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "reorderIterations",
    "projectId": "'"$PROJECT_ID"'",
    "order": ["Pre-Launch", "AI I/O", "Month 1", "Month 2–3", "Later"]
  }'
```

### Navigate the Tree

Structured traversal — use instead of pulling all nodes flat.

```bash
# Ancestor chain (leaf to root) — includes path array of IDs
curl -s "$API_URL/tree?projectId=$PROJECT_ID&nodeId=job-1&direction=up" \
  -H "Authorization: Bearer $API_KEY" | jq '{path: .path, labels: [.nodes[] | .data.label]}'

# Subtree (depth-first, with depth field on each node)
curl -s "$API_URL/tree?projectId=$PROJECT_ID&nodeId=goal-1&direction=down" \
  -H "Authorization: Bearer $API_KEY" | jq '.nodes[] | {id, depth, label: .data.label}'

# Siblings (same parent, excluding the queried node)
curl -s "$API_URL/tree?projectId=$PROJECT_ID&nodeId=approach-1&direction=lateral" \
  -H "Authorization: Bearer $API_KEY" | jq '.nodes[] | {id, label: .data.label}'

# Full context: ancestors + siblings + children in one call
curl -s "$API_URL/tree?projectId=$PROJECT_ID&nodeId=approach-1&direction=context" \
  -H "Authorization: Bearer $API_KEY" | jq '{ancestors: [.ancestors[] | .data.label], siblings: [.siblings[] | .data.label], children: [.children[] | .data.label]}'
```

### Semantic Search

Find nodes by meaning, not exact label match. Uses embeddings + cosine similarity. Results include an `ancestors` array (id, type, label from node to root) for context.

```bash
# Search by natural language
curl -s "$API_URL/search?projectId=$PROJECT_ID&q=reduce+churn+through+onboarding&limit=5" \
  -H "Authorization: Bearer $API_KEY" | jq '.results[] | {id: .node.id, label: .node.data.label, similarity, ancestors}'

# Filter by node type
curl -s "$API_URL/search?projectId=$PROJECT_ID&q=onboarding&type=approach&limit=3" \
  -H "Authorization: Bearer $API_KEY" | jq '.results[]'
```

Parameters: `q` (required), `projectId` (required), `type` (optional filter), `limit` (optional, default 5, max 20).

### View Control

Control the ProductBrain UI from the API. Commands are delivered via Realtime — the app responds instantly.

```bash
# Switch to matrix view
curl -X POST "$API_URL/view-command" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"projectId": "'"$PROJECT_ID"'", "command": {"type": "setViewMode", "mode": "matrix"}}'

# Switch to tree view
curl -X POST "$API_URL/view-command" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"projectId": "'"$PROJECT_ID"'", "command": {"type": "setViewMode", "mode": "tree"}}'

# Select an iteration
curl -X POST "$API_URL/view-command" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"projectId": "'"$PROJECT_ID"'", "command": {"type": "selectIteration", "iterationName": "Alpha release"}}'

# Zoom to a node
curl -X POST "$API_URL/view-command" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"projectId": "'"$PROJECT_ID"'", "command": {"type": "zoomToNode", "nodeId": "approach-1"}}'

# Expand a node
curl -X POST "$API_URL/view-command" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"projectId": "'"$PROJECT_ID"'", "command": {"type": "expandNode", "nodeId": "goal-1"}}'

# Collapse a node
curl -X POST "$API_URL/view-command" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"projectId": "'"$PROJECT_ID"'", "command": {"type": "collapseNode", "nodeId": "goal-1"}}'

# Focus on specific goals
curl -X POST "$API_URL/view-command" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"projectId": "'"$PROJECT_ID"'", "command": {"type": "focusGoals", "goalIds": ["goal-1", "goal-2"]}}'
```

Command types: `setViewMode`, `selectIteration`, `expandNode`, `collapseNode`, `focusGoals`, `zoomToNode`.

### Read View State

See what the user is currently looking at before sending view commands:

```bash
curl -s "$API_URL/view-state?projectId=$PROJECT_ID" \
  -H "Authorization: Bearer $API_KEY" | jq '.'
```

Response:
```json
{
  "view_mode": "tree",
  "selected_iteration": "Alpha release",
  "expanded_nodes": ["goal-1", "need-1"],
  "focused_goals": ["goal-1"],
  "selected_node": "approach-1",
  "updated_at": "2026-05-17T..."
}
```

Use this to make contextual decisions — e.g. zoom to a node only if the user is in tree view, or check which iteration is selected before adding jobs.

### Placing New Work in the Tree

Before creating a node, search for where it belongs:

```bash
# 1. Search — find the right parent
curl -s "$API_URL/search?q=users+cant+compare+prices&projectId=$PROJECT_ID" \
  -H "Authorization: Bearer $API_KEY" | jq '.results[:3]'

# Response includes ancestor paths — often enough to decide placement:
# { "id": "need-5", "label": "Compare prices across stores", "similarity": 0.87,
#   "ancestors": [{ "id": "goal-1", "type": "goal", "label": "Help shoppers save money" }] }

# 2. Context (only if ambiguous) — see siblings and children
curl -s "$API_URL/tree?nodeId=need-5&direction=context&projectId=$PROJECT_ID" \
  -H "Authorization: Bearer $API_KEY" | jq '.'

# 3. Create under the right parent
curl -X POST "$API_URL/mutate" ...
```

At most two calls. Never pull all nodes to find placement.

## Common Patterns

### Capturing Decisions in Notes

When you make a decision or discuss tradeoffs, write to the relevant node's notes:

```bash
curl -X POST "$API_URL/mutate" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "update",
    "projectId": "'"$PROJECT_ID"'",
    "nodeId": "approach-5",
    "data": { "notes": "## Decision\nChose X over Y because..." }
  }'
```

### Adding Work Discovered During Implementation

When implementing a job, if you discover new work needed:

1. Add new jobs under the same approach (or a new approach if it's a different bet)
2. Leave them unassigned to iteration (backlog)
3. Note in the original job's notes that you discovered additional work

### Marking Work Complete

```bash
# Only include the field you're changing — PATCH semantics preserves existing data
curl -X POST "$API_URL/mutate" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "update",
    "projectId": "'"$PROJECT_ID"'",
    "nodeId": "job-45",
    "data": { "status": "done" }
  }'
```

## Generating a Brain from a Project

When asked to create a ProductBrain tree from a codebase or artifact:

### Working Incrementally

For large projects, do NOT attempt to build the entire tree in one pass. Work top-down in layers:

1. **First pass: Goals and Needs only.** Get the strategic structure right before going deeper. Write all goals, then decompose each into needs. Stop and verify.
2. **Second pass: Approaches.** For each need, identify approaches. Write them with measures. Stop and verify.
3. **Third pass: Jobs (only where needed).** Only decompose into jobs for approaches that are actively being worked or have clear deliverables. Many approaches don't need jobs yet.

Each pass should be committed to the database before starting the next. This prevents half-baked trees where the top is thoughtful and the bottom is filler. It also lets you catch structural mistakes early — a bad goal pollutes everything beneath it.

### 1. Identify Goals (1-6 typically)

Goals are the top-level outcomes. Ask: "What is this project trying to achieve?"

Examples:
- "Deliver a usable audio knowledge platform"
- "Build a sustainable product business"
- "AI augments the founder's thinking"

### 2. Decompose into Needs

For each goal, ask: "What must happen for this to succeed?"

Classify each need with Kano:
- Infrastructure, reliability, auth, payments - `must-have`
- Speed, accuracy, capacity improvements - `performance`
- Unexpected delight, wow moments - `delighter`

### 3. Identify Approaches

For each need, ask: "How might we address this?"

Multiple approaches per need is normal — they can be alternative bets (pick one) or complementary bets (pursue in parallel). Either is fine.

**The test:** "Is this a coherent bet you could pursue independently?"

Each approach should stand on its own as something that could succeed or fail. If it only makes sense as part of delivering something else, it's a job, not an approach.

Example of the mistake:
- Bad: "LLM monitors brain", "Notification UI", "Encode heuristics" as three approaches — these are implementation pieces, not bets
- Good: "Proactive coaching from canonical sources" as one approach, with those three as jobs beneath it
- Complementary approaches might be: "Proactive coaching" AND "On-demand coaching via chat" (both bets, could pursue both)
- Alternative approaches might be: "Push notifications" vs "Pull-based dashboard" (choose one)

**Every approach needs a measure** — how will you know it's working?

Size them relatively:
- Quick wins, obvious implementations - `skateboard` or `vespa`
- Real engineering effort - `car`
- Complex, cross-cutting - `truck`
- Needs decomposition - `antonov` (then break it down)

### 4. Break into Jobs

For each approach, ask: "What checkpoints prove this is working?"

A job is a **verifiable checkpoint** — an observable result you can confirm before moving to the next step. Not an implementation task, not a full feature, but a gate: if this doesn't pass, nothing after it matters.

**Three fields, three audiences:**
- **Label** = what you can observe ("Upload modal opens and accepts PDF files")
- **Description** = where to verify it (UI, devtools, API response, console trace)
- **Notes** = how to build it (implementation detail, for coding agents — not shown prominently in UI)

**Examples — approach: "PDF document import":**

| Job label | Description | Notes |
|-----------|-------------|-------|
| Upload modal opens and accepts PDF files | Click import → modal appears, PDF file type accepted | Wire file input with accept=".pdf", add modal component |
| Server confirms receipt and returns document ID | Devtools network tab: POST /api/import returns 200 with { documentId } | Multer middleware, S3 or temp storage, return ID |
| Extracted key points visible in devtools | Console: trace shows extracted sections and headings | pdf-parse library, LLM extraction prompt, log output |
| Goal tree renders from extracted content | Canvas shows goal + needs + approaches populated from PDF | Map extraction output to draft nodes via useDraftMode |
| User can review and edit tree before committing | Draft nodes editable in side panel, commit/discard buttons work | Reuse existing draft commit flow |

Each job is independently verifiable. A strategist checks the UI-visible ones (1, 4, 5). A builder agent checks all of them. If job 3 fails, you know the break is between "server received the file" and "extraction worked" — no untangling.

**This is also execution discipline for agents.** When working through jobs:
1. Do the work for this checkpoint
2. Verify it passes (check what the description says to check)
3. Commit
4. Move to the next job

**Never batch multiple checkpoints into one pass.** Each job is a gate. If you skip verification, you compound errors and spend days debugging instead of hours building.

Format: **Outcome-first, not implementation-first.**
- Good: "Share link shows read-only tree" (observable checkpoint)
- Good: "Filtered iteration shows only assigned jobs" (verifiable in UI)
- Bad: "Create payments table migration" (implementation step — goes in notes)
- Bad: "As a user, I want to pay with my credit card" (user story format)

Tag with maturity:
- Foundation, dependencies, spikes - `mvp`
- User-visible value - `releasable`

**Job Granularity Guidelines:**

- **Jobs are verification gates, not brainstorming.** Don't decompose into jobs until the approach is actively being worked. The approach captures the bet — jobs are the checkpoints when ready to execute.
- **Each job should be verifiable in under a minute.** If you can't confirm it passed quickly, it's too big — split it. If it takes 10 seconds to check, it's the right size.
- **When in doubt, use a Task.** If work feels disconnected from the strategic tree — useful but not advancing a specific goal — create a Task instead. Tasks are guilt-free parking for real work that doesn't fit the hierarchy.
- **The approach is often the job.** For simple approaches, don't create jobs at all. The approach itself is the trackable unit. Only decompose when there's genuine complexity worth tracking separately. *Caveat: if you need to schedule the work, create a single job — approaches can't be assigned to iterations directly.*

### 5. Assign to Iterations (Optional)

If the project has release milestones, assign jobs and tasks to iterations. Unassigned jobs are backlog — captured but not committed. Only jobs and tasks carry iteration assignments.

## Example: Minimal Well-Formed Tree

```json
{
  "nodes": [
    {
      "id": "goal-1",
      "type": "goal",
      "data": {
        "label": "Users can manage their audio library",
        "description": "Core value prop: upload, organize, and access audio content."
      }
    },
    {
      "id": "need-1",
      "type": "need",
      "parentId": "goal-1",
      "data": {
        "label": "Upload audio reliably",
        "kano": "must-have",
        "description": "Table stakes. If uploads fail, nothing else matters."
      }
    },
    {
      "id": "approach-1",
      "type": "approach",
      "parentId": "need-1",
      "data": {
        "label": "Chunked upload with resume",
        "measure": "99.9% upload success rate for files up to 2GB",
        "size": "car",
        "description": "Handle large files and flaky connections gracefully."
      }
    },
    {
      "id": "job-1",
      "type": "job",
      "parentId": "approach-1",
      "data": {
        "label": "Upload progress bar reaches 100% for a 500MB file",
        "maturity": "mvp",
        "iteration": "MVP",
        "description": "UI: progress bar advances smoothly. Devtools: chunked PUT requests visible in network tab. Server returns upload ID on completion.",
        "notes": "Use tus.io or custom chunked endpoint. 5MB chunks, retry on failure. Store reassembled file in S3. Return { uploadId } on final chunk."
      }
    },
    {
      "id": "task-1",
      "type": "task",
      "data": {
        "label": "Fix upload timeout on slow connections",
        "iteration": "MVP",
        "description": "Discovered during testing. Not tied to a specific approach."
      }
    }
  ],
  "iterations": [
    { "name": "MVP", "status": "active" },
    { "name": "Later", "status": "active", "system": true }
  ]
}
```

## Validation Rules

1. **Non-goal/task nodes require parentId** — needs need goals, approaches need needs, jobs need approaches
2. **Label is required** on all nodes
3. **Measure is expected** on approaches (not enforced, but should be present)
4. **Kano is expected** on needs
5. **IDs must be unique** (enforced by primary key)
6. **Parent must exist** (enforced by foreign key)
7. **Fields must be on correct node type** — e.g., `iteration` only on jobs/tasks, `maturity` only on jobs
8. **Deletes are permanent** — no soft delete, no undo. Changelog retains audit trail.

## Schema Reference

See `docs/BRAIN_SCHEMA.json` for the complete JSON Schema. Use it to validate generated brains before committing.

---

*This guide is the contract between ProductBrain and any LLM that operates on it. If behavior diverges from this document, the document should be updated.*
