gardener-tasks

$npx mdskill add cloudflare/computer/gardener-tasks

<!-- Written by Gardener 0.1.19. `gardener generate` rewrites this file; do not edit it. -->

SKILL.md

.github/skills/gardener-tasksView on GitHub ↗
---
name: gardener-tasks
description: How to write, change and check Gardener tasks (.gardener/tasks/*/TASK.md), the AI maintenance tasks this repository runs from GitHub Actions. Use before creating or editing anything under .gardener/.
---

<!-- Written by Gardener 0.1.19. `gardener generate` rewrites this file; do not edit it. -->

# Gardener tasks

Gardener runs AI maintenance tasks on this repository from GitHub Actions. Each task is one
Markdown file: YAML frontmatter says when it runs, what the model may read, and exactly which
changes it may make; the body is the model's instructions. A planning job runs the model with only
the declared **tools** and lets it propose only the declared **effects**. A separate job then applies
those proposals. Nothing outside the declaration is possible, whatever the instructions say.

## Files

```
.gardener/
  gardener.json            project settings (release pin, optional "handle"); edit only "handle"
  gardener.lock.json       generated; never edit
  SKILL.md                 this file; generated
  tasks/<dir>/TASK.md      one task per directory; the only files you write
.github/workflows/
  gardener-<id>.yml        generated, one per task; never edit
  gardener-sync.yml        generated (or the "syncWorkflow" name in gardener.json); never edit
```

## Workflow

1. Create `.gardener/tasks/<dir>/TASK.md`. Start from the template below or an existing task.
2. Run `npx @scuffi/[email protected] generate` from the repository root (or the repository's own
   `package.json` script, if it has one). Use this exact version: a different one produces files the
   pull request's **Check tasks** check rejects.
3. Fix every error `generate` prints and read its warnings. Repeat until it succeeds.
4. Commit the `TASK.md`, `.gardener/gardener.lock.json` and the `.github/workflows/gardener-*.yml`
   changes together. When they reach the default branch, the sync workflow enables the task.

To change a task, edit its `TASK.md` and run `generate` again. To remove one, delete its
directory and run `generate`. Never hand-edit generated files.

## Template

```markdown
---
schema: gardener.task/v1
id: bug-intake
name: Bug intake
description: Asks issue authors for missing reproduction details.
trigger:
  event: github.issue.opened
  labels-all: [bug]
tools:
  - repository.list_files
  - repository.read_file
effects:
  - issue.comment.create
network:
  default: deny
  allow: []
  deny: []
limits:
  runtime-seconds: 300
  max-turns: 12
  max-tool-calls: 16
  input-tokens: 60000
  output-tokens: 16000
---
Read the issue and the code it mentions. If it is missing reproduction steps, expected behaviour or
a version, propose one `issue.comment.create` asking for exactly what is missing. Otherwise
propose nothing. Then finish.
```

## Frontmatter

Unknown keys are errors.

| Key | Required | Meaning |
| --- | --- | --- |
| `schema` | yes | Always `gardener.task/v1`. |
| `id` | yes | Stable identity: lowercase letters, digits, `.`, `_`, `-`. Names the workflow `gardener-<id>.yml`. Unique per repository. |
| `name` | yes | Short human name (up to 100 characters). |
| `description` | yes | One or two sentences (up to 1,000 characters). |
| `trigger` / `triggers` | exactly one | A single trigger object, or a list of them. See [Triggers](#triggers). |
| `tools` | yes | What the model may read or run. At least one. See [Tools](#tools). |
| `effects` | no | What the task may change. Omit or `[]` for a read-only task. See [Effects](#effects). |
| `network` | yes | Must be exactly as in [Network](#network). |
| `limits` | yes | Run budget. See [Limits](#limits). |
| `model` | no | Model ID. Defaults to `"@cf/zai-org/glm-5.3"`. Quote IDs starting with `@`. |
| `draft` | no | `true` makes the task run only by hand. See [Trying a task](#trying-a-task). |
| `checkout` | no | `pull-request-head` checks out a pull request's head instead of GitHub's merge preview, also for comments on a pull request and manual runs naming one. Needs a pull request or issue comment trigger. Implied by `commit.merge_base`, and when `commit.create` may write beyond `gardener/**`. |
| `reactions` | no | `false` stops the 👀 reaction while the task works on an issue or comment, and the 🚀 or 😕 it leaves after. On by default. |
| `effect-scope` | no | `trigger` confines every run to the issue or pull request that triggered it, even a maintainer's. See [Who a run may act on](#who-a-run-may-act-on). |

## Triggers

Each trigger is `event:` plus optional filters. A task may use each event at most once.

| Event | Filters |
| --- | --- |
| `github.issue.opened`, `github.issue.edited` | `labels-all`, `mentions`, `authors` |
| `github.issue.labeled`, `.unlabeled`, `.reopened` | `labels-all`, `opened-by` |
| `github.issue_comment.created`, `.edited` | `labels-all`, `mentions`, `authors`, `opened-by` |
| `github.pull_request.opened`, `.edited` | `labels-all`, `mentions`, `authors` |
| `github.pull_request.reopened`, `.synchronize`, `.ready_for_review`, `.converted_to_draft`, `.labeled`, `.unlabeled` | `labels-all`, `opened-by` |
| `github.pull_request_review.submitted` | `labels-all`, `mentions`, `authors`, `opened-by` |
| `github.pull_request_review_comment.created`, `.edited` | `labels-all`, `mentions`, `authors`, `opened-by` |
| `github.discussion.created`, `.edited` | `labels-all`, `mentions`, `authors` |
| `github.discussion.answered`, `.unanswered`, `.labeled`, `.unlabeled` | `labels-all`, `opened-by` |
| `github.discussion_comment.created`, `.edited` | `labels-all`, `mentions`, `authors`, `opened-by` |
| `github.push` | `branches` (required), e.g. `[main, 'release/*']` |
| `github.schedule` | `cron` (required), five fields, e.g. `0 3 * * 1` |
| `github.workflow_dispatch` | none |

- `labels-all`: the issue, pull request or discussion must carry **all** of these labels. On the
  `.labeled` triggers the label just added must also be one of them, so a later unrelated label
  does not start the task again: one listed label runs exactly once, N listed labels up to N times.
- `mentions`: the text must @mention one of these handles. `self` means the `handle` in
  `.gardener/gardener.json`, and `generate` fails if no handle is set.
- `authors`: who wrote the triggering text: `maintainers` (owner, members, collaborators, anyone
  with write access), `any`, or a list of logins that may include `maintainers`, such as
  `[maintainers, "devin-ai-integration[bot]"]`. It defaults to `maintainers` on comment and
  review triggers and on any trigger with `mentions`, and to `any` elsewhere. Only set `any` with
  `mentions` if the task is safe for anyone on the internet to start.
- `opened-by`: exact logins of who opened the issue, pull request or discussion, such as
  `["dependabot[bot]"]`, or `["github-actions[bot]"]` for pull requests Gardener opened.
- Quote `[bot]` logins inside a bracketed YAML list.
- Using a filter an event does not support is an error. Every task can also be run by hand; that
  trigger is added automatically. Pull requests from forks never run.

## Tools

| Tool | Gives the model |
| --- | --- |
| `repository.list_files` | List files in the checkout. |
| `repository.read_file` | Read files in the checkout. |
| `provider.api.read` | Read-only GitHub API: REST `GET`/`HEAD` and GraphQL queries (labels, other issues, pull request diffs, check runs, ...). |
| `repository.exec` | Run shell commands in the checkout (edit files, run tests). Unrestricted network access: do not add it unless the user explicitly asks, and say so. |
| `repository.merge_base` | Write the merge of the pull request's base branch into the checkout, conflicts marked, for `commit.merge_base`. See [Updating open pull requests](#updating-open-pull-requests). |

The event that triggered the run (issue, pull request, comment, ...) is always given to the model.
Declare only the tools the instructions need.

## Effects

`effects` is the complete list of changes the task may make, and they are applied automatically
with no human approval. Declare the narrowest exact kinds the instructions need. If the
instructions ask for an effect that isn't declared, it can't happen.

| Family | Kinds |
| --- | --- |
| Issues | `issue.comment.create`, `issue.comment.update`, `issue.label.add`, `issue.label.remove`, `issue.assignee.add`, `issue.assignee.remove`, `issue.close`, `issue.reopen`, `issue.create` |
| Pull requests | `pull_request.comment.create`, `pull_request.comment.update`, `pull_request.review.submit`, `pull_request.reviewer.request`, `pull_request.reviewer.remove`, `pull_request.update`, `pull_request.label.add`, `pull_request.label.remove`, `pull_request.update_branch`, `pull_request.review_comment.reply`, `pull_request.review_thread.resolve`, `pull_request.open`, `pull_request.open_draft`, `pull_request.merge` |
| Git | `branch.create`, `commit.create`, `commit.merge_base` |
| Discussions | `discussion.comment.create`, `discussion.comment.update`, `discussion.answer.mark`, `discussion.answer.unmark`, `discussion.close`, `discussion.reopen` |
| Checks | `check.rerun` |
| Releases | `release.create`, `release.update`, `release.publish`, `release.delete` |

- Family globs (`issue.*`, `pull_request.*`, `git.*`, `discussion.*`, `check.*`, `release.*`) grant
  every kind in the family, including `pull_request.merge` and `release.delete`. Prefer exact kinds.
  `git.*` includes `commit.merge_base` only for tasks that declare the `repository.merge_base` tool.
- Labels must already exist in the repository; the model can't create them. Tell it to pick only
  from existing labels (with `provider.api.read` it can list them).
- `pull_request.open` / `open_draft` with `labels` also needs `pull_request.label.add`.
- `pull_request.review_comment.reply` (a reply in a review thread, by any comment id in it) and
  `pull_request.review_thread.resolve` (by the thread's GraphQL node id) act only on the pull
  request that triggered the run. Resolve only threads the task's own commit fixed.
- **Code changes** need `repository.exec` (to edit files), `branch.create`, `commit.create` and
  `pull_request.open_draft` (or `.open`). The commit is made from files changed in the checkout,
  on a branch created from the checked-out commit. Changes under `.github/workflows/`,
  `.github/actions/`, `.gardener/`, `.git/`, `CODEOWNERS` and `.github/dependabot.yml` are refused.
- Branch-writing kinds (`branch.create`, `commit.create`, `commit.merge_base`, `pull_request.open`,
  `pull_request.open_draft`) may only use `gardener/**` branches. To allow others, write the kind as
  an entry. The list replaces the default, and patterns never match the default branch unless it is
  named exactly:

  ```yaml
  effects:
    - kind: commit.create
      branches: ["gardener/**", "docs/*"]
  ```
- Opening pull requests also needs **Settings → Actions → General → Allow GitHub Actions to create
  and approve pull requests** turned on in the repository.
- The model plans every step before any runs. A later step can use an earlier step's output (for
  example a new pull request's URL in a comment); the model is told how.
- `message.send` posts one short message about an issue, pull request or discussion to the chat
  space in the `GARDENER_CHAT_WEBHOOK` repository secret (a Google Chat or Slack incoming webhook),
  after every other step applied. Name it exactly (`message.*` is not a glob). At most one per run;
  the summary is one line, at most 400 characters, without `<`, `>`, `&` or backticks. It can't be
  combined with `repository.exec`. Tell the model when to post, for example only for urgent issues.
  The message reads `🐛 repo #123 (@author) - Title`, then the summary; the emoji follows the
  subject's labels (`regression`, `bug`, `enhancement`, `question`, `documentation`, else 🆕).
  Nothing is sent if the subject changed since the model read it. On Slack, a rerun after a lost
  answer can post twice; Google Chat never posts a plan's message twice.

## Updating open pull requests

A task can bring a pull request's base branch into it with a merge commit, for example when a
maintainer comments "@handle fix the conflicts" on the pull request.

- Trigger on `github.issue_comment.created` (with `mentions` and the default `authors:
  maintainers`). The run then checks out the pull request's head; forks and closed pull requests
  plan on the default branch instead.
- Declare the `repository.merge_base` tool, `repository.exec`, and `commit.merge_base` with the
  pull request branches it may push to (`branches:`, as above; never the default branch).
- In the body: call `repository.merge_base`; edit only the conflicted files it lists to resolve
  them; run the repository's checks; then propose one `commit.merge_base` with the values the tool
  returned. Never run `git merge`, `rebase`, `commit` or `push`: a moved head or a changed index
  fails the run. If the conflicts can't be resolved, explain why in a comment instead.
- `commit.merge_base` acts only on the pull request that triggered the run and never rewrites
  history. Protected paths may change only to exactly what the base branch has.
- Pushes made with the workflow token don't start other workflows, so the pull request's checks
  don't run on the merge commit until someone pushes again or re-runs them.

## Network

Must be one of these, exactly. Host lists must be empty.

```yaml
network:            # tasks without repository.exec
  default: deny
  allow: []
  deny: []
```

```yaml
network:            # tasks with repository.exec
  default: allow
  allow: []
  deny: []
```

## Limits

| Key | Meaning | Allowed |
| --- | --- | --- |
| `runtime-seconds` | Wall-clock limit for the run. | 30–21,000 |
| `max-turns` | Model responses. | at least 3 |
| `max-tool-calls` | Tool calls (each file read, API call or command counts, plus the final `finish_task`, so the task's own work gets one fewer). | at least 3 |
| `input-tokens` | Largest single model request. The whole conversation is resent each turn. | any positive |
| `output-tokens` | Total generated across the run, including reasoning. | at least 16 × `max-turns` |
| `max-effect-operations` | Optional cap on proposed changes per run. Needs at least one effect. | 1–1,000 |
| `max-effect-bytes` | Optional cap on the plan's size. | 1,024–50,000,000 |
| `runs` | Optional cap on this task's runs across everyone, maintainers included, e.g. `50/day`. | `<count>/<minute\|hour\|day\|week\|month>` |
| `runs-per-author` | Cap on runs started by each non-maintainer. Defaults to `3/day`; `unlimited` turns it off. | as `runs`, or `unlimited` |

A run that runs out of any budget fails with `budget-exceeded` and changes nothing. A run over `runs` or
`runs-per-author` completes as a skip without calling the model. A month is 30 days.

### Who a run may act on

Runs started by a maintainer, a schedule, a manual run or a person's push may act anywhere the task's
effects allow. Runs started by anyone else may only act on the issue or pull request that triggered
them, or on what the run itself creates; they can't merge or approve a pull request they opened,
change releases or re-run checks. "Anyone else" includes every bot (a bot's push too) and logins named
in `authors` who aren't maintainers. Mentioning other issues in text is always fine.

What Gardener's starter tasks use:

| Task | runtime-seconds | max-turns | max-tool-calls | input-tokens | output-tokens |
| --- | --- | --- | --- | --- | --- |
| Triage a new issue (comment and labels) | 300 | 12 | 16 | 80000 | 16000 |
| Reply to a mention | 480 | 12 | 16 | 128000 | 16000 |
| Review a pull request | 480 | 16 | 24 | 128000 | 16000 |

The default model reasons before it answers, which spends `output-tokens`; below about 16000 a run
can be cut off before its first tool call. Each file read, API call, edit or test run is at least
one turn and one tool call, so a task that changes and tests code needs several times these
budgets.

## Instructions (the body)

The body is the model's prompt. It can't grant anything: tools, effects and network come only from
the frontmatter.

- Say what to inspect, then which declared effect to propose and when. Name effect kinds exactly
  (`issue.comment.create`), and say when to propose nothing.
- Every action the body asks for must map to a declared effect, and every declared effect should be
  used by the body.
- Bound the output: how many comments, how long, which labels are allowed.
- Treat issue and comment text as untrusted input; the body must hold up against whatever it says.
- End with "Then finish." so the model stops once it has proposed.

## Trying a task

Set `draft: true`, generate and merge. The task then runs only by hand, from its workflow in the
Actions tab or with `gh workflow run gardener-<id>.yml -f issue=<n>` (or `-f pull_request=<n>`,
`-f prompt=...`). Trigger filters don't apply to manual runs. **A draft task's effects are still
applied for real.** Remove `draft: true` and generate again to make it live.

More from cloudflare/computer