# Conductor

> Compile-then-execute DAG engine for building visual workflow systems.

Conductor is a Python library for building DAG-based workflow/agent execution systems. It provides node registration, graph compilation with type checking, eager parallel streaming execution with retry, shared references across region boundaries, human-in-the-loop checkpointing, and frontend UI metadata generation — all from plain Python functions with type annotations.

## Quick Start

```python
from typing import Annotated
from conductor import NodeRegistry, GraphNode, GraphEdge, compile
from conductor.execution.engine import execute_sync
from conductor.widgets import Text, Output

registry = NodeRegistry()

@registry.node("echo", version=1, name="Echo", description="Returns input")
def echo(text: Annotated[str, Text(label="Input")]) -> Annotated[str, Output(label="Output")]:
    return text

compiled = compile(
    nodes=[GraphNode("n1", "echo@1", {"text": "hello"})],
    edges=[],
    registry=registry,
)
results = execute_sync(compiled)
# results["n1"]["result"] == "hello"
```

## Core Concepts

### Registration
Nodes are plain Python functions decorated with `@registry.node()`. Type hints with `Annotated[T, Widget]` are the single source of truth for:
- Backend execution and input validation (auto-generated Pydantic model)
- Frontend UI rendering (widget type, label, choices, constraints)
- Compile-time type checking (type_str on inputs/outputs)

Nodes are versioned as `base_id@version` (e.g., `echo@2`). Old versions are deprecated but keep working.

Class-based nodes use `BaseNode` ABC with `registry.register_class()` for complex nodes needing state.

### Compilation
`compile(nodes, edges, registry)` validates everything before execution:
- Node types exist, edges reference real nodes, no cycles (cycles through shared references are also detected)
- Type compatibility on every edge AND every consume binding (warnings or strict errors)
- Compound node regions discovered (for-each loops)
- Shared-reference produce/consume declarations validated (§ below)
Returns an immutable `CompiledGraph` with `execution_order`, `edge_map`, `consume_map`, `managed_to_region_start`, `compound_nodes`, `managed_ids`, and `type_warnings`.

### Execution
`execute(compiled)` is an async generator yielding events: `node_start`, `node_complete`, `node_skipped`, `node_error`, `node_retry`, `node_progress`, `flow_complete`, `flow_error`, `flow_timeout`, `flow_paused`, `flow_cancelled`.

`execute_sync(compiled)` is a blocking wrapper. Both support timeout, caching, cancellation, and retry.

Execution is **eager and parallel by default**: as soon as a node's dependencies complete, its task is dispatched via `asyncio.create_task` (sync functions offloaded through `asyncio.to_thread`). Independent branches in the DAG run concurrently with no flags or configuration.

### Retry
Retries are node-level first, global second.

Node-level — wins over any global config:
```python
@registry.node("fetch", version=1, name="Fetch", description="...",
               max_retries=3, retry_delay=0.5)
def fetch(...): ...
```

Global:
```python
from conductor.execution.retry import RetryConfig
execute_sync(compiled, retry=RetryConfig(max_retries=2, delay=1.0, backoff_factor=2.0))
```

- Delay formula: `delay * backoff_factor ** (attempt - 1)` (node-level uses backoff=2.0).
- Retried: `NodeExecutionError`, `NodeConnectionError`.
- Never retried: `NodeValidationError` (bad input), `HumanInputRequired` (pauses).
- Each retry emits a `node_retry` event with `{attempt, max_retries, error, delay}`.

### Data Flow
Resolver precedence, first match wins:
1. **Edge** targeting the input
2. **Consume binding** (shared reference) — see below
3. **Static data** on `GraphNode.data`
4. **Widget default** (Pydantic)

- **Edges**: Primary data flow. `InputResolver` extracts source outputs by handle name.
- **Shared references**: Declarative fan-out / cross-region wires. See "Shared References" below.
- **FlowStore**: Side-channel key-value cache. Function nodes declare `store: FlowStore` parameter for auto-injection. Not part of the DAG; use sparingly.
- **SKIPPED sentinel**: Conditional branching. If all inputs (edges + consumes) to a node are SKIPPED, it's skipped too.
- **ConnectionList**: Widget for N-input aggregation. Resolver builds labeled dict from all connected sources.

### Shared References (produce / consume)

An alternative to drawn edges for fan-out and cross-region binding. Per-instance opt-in on `GraphNode`:

```python
GraphNode(
    "mapper", "build-map@1", {"seed": "x"},
    produces={"result": "pseudonym map"},     # output handle → display label
)

GraphNode(
    "redactor", "redact@1", {"text": "Alice met Bob."},
    consumes={"mapping": ("mapper", "result")},  # input → (producer_id, output_handle)
)
```

No `GraphEdge` needed between them. Reference identity is `(producer_node_id, output_handle)`; labels are UI-only so renames don't break subscribers.

Compile-time rules:
- Producer's output handle must exist on its node type
- Producer must be top-level in v1 (not inside any compound region)
- Consumer's producer must exist and must declare the handle in `produces`
- Consumer's input handle must exist on the consumer's node type
- The same input handle cannot be both a consume target and an edge target
- Cycles through shared references are detected the same as edge cycles

Runtime:
- Consumes participate in scheduling (the scheduler waits for producers)
- For consumers inside compound regions, the dependency is redirected onto the region's start node via `managed_to_region_start`, so the whole region waits for top-level producers
- Broadcast semantics: the same producer value is read on every iteration of a for-each body
- Values survive checkpoint/resume (they live in `state.results`)
- Duplicate display labels emit a non-fatal `TypeWarning` with `code="shared-label-collision"`

Full spec: `docs/shared-references.md`. Example: `examples/07_shared_references.ipynb`.

### Human-in-the-Loop
Nodes raise `HumanInputRequired(prompt, schema)` to pause execution. Engine checkpoints state (JSON-serializable `FlowCheckpoint`), yields `flow_paused` event. Resume with `resume(compiled, checkpoint, response)`. FlowStore survives the cycle. Multiple sequential pauses supported.

Sync: `execute_sync` raises `FlowPausedException(checkpoint)`, resume with `resume_sync(compiled, checkpoint, response)`.

### Compound Nodes
For control flow patterns (for-each, while, subprocess) that manage a region of sub-nodes. `CompoundNodeType` protocol: `discover()` finds regions, `factory()` creates executor. Body nodes are managed by the compound node, skipped by the main loop.

Built-in compound types:
- `FOR_EACH` — iterate a collection. Markers: `for-each-start` / `for-each-end`.
- `WHILE` — iterate on a CEL condition, with `max_iterations` safety cap. Markers: `while-start` / `while-end`. Raises `LoopRunawayError` on cap.
- `SUBPROCESS` — call another flow by `(flow_id, version)`. Single-node region (start == end). Register target flows in a `SubprocessRegistry` and pass to `compile(subprocess_registry=...)`. Bubbles inner errors as `SubprocessFailedError`; runtime recursion depth cap.

### Decision Nodes + Edge Guards
A decision node (`is_decision=True` on the registration) branches by evaluating CEL expressions on its outgoing edges.

```python
@registry.node("decision", version=1, name="Decision", description="...",
               is_decision=True)
def decision(value): return value

GraphEdge("e1", "d", "branch-a", "result", None, when="amount > 1000", priority=10),
GraphEdge("e2", "d", "branch-b", "result", None),  # else
```

Compile-time: exactly one edge without `when` (else), at least one guarded. Invalid CEL = `CompilationError`. Runtime: first matching guard (in priority-desc order) wins; every other outgoing edge is added to `state.skipped_edges` so its target is skipped via normal SKIPPED propagation.

### CEL Expressions
Sandboxed expression language used by edge guards, loop conditions, idempotency keys, signal correlation, and subprocess input mapping. Module: `conductor.expr`.

- Literals: int, float, string (`'x'` or `"x"`), `true`/`false`/`null`/`None`, lists `[1,2]`, maps `{"k": 1}`.
- Operators: `+ - * / %`, `== != < <= > >=`, `&& ||` (also `and or not`), `! -`, `in`, ternary `? :`. `int / int` returns an int truncated toward zero (CEL spec); mixed `int/float` returns float.
- Identifiers: dotted (`invoice.amount`), indexed (`a[0]`, `m["k"]`), `$` root alias.
- Built-in functions: `size`, `has`, `contains`, `startsWith`, `endsWith`, `matches`, `lower`, `upper`, `string`, `int`, `double`, `bool`, `exists`, `min`, `max`, `abs`. Also `.contains()`, `.startsWith()`, etc. as method calls.

### Actor Metadata
Declarative "who performs this step" on node registration:
```python
@registry.node("approve", ..., actor={"kind": "human", "role": "manager"})
```
Kinds: `system`, `human`, `agent`, `external_service`. Surfaces on `NodeDefinition.actor` and the registry JSON schema. Pure metadata — engine is indifferent.

### Per-Node Timeout and Idempotency Key
```python
@registry.node("charge", ..., timeout=30, idempotency_key='"ch-" + string(amount)')
def charge(amount, idempotency_key=None): ...
```
- `timeout` accepts seconds (float), ISO 8601 (`PT30S`), or shorthand (`30s`, `250ms`, `5m`).
- `idempotency_key` is a CEL expression evaluated against the node's inputs. Surfaced on `node_start` events and injected into the function when it declares an `idempotency_key` parameter. Stable across retries.

### Compensation / Saga
Per-node `compensation:` field pointing at another node. When a flow fails, the engine walks `state.completed_order` in reverse and dispatches each completed node's compensation. Compensation nodes receive `(target_node_id, original_inputs, original_output)`. Events: `compensation_start`, `compensation_complete`, `compensation_failed`. Best-effort — one failure doesn't abort the cascade. Per-node `on_error:` policy: `fail` (default), `continue` (treat failure as null result), `compensate` (trigger cascade immediately).

### Flow-Level Metadata (dependencies, triggers)
`Flow` dataclass wraps nodes/edges with optional metadata:
- `dependencies: tuple[FlowDependency, ...]` — external systems the flow touches. Nodes' `uses=[...]` list must reference declared dep ids (compile-time check).
- `triggers: tuple[FlowTrigger, ...]` — declarative trigger config (manual/schedule/event/webhook). Conductor stores; the host wires external machinery.
- `on_error_default` — flow-level default for node `on_error`.

### Signals / External Events
`signal-wait` / `signal-timer` nodes raise `SignalRequired(name, correlation=..., timeout_seconds=...)` to pause the flow. Engine checkpoints `(signal_name, correlation, signal_timeout_seconds)` and yields `signal_waiting` + `flow_paused` events. Host resumes with `resume_sync(compiled, checkpoint, payload)`.

### YAML / JSON Flow Format
`conductor.flow_format` module converts `Flow` ↔ YAML/JSON. `load_flow(dict)`, `yaml_to_flow(str)`, `load_flow_from_path(path)`, `flow_to_yaml(flow)`, `flow_to_dict(flow)`, `dump_flow(flow, path)`. Requires PyYAML at call time (optional extra: `conductor[yaml]`).

### Type Checking
Compile-time validation of edge type compatibility:
- Exact match, numeric interchangeability (int↔float), string coercion
- list[T] accepts T (auto-wrap), ConnectionList accepts anything
- Union types (`str | int`) are compared alternative-by-alternative on either side; any matching pair passes
- Default: warnings on `compiled.type_warnings`. With `strict_types=True`: raises `CompilationError`.

### Extension Points
- **Custom widgets**: Subclass `Widget` ABC
- **Custom types**: `NewType("MyType", str)` → surfaces as `"mytype"` in schema
- **Extension resolver**: Protocol for host-app node types the registry doesn't know
- **Compound node types**: New control flow without modifying the engine
- **Auto-discovery**: `registry.discover("package.name")` imports all modules

## API Reference

### conductor (top-level)
- `NodeRegistry` — Node registration and lookup. Key methods: `node(...)`
  decorator, `register_class(cls)`, `get(full_id)`, `get_latest(base_id)`,
  `is_deprecated(full_id)`, `all()`, `all_current()`, `contains(full_id)`,
  `discover(package_name)`, and
  **`merge(other, on_conflict="raise"|"skip"|"error-summary")`** for composing
  registries. Returns self for chaining.
- `GraphNode(id, type, data, produces=None, consumes=None, node_label=None, output_labels=None)` — Node in the graph.
  - `produces: dict[str, str] | None` — output handle → display label (marks that output as shareable)
  - `consumes: dict[str, tuple[str, str]] | None` — input handle → `(producer_node_id, output_handle)`
  - `node_label: str | None` — host-supplied display name (UI only)
  - `output_labels: dict[str, str] | None` — host-supplied per-output display names (UI only); flow into `ConnectionList` aggregator key generation
- `GraphEdge(id, source, target, source_handle, target_handle)` — Edge connecting nodes
- `compile(nodes, edges, registry, *, compound_types, extension_resolver, strict_types)` → `CompiledGraph`

`CompiledGraph` fields:
- `execution_order: tuple[str, ...]`
- `edge_map: dict[(target_id, target_handle), list[(source_id, source_handle)]]`
- `consume_map: dict[(target_id, target_handle), (source_id, source_handle)]`
- `managed_to_region_start: dict[str, str]` — managed node id → region start id
- `node_map`, `compound_nodes`, `managed_ids`, `registry`, `extension_resolver`
- `type_warnings: tuple[TypeWarning, ...]` — each has a `code` field (`"type-mismatch"`, `"shared-label-collision"`, ...)

### conductor.execution.engine
- `execute(compiled, *, timeout_seconds, context, cache, retry)` → `AsyncGenerator[ExecutionEvent]`
- `execute_sync(compiled, **kwargs)` → `dict[str, Any]`
- `resume(compiled, checkpoint, response, *, timeout_seconds, context, retry)` → `AsyncGenerator[ExecutionEvent]`
- `resume_sync(compiled, checkpoint, response, **kwargs)` → `dict[str, Any]`
- `collect(events)` → `dict[str, Any]` (consumes async generator)

### conductor.execution.retry
- `RetryConfig(max_retries=0, delay=1.0, backoff_factor=2.0)` — global retry config
- `NO_RETRY` — sentinel (max_retries=0)

### conductor.widgets
Concrete `Widget` subclasses, one per `WidgetType` enum value. The frontend renders any registered node by reading the registry — no custom backend code per widget.

- `Widget` — abstract base; every subclass has a `to_schema()` producing a JSON dict
- **Text-ish:** `Text`, `Textarea`, `TemplateTextarea(variables)`, `CodeEditor(language)`
- **Choice-ish:** `Dropdown(choices)`, `DependentDropdown(depends_on, choices_map)`, `Multiselect(choices, min_selected, max_selected)`, `EntityDropdown(entity_kind, multiple)`
- **Numeric:** `Number(min_val, max_val, step, integer_only)`, `Range(min_val, max_val, step)`
- **Boolean:** `Checkbox`, `Switch`
- **Date:** `DatePicker(min_date, max_date)`
- **File:** `FileUpload(accept, max_size_mb, multiple)`
- **Structured:** `List(item_widget, min_items, max_items)`, `SchemaBuilder(schema, allow_additional)`, `IfElseBuilder(variables)`
- **Special:** `ConnectionList` (aggregates N upstream edges into a dict), `Output(download, filename)`

### conductor.types
- `WidgetType`, `ResultFormat`, `NodeCategory` (enums)
- `Base64Str`, `Date`, `NamedFile`, `MultiNamedFile` (custom type aliases)
- `RESULT_KEY = "result"`, `OUTPUT_PREFIX = "output_"`

### conductor.errors
Hierarchy (all inherit from `ConductorError`):
- `CompilationError`
  - `CycleDetectionError`
  - `TypeCheckError`
- `NodeError(message, *, node_id, node_type, original)` — carries node context
  - `NodeValidationError` — pydantic validation failure, **never retried**. Renders a one-line-per-field summary; the original pydantic `ValidationError` is preserved on `.original` for hosts that want structured access.
  - `NodeExecutionError` — node function raised, retried if configured
  - `NodeTimeoutError`
  - `NodeConnectionError` — raise from node code for transient network/API failures (retried)
- `InputResolutionError(message, *, node_id)`
- `FlowExecutionError(message, *, node_id, node_error)` — raised by `execute_sync` on failure
- `HumanInputRequired(prompt, *, schema, node_id)` — pauses execution
- `FlowPausedError(checkpoint)` — sync counterpart to `flow_paused` event

Legacy aliases still exported: `NodeValidationException`, `NodeExecutionException`, `FlowExecutionException`, `FlowPausedException` (map to the new `*Error` names).

### conductor.node
- `BaseNode` (ABC) — `node_id`, `node_name`, `node_description`, `node_category`, `execute(req) → Any`

### conductor.execution.store
- `FlowStore` — `set(key, value)`, `get(key, default)`, `has(key)`, `keys()`, `clear()`

### conductor.execution.request
- `NodeExecRequest(node_id, node_type, inputs, data, state)` — DTO passed to every node

### conductor.execution.checkpoint
- `FlowCheckpoint` — `to_dict()`, `from_dict(data)` (JSON-serializable)

### conductor.graph.shared_refs
Internal module called from `compile()`. Validates produce/consume decorations and builds the `consume_map`. Exports `ConsumeMap = dict[tuple[str, str], tuple[str, str]]` type alias and `validate_and_build_consume_map()`.

### conductor.graph.type_check
- `TypeWarning(edge_id, source_node, source_output, source_type, target_node, target_input, target_type, message, code="type-mismatch")` — frozen dataclass
- `check_edge_types(edges, node_map, registry)` → `list[TypeWarning]`
- `check_consume_types(consume_map, node_map, registry)` → `list[TypeWarning]`

### conductor.compound.for_each
- `FOR_EACH` — CompoundNodeType constant, pass to `compile(compound_types=[FOR_EACH])`

### conductor.registry.schema
- `serialize_registry(registry)` → `list[dict]` (frontend JSON)

### conductor.registry.discovery
- `discover_nodes(package_name, registry)` → `int` (count of nodes registered)

## Widgets Reference

| Widget | WidgetType | Key Options |
|--------|------------|-------------|
| Text | text | min_length, max_length, pattern |
| Textarea | textarea | rows, min_length, max_length |
| TemplateTextarea | template-textarea | rows, variables |
| CodeEditor | code-editor | language, min_length, max_length |
| Dropdown | dropdown | choices |
| DependentDropdown | dependent-dropdown | depends_on, choices_map |
| Multiselect | multiselect | choices, min_selected, max_selected |
| EntityDropdown | entity-dropdown | entity_kind, multiple |
| Number | number | min_val, max_val, step, integer_only |
| Range | range | min_val, max_val, step |
| Checkbox | checkbox | — |
| Switch | switch | — |
| DatePicker | datepicker | min_date, max_date |
| FileUpload | file | accept, max_size_mb, multiple |
| List | list | item_widget, min_items, max_items |
| SchemaBuilder | schema-builder | schema, allow_additional |
| IfElseBuilder | if-else-builder | variables |
| ConnectionList | connection-list | — |
| Output | output | download, filename |

## Type → Default Widget

When a parameter has no `Widget` on its `Annotated[...]` (or no `Annotated` at all), the registry infers one from the Python type:

| Python type | Default widget |
|---|---|
| `str` | `Text(label=param_name)` |
| `int` | `Number(integer_only=True)` |
| `float` | `Number` |
| `bool` | `Checkbox` |
| `Date` | `DatePicker` |
| `Base64Str`, `NamedFile`, `MultiNamedFile` | `FileUpload` |
| `list[str]` | `List(item_widget=Text())` |
| `list[int]` | `List(item_widget=Number(integer_only=True))` |
| `list[T]` (bare or other T) | `List` (no `item_widget`) |
| `dict`, `dict[str, Any]` | `SchemaBuilder` |
| anything else | no widget (rare — annotate explicitly) |

Explicit `Annotated[T, Widget(...)]` always wins.

## Result Formats

Single output: `{"result": value}`
Multi output (tuple return): `{"output_1": v1, "output_2": v2, ...}`
Dict output: `{"result": dict, **dict}` (keys spread for sub-output access)

## Workspace Packages

Three workspace packages ship today. PyPI distribution names are prefixed with `syv-` (Apache-2.0); Python import paths are unchanged.

1. **`conductor`** (dist: `syv-conductor`) — the core engine. All compile/execute/registry/widget types. Optional extra: `syv-conductor[yaml]` for the YAML/JSON flow format.
2. **`conductor-nodes`** (dist: `syv-conductor-nodes`) — reusable standard-library nodes.
3. **`conductor-providers`** (dist: `syv-conductor-providers`) — framework adapters; ships React and FastAPI providers today.

### conductor-nodes

```python
from conductor import NodeRegistry
from conductor_nodes import register_all, text, math

reg = NodeRegistry()
register_all(reg)                                   # all categories
register_all(reg, categories=["text", "math"])      # subset
text.register(reg)                                  # single module
```

Alternate composition path — grab a pre-populated registry and merge:

```python
from conductor_nodes import get_default_registry
mine.merge(get_default_registry())                  # raises on conflicts
mine.merge(other, on_conflict="skip")               # tolerate existing
```

Categories: `text`, `math`, `logic`, `loop`, `json`, `regex`. Node IDs are
category-prefixed (`text-uppercase`, `math-add`, etc.) to avoid collisions
with host-app node IDs. The for-each markers (`for-each-start`,
`for-each-end`) are unprefixed because the `FOR_EACH` compound discovers
them by base_id prefix.

### conductor-providers

```python
from conductor_providers import react

palette = react.palette_from_registry(registry)    # sidebar JSON
flow    = react.graph_to_react(nodes, edges)       # conductor → ReactFlow
nodes2, edges2 = react.react_to_graph(flow)        # ReactFlow → conductor
positions = react.topological_positions(nodes, edges)
```

Wire format: conductor's `produces` / `consumes` (and `node_label` /
`output_labels`) ride on each ReactFlow node's `data` payload. Tuples
become lists in JSON and are restored when parsed. Unknown keys are
ignored — hosts can decorate safely.

The FastAPI provider (`conductor_providers.fastapi`) exposes a
`conductor_router(...)` `APIRouter` factory mounting `/execute`,
`/execute-stream`, `/compile`, `/nodes`, and `/entities/{kind}`. Pydantic
request models mirror conductor's `GraphNode` / `GraphEdge`, including
`produces`, `consumes`, `node_label`, and `output_labels`. Pass an
`entity_resolver` callable to back `EntityDropdown` widgets.

New providers live in sibling subpackages (`conductor_providers.svelte`,
…); there is no abstract base class, each provider shapes itself to its
framework.

## Project Structure

```
packages/
├── conductor/src/conductor/
│   ├── types.py, widgets.py, metadata.py, validation.py, errors.py, node.py, _sentinel.py
│   ├── registry/        — NodeRegistry, NodeDefinition, discovery, schema
│   ├── graph/           — GraphNode/Edge, topology, compiler, regions, type_check, shared_refs
│   ├── execution/       — engine (eager+parallel), retry, state, store, request, resolver, results, events, skip, checkpoint
│   ├── compound/        — protocol, for_each
│   └── about/           — runnable library reference (python -m conductor.about)
├── conductor-nodes/src/conductor_nodes/
│   └── text, math, logic, loop, json_ops, regex_ops
└── conductor-providers/src/conductor_providers/
    └── react/            — graph, schema, layout
```

## Feature Map (finding the right thing)

| You want... | Use |
|---|---|
| A node to read the output of another node | `GraphEdge` |
| One node's output to feed many downstream nodes, no visible wires | `produces` on the source, `consumes` on each target |
| A constant or top-level value to be available inside a for-each body on every iteration | `produces` on the top-level node, `consumes` on the body node |
| Ambient per-run scratch data not tied to the DAG | `FlowStore` via `store: FlowStore` param |
| Automatic retry on transient errors | `max_retries` / `retry_delay` on the node, or global `RetryConfig` |
| Pause for human approval | Raise `HumanInputRequired`, persist `checkpoint` dict, later `resume()` |
| A region that runs per item | `for-each-start` / `for-each-end` nodes + `compile(compound_types=[FOR_EACH])` |
| Route around a branch based on a condition | Return `SKIPPED` sentinel on the inactive output |
| Custom node types the core doesn't know about | `ExtensionResolver` protocol |
| Common node implementations (text, math, etc.) | `conductor-nodes` package |
| Serialize a graph for a ReactFlow frontend | `conductor_providers.react.graph_to_react` |
| Parse ReactFlow JSON back into conductor | `conductor_providers.react.react_to_graph` |
| Build a sidebar palette from the registry | `conductor_providers.react.palette_from_registry` |
| Get a sensible widget without writing Annotated | Just use the type hint — registry picks a default |
| User-authored array input | `Annotated[list[T], List(item_widget=...)]` |
| Aggregate N upstream edges as one input | `Annotated[..., ConnectionList(...)]` |

## Design Docs

- `docs/shared-references.md` — v1 design spec for produce/consume
- `docs/conductor-design.md` — original library design document
- `docs/widgets.md` — widget catalog, type → default dispatch, and recipe for adding new widgets

## CLI Reference (this document)

This text is available at runtime from any environment with `conductor` installed:

```bash
python -m conductor.about                 # full text (this file)
python -m conductor.about sections        # list section slugs
python -m conductor.about shared          # print a section by slug (prefix/substring match)
```

Or programmatically:

```python
from conductor.about import get_content, list_sections, get_section
```

The wheel bundles this file as `conductor/about/llms.txt`, so downstream projects that `pip install syv-conductor` can read the reference without cloning the repository.
