> ## Documentation Index
> Fetch the complete documentation index at: https://docs.doppels.so/llms.txt
> Use this file to discover all available pages before exploring further.

# Doppels Template Expressions: Wiring Inputs and Outputs

> Use {{ inputs.name }} and {{ steps.id.result }} expressions in env, produces paths, and returns fields to wire inputs and outputs between steps.

Doppels manifests use `{{ ... }}` template expressions to wire inputs into step environments and pass step outputs into your Capability's declared returns. Expressions let you write manifests that are fully self-contained and deterministic — the same inputs always flow through the same paths to the same outputs, with no side channels or implicit state.

***

## Syntax

There are exactly two expression forms:

| Expression                      | Resolves to                                            |
| ------------------------------- | ------------------------------------------------------ |
| `{{ inputs.<name> }}`           | The value of the named input, as provided at run time. |
| `{{ steps.<stepId>.<result> }}` | The named result produced by a completed step.         |

Expressions appear inside double-curly-brace delimiters and must be placed within quoted YAML strings. For example:

```yaml theme={null}
env:
  VERSION: "{{ inputs.version }}"
```

***

## Where expressions are allowed

Expressions are valid in exactly three locations within a `shell` Recipe:

### 1. `env` values in steps

Use expressions to inject inputs (or prior step results) into a step's environment before the script runs.

```yaml theme={null}
steps:
  - id: build
    env:
      VERSION: "{{ inputs.version }}"
      PREV_CHECKSUM: "{{ steps.prepare.checksum }}"
    run:
      shell: sh
      script: |
        # Consume env vars — never template expressions — inside the script
        echo "Building version $VERSION"
        tar -czf "release-$VERSION.tgz" dist/
```

### 2. Declarative `produces.file` paths

Use expressions to construct artifact file paths that incorporate input values or step results.

```yaml theme={null}
    produces:
      archive:
        file: "release-{{ inputs.version }}.tgz"
```

### 3. `returns` values

Use expressions to map step results to the outputs declared in the Capability contract.

```yaml theme={null}
returns:
  archive: "{{ steps.build.archive }}"
  checksum: "{{ steps.build.checksum }}"
```

***

## Where expressions are NOT allowed

### Inside `run.script`

You must **never** place `{{ }}` expressions directly inside a step's `run.script` block. Instead, inject the value through `env:` and reference it as a normal shell variable.

**Wrong — do not do this:**

```yaml theme={null}
    run:
      shell: sh
      script: |
        tar -czf "release-{{ inputs.version }}.tgz" dist/
```

**Correct:**

```yaml theme={null}
    env:
      VERSION: "{{ inputs.version }}"
    run:
      shell: sh
      script: |
        tar -czf "release-$VERSION.tgz" dist/
```

This restriction exists to prevent shell injection: if a user provides a malicious input value, an expression embedded directly in the script would execute arbitrary code. Routing values through environment variables eliminates that risk.

### Anywhere outside the three permitted fields

Expressions are not evaluated in `metadata` fields, `requires`, `defaults`, `procedure`, `evidence`, or any other part of a manifest. Use them only in `env` values, declarative `produces.file` paths, and `returns` values.

***

## Complete wiring example

The example below shows all three permitted uses in a single step, along with the corresponding `returns` section.

```yaml theme={null}
# Injecting an input into a step environment
steps:
  - id: build
    env:
      VERSION: "{{ inputs.version }}"
    run:
      shell: sh
      script: |
        # Use the env var, not a template expression
        echo "Building version $VERSION"
        tar -czf "release-$VERSION.tgz" dist/
        export CHECKSUM="$(sha256sum "release-$VERSION.tgz" | cut -d ' ' -f 1)"

# Capturing an artifact with a dynamic path
    produces:
      archive:
        file: "release-{{ inputs.version }}.tgz"
      checksum:
        env: CHECKSUM

# Referencing a prior step's result
returns:
  archive: "{{ steps.build.archive }}"
  checksum: "{{ steps.build.checksum }}"
```

***

## Type coercion in `returns`

When a step's `produces.env` captures an exported shell variable, the value arrives as plain text. The `returns` block converts that text to the type declared in the Capability's `outputs`. The rules are strict — there is no lenient coercion and no whitespace trimming:

| Output type | Conversion rule                                                                                                                             |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `string`    | Value is passed as-is.                                                                                                                      |
| `integer`   | Must be a base-10 decimal within the JSON portable range `[-9007199254740991, 9007199254740991]`. Any other value is an error.              |
| `number`    | Must be a finite JSON number. If the value is mathematically integral, the same portable range applies. Infinity and NaN are not permitted. |
| `boolean`   | Must be exactly `true` or `false`. No truthy/falsy conversion — `1`, `yes`, `True`, and similar values are errors.                          |

If a captured value does not satisfy the target type's rule, the run fails with a type conversion error. Export your shell variables in the exact format the Capability expects.
