Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions .github/workflows/tutorials.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
name: Tutorial tests

# Runs the beginner path's commands for real against the pinned OpenClaw
# (scripts/openclaw-version.txt): onboarding, a Gateway on loopback with token
# auth, automations, skills, backups, audits. One script per page in
# scripts/tutorial-tests/. Steps that need a model key or a channel account are
# listed as skipped, with the reason. See scripts/README.md.
#
# Report only for now: the job passes even when a page fails, and the summary
# and tutorial-report.json say which steps failed. To make it blocking, set the
# repository variable TUTORIALS_STRICT=1 (Settings > Secrets and variables >
# Actions > Variables), or run it manually with "strict" ticked.

on:
push:
branches: [gh-pages]
paths:
- 'docs/**'
- 'scripts/tutorial-tests/**'
- 'scripts/openclaw-version.txt'
- '.github/workflows/tutorials.yml'
pull_request:
paths:
- 'docs/**'
- 'scripts/tutorial-tests/**'
- 'scripts/openclaw-version.txt'
- '.github/workflows/tutorials.yml'
schedule:
- cron: '17 5 * * 1' # Mondays 05:17 UTC
workflow_dispatch:
inputs:
strict:
description: Fail the job when a page fails
type: boolean
default: false

permissions:
contents: read

concurrency:
group: tutorials-${{ github.ref }}
cancel-in-progress: true

jobs:
tutorials:
runs-on: ubuntu-latest
timeout-minutes: 45
env:
TUTORIALS_STRICT: ${{ (github.event_name == 'workflow_dispatch' && inputs.strict) && '1' || vars.TUTORIALS_STRICT || '0' }}
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v6
with:
node-version: 24 # openclaw needs >=24.16
- name: Install pinned OpenClaw
run: |
npm i --prefix tools/openclaw --no-save --ignore-scripts --no-audit --no-fund \
"openclaw@$(tr -d '[:space:]' < scripts/openclaw-version.txt)"
tools/openclaw/node_modules/.bin/openclaw --version
node --version; npm --version
- name: Run tutorial tests
run: node scripts/tutorial-tests/run.mjs --json tutorial-report.json
- uses: actions/upload-artifact@v7
if: always()
with:
name: tutorial-report
path: |
tutorial-report.json
tutorial-report/
if-no-files-found: ignore
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,7 @@ yarn-error.log*
# Snippet gates
/tools/openclaw
snippet-report.json

# Tutorial tests
tutorial-report.json
/tutorial-report
8 changes: 6 additions & 2 deletions docs/getting-started/quick-start.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
last_update:
date: 2026-10-18
verified_with: 2026.10.4
ci_verified:
version: 2026.10.4
date: 2026-10-18
steps: 24
title: Quick Start
description: Get OpenClaw running and talking to you in about 5 minutes, from install to first conversation
keywords: [openclaw, openclaw getting started, openclaw tutorial, openclaw quick start, openclaw first steps, how to use openclaw, openclaw beginner guide]
Expand Down Expand Up @@ -168,7 +172,7 @@ openclaw channels add discord
Each command opens guided setup for that channel. By default, unknown senders who DM your bot get a pairing code instead of the agent. Approve them with:

```bash
openclaw pairing list
openclaw pairing list telegram
openclaw pairing approve telegram <code>
```

Expand Down Expand Up @@ -206,7 +210,7 @@ openclaw cron add --name "Morning brief" --cron "0 9 * * *" --session isolated \
`openclaw automations` is the same command under its new name. To give the heartbeat a short standing checklist, store it in its monitor scratch: find the job with `openclaw cron list --all` (it is listed as `Heartbeat (main)`), then `openclaw cron scratch <jobId> --set "..."`.

:::note Coming from an older version?
`HEARTBEAT.md` is retired and no longer read. `openclaw doctor --fix` imports an existing file into the heartbeat's monitor scratch and converts its `tasks:` entries into automations.
`HEARTBEAT.md` is retired and no longer read. `openclaw doctor --fix`, run with the Gateway stopped, imports an existing file into the heartbeat's monitor scratch and converts its `tasks:` entries into automations.
:::

See the [Heartbeat Guide](/guides/heartbeat), [Automation](/guides/automation) and the official [heartbeat docs](https://docs.openclaw.ai/gateway/heartbeat).
Expand Down
8 changes: 6 additions & 2 deletions docs/guides/first-7-days/day-1-make-it-yours.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
last_update:
date: 2026-10-18
verified_with: 2026.10.4
ci_verified:
version: 2026.10.4
date: 2026-10-18
steps: 17
title: "Day 1: Make It Yours"
description: "Personalize your OpenClaw bot — name, personality, boundaries, and your first real conversation"
keywords: [openclaw, setup, personalize, soul.md, agents.md, user.md, first day, customize agent, bot personality]
Expand Down Expand Up @@ -53,7 +57,7 @@ The workspace holds a handful of Markdown files that load at the start of every
| `MEMORY.md` | Curated long-term facts and decisions (optional). |
| `memory/YYYY-MM-DD.md` | Daily notes the agent writes as it goes. |

You can edit them with any text editor, or in the Control UI under **Settings → Agents → Files**. If you delete one by accident, `openclaw setup` recreates missing defaults without overwriting the files you kept.
You can edit them with any text editor, or in the Control UI under **Settings → Agents → Files**. If you delete one by accident, `openclaw setup --baseline` recreates missing defaults without overwriting the files you kept. (Bare `openclaw setup` on a configured install opens a chat instead.)

---

Expand Down Expand Up @@ -127,7 +131,7 @@ Edit `~/.openclaw/workspace/IDENTITY.md`:
- **Avatar:** avatars/nova.png
```

Replace `(pick one)` with a single emoji. Placeholder text in parentheses is ignored, not saved. The avatar is a workspace-relative path, an `http(s)` URL, or a data URI; drop the line if you don't have one.
Replace `(pick one)` with a single emoji, or delete the line. Only the template's own placeholder text is ignored; anything else you leave there, including `(pick one)`, is saved as the value. The avatar is a workspace-relative path, an `http(s)` URL, or a data URI; drop the line if you don't have one.

To push the file into the agent's config (used by the Control UI and other surfaces), run:

Expand Down
6 changes: 5 additions & 1 deletion docs/guides/first-7-days/day-3-automation.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
last_update:
date: 2026-10-18
verified_with: 2026.10.4
ci_verified:
version: 2026.10.4
date: 2026-10-18
steps: 30
title: "Day 3: Set It Free"
description: "Schedule automations for morning briefings and checks, and tune the heartbeat monitor so your bot reaches out on its own"
keywords: [openclaw, automation, automations, cron, heartbeat setup, scheduled tasks, morning briefing, proactive alerts]
Expand All @@ -28,7 +32,7 @@ OpenClaw has two kinds of scheduled work, and both are run by the Gateway's auto
Rule of thumb: if the task has a time or interval in its name, it's an automation.

:::info Coming from an older guide?
Older OpenClaw versions read heartbeat tasks from a `HEARTBEAT.md` file in the workspace. That file was retired in 2026.8.1: OpenClaw no longer creates or reads it. If you still have one, run `openclaw doctor --fix`. Doctor imports its instructions into the heartbeat's monitor scratch, turns valid `tasks:` entries into automations, and archives the original ([retired HEARTBEAT.md](https://docs.openclaw.ai/reference/templates/HEARTBEAT)).
Older OpenClaw versions read heartbeat tasks from a `HEARTBEAT.md` file in the workspace. That file was retired in 2026.8.1: OpenClaw no longer creates or reads it. If you still have one, stop the Gateway (`openclaw gateway stop`) and run `openclaw doctor --fix`. Doctor imports its instructions into the heartbeat's monitor scratch, turns valid `tasks:` entries into automations, and archives the original ([retired HEARTBEAT.md](https://docs.openclaw.ai/reference/templates/HEARTBEAT)).
:::

## Before You Start: Where Messages Go
Expand Down
4 changes: 4 additions & 0 deletions docs/guides/first-7-days/day-4-skills.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
last_update:
date: 2026-10-18
verified_with: 2026.10.4
ci_verified:
version: 2026.10.4
date: 2026-10-18
steps: 20
title: "Day 4: Supercharge It"
description: "Use the skills that ship with OpenClaw, find more on ClawHub, and check them before you install"
keywords: [openclaw, skills, install skills, clawhub, skill tutorial, community skills, extend bot]
Expand Down
8 changes: 7 additions & 1 deletion docs/guides/first-7-days/day-7-production.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
last_update:
date: 2026-10-18
verified_with: 2026.10.4
ci_verified:
version: 2026.10.4
date: 2026-10-18
steps: 30
title: "Day 7: Lock It Down"
description: "A validated security baseline, cost checks, monitoring, backups and safe upgrades for a long-running OpenClaw setup"
keywords: [openclaw, production, hardening, monitoring, security, backup, upgrade, cost]
Expand Down Expand Up @@ -207,10 +211,12 @@ Use `openclaw update`, not `npm update -g openclaw`. The updater validates the t
**4. Check:**

```bash
openclaw doctor --fix
openclaw doctor
openclaw status --deep
```

The updater already ran the required Doctor migrations; `openclaw doctor` reports anything left. If it suggests repairs, stop the Gateway first (`openclaw gateway stop`), run `openclaw doctor --fix`, then `openclaw gateway start`: with the Gateway running, `doctor --fix` cannot lock the state database and exits with `GatewayStateOwnerContentionError`.

Confirm every channel reconnects and your automations are listed in `openclaw cron list`.

**5. If something breaks:** don't just reinstall an older version. Downgrading the package does **not** reverse config or database migrations. Upstream's supported recovery is to restore a verified pre-update backup together with the OpenClaw release that made it ([rollback and recovery](https://docs.openclaw.ai/install/updating/rollback-and-recovery)):
Expand Down
14 changes: 12 additions & 2 deletions docs/guides/heartbeat.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
last_update:
date: 2026-10-18
verified_with: 2026.10.4
ci_verified:
version: 2026.10.4
date: 2026-10-18
steps: 39
title: Heartbeat Guide
description: How OpenClaw's heartbeat works in v2026.10 — agents.defaults.heartbeat, monitor scratch, automation jobs for recurring checks, cost control, and migrating from HEARTBEAT.md
keywords: [openclaw, openclaw heartbeat, monitor scratch, automations, activeHours, HEARTBEAT.md migration]
Expand Down Expand Up @@ -118,12 +122,18 @@ Scratch becomes part of the prompt. Do not put API keys, tokens or phone numbers

### Migrating from HEARTBEAT.md {#migrating-from-heartbeatmd}

If you used `~/.openclaw/workspace/HEARTBEAT.md` (or a per-agent workspace copy) before v2026.8.1, run:
If you used `~/.openclaw/workspace/HEARTBEAT.md` (or a per-agent workspace copy) before v2026.8.1, stop the Gateway and run Doctor:

```bash
openclaw gateway stop
openclaw doctor --fix
openclaw gateway start
```

Doctor needs exclusive access to the state database. With the Gateway running, `doctor --fix` exits with `GatewayStateOwnerContentionError` and changes nothing.

Doctor only imports into an empty scratch. If the heartbeat already has different scratch, or you cleared it with `--unset`, Doctor warns and leaves `HEARTBEAT.md` where it is; copy what you need with `openclaw cron scratch <jobId> --file ~/.openclaw/workspace/HEARTBEAT.md`.

Doctor creates the monitor rows from `agents.*.heartbeat`, imports each workspace `HEARTBEAT.md` into that agent's monitor scratch, converts a structured `tasks:` block into separate automation jobs, archives the original under the state directory (`backups/heartbeat-migration/`), and deletes the file. After that the runtime reads scratch only.

:::warning Bare `interval` numbers are read as minutes
Expand Down Expand Up @@ -305,7 +315,7 @@ Common skip reasons:
Checklist when nothing happens:

1. `openclaw config validate` passes, and the config has no top-level `heartbeat` key.
2. `openclaw cron list --all` shows an enabled `Heartbeat (<agent>)` job. If not, run `openclaw doctor --fix`.
2. `openclaw cron list --all` shows an enabled `Heartbeat (<agent>)` job. If not, stop the Gateway and run `openclaw doctor --fix`.
3. `cron.enabled` is not `false` and `OPENCLAW_SKIP_CRON` is not set.
4. `every` is not `"0m"`, and the current time is inside `activeHours`.
5. An owner route resolves (no `no-route` skips in `openclaw cron runs`).
Expand Down
95 changes: 95 additions & 0 deletions scripts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,101 @@ check whether the page's prose (batch sections, the latest release passed to
`GITHUB_TOKEN` don't trigger other workflows; re-run the snippet check by hand
or push a commit to the branch if needed.

## Tutorial tests (`tutorial-tests/`)

The snippet gates check that commands *exist*. The tutorial tests check that
the beginner path *works*: each `*.test.sh` walks one page in order and runs its
commands for real against the pinned OpenClaw, with a Gateway running on the
GitHub Actions runner (`.github/workflows/tutorials.yml`: pushes and PRs that
touch `docs/` or the tests, weekly, and on demand).

| Test | Page |
|---|---|
| `quick-start.test.sh` | `getting-started/quick-start.md` |
| `day-1-make-it-yours.test.sh` | `guides/first-7-days/day-1-make-it-yours.md` |
| `day-3-automation.test.sh` | `guides/first-7-days/day-3-automation.md` |
| `day-4-skills.test.sh` | `guides/first-7-days/day-4-skills.md` |
| `day-7-production.test.sh` | `guides/first-7-days/day-7-production.md` |
| `heartbeat.test.sh` | `guides/heartbeat.md` |

Each test gets a fresh sandbox `HOME` (so `~/.openclaw/...` on the page is the
sandbox), a non-interactive onboarding with no model credentials, and its own
Gateway (`openclaw gateway run`, loopback, token auth), which is stopped at the
end. Tests re-run themselves under `env -i`, so no API key or token from the
calling shell reaches OpenClaw.

**The page is the source.** A test names commands by their text on the page;
`lib.sh` finds them in the page's shell blocks (or inline code) and runs the
page's own text, so a test cannot drift from its page silently:

```bash
source "$(dirname "$0")/lib.sh"
page docs/guides/heartbeat.md
onboard # CI stand-in for interactive onboarding
gateway_start
run -e 'Heartbeat \(main\)' "openclaw cron list --all" # -e: output must match
run "openclaw automations add \"every 30m\"..." # "...": the one command with this prefix
capture jobId "openclaw cron list --all --json" '(j.jobs).find(x=>x.displayName==="Heartbeat (main)").id'
run "openclaw cron scratch <jobId>" # <placeholders> come from capture/ph
config_block 1 # Nth openclaw.json block: config patch + validate
config_block 4 --dry-run # reference block: validate the merge, don't apply
file_block "~/.openclaw/workspace/SOUL.md" # write a titled block to its path
skip "openclaw tui" "interactive terminal UI"
finish
```

Other helpers: `run_as "<page command>" "<CI variant>"` for steps that need a
non-interactive form or the pinned version, `setup "<label>" "<command>"` for
CI-only checks between steps, `skip_step` for non-command steps, `-x 0,1` for
accepted exit codes, `-t <secs>` for a timeout. A step that needs a model key
or a channel account is a `skip` with the reason, never faked. Page commands a
test neither runs nor skips are listed as "unmapped" in the report.

**Output:** a job summary table (page, steps run, skipped, failed, unmapped,
with every step and reason under each page) and `tutorial-report.json`
(artifact `tutorial-report`). For a passing page the summary prints the front
matter to add.

**Report only for now.** The job passes even when a page fails. To make it
blocking, set the repository variable `TUTORIALS_STRICT=1`, or tick "strict"
on a manual run (`run.mjs --strict` exits 1 on any failed page).

### Running locally

```bash
TT_DRY=1 bash scripts/tutorial-tests/heartbeat.test.sh # check the test/page mapping; runs nothing
node scripts/tutorial-tests/run.mjs # full run: needs a Gateway, so CI only
```

`gateway_start` refuses to run outside GitHub Actions unless you set
`TT_ALLOW_GATEWAY=1`. Do that only in a disposable VM or container: it starts a
real Gateway on port 18789.

### The "Verified in CI" banner

Pages that pass get a front matter entry, added by hand after a green run
(the workflow never commits):

```yaml
ci_verified:
version: 2026.10.4
date: 2026-10-18
steps: 24
```

`src/theme/DocItem/Content` (a wrapper around the theme's component) reads it
and renders `src/components/CiVerified.tsx` above the page: "Verified in CI on
OpenClaw 2026.10.4, 2026-10-18 — 24 steps run", linking to the workflow.
`steps` is the "Steps run" count minus failures from the summary. When a page
starts failing, fix the page or remove its `ci_verified` entry. Bump the
entries when the pin moves.

### Adding a page

Copy a test, point `page` at the new file, and walk the page top to bottom.
Run it with `TT_DRY=1` until it reports `0 page commands not mapped`, then
push and read the job summary.

## Link check

`.github/workflows/links.yml` runs lychee weekly over external links in
Expand Down
48 changes: 48 additions & 0 deletions scripts/tutorial-tests/day-1-make-it-yours.test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#!/usr/bin/env bash
# Tutorial test: docs/guides/first-7-days/day-1-make-it-yours.md
source "$(dirname "$0")/lib.sh"
page docs/guides/first-7-days/day-1-make-it-yours.md

onboard
setup -e '^ok$' "onboarding seeded the workspace (BOOTSTRAP.md, AGENTS.md, SOUL.md, IDENTITY.md, USER.md)" \
"cd ~/.openclaw/workspace && ls BOOTSTRAP.md AGENTS.md SOUL.md IDENTITY.md USER.md > /dev/null && echo ok"
gateway_start

# Meet your bot
skip "openclaw dashboard" "opens a browser"
skip "openclaw tui" "interactive terminal UI"
skip "openclaw agent --agent main --message \"Who are you?\"" "needs a model provider key"

# The workspace files: deleting one and recreating it with setup
setup "delete AGENTS.md and keep a custom SOUL.md" \
"rm ~/.openclaw/workspace/AGENTS.md && echo '# kept' > ~/.openclaw/workspace/SOUL.md"
run "openclaw setup --baseline"
setup -e '^ok$' "AGENTS.md is back and SOUL.md was not overwritten" \
"test -f ~/.openclaw/workspace/AGENTS.md && grep -qx '# kept' ~/.openclaw/workspace/SOUL.md && echo ok"

# Write your SOUL.md
file_block "~/.openclaw/workspace/SOUL.md"
skip "openclaw agent --agent main --message \"Who are you?\"" "needs a model provider key"

# Set your identity
file_block "~/.openclaw/workspace/IDENTITY.md"
run -e 'Name: Nova' "openclaw agents set-identity --agent main --from-identity"
setup -e '"name": "Nova"' "identity stored in config" "openclaw config get agents.entries.main.identity"

# Tell it about you
file_block "~/.openclaw/workspace/USER.md"
run "openclaw config set agents.defaults.userTimezone Europe/Berlin"
setup -e 'Europe/Berlin' "timezone stored" "openclaw config get agents.defaults.userTimezone"
setup "config still valid with the gateway running" "openclaw config validate && openclaw health"

# Have a real conversation
skip_step "The five example prompts" "need a model provider key"

# Verify it remembers
skip "openclaw agent --agent main --message \"What do you know about me?\"" "needs a model provider key"
skip "cat ~/.openclaw/workspace/MEMORY.md" "MEMORY.md is written by the agent during a conversation; a fresh install has none"
skip "ls ~/.openclaw/workspace/memory/" "daily notes are written by the agent during a conversation"
run "openclaw memory search \"timezone\""
run "openclaw memory index --force"

finish
Loading
Loading