# Extension playbook

Where ThunderGraph Model is designed to be extended safely—and where you should **not** hook in without treating the change as a library contribution.

## Supported extension surfaces

### Authoring types (`tg_model.model`)

- **`System`**, **`Part`**, **`Requirement`** — override **`define(cls, model)`** (and **`compile()`** only where the framework’s contract requires it). This is the primary **domain** extension point: structure, values, and **composable requirement packages**.
  - **Root `System` restriction:** treat `System.define()` as **structural only**. Declare top-level input `parameter(...)` values there, compose owned parts, and wire requirements/citations/allocations there. Put `attribute(...)`, `constraint(...)`, roll-ups, `solve_group(...)`, and `computed_by=` on owned `Part` instances or requirement packages.
  - Register a package from a **`System`** / **`Part`** with **`model.requirement_package(name, YourRequirementSubclass)`**; navigate with **`RequirementRef`** dot access (e.g. **`reqs.some_leaf`**).
  - Inside **`Requirement.define()`**, you may declare **leaf** **`model.requirement(...)`**, nested **`requirement_package`**, **`requirement_input`** / **`requirement_attribute`** / **`requirement_accept_expr`**, **package-level** **`parameter`** / **`attribute`** / **`constraint`**, plus **`citation`** / **`references`** where allowed. Package-level slots compile to **`ValueSlot`**s under the configured part (e.g. **`cm.root.<pkg>.<slot>`**); limits (no **`computed_by=`** / rollups on those slots yet, package **`constraint`** must have **`expr=`**) match the **`Requirement`** class docstring.
  - **Implementation note:** compiled artifacts still use internal node **`kind`** **`"requirement_block"`** for backward compatibility — when reading JSON or debugging **`compile_type`**, do not assume the string matches the public type name.
- **`ModelDefinitionContext`** — you do not subclass this; you call its methods **from** `define` on your element types. Public methods are the vocabulary for declarations.
- **Refs** (`AttributeRef`, `PartRef`, …) — returned by `model` APIs; you **use** them in expressions and `allocate`, not subclass them.
  - **`AttributeRef` passthrough:** for **`attribute(..., expr=…)`**, another slot's ref alone copies that slot at evaluation time; use **`.sym`** when mixing that slot into unitflow **Expr** arithmetic.

### Execution (`tg_model.execution`)

**Application code (default):** Prefer **`instantiate(SomeSystem)`** or **`SomeSystem.instantiate()`** to get a **`ConfiguredModel`**, then **`ConfiguredModel.evaluate(inputs={slot: Quantity, …})`**. Compilation is **lazy** (cached on the model), validation runs per the **`evaluate`** policy (default: on), and each call uses a **fresh** **`RunContext`** unless you pass **`run_context=`** for tests or custom runners. Input keys should be **`ValueSlot`** handles from **that** model (or the slot’s **`stable_id`** string for scripts).

**Extensions, tools, async, and debugging (explicit pipeline):** Call **`compile_graph`**, **`validate_graph`**, **`Evaluator`**, and **`RunContext`** directly when you need **`Evaluator.evaluate_async`**, to **reuse** one context across steps, to **inspect** the graph or handlers, or to mirror low-level behavior in tests. Do **not** fork the evaluator unless you are fixing a bug in the library.

**`RunContext`** remains the per-run mutable state carrier; the façade builds one for you on each **`evaluate`** by default.

### External compute (`tg_model.integrations`)

- Implement **`ExternalCompute`** (sync) or **`AsyncExternalCompute`** (async with `evaluate_async`).
- Optionally implement **`ValidatableExternalCompute.validate_binding`** so `validate_graph(..., configured_model=cm)` can check units before evaluation.
- Wire with **`ExternalComputeBinding`** and **`attribute(..., computed_by=...)`**; use **`link_external_routes`** when returning multiple outputs.

### Analysis (`tg_model.analysis`)

- Use **`sweep`**, **`compare_variants`**, **`dependency_impact`** as **tools** over existing `ConfiguredModel` / `DependencyGraph` / `RunResult` instances. New analysis is usually **new functions** in this package or in your product code, not a plugin API.

## What is not a stable extension API

- **Internal graph construction** (`graph_compiler`, dependency node kinds) — treat as library internals unless you are contributing to `tg_model`.
- **Private helpers** (`_*` modules, ad hoc compiler passes) — may change without notice.
- **Subclassing** `Evaluator`, `ConfiguredModel`, or `DependencyGraph` — not supported; open an issue or PR if you need a seam.

## Practical workflow

1. Model your domain with **`System` / `Part` / `Requirement`** and `define`.
2. Keep the root `System` structural; move executable logic into owned parts or requirement packages.
3. Run scenarios with **`ConfiguredModel.evaluate`** in product code and notebooks unless you need the explicit pipeline (see **Execution** above).
4. For tool-backed values, **`ExternalComputeBinding`** + **`validate_graph`** when possible (the façade runs validation before evaluation by default; explicit callers run **`validate_graph`** after **`compile_graph`**).
5. Add **tests** under `tests/unit/` or `tests/integration/` that mirror your usage (see {doc}`testing`).
6. If you need behavior that cannot be expressed with declarations and external compute, **document the gap** and consider a focused PR to `tg_model` rather than monkey-patching.

## References

- End-user narrative: {doc}`../user/quickstart` (recommended **`evaluate`** path vs explicit pipeline).
- Requirement authoring (leaf vs package, migration): {doc}`../user/concepts_requirements`, {doc}`../user/faq`.
- {doc}`architecture`
- {doc}`../drafts/execution_pipeline`
- API: {doc}`../api/index`
