<!--
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)
- [Deploy a box to Railway](/tutorials/deploy-to-railway)
- [Operate a hosted daemon](/tutorials/operate-a-hosted-daemon)
- [Configuration](/configuration)
- [Members](/members)
- [Knowledge base](/knowledge-base/)
- [Method and 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 trigger](/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)
- [Secrets](/cli/secrets)
- [Lifecycle](/cli/lifecycle)
- [Sandbox image](/cli/image)
- [Sandboxes](/cli/sandbox)
- [Observability](/cli/observability)
- [Diagnostics](/cli/diagnostics)
- [Triggers](/cli/triggers)
- [Workbench](/cli/workbench)
- [Auth](/cli/auth)
- [Knowledge](/cli/knowledge)
- [Evals](/cli/evals)
- [Bento](/index)
- [Runtime wrapper](/architecture/runtime-wrapper)
- [Skill evolve](/architecture/skill-evolve)
-->

# Build your first pipeline

This tutorial shows how to create a pipeline, supply typed inputs, and inspect its trace. You run the same pipeline three ways: with an explicit `--arg`, from a pasted URL, and from a default.

For all configuration fields, see [Pipelines / Config](/pipelines/config).

## 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 repository to check out. You fire every pipeline 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.

Validate the configuration with `bento config validate`. Then apply it with `bento daemon restart`.

## 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 bento rejects the fire:

```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 bento rejects it 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 thing that permits an omitted input.

## 4. Map an arg off a pasted URL

So far you typed the input. You also **derive an input from the trigger** with `from:`, a template that renders against the parsed payload (`{{event.*}}`) and, on the URL surface, against 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`**. Bento does not parse a query arg off a pasted URL, so it rejects a URL fire that carries one. Every input therefore resolves without a flag, from `from:`, from a `default`, or from 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` and `tone` fall back to their defaults:

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

Precedence runs `--arg`, then `from:`, then `default`. A typed `--arg` wins, then a `from:` render, then the declared default. URL execution accepts no `--arg`, so it uses `from:` and then `default`. A manual fire such as `bento trigger fire greet --arg issue=42` is where an explicit value overrides `from:`.

Trust follows the source. Bento trusts the `--arg` an operator types. An arg that `from:` fills from a URL or a webhook carries untrusted payload text, so bento wraps it in `<untrusted>`, as it does for `{{event.*}}`.

## 5. Read the trace

Bento records each fire 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 run of the agent. That is 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)
