InitRunner

Blackboard

A blackboard is a small typed key/value store that lives for the duration of one flow run. Agents post attributed values under named keys, and other agents (or a fan-in join) read those values back without the data having to be threaded through prompt text. An upstream planner can hand a downstream worker named, structured data instead of burying it in prose.

Without the blackboard, the only thing that travels along each flow edge is the prompt string, and a fan-out then fan-in just concatenates those strings. The blackboard adds a second channel: a shared board that every step in the run can write to and read from. The board is the flow graph's run state, so it starts fresh and empty at the beginning of each run and is discarded when the run ends.

The blackboard is flow-only. Inside a flow run it is auto-injected into each agent step. A standalone single-shot run has no board, so the tool is never built outside a flow.

When to use it

Reach for the blackboard when agents need to share named values, not just forward prose:

  • A planner needs to hand workers an exact split of the work, and each worker needs to read its own slice by key.
  • A fan-in join should merge based on a value an upstream agent computed, rather than re-deriving it from concatenated text.
  • One of several parallel workers should claim a unit of work so no sibling picks up the same item.

If your agents only pass prose forward, you do not need the blackboard. The default branch-output concatenation at a join already covers that case.

Enabling it

Add type: blackboard to the spec.tools list of any flow agent that should read or write shared run state. Each participating agent declares the tool independently.

spec:
  tools:
    - type: blackboard
      max_entries: 100
      max_value_chars: 10000

An agent that only needs the merged result, such as a final editor, can omit the tool and still see posted entries. The fan-in join folds the board into that agent's input regardless of whether it holds the tool itself (see How fan-in joins read the board).

Options

FieldTypeDefaultDescription
max_entriesint100Board capacity for this run (range 1 to 1000). A full board rejects further posts until an entry is claimed.
max_value_charsint10000Per-value size cap (range 1 to 100000). Values are stored verbatim as strings; post JSON when you need structure.

Registered functions

Declaring the tool registers four functions, all scoped to the current flow run's board:

ToolDescription
blackboard_post(key, value)Add a new entry under key. Keys are letters, digits, and underscore only, up to 64 chars. Posting a key that already exists is an error; claim the old entry first to replace it. Returns Posted '{key}' as {entry_id}.
blackboard_read(key)Return the entry as JSON with key, value, author, timestamp, and entry_id, without removing it.
blackboard_claim(key)Read the entry and remove it so no other agent can claim it again. Returns the entry as JSON. Use it for work-stealing handoffs.
blackboard_list()List the current keys with a short value preview (truncated at 80 chars). Returns Blackboard is empty. when nothing has been posted.

Every entry records provenance: author is the posting agent's metadata.name, and timestamp is an ISO-8601 UTC string. Values are opaque strings, so post JSON when you need structured fields.

Example: planner posts, writers read, editor merges

A planner splits an outline into two sections and posts each to the board. Two writers run in parallel, each reading its assigned section. A plain editor agent merges the result. Only the planner and writers declare the blackboard tool; the editor does not.

# flow.yaml
apiVersion: initrunner/v1
kind: Flow
metadata:
  name: article-pipeline
  description: Planner splits sections, writers draft in parallel, editor merges
spec:
  agents:
    planner:
      role: roles/planner.yaml
      sink:
        type: delegate
        target: [writer-a, writer-b]
    writer-a:
      role: roles/writer-a.yaml
      sink:
        type: delegate
        target: editor
    writer-b:
      role: roles/writer-b.yaml
      sink:
        type: delegate
        target: editor
    editor:
      role: roles/editor.yaml

roles/planner.yaml posts each section to the board:

apiVersion: initrunner/v1
kind: Agent
metadata:
  name: planner
  description: Splits an outline into per-section assignments
spec:
  role: >
    You are a planning agent. Split the requested article into two sections.
    Post the first section's brief under the key "section_a" and the second
    under "section_b" using blackboard_post. Each value should be a short
    JSON object with a title and key points.
  model:
    provider: openai
    name: gpt-5-mini
  tools:
    - type: blackboard

roles/writer-a.yaml reads its assigned section:

apiVersion: initrunner/v1
kind: Agent
metadata:
  name: writer-a
  description: Drafts the first article section
spec:
  role: >
    You are a writer. Read "section_a" from the blackboard with
    blackboard_read, then draft that section. Return the finished prose.
  model:
    provider: openai
    name: gpt-5-mini
  tools:
    - type: blackboard

The editor role is a plain agent with no blackboard tool. At the join, its input combines the two writers' drafts and a structured view of the board:

<writer-a draft>

---

<writer-b draft>

---

=== Shared blackboard ===
- section_a (by planner): {"title": "...", "points": ["..."]}
- section_b (by planner): {"title": "...", "points": ["..."]}

Run it with the standard flow command:

initrunner flow up flow.yaml

How fan-in joins read the board

A fan-in join still concatenates each branch's output for the downstream agent, joined with --- separators. In addition, it reads the structured entries currently on the board and appends them as a dedicated === Shared blackboard === section, with one - {key} (by {author}): {value} line per entry.

Two consequences follow:

  1. Posted entries surface at the join. A value an upstream agent posted is visible to the join target as named, attributed data, even though it never appeared in any branch's prompt.
  2. Claimed entries disappear. An entry a branch agent claimed is gone from the board and does not reappear at the join. This is how a parallel worker signals "I took this" so no sibling and no downstream merge picks it up again.

Per-entry values in the join section are truncated at 500 chars with a [truncated] marker, so a large board cannot balloon the merged prompt.

Persistence and audit

On flow-run completion, the final board is recorded on the signed audit chain as a single record with trigger type blackboard_state. The snapshot holds the unclaimed entries (value, author, timestamp) plus the sorted list of claimed keys. Entry values are truncated and secret-scrubbed before they enter the chain, and the record's output summary reads {N} entries, {M} claimed.

Persistence is safe and conditional. It writes nothing when there is no audit logger or when the board never held an entry, so an ordinary flow with no blackboard tool records nothing extra. The persistence path never raises, so a logging failure cannot crash a flow. The board is persisted for both one-shot CLI flow runs and daemon flow runs.

The snapshot is queryable like any other audit record:

from initrunner.audit.logger import AuditLogger

log = AuditLogger()
records = log.query(trigger_type="blackboard_state")

Each returned record carries the board snapshot in its trigger metadata (scope, flow_name, flow_run_id, entries, claimed).

Limits

  • Keys are letters, digits, and underscore only, non-empty, up to 64 chars. Invalid keys return an error string from blackboard_post.
  • Values are strings capped at max_value_chars (default 10000). Post JSON when you need structure; an oversized value returns an error.
  • Board capacity is max_entries per run (default 100). A full board rejects further posts until something is claimed, which frees a slot.
  • The board is per run. It is not shared across separate flow runs, and it is not a substitute for long-term memory or shared documents. Use shared memory when agents need state that outlives a single run.

See also: Flow for how agents are wired into a graph, and Team Mode for single-file multi-persona collaboration on one task.

On this page