# PromptBoard for AI Assistants

> Companion guide to JSON exports from PromptBoard.
> Latest copy is hosted at <https://ytlol-9946e.web.app/PROMPTBOARD_FOR_AI.md>.
> Attach this file alongside any export when consulting an AI assistant.

PromptBoard is a Figma-like canvas for visually planning workflows. It runs in the browser and exports its state as JSON. This file explains the format and the conventions, so an LLM can both **read an export** correctly and **help the user author a new canvas** without inventing things.

The format is shared across PromptBoard's four modes:
- **design** — prompt engineering / agent flows
- **research** — paper planning, argument structure
- **cybernetics** — system / module design (the rich-spec mode)
- **review** / **execution** — transient overlays of the above; not persisted

There is one connector vocabulary shared by all modes (`flows_to` / `part_of` / `depends_on` / `triggers` / `blocks`), and one set of structural conventions in the JSON.

---

## TL;DR — Reading a canvas

You've been handed a PromptBoard JSON export. Do these in order:

1. **Read `_format` and `_instructions` first.** They confirm the version (≥3.2 has direction-aware connectors and a precomputed `_topology` block) and prime your interpretation.
2. **Glance at `_topology`:**
   - `roots` are entry points (no incoming connections).
   - `sinks` are terminals (no outgoing connections).
   - `isolated` are nodes with no edges at all — usually annotations, sometimes errors.
   - `has_cycles=true` + `cycle_members[]` enumerate feedback loops. Don't assume these are bugs; they may be intentional retry loops.
   - `topological_order` is a safe traversal order (best-effort if cycles exist).
3. **For each node:**
   - `incoming_connections` and `outgoing_connections` carry the *local neighborhood*, including titles (`from_title`, `to_title`). You do **not** need to cross-reference the central `connections` array unless you want to.
   - For modules (`type === "module"`), read `module_spec` for the rich design info.
4. **Use `_relationship_types` to interpret edges.** Each entry has both `semantics` (what it means) and `direction` (which endpoint is which). Critical for `depends_on` — direction is `from requires to`, i.e. opposite of casual reading.
5. **Reconcile module dependencies.** A module's `module_spec.dependencies` (upstream / downstream / cross_cutting / external) is the **human-curated superset**. The canvas-drawn edges cover the subset that's worth visualizing. Both are authoritative.

---

## TL;DR — Helping author a canvas

The user wants you to help design a canvas with them. Do these:

1. **Ask what they're modeling.** Pick a mode: design / research / cybernetics. Don't mix vocabularies in one canvas.
2. **Start small.** Suggest 5–10 nodes that capture the spine, then add detail. Don't dump 30 nodes at once.
3. **For modules:** assign `stage`, `module_type`, and `status` to every module. Without these, the visual fingerprint (stage colour, A/S/D/P letter, status badge) breaks.
4. **Write `purpose` before anything else.** Two or three sentences. The "why this exists" answer.
5. **Wire only meaningful edges.** Cross-cutting infrastructure (Audit, Notify, Auth) and external systems (Modera, Ford) belong in `module_spec.dependencies.cross_cutting` / `external`, **not** as drawn edges. Drawing them clutters the canvas.
6. **Use connector types precisely** — see vocabulary table below.
7. **Don't invent values.** If the user hasn't told you a role or a failure mode, ask. Plausible-sounding fabrications are worse than blank fields.

---

## Modes

| Mode | When to use | Node type vocabulary |
|---|---|---|
| **design** | LLM workflows, agent flows, prompt-engineering chains, build-time tool plans | `prompt` `llm_call` `tool_call` `human_review` `variable` `output` `decision` `note` |
| **research** | papers, lit reviews, argument planning, ARGUS-fed evidence capture | `claim` `evidence` `counter` `warrant` `source` `question` `gap` `method` |
| **cybernetics** | system / application / module design — backed by rich spec sections | `module` |

A user *can* mix modes on one canvas (e.g. a `prompt` node that exercises a `module`'s HTTP route), but that's an advanced use. Default: pick one and stick to it.

---

## Connector vocabulary

Five typed connectors, all with explicit direction semantics. Read `_relationship_types` in the export to confirm.

| Type | Direction | Semantics | When to use |
|---|---|---|---|
| `flows_to` | from precedes to | Sequential flow | A finishes → B starts |
| `part_of` | from is contained in to | Sub-step / containment | A is a sub-step of B; parent-child grouping |
| `depends_on` | **from requires to** | Dependency / back-link | A needs B's output to exist; **arrowhead points from requirer to provider** |
| `triggers` | from causes to | Cross-flow trigger | A causes B in a different lane / stage |
| `blocks` | from blocks to | Constraint / prevents | A blocks B; not a flow but a guardrail |

Each edge can carry `label` and `condition` strings. Use `label` for cosmetic naming ("happy path"), `condition` for the predicate that selects this branch (`approved == true`). Both are free-text — interpret them generously.

---

## Node types — full catalog

### design (prompt engineering)

| Type | Icon | Use it for | Key fields on the node |
|---|---|---|---|
| `prompt` | 💬 | A prompt template sent to an LLM | `template` |
| `llm_call` | 🤖 | An LLM invocation | `provider`, `model`, `temperature`, `maxTokens` |
| `tool_call` | 🔧 | A function / tool invocation | `toolName`, `inputSchema` |
| `human_review` | 👤 | A gating step requiring human approval | `instructions` |
| `variable` | `{}` | An input/output variable declaration | `varName`, `varDescription` |
| `output` | 📤 | The final output of a chain | `outputDescription` |
| `decision` | ◆ | A conditional branch point | (use connector `label` / `condition` to encode branches) |
| `note` | 📝 | A non-executable annotation | `noteContent` |

### research

| Type | Icon | Use it for | Key fields |
|---|---|---|---|
| `claim` | 🎯 | A thesis or sub-claim | `statement`, `qualifier` |
| `evidence` | 📑 | A captured quote with source (typically pushed by ARGUS) | `quote`, `weight` (`green`/`orange`/`red`), `sourceTitle`, `sourceUrl`, `sourcePage`, `argusCaptureId` |
| `counter` | ⚠️ | A counter-argument to a claim | `statement`, `strength` (`mild`/`moderate`/`strong`) |
| `warrant` | 🔗 | Reasoning that links evidence to a claim (Toulmin) | `reasoning` |
| `source` | 📚 | A reference / citation | `citation`, `authors`, `year`, `sourceUrl` |
| `question` | ❓ | An open research question | `question` |
| `gap` | 🕳 | An identified gap in the literature | `description` |
| `method` | 🧪 | A methodology element | `description`, `instrument` |

### cybernetics

| Type | Icon | Use it for | Where the data lives |
|---|---|---|---|
| `module` | 🧩 | A system module backed by a rich spec | identity fields on the node + `module_spec` block in the export |

---

## Module spec — section-by-section guide

Every module node carries a `module_spec` object in the export. In v1 sections are stored as raw text strings — don't expect structured arrays inside. (A markdown spec parser is planned for a later release.)

### Identity

| Field | Format | Notes |
|---|---|---|
| `module_id` | `M01`, `M14`, `M-AcceptOffer` | Stable, short, human-readable. Prefer `M01` over the canvas-internal `N5`. |
| `stage` | one of: `Cross-cutting`, `Offer`, `Contract`, `Order`, `Arrival`, `Delivery`, `Issued` | Maps to the conveyor stage; controls the node header colour. |
| `module_type` | `action`, `system_step`, `decision`, `periodic` | Drives the A/S/D/P letter badge. |
| `status` | `Draft`, `Specced`, `Implemented`, `Archived` | Lifecycle state. Draft → dotted outline; Archived → strikethrough. |
| `role` | comma-separated, e.g. `Salesman, SalesManager` | Who can run / invoke the module. |
| `brand_scope` | `All` or comma-separated brand list | When the module is brand-specific. |
| `spec_file` | path string | Pointer to the canonical markdown spec. |

### Body sections (free text in v1)

- **`purpose`** — 2–3 sentences. *Why* the module exists. The thing a stakeholder would understand.
- **`trigger`** — Bulleted list. *What initiates* the module: events, user actions, schedules.
- **`http_route`** — Method + URL + request body shape + response cases + auth. **Only present when the module exposes a backend endpoint.** A module that's a sub-step of another module's route should reference the parent here.
- **`function`** — 3–5 sentences. *How* the module works step by step. Implementation-focused, not user-facing.
- **`inputs`** — One per line. Format: `{source}: {field} [path]`. Common sources: `operator`, `fs_read`, `cache_read`, `external`.
- **`outputs`** — One per line. Format: `{target}: {effect} [path]`. Common targets: `fs_write`, `cache_update`, `audit_event`, `downstream`, `ui_feedback`.
- **`side_signals`** — Cross-cutting effects. Format: `{channel}: {recipient} {kind}`. Channels: `notification`, `audit_event`, `lifecycle_marker`, `conveyor_advance`.
- **`auth`** — `required_role`, `department_check`, `ownership_rule`, `permission_failure` message.
- **`failure_modes`** — One per line. Format: `failure → behavior`. Idempotency notes go here.
- **`dependencies`** — Structured: `upstream`, `downstream`, `cross_cutting`, `external`. **The superset of relationships.** Cross-cutting (e.g. `Audit`, `Notify`) and external systems (e.g. `Modera`, `Ford`) often live ONLY here, not as drawn canvas edges.
- **`lifeline`** — `activity_signal` (concrete measurable signal that the module is alive), `sunset_trigger` (when to archive), `owner` (who maintains).
- **`acceptance_criteria`** — One per line. Format: `Given X / When Y / Then Z`.
- **`notes`** — Free-form notes about implementation choices, exceptions, open questions.
- **`decisions_log`** — One per line. Format: `YYYY-MM-DD — what was decided`.

### What "good" looks like

**Good `purpose`:**
> The app needs to know who's using it without making them log in. We resolve identity from the Windows session and look up role from a manually-edited `users.json`. This is the foundation other modules use to gate behavior.

**Weak `purpose`:**
> Auth module.

(Too short — doesn't explain *why*. The "why" is what an LLM needs to reason about consequences.)

**Good `failure_modes`:**
```
Missing input → falls back to guest identity
Storage unreachable → cache fallback, log warning
Idempotency: pure read, safe to call N times
```

**Weak `failure_modes`:**
> Errors are handled.

(Vague — no actionable behavior.)

---

## Common topology patterns

Use these as templates when authoring. Each has a one-line ASCII sketch.

### Linear chain

```
M01 ──flows_to──▶ M02 ──flows_to──▶ M03
```
Sequential conveyor. Each module's `outputs` feed the next's `inputs`.

### Decision branch

```
            ┌─flows_to (label "approved")──▶ M03
M01 ──▶ M02─┤
            └─flows_to (label "rejected")──▶ M04
```
Encode the predicate in the connector's `label` or `condition` field.

### Parallel fan-out / fan-in

```
M01 ──flows_to──▶ M02a ──flows_to──┐
   ──flows_to──▶ M02b ──flows_to──┼─▶ M03
   ──flows_to──▶ M02c ──flows_to──┘
```
Multiple sub-modules run independently, then a join. Three drawn edges out, three back in.

### Cross-cutting infrastructure (preferred)

```
M01 ──flows_to──▶ M02         (M-Audit and M-Auth are NOT drawn)
                              (they live in module_spec.dependencies.cross_cutting)
```
Keeps the canvas readable. The relationship survives in the spec.

### Cross-cutting infrastructure (visible — use sparingly)

```
                    ┌──depends_on──▶ M-Audit
M01 ──flows_to──▶ M02
                    └──depends_on──▶ M-Auth
```
Use only when the dependency is the point — e.g. when reviewing audit coverage.

### Trigger across lanes

```
[Order lane]   M-Order ──triggers──▶ M-Notification [Cross-cutting lane]
```
Use `triggers`, not `flows_to`, when cause and effect belong to different stages.

### Feedback loop

```
M01 ──flows_to──▶ M02 ──flows_to──▶ M03
                                        │
                  ▲──────depends_on─────┘
```
`_topology.has_cycles` will be `true`; `cycle_members` will list `[M01, M02, M03]`. Confirm with the user that the loop is intentional before flagging it.

---

## JSON schema reference (high level)

A v3.2 export at the top level looks like:

```jsonc
{
  "_format":             { "name", "version", "designed_for" },
  "_instructions":       "string — how to read this file",
  "_relationship_types": { "<type>": { "semantics", "direction" }, ... },
  "_topology":           { "roots":[...], "sinks":[...], "isolated":[...],
                           "has_cycles":bool, "cycle_members":[...],
                           "topological_order":[...] },
  "_completeness":       { ... },
  "_warnings":           [ { "type", "id", "hint" }, ... ],
  "_documentation_url":  "URL of this guide",

  "exported_at":     "ISO8601",
  "project":         "string",
  "variables":       { "name": "value", ... },
  "flows":           ["named flow groups"],
  "total_nodes":     N, "total_connections": N, "total_sections": N,
  "annotated_nodes": N,

  "sections":    [ { "id", "title", "color", "bounds", "contained_node_ids", "spec" } ],
  "nodes":       [ {
    "id", "title", "owner", "category", "type", "flow", "position",
    "incoming_connections": [...], "outgoing_connections": [...],
    "annotations": { ... },
    "module_spec":  { ... }   // ONLY present when type === "module"
  } ],
  "connections": [ { "from", "from_title", "to", "to_title", "relationship", "label", "condition", "notes" } ]
}
```

---

## Authoring an importable canvas (LLM as generator)

The schema above describes the **export** shape (what `⬆ Export → JSON` produces). PromptBoard's `Load` button accepts a different but related shape — the **import** shape — which is simpler and is what the canvas actually stores.

**You can write either shape.** PromptBoard's loader detects export-shape input and converts it on the fly. But the import shape is shorter, less error-prone, and is what the user gets when they click `💾 Save`. Prefer it when generating a canvas from scratch.

### Import shape — minimum viable

```json
{
  "projectName": "My Canvas",
  "nextId": 100,
  "variables": [],
  "nodes": {
    "N1": {
      "id": "N1", "x": 200, "y": 200,
      "title": "First node",
      "type": "module",
      "category": "human",
      "owner": "", "flow": "",
      "description": "", "rules": "", "notes": "",
      "automation": "manual"
    }
  },
  "connectors": [],
  "sections": {}
}
```

**Critical differences from the export shape:**

| Concern | Export shape | Import shape |
|---|---|---|
| Node container | `nodes: [...]` array | `nodes: { "N1": {...} }` keyed object |
| Edges | `connections: [...]` | `connectors: [...]` |
| Edge type field | `relationship` | `type` |
| Position | `position: { x, y }` | top-level `x`, `y` |
| Annotation block | `annotations: { description, rules_and_conditions, notes, automation }` | top-level `description`, `rules`, `notes`, `automation` |
| Per-node neighborhood | `incoming_connections`, `outgoing_connections` (denormalized) | OMIT — derived at render time from the connectors array |
| Module rich data | `module_spec: { module_id, http_route, ... }` | flat top-level: `moduleId`, `httpRoute`, `functionDetail`, `authSpec`, `acceptanceCriteria`, `moduleNotes`, `decisionsLog`, plus the rest |
| Top-level metadata | `_format`, `_topology`, `_relationship_types`, `_warnings`, `_documentation_url`, `exported_at`, `total_*`, `flows`, `annotated_nodes` | OMIT all of these |
| Sections | array, with `bounds`, `spec`, `contained_node_ids` | keyed object, with top-level `x`, `y`, `w`, `h`, `description`, `acceptance`, `deps`, `notes` |

### ID conventions

- **Node IDs**: `N` + integer, e.g. `N1`, `N2`, `N42`. Stable but canvas-internal.
- **Section IDs**: `SEC` + integer, e.g. `SEC1`.
- **`nextId`**: must be strictly greater than every numeric suffix on any node or section ID. The canvas uses this counter when adding new nodes.
- **Module ID** (`moduleId` field, distinct from `id`): short human-readable, e.g. `M01`, `M-AcceptOffer`. Show this to the user; use `id` only for connector wiring.

### Position guidance

- Coordinates are in pixels in canvas space. Origin top-left. Common ranges: `x` 0–3000, `y` 0–2000.
- A typical node footprint is ~200×100 px. Space nodes ≥ 300px apart horizontally and ≥ 160px apart vertically to avoid overlap.
- For a linear chain, increment `x` by ~300 per node, keep `y` constant.
- For lanes (e.g. modules grouped by `stage`), assign each stage a `y` band: e.g. Cross-cutting at y=80, Offer at y=300, Contract at y=520, etc.
- Sections (group containers) wrap nodes: their `x`/`y` is the top-left corner, `w`/`h` is the size. Make sections big enough to enclose their nodes plus ~40px padding.

### Cybernetics canvas — copy-paste template

A minimal three-module conveyor in cybernetics mode. Save as `.json`, click `Load` in PromptBoard.

```json
{
  "projectName": "Example conveyor",
  "nextId": 100,
  "variables": [],
  "sections": {
    "SEC1": {
      "id": "SEC1", "x": 80, "y": 60, "w": 920, "h": 220,
      "title": "Offer stage", "color": "cyan",
      "description": "Modules that run during the Offer phase.",
      "acceptance": "", "deps": "", "notes": ""
    }
  },
  "nodes": {
    "N1": {
      "id": "N1", "x": 140, "y": 140,
      "title": "Resolve user identity",
      "type": "module",
      "category": "system",
      "owner": "", "flow": "",
      "description": "", "rules": "", "notes": "",
      "automation": "manual",
      "moduleId": "M01",
      "stage": "Cross-cutting",
      "moduleType": "system_step",
      "status": "Specced",
      "role": "System",
      "brandScope": "All",
      "specFile": "specs/M01-resolve-windows-identity.md",
      "purpose": "The app needs to know who's using it without making them log in. We resolve identity from the Windows session and look up role from a manually-edited users.json.",
      "trigger": "App startup\nCache invalidation: explicit Refresh roles",
      "functionDetail": "At app startup the module reads the username from process.env.USERNAME, then looks up role assignments in users.json. The result is cached in memory for the session.",
      "inputs": "external: process.env.USERNAME\nfs_read: users.json",
      "outputs": "cache_update: in-memory identity object\nui_feedback: notice when no role assigned",
      "failureModes": "Missing input → guest identity\nStorage unreachable → cache fallback\nIdempotency: pure read, safe to call N times",
      "dependencies": "upstream: \ndownstream: M14, M03\ncross_cutting: \nexternal: ",
      "lifeline": "activity_signal: ≥1 distinct user_id in audit/day\nsunset_trigger: SSO replaces this\nowner: backend"
    },
    "N2": {
      "id": "N2", "x": 460, "y": 140,
      "title": "Create offer in Modera",
      "type": "module",
      "category": "human",
      "owner": "", "flow": "",
      "description": "", "rules": "", "notes": "",
      "automation": "manual",
      "moduleId": "M03",
      "stage": "Offer",
      "moduleType": "action",
      "status": "Draft",
      "role": "Salesman",
      "brandScope": "All",
      "specFile": "",
      "purpose": "Salesman composes a draft offer for the customer in Modera.",
      "trigger": "Salesman clicks New Offer in Modera",
      "dependencies": "upstream: M01\ndownstream: M14\ncross_cutting: Audit\nexternal: Modera"
    },
    "N3": {
      "id": "N3", "x": 780, "y": 140,
      "title": "Provision deal folder on accept",
      "type": "module",
      "category": "system",
      "owner": "", "flow": "",
      "description": "", "rules": "", "notes": "",
      "automation": "manual",
      "moduleId": "M14",
      "stage": "Offer",
      "moduleType": "system_step",
      "status": "Draft",
      "role": "System",
      "brandScope": "All",
      "specFile": "",
      "purpose": "On offer accept, create the per-deal folder structure used by downstream stages.",
      "httpRoute": "POST /api/calculation/:calc_id/outcome\nBody: { outcome, offer_code }\n200: { status: 'provisioned', deal_id, folder_path }\n409: { status: 'conflict', existing_deal_id }\n503: { status: 'storage_unavailable' }\nAuth: Salesman | SalesManager",
      "dependencies": "upstream: M03\ndownstream: \ncross_cutting: Audit, Notify\nexternal: ",
      "acceptanceCriteria": "Given an accepted offer / When the route is called / Then the deal folder exists at the expected path"
    }
  },
  "connectors": [
    { "from": "N1", "to": "N2", "type": "depends_on", "waypoints": [], "label": null, "condition": null, "notes": null },
    { "from": "N2", "to": "N3", "type": "flows_to",   "waypoints": [], "label": "accepted", "condition": null, "notes": null }
  ]
}
```

Save this as e.g. `example.json`, open the live site, click `Load`, pick the file. You should see three coloured module nodes with stage backgrounds, role chips, and the rich annotation panel populated.

### Common authoring pitfalls

- **Putting `incoming_connections` / `outgoing_connections` on nodes.** These are export-only; remove from import. The renderer derives them from the connectors array.
- **Using `connections` instead of `connectors`.** The loader auto-converts, but native `connectors` is preferred.
- **Nesting position under `position`.** Native is top-level `x` and `y`.
- **Wrapping module fields in `module_spec`.** Native is flat (`moduleId`, `stage`, `purpose`, `httpRoute`, etc.).
- **Forgetting `nextId`.** Without it, the canvas may collide IDs when adding new nodes. Set it to one greater than the largest numeric suffix used.
- **Every connector references an existing node ID.** Stale connectors are silently dropped on import; if an edge disappears, that's why.
- **`type` must match a known NODE_TYPES key.** Unknown types fall back to `prompt`.

---

## Reading checklist (LLM consumers)

When handed a PromptBoard JSON export, run through this:

- [ ] Read `_format.version`. v3.2 has direction-aware connectors and `_topology`. Older exports may need direction inferred.
- [ ] Skim `_topology.roots` / `sinks` — these anchor your understanding of flow.
- [ ] If `_topology.has_cycles`, list `cycle_members` and confirm with the user whether they're intentional (retry loops, feedback) or errors.
- [ ] Use `_topology.topological_order` for any operation that should respect dependency order.
- [ ] For each module, read `module_spec.purpose` first — it's the load-bearing summary.
- [ ] When `module_spec.dependencies` references an ID that has no drawn edge, **that's intentional**. Cross-cutting deps stay in the spec; don't insist they should be on the canvas.
- [ ] Quote `module_id` (M01) over `id` (N5). N-IDs are canvas-internal and unstable.
- [ ] If `_warnings` is non-empty, surface them to the user — they describe known incompleteness.

---

## Authoring checklist (LLM collaborators)

When helping the user build a canvas:

- [ ] Ask first: *what are we modeling?* Pick the mode. Don't mix vocabularies.
- [ ] Start small (5–10 nodes), not exhaustive. Iterate.
- [ ] For modules: every module gets `stage`, `module_type`, `status` set. Without these, the canvas visual is broken.
- [ ] For modules: write `purpose` first. Don't proceed without it.
- [ ] Module IDs of the form `M01` or `M-AcceptOffer`. Short, recognizable, stable.
- [ ] **Don't draw cross-cutting edges.** Audit/Notify/Auth go in `module_spec.dependencies.cross_cutting`. External systems (Modera, Ford) go in `external`.
- [ ] **Don't invent values.** If the user hasn't given you a role, an HTTP method, a failure mode — ask. Plausible-sounding fabrications are worse than blank fields.
- [ ] Choose connector types carefully: `flows_to` is sequence; `triggers` crosses lanes; `depends_on` is reversed (from-requirer to-provider); `part_of` is containment; `blocks` is a constraint, not a flow.
- [ ] After a draft, mentally run the *Reading checklist* above on what you just produced. Roots / sinks / cycles — does the structure match the user's stated intent?

---

## When in doubt

- The **spec file** referenced by `module_spec.spec_file` is the source of truth. The canvas is a renderer.
- A **drawn edge** is curated — the user chose to make it visible.
- A **`module_spec.dependencies` entry without a matching edge** is curated too — the user chose to keep it off the canvas. Don't second-guess.
- **Cycles** are not automatically wrong. Ask before flagging.
- **Empty fields** are not automatically wrong. Many fields are genuinely N/A for a given module (e.g. a service has no `http_route`).

---

*Generated alongside PromptBoard. Update this file when the JSON shape, vocabulary, or conventions change.*
