Skip to content

Tutorial

kadyapam edited this page Oct 17, 2026 · 7 revisions

Tutorial

This walks through running the inbox-triage agent in both billing modes, writing an agent playbook of your own, and scheduling it daily. Commands assume Linux with bash.

1. Get a noetl CLI with kind: agent

kind: agent ships in noetl 5.1.0 and later. Install it one of these ways.

Debian / Ubuntu:

echo 'deb [trusted=yes] https://noetl.github.io/apt jammy main' | sudo tee /etc/apt/sources.list.d/noetl.list && sudo apt-get update && sudo apt-get install noetl

macOS / Linux with Homebrew:

brew tap noetl/tap && brew install noetl

From crates.io (needs Rust):

cargo install --bins noetl

With pip (the wheel bundles the same CLI; wrapper 5.3.2 carries CLI 5.3.2):

pip install "noetl>=5.3.2"

Check that you have 5.1.0 or newer:

noetl --version

2. Clone the playbooks

git clone https://github.com/noetl/agent-playbooks.git ~/projects/agent-playbooks && cd ~/projects/agent-playbooks

3a. Run with an API key (framework: claude)

  1. Create a key at console.anthropic.com → API Keys. It starts with sk-ant-api.

  2. Add credits under Settings → Billing. API usage is prepaid and separate from any Claude subscription; without credits every call returns credit balance is too low.

  3. Store the key in a file only you can read. Open ~/.config/noetl/anthropic.key in an editor and paste the key alone on one line:

    install -d -m 700 ~/.config/noetl && touch ~/.config/noetl/anthropic.key && chmod 600 ~/.config/noetl/anthropic.key

    Verify the shape without printing it:

    head -c 7 ~/.config/noetl/anthropic.key | grep -q '^sk-ant-' && echo ok || echo "wrong key"
  4. Run:

    NOETL_CREDENTIAL_ANTHROPIC="$(cat ~/.config/noetl/anthropic.key)" noetl run playbooks/inbox-triage/triage_api.yaml --runtime local --set account=work --verbose

What you should see: the agent calls list_unread, the line 🔧 Agent tool list_unread → …/tools/list_unread.yaml appears, and the step finishes after 2 turns with a list of the messages that need action. A run uses about 1,100 input and 170 output tokens.

3b. Run on a Claude subscription (framework: claude-code)

  1. Install the Claude Code CLI:

    curl -fsSL https://claude.ai/install.sh | bash
  2. Sign in once, interactively, choosing Claude account with subscription:

    ~/.local/bin/claude
  3. Confirm headless mode works:

    echo "Reply with exactly: pong" | ~/.local/bin/claude -p --output-format json | jq '{is_error, result}'
  4. Run:

    noetl run playbooks/inbox-triage/triage_subscription.yaml --runtime local --set account=work --set claude_bin=$HOME/.local/bin/claude --verbose

Here NoETL fetches the mail in step fetch and passes it to Claude in the prompt; Claude gets no tools. The result's framework field reads claude-code.

4. Write your own agent playbook

An agent playbook is an ordinary playbook with a kind: agent step. Tools are other playbooks.

The tool (playbooks/weather/tools/forecast.yaml): the model's input object arrives as workload.*; the last step's output is what the model sees.

apiVersion: noetl.io/v2
kind: Playbook
metadata:
  name: forecast
workload:
  city: ""
workflow:
  - step: start
    next:
      - step: get
  - step: get
    tool:
      kind: http
      method: GET
      url: "https://wttr.in/{{ workload.city }}"
      params:
        format: "3"

The agent (playbooks/weather/brief.yaml):

apiVersion: noetl.io/v2
kind: Playbook
metadata:
  name: weather_brief
executor:
  profile: local
  requires:
    tools: [agent]
workflow:
  - step: start
    next:
      - step: brief
  - step: brief
    tool:
      kind: agent
      framework: claude
      model: claude-sonnet-5-5
      prompt: Should I take an umbrella in Berlin and in Paris today?
      max_turns: 4
      auth:
        type: api_key
        credential: anthropic
      tools:
        - name: forecast
          description: Today's weather for one city.
          input_schema:
            type: object
            properties:
              city: { type: string }
            required: [city]
          playbook: tools/forecast.yaml

Rules of thumb:

  • Describe tools for the model. description and input_schema are all it sees; the playbook: binding never reaches it.
  • Keep tool output small. It is sent back to the model on every later turn. Return sender/subject/snippet, not full message bodies.
  • Set max_turns to the calls you expect plus one or two. The step fails rather than loops past it.
  • Declare executor.requires.tools: [agent] so an older noetl refuses the playbook with a clear message instead of skipping the step.

The same agent on a subscription: switch framework: claude-code, delete auth: and tools:, and either fetch the data in earlier steps (as triage_subscription.yaml does) or grant Claude Code tools with allowed_tools / mcp_config. See Configuration Reference.

5. Read a real mailbox

playbooks/inbox-triage uses stub mail so it runs anywhere. To read your real Gmail accounts, follow Connect Gmail:

  1. a one-time Google OAuth client;
  2. noetl credential google gmail-<account> … once per account;
  3. playbooks/gmail-triage/triage.yaml.

Each Gmail call there is an http step with auth: {type: google_oauth, credential: gmail-<account>}.

6. Run it every morning

mkdir -p ~/.config/systemd/user && cp systemd/inbox-triage.* ~/.config/systemd/user/
systemctl --user daemon-reload && systemctl --user enable --now inbox-triage.timer
  • The timer fires at 07:30; Persistent=true runs a missed job at the next start.
  • The service reads the key file at run time; the unit never contains the key.
  • To use the subscription instead, swap the commented ExecStart line in inbox-triage.service.
  • Results: journalctl --user -u inbox-triage.service. Next run: systemctl --user list-timers.
  • To run while you're logged out: sudo loginctl enable-linger $USER.

Next: Architecture explains what happens inside each of these runs.