Introduction
# HZL: Persistent task tracking for agents
HZL (https://github.com/tmchow/hzl) is a local-first task ledger (database-backed, optionally cloud-synced for backup) that an agent can use to:
- plan multi-step work into projects + tasks - checkpoint progress (so work survives session boundaries) - coordinate sub-agents or multiple coding tools with leases - generate reliable status reports ("what's done vs what's left")
This skill teaches an agent how to use the `hzl` CLI.
## When to use HZL
**OpenClaw has NO native task tracking tools.** Unlike Claude Code (which has TodoWrite) or Codex (which has update_plan), OpenClaw relies on memory and markdown files for tracking work. This makes HZL especially valuable for OpenClaw.
**Use HZL by default for any non-trivial task tracking:**
- Multi-step projects with real sequencing (dependencies) and handoffs - Work that may outlive this session or span multiple tools/agents - Orchestration: delegating work to sub-agents and needing recovery if they crash - Anything where "resume exactly where we left off" matters - **Any work you want to persist beyond this session** - **Any work that needs structure (nesting, dependencies, progress tracking)** - **Any work that benefits from a durable record of decisions or ownership**
Multi-session or multi-agent work are common reasons to use HZL, not requirements. Use HZL for single-session, single-agent work when the task is non-trivial.
**Why HZL is the right choice for OpenClaw:**
Without HZL, OpenClaw tracks tasks in-context (burns space, fragments during compaction) or in markdown files (requires manual management, no nesting/dependencies, no dashboard). HZL provides:
- Persistent storage that survives session boundaries - Nesting (parent tasks + subtasks) and dependencies - Web dashboard for human visibility (`hzl serve`) - Leases for multi-agent coordination - Checkpoints for progress recovery
**Only skip HZL for:**
- Truly trivial, one-step tasks you will complete immediately in this session - Time-based reminders/alerts (use OpenClaw Cron instead) - Longform notes or knowledge capture (use a notes or memory system)
**Rule of thumb:** If you feel tempted to make a multi-step plan or there is any chance you will not finish in this session, use HZL.
Example: "Investigate failing tests and fix root cause" -> use HZL because it likely involves multiple subtasks, even if you expect to finish within a session.
Personal tasks: HZL is not a polished human to-do app, but it is usable for personal task tracking, and it can also serve as a backend for a lightweight UI.
## Core concepts
- **Project**: stable container. For OpenClaw, use a single `openclaw` project—this keeps `hzl task next` simple. Check `hzl project list` before creating. - **Task**: top-level work item. For multi-step requests, this becomes a parent task. - **Subtask**: breakdown of a task into parts (`--parent <id>`). Max 1 level of nesting. Parent tasks are organizational containers—never returned by `hzl task next`. - **Checkpoint**: short progress snapshot to support recovery - **Lease**: time-limited claim (prevents orphaned work in multi-agent flows)
## ⚠️ DESTRUCTIVE COMMANDS — READ FIRST
The following commands **PERMANENTLY DELETE HZL DATA** and cannot be undone:
| Command | Effect | |---------|--------| | `hzl init --force` | **DELETES ALL DATA.** Prompts for confirmation. | | `hzl init --force --yes` | **DELETES ALL DATA WITHOUT CONFIRMATION.** Extremely dangerous. | | `hzl task prune ... --yes` | **PERMANENTLY DELETES** old done/archived tasks and their event history. |
**AI agents: NEVER run these commands unless the user EXPLICITLY asks you to delete data.**
- `hzl init --force` deletes the entire event database: all projects, tasks, checkpoints, and history - `hzl task prune` deletes only tasks in terminal states (done/archived) older than the specified age - There is NO undo. There is NO recovery without a backup.
## Anti-pattern: Project Sprawl
Use a single `openclaw` project. Requests and initiatives become **parent tasks**, not new projects.
**Wrong (creates sprawl):** ```bash hzl project create "garage-sensors" hzl project create "query-perf" # Now you have to track which project to query ```
**Correct (single project, parent tasks):** ```bash # Check for existing project first hzl project list
# Use single openclaw project hzl task add "Install garage sensors" -P openclaw # → Created task abc123
hzl task add "Wire sensor to hub" --parent abc123 hzl task add "Configure alerts" --parent abc123
# hzl task next --project openclaw always works ```
Why this matters: - Projects accumulate forever; you'll have dozens of abandoned one-off projects - `hzl task next --project X` requires knowing which project to query - With a single project, `hzl task next --project openclaw` always works
## Sizing Parent Tasks
HZL supports one level of nesting (parent → subtasks). Scope parent tasks to completable outcomes.
**The completability test:** "I finished [parent task]" should describe a real outcome. - ✓ "Finished installing garage motion sensors" - ✓ "Finished fixing query performance" - ✗ "Finished home automation" (open-ended domain, never done) - ✗ "Finished backend work" (if frontend still pending for feature to ship)
**Scope by problem, not technical layer.** A full-stack feature (frontend + backend + tests) is usually one parent if it ships together.
**Split into multiple parents when:** - Parts deliver independent value (can ship separately) - You're solving distinct problems that happen to be related
**Adding context:** Use `-d` for details, `-l` for reference docs: ```bash hzl task add "Install garage sensors" -P openclaw \ -d "Per linked spec. Mount sensors at 7ft height." \ -l docs/sensor-spec.md,https://example.com/wiring-guide ```
**Don't duplicate specs into descriptions**—this creates drift. Reference docs instead.
**If no docs exist**, include enough detail for another agent to complete the task: ```bash hzl task add "Configure motion alerts" -P openclaw -d "$(cat <<'EOF' Trigger alert when motion detected between 10pm-6am. Use Home Assistant automation. Notify via Pushover. EOF )" ``` Description supports markdown (16KB max).
## Core Workflows
**Setup:** ```bash hzl project list # Always check first hzl project create openclaw # Only if needed ```
**Adding work:** ```bash hzl task add "Feature X" -P openclaw -s ready # Ready to claim hzl task add "Subtask A" --parent <id> # Subtask hzl task add "Subtask B" --parent <id> --depends-on <subtask-a-id> # With dependency ```
**Working on a task:** ```bash hzl task next -P openclaw # Next available task hzl task next --parent <id> # Next subtask of parent hzl task next -P openclaw --claim # Find and claim in one step hzl task claim <id> # Claim specific task hzl task checkpoint <id> "milestone X" # Notable progress or before pausing ```
**Changing status:** ```bash hzl task set-status <id> ready # Make claimable (from backlog) hzl task set-status <id> backlog # Move back to planning ``` Statuses: `backlog` → `ready` → `in_progress` → `done` (or `blocked`)
**When blocked:** ```bash hzl task block <id> --comment "Waiting for API keys from DevOps" hzl task unblock <id> # When resolved ```
**Finishing work:** ```bash hzl task comment <id> "Implemented X, tested Y" # Optional: final notes hzl task complete <id>
# After completing a subtask, check parent: hzl task show <parent-id> --json # Any subtasks left? hzl task complete <parent-id> # If all done, complete parent ```
**Troubleshooting:** | Error | Fix | |-------|-----| | "not claimable (status: backlog)" | `hzl task set-status <id> ready` | | "Cannot complete: status is X" | Claim first: `hzl task claim <id>` |
---
## Extended Reference (look up as needed — skip on first read)
```bash # Setup hzl init # Initialize (safe, won't overwrite) hzl init --reset-config # Reset config to default location hzl status # Database mode, paths, sync state hzl doctor # Health check for debugging
# Create with options hzl task add "<title>" -P openclaw --priority 2 --tags backend,auth hzl task add "<title>" -P openclaw --depends-on <other-id> hzl task add "<title>" -P openclaw -s in_progress --assignee <name> # Create and claim
# List and find hzl task list -P openclaw --available # Ready tasks with met dependencies hzl task list --parent <id> # Subtasks of a parent hzl task list --root # Top-level tasks only
# Dependencies hzl task add-dep <task-id> <depends-on-id> hzl validate # Check for circular dependencies
# Web Dashboard hzl serve # Start on port 3456 (network accessible) hzl serve --host 127.0.0.1 # Restrict to localhost only hzl serve --background # Fork to background hzl serve --status # Check if running hzl serve --stop # Stop background server
# Multi-agent recovery hzl task claim <id> --assignee <agent-id> --lease 30 hzl task stuck hzl task steal <id> --if-expired --author <agent-id> ```
Tip: When a tool needs to parse output, prefer `--json`.
## Authorship tracking
HZL tracks authorship at two levels:
| Concept | What it tracks | Set by | |---------|----------------|--------| | **Assignee** | Who owns the task | `--assignee` on `claim` or `add` | | **Event author** | Who performed an action | `--author` on other commands |
The `--assignee` flag on `claim` and `add` (with `-s in_progress`) sets task ownership. The `--author` flag on other commands (checkpoint, comment, block, etc.) records who performed each action:
```bash # Alice owns the task hzl task claim 1 --assignee alice
# Bob adds a checkpoint (doesn't change ownership) hzl task checkpoint 1 "Reviewed the code" --author bob
# Task is still assigned to Alice, but checkpoint was recorded by Bob ```
For AI agents that need session tracking, use `--agent-id` on claim: ```bash hzl task claim 1 --assignee "Claude Code" --agent-id "session-abc123" ```
## Recommended patterns
### Start a multi-step project
1) Use the single `openclaw` project (create only if it doesn't exist). 2) Create a parent task for the initiative. 3) Decompose into subtasks with dependencies. 4) Validate.
```bash # Check if project exists first hzl project list # Create only if needed hzl project create openclaw
# Parent task for the initiative hzl task add "Implement auth system" -P openclaw --priority 3 # → abc123
# Subtasks with sequencing hzl task add "Clarify requirements + acceptance criteria" --parent abc123 --priority 5 hzl task add "Design API + data model" --parent abc123 --priority 4 --depends-on <reqs-id> hzl task add "Implement endpoints" --parent abc123 --priority 3 --depends-on <design-id> hzl task add "Write tests" --parent abc123 --priority 2 --depends-on <impl-id> hzl task add "Docs + rollout plan" --parent abc123 --priority 1 --depends-on <tests-id>
hzl validate ```
### Resume from a previous session
This is THE core use case for OpenClaw agents — you wake up fresh and need to pick up where the last session left off.
```bash # 1. Check what's in progress or stuck hzl task list -P openclaw --available # What's ready to work? hzl task stuck # Any expired leases from crashed sessions?
# 2. If there are stuck tasks, review their checkpoints before stealing hzl task show <stuck-id> --json # Read last checkpoint to understand state
# 3. Steal the expired task and continue hzl task steal <stuck-id> --if-expired --author orchestrator
# 4. Read the last checkpoint to know exactly where to resume hzl task show <stuck-id> --json | jq '.checkpoints[-1]'
# 5. Continue working, checkpoint your progress hzl task checkpoint <stuck-id> "Resumed from previous session. Continuing from: <last checkpoint>" ```
**If no stuck tasks:** just use `hzl task next -P openclaw --claim` to grab the next available work.
### Work a task with checkpoints
Checkpoint at notable milestones or before pausing work. A checkpoint should be short and operational: - what you accomplished - what's next (if continuing)
**When to checkpoint (for AI agents):** - Before any tool call that might fail (API calls, deploys, installs) - Before spawning a sub-agent (in case it crashes) - Before a session might compact (long-running work) - After completing a meaningful unit of work - Before pausing or handing off to another agent
**Rule of thumb:** If the session died right now, could another agent resume from your last checkpoint? If not, checkpoint now.
```bash hzl task claim <id> --assignee orchestrator # ...do work... hzl task checkpoint <id> "Implemented login flow. Next: add token refresh." --progress 50 # ...more work... hzl task checkpoint <id> "Added token refresh. Testing complete." --progress 100 hzl task complete <id> ```
You can also set progress without a checkpoint: ```bash hzl task progress <id> 75 ```
### Handle blocked tasks
When stuck on external dependencies, mark the task as blocked:
```bash hzl task claim <id> --assignee orchestrator hzl task checkpoint <id> "Implemented login flow. Blocked: need API key for staging." hzl task block <id> --comment "Blocked: waiting for staging API key from DevOps"
# Later, when unblocked: hzl task unblock <id> --comment "Unblocked: received API key from DevOps" hzl task checkpoint <id> "Got API key, resuming work" hzl task complete <id> ```
**Comment best practices:** Include context about the action, not just the state: - Good: "Blocked: waiting for API keys from infra team" - Good: "Unblocked: keys received, resuming work" - Bad: "waiting for API keys" (missing action context)
Blocked tasks stay visible in the dashboard (Blocked column) and keep their assignee, but don't appear in `--available` lists.
### Coordinate sub-agents with leases
Use leases when delegating, so you can detect abandoned work and recover.
```bash hzl task add "Implement REST endpoints" -P myapp-auth --priority 3 --json hzl task claim <id> --assignee subagent-claude-code --lease 30 ```
Delegate with explicit instructions: - claim the task (with their author id) - checkpoint progress as they go - complete when done
Monitor: ```bash hzl task show <id> --json hzl task stuck hzl task steal <id> --if-expired --author orchestrator ```
### Break down work with subtasks
Use parent/subtask hierarchy to organize complex work:
```bash # Create parent task hzl task add "Implement vacation booking" -P portland-trip --priority 2 # → abc123
# Create subtasks (project inherited automatically) hzl task add "Research flights" --parent abc123 hzl task add "Book hotel" --parent abc123 --depends-on <flights-id> hzl task add "Plan activities" --parent abc123
# View breakdown hzl task show abc123
# Work through subtasks hzl task next --parent abc123 ```
**Important:** `hzl task next` only returns leaf tasks (tasks without children). Parent tasks are organizational containers—they are never returned as "next available work."
**Finishing subtasks:** After completing each subtask, check if the parent has remaining work: ```bash hzl task complete <subtask-id>
# Check parent status hzl task show abc123 --json # Any subtasks left? hzl task complete abc123 # If all done, complete parent ```
## Web Dashboard
HZL includes a built-in Kanban dashboard for monitoring task state. The dashboard shows tasks in columns (Backlog → Blocked → Ready → In Progress → Done), with filtering by date and project.
### Setting up the dashboard (recommended for OpenClaw)
For always-on access on your OpenClaw box, set up as a systemd service (Linux only):
```bash # Check if service already exists before overwriting systemctl --user status hzl-web 2>/dev/null && echo "Service already exists — skip setup" && exit 0
# Create the systemd user directory if needed mkdir -p ~/.config/systemd/user
# Generate and install the service file hzl serve --print-systemd > ~/.config/systemd/user/hzl-web.service
# Enable and start systemctl --user daemon-reload systemctl --user enable --now hzl-web
# IMPORTANT: Enable lingering so the service runs even when logged out loginctl enable-linger $USER
# Verify it's running systemctl --user status hzl-web ```
The dashboard will be available at `http://<your-box>:3456` (accessible over Tailscale).
To use a different port: ```bash hzl serve --port 8080 --print-systemd > ~/.config/systemd/user/hzl-web.service ```
**macOS note:** systemd is Linux-only. On macOS, use `hzl serve --background` or create a launchd plist manually.
### Quick commands
```bash hzl serve # Start in foreground (port 3456) hzl serve --background # Fork to background process hzl serve --status # Check if background server is running hzl serve --stop # Stop background server hzl serve --host 127.0.0.1 # Restrict to localhost only ```
Use `--background` for temporary sessions. Use systemd for always-on access.
## Best Practices
1. **Always use `--json`** for programmatic output 2. **Checkpoint at milestones** or before pausing work 3. **Check for comments** before completing tasks 4. **Use a single `openclaw` project** for all work 5. **Use dependencies** to express sequencing, not priority 6. **Use leases** for long-running work to enable stuck detection 7. **Review checkpoints** before stealing stuck tasks
## What HZL Does Not Do
HZL is deliberately limited:
- **No orchestration** - Does not spawn agents or assign work - **No task decomposition** - Does not break down tasks automatically - **No smart scheduling** - Uses simple priority + FIFO ordering
These are features for your orchestration layer, not for the task tracker.
## OpenClaw-specific notes
- Run `hzl ...` via the Exec tool. - OpenClaw skill gating checks `requires.bins` on the host at skill load time. If sandboxing is enabled, the binary must also exist inside the sandbox container too. Install it via `agents.defaults.sandbox.docker.setupCommand` (or use a custom image). - If multiple agents share the same HZL database, use distinct `--assignee` ids (for example: `orchestrator`, `subagent-claude`, `subagent-gemini`) and rely on leases to avoid collisions.