# ng Block Design: enrich + execute

Date: 2026-03-09
Source: ng model_debate (run 8fb0dedb3b21, $0.09, gemini-flash winner at 82/100)

## Design Decisions

### Q1: enrich — ADD new Things or MODIFY existing?

**MODIFY.** Augment props on existing Things. Fan-out (adding new Things) breaks linear
pipelines — a downstream `revise` would suddenly get 5 search results instead of 1 answer.
Store fetched content in `props["context"]`.

### Q2: execute — extracted code or template?

**Both**, via `source` param: `"content"` (run the Thing's text as code) or `"template"`
(run a fixed command with `{content}` interpolation).

### Q3: kind — typed enum or soft string?

**Soft string.** Matches ng philosophy. Establish well-known kinds for discoverability:
`answer`, `source_material`, `code_output`, `subtask`, `error`.

### Q4: Failure handling?

**Graceful degradation.** Don't raise — pass Thing through with
`props["enrichment_status"] = "partial" | "failed"` and `props["errors"]` list.
Downstream blocks can react or ignore.

## Block Interfaces

### enrich

Wraps `lib/ingest.fetch`, `lib/ingest.search` (Serper), `lib/extract.analyze_doc`.

```python
async def enrich(
    upstream: AsyncIterator[Thing],
    params: dict[str, Any],
    ctx: Context,
) -> AsyncIterator[Thing]:
```

**Params:**

| Param          | Type   | Default     | Description                                          |
|----------------|--------|-------------|------------------------------------------------------|
| `method`       | str    | `"search"`  | `"search"` / `"fetch"` / `"lookup"`                 |
| `query`        | str    | `"{content}"` | f-string template using `{content}`, `{props.X}`   |
| `target_prop`  | str    | `"context"` | Prop key where fetched content is stored             |
| `limit`        | int    | 5           | Max results (for search)                             |
| `extract`      | bool   | True        | Run analyze_doc on fetched content                   |
| `on_error`     | str    | `"continue"` | `"continue"` (degrade) or `"abort"` (raise)         |

**Behavior:**
- For each upstream Thing, fetches context based on method
- Adds `props[target_prop]` with fetched content
- Adds `props["sources"]` (list of URLs)
- Adds `props["enrichment_status"]` = `"ok"` / `"partial"` / `"failed"`
- On failure with `on_error="continue"`: passes Thing through, appends to `props["errors"]`
- Does NOT replace content — augments alongside

### execute

Wraps `subprocess` (code/shell), `lib/http_client` (API calls).

```python
async def execute(
    upstream: AsyncIterator[Thing],
    params: dict[str, Any],
    ctx: Context,
) -> AsyncIterator[Thing]:
```

**Params:**

| Param      | Type   | Default    | Description                                          |
|------------|--------|------------|------------------------------------------------------|
| `runtime`  | str    | `"python"` | `"python"` / `"bash"` / `"nodejs"`                  |
| `source`   | str    | `"content"` | `"content"` (run Thing text) / `"template"` (fixed) |
| `command`  | str    | —          | For template mode: command with `{content}`          |
| `timeout`  | float  | 30         | Max seconds per execution                            |
| `sandbox`  | bool   | True       | Run in temp dir, restricted env                      |
| `capture`  | str    | `"both"`   | `"stdout"` / `"stderr"` / `"both"`                  |

**Behavior:**
- For each upstream Thing, executes code/command
- Adds `props["exit_code"]`, `props["stdout"]`, `props["stderr"]`
- Adds `props["executed"] = True`, `props["execution_time"]`
- If exit_code == 0: `props["verified"] = True`
- Content replaced with stdout (the execution result)

### decompose (future)

```python
async def decompose(
    upstream: AsyncIterator[Thing],
    params: dict[str, Any],
    ctx: Context,
) -> AsyncIterator[Thing]:
```

**Params:** `model`, `n` (target subtask count), `strategy` ("parallel" / "sequential" / "tree")

**Behavior:** LLM breaks problem into subtasks, yields each as new Thing with `kind="subtask"`,
`props["parent_id"]`, `props["subtask_index"]`.

## Recipe Examples

### Research & Summarize

```yaml
stages:
  - type: enrich
    params:
      method: search
      query: "{content} latest developments 2026"
      limit: 5
  - type: produce
    params:
      n: 3
      models: [sonnet, gpt, gemini]
      system: "Summarize based on the search context in props.context"
  - type: score
    params:
      rubric:
        - completeness: "Covers all key findings from search results?"
        - accuracy: "Claims verifiable from the enriched sources?"
  - type: reduce
    params: { method: combine, k: 3 }
```

### Code Verification

```yaml
stages:
  - type: produce
    params:
      n: 5
      system: "Write Python code to solve the problem. Output only code."
  - type: execute
    params:
      runtime: python
      source: content
      timeout: 10
  - type: score
    params:
      intent: verify
  - type: reduce
    params: { method: top_k, k: 1 }
```

### VC Firm Analysis

```yaml
stages:
  - type: enrich
    params:
      method: search
      query: "{content} portfolio companies founders"
      limit: 10
  - type: produce
    params:
      n: 3
      models: [sonnet, gpt, gemini]
  - type: score
    params:
      rubric:
        - completeness: "All key partners and portfolio companies identified?"
        - accuracy: "Facts verifiable from enriched sources?"
  - type: reduce
    params: { method: combine, k: 3 }
```
