<!--
Sitemap:
- [Installation](/installation)
- [Upgrading](/upgrading): Version-specific steps for upgrading an existing Bento install.
- [Concepts](/concepts)
- [Build your first pipeline](/tutorials/pipeline-args)
- [Target a specific issue or PR from a URL](/tutorials/url-targeting)
- [Keep state across runs](/tutorials/pipeline-state)
- [Fire a pipeline on a schedule or on demand](/tutorials/schedule-and-fire)
- [Configuration](/configuration)
- [Knowledge Base](/knowledge-base/)
- [Method & delivery](/knowledge-base/modes)
- [Config](/knowledge-base/config)
- [MCP](/knowledge-base/mcp)
- [Pipeline configuration reference](/pipelines/config)
- [Filters](/pipelines/filters)
- [Triggers](/triggers/)
- [GitHub Trigger](/triggers/github)
- [Linear Trigger](/triggers/linear)
- [Webhook](/triggers/webhook)
- [Schedule Trigger](/triggers/schedule)
- [Manual Trigger](/triggers/manual)
- [Traces](/pipelines/traces)
- [Slack](/integrations/slack)
- [Public Access](/public-access)
- [Context Engineering](/context-engineering)
- [Best Practices](/best-practices)
- [Troubleshooting](/troubleshooting)
- [Architecture](/architecture/vision)
- [Workspaces](/workspaces)
- [Authentication](/authentication)
- [Identity](/identity)
- [Security](/security)
- [References](/references)
- [Changelog](/changelog): Bento release history.
- [CLI Reference](/cli/)
- [Setup](/cli/setup)
- [Lifecycle](/cli/lifecycle)
- [Sandbox Image](/cli/image)
- [Observability](/cli/observability)
- [Diagnostics](/cli/diagnostics)
- [Triggers](/cli/triggers)
- [Workbench](/cli/workbench)
- [Auth](/cli/auth)
- [Knowledge](/cli/knowledge)
- [Bento](/index)
- [Runtime Wrapper](/architecture/runtime-wrapper)
- [Skill Evolve](/architecture/skill-evolve)
-->

# Build your first pipeline

This tutorial takes you from an empty repo to a pipeline you fire by hand,
pass a typed input to, and read the trace of. The spine is **pipeline args** —
the typed inputs a pipeline accepts. By the end you will have fired the same
pipeline three ways: with an explicit `--arg`, from a pasted URL, and falling
back to a default.

For the exhaustive field reference, see [Pipelines / Config](/pipelines/config).
This is the guided path; that is the spec.

## 1. Write the pipeline

A pipeline is a YAML file under `.bento/pipelines/`. Create
`.bento/pipelines/greet.yaml`:

```yaml
name: greet
trigger:
  repo: acme/my-project   # owner/name — must match a repos: entry in daemon.yaml
agent: solver
args:
  name:
    type: string
    required: true        # the fire is rejected if this is missing
  tone:
    type: string
    default: terse        # used when --arg tone=… is omitted
instructions: |
  Write a one-line greeting for {{args.name}} in a {{args.tone}} voice.
```

Three things to notice:

* `trigger` carries only the repo to check out. Every pipeline is fireable by
  hand; there is no key to opt in.
* `args:` declares the typed inputs. `name` is required; `tone` has a default.
* `{{args.<name>}}` substitutes a validated input into `instructions`. This is a
  separate namespace from the `{{event.*}}` webhook payload.

The daemon picks up the file on its next config reload — restart the daemon if
your setup does not reload automatically.

## 2. Fire it with an explicit arg

Pass inputs with repeatable `--arg key=value` flags:

```bash
bento trigger fire greet --arg name="Ada" --arg tone="formal"
```

`instructions` renders with `{{args.name}}` → `Ada` and `{{args.tone}}` →
`formal`. The command prints a trigger id — keep it for step 4.

Validation is strict. Try an undeclared key and the fire is rejected:

```bash
bento trigger fire greet --arg name="Ada" --arg color="blue"
# invalid args: (root): Unrecognized key(s) in object: 'color'
```

Omit the required input and it is rejected too:

```bash
bento trigger fire greet --arg tone="formal"
# invalid args: name: Required
```

## 3. Let a default fill the gap

`tone` has a `default`, so firing without it is valid — the default fills in:

```bash
bento trigger fire greet --arg name="Ada"
# tone falls back to "terse"
```

A pipeline with no `args:` accepts no inputs at all; a declared `default` is the
only way an omitted input is allowed.

## 4. Map an arg off a pasted URL

So far you typed the input. An input can also be **derived from the trigger**
with `from:`, a template rendered against the parsed payload (`{{event.*}}`) and,
on the URL surface, the raw URL parts (`{{url.*}}`). This is how a pasted URL
targets a specific item with no extra flags.

The URL surface takes **no `--arg`** — query-arg parsing off a pasted URL is not
supported, so a URL fire is rejected if you add one. That means every input must
be satisfiable without a flag: from `from:`, a `default`, or both. Relax `name`
to a default so the URL fire is valid, add an `issue` input mapped from the
payload, and add a `patterns` list so a GitHub issue URL routes here:

```yaml
name: greet
trigger:
  patterns:
    - https://github.com/acme/my-project/issues
args:
  name:
    type: string
    default: friend                  # was required; a URL fire can't pass --arg
  tone:
    type: string
    default: terse
  issue:
    type: string
    default: ""                      # blank → no specific issue
    from: "{{event.issue.number}}"   # a pasted issue URL fills this
agent: solver
instructions: |
  Write a one-line greeting for {{args.name}} in a {{args.tone}} voice.
  {{args.issue}}
```

Now fire by pasting a URL instead of a pipeline name, with no flags — `patterns`
prefix-matches the URL to this pipeline, `from:` fills `issue`, and `name`/`tone`
fall back to their defaults:

```bash
bento trigger fire https://github.com/acme/my-project/issues/42
# issue renders as 42
```

Precedence is `--arg` → `from:` → `default`: a typed `--arg` wins, then a
`from:` render, then the declared default. The URL surface has no `--arg` rung,
so there it is `from:` then `default` — a manual `bento trigger fire greet --arg
issue=42` is where an explicit value would override `from:`. Trust follows the
source: an operator's `--arg` is trusted, but an arg filled by `from:` on a URL
or webhook may carry untrusted payload text, so it is wrapped in `<untrusted>`
like `{{event.*}}`.

## 5. Read the trace

Each fire is recorded as a trigger. Find it and follow it through every layer:

```bash
bento trace <trigger-id>
```

`bento trace` accepts any unique id prefix. The trace shows the trigger, the
workload, and the agent's run — the verifiable end state for this lesson. See
[Pipelines / Traces](/pipelines/traces) for the full anatomy.

## What you built

One pipeline, fired three ways: an explicit `--arg`, a default fallback, and a
`from:`-mapped value off a pasted URL. That is the whole args mechanism. Next:

* [Target a specific issue or PR from a URL](/tutorials/url-targeting)
* [Keep state across runs](/tutorials/pipeline-state)
* [Fire on a schedule or on demand](/tutorials/schedule-and-fire)
