Skip to content

Plans ​

A feature spanning a few PRs doesn't fit one unit. Give its card a deliver: plan rule and its step splits the work into tasks you approve first:

yaml
queue:
  - skill: plan
    where: { label: awaywork-feature }
    deliver: plan
    on_done: { move: Plan review }
  - skill: implement
    where: { planned: true }
    deliver: pr

The second rule takes the tasks once you approve. The plan rule's steps all report, and the prompt asks for .away/out/plan.yml too: the built-in plan skill writes it, any skill can.

plan.yml ​

tasks: holds up to the plan rule's plan_tasks tasks (8 by default) when the rule writes it; a posted plan isn't counted again. Each task has:

  • id: lowercase letters, digits and dashes;
  • title, and optionally a description;
  • skill: one a planned rule takes, implement when unset;
  • repo: the card's when unset, or another the config names (Across repos);
  • ships: files its PR will hold that tasks in other repos need;
  • after: task ids mapped to merged, opened (The feature block) or a check's name (Waiting on a check).

An optional contract: names one file the step wrote under .away/out/, like an OpenAPI diff or a shared schema, posted with the plan.

The prompt lists the skills planned rules take, marking (reports) those whose rule opens no PR (a reporting skill like review, or an all-report chain): they make report tasks.

awaywork checks the file in plain code:

  • ids unique and not retired, under the cap;
  • every skill taken by a planned rule, no after naming a report task;
  • every repo one the config names, ships paths inside the repo;
  • every after naming another task, no cycles, a task's opened and check tasks in its repo on one chain;
  • a contract under .away/out/, no unknown key.

The plan comment ​

awaywork posts report, contract and file as one comment on your item, where questions would go. The model writes report and file; awaywork adds the fences, a contract's heading, and the last two paragraphs:

VAT export lands in two PRs: the endpoint first, then the button that calls it.

- `api`: `GET /exports/vat` in `src/routes/exports.ts`, paged like `/exports/sales`, with a test next to `sales.test.ts`.
- `web`: an Export button on the reports page, behind the `vat_export` flag; it waits for the endpoint to merge.

Open question: should the export include voided invoices? I left them out.

```yaml
tasks:
  - id: api
    title: VAT export endpoint
    description: Add GET /exports/vat to src/routes/exports.ts, paged like /exports/sales.
  - id: web
    title: Export button
    description: Add an Export button to the reports page behind the vat_export flag.
    after: { api: merged }
```

To approve, reply with approve as the first line; the lines below it reach the tasks as notes. Any other reply is feedback: awaywork plans again with it.

— awaywork

The run says where it went, and on_done fires once it's posted:

  1  ENG-2101  plan   done    report
     VAT export lands in two PRs: the endpoint first, then the button that calls it.
     plan posted on https://acme.atlassian.net/browse/ENG-2101, tasks api, web; it waits for your approve
     on_done moved ENG-2101 to "Plan review"

Until you answer, the card is skipped as waiting for your approval, under Needs you as approve.

Your answer ​

  • Approve: reply from your own account with approve as the first line, any case; lines below are notes for the tasks. awaywork approve <source:key> [notes], or a in the report, posts it for you, marked like any awaywork comment, through the same ownership check, and refuses a plan you already answered. The next run fans it out:

    $ awaywork approve tracker:ENG-2101 Leave voided invoices out.
    plan approved: replied approve, with your notes, on https://acme.atlassian.net/browse/ENG-2101
  • Anything else of yours is feedback, approve if web waits included. The next run plans again with the whole thread in item.md (the plan headed awaywork's plan) and posts a new plan, which waits for a fresh approval whatever wait says. A re-plan that isn't posted is skipped as unchanged until the card changes.

  • Only you answer, with your first reply after the plan. A teammate's, code owner's or bot's comment is context in item.md, and your later comments change nothing. Edit your reply to change your mind before the next run reads it; one someone else edited isn't yours anymore.

  • wait: false posts the plan and goes on, its mark line saying — awaywork · went ahead and that a reply changes nothing. It counts as its own answer; the next run fans it out with no notes. Its gates still run, and one that fires makes it wait, the card on top.

Details ​

  • The plan is awaywork's newest plan comment, read off the card every run. GitHub reads the whole thread, past its last 50 comments.
  • Edit the plan comment to fix a task, and the next run checks your edit; one that breaks it skips the card as plan refused with the reason, under Needs you. A plan comment someone else edited isn't awaywork's anymore (Safety), so the card gets a new plan.
  • A plan isn't posted when it fails its checks (in the file, or read back from the comment), plan.yml or the contract is missing, or the comment with the contract passes 30 KB (a lean plan pins them, and isn't posted when the store fails to put either or skips it: plan.yml isn't in the store). The reason stays in the morning report (plan not posted: plan.yml: task id "api" is used twice) and on_done doesn't fire. Nor on an item that isn't yours, a source that can't write on a rule without a thread, or a failed post. A step that asks questions posts those instead.
  • A deliver: plan rule that matches only merged or closed items, names a source that can't write without thread: github, or has no planned rule for its tasks is a config error.