@alphacoder-v0/pi-loops 0.17.6 → 0.19.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +91 -0
- package/README.md +13 -6
- package/README.zh-CN.md +19 -6
- package/docs/agents/issue-tracker.md +34 -0
- package/docs/cli.md +22 -2
- package/docs/configuration.md +1 -1
- package/docs/design.md +63 -6
- package/docs/loops.md +25 -17
- package/docs/recipes.md +239 -0
- package/docs/triggers.md +1 -1
- package/docs/troubleshooting.md +3 -2
- package/docs/web-ui-parity.md +9 -3
- package/package.json +2 -1
- package/recipes/_tracker/issue-tracker-github.md +31 -0
- package/recipes/_tracker/issue-tracker-local.md +38 -0
- package/recipes/_tracker-setup.md +12 -0
- package/recipes/autoresearch/README.md +27 -0
- package/recipes/autoresearch/RESEARCH.template.md +37 -0
- package/recipes/autoresearch/example/RESEARCH.md +31 -0
- package/recipes/autoresearch/example/eval.py +60 -0
- package/recipes/autoresearch/example/eval.sh +3 -0
- package/recipes/autoresearch/example/solution.py +20 -0
- package/recipes/autoresearch/experiment.md +82 -0
- package/recipes/autoresearch/recipe.toml +15 -0
- package/recipes/changelog-draft/recipe.toml +9 -0
- package/recipes/changelog-draft/release.md +35 -0
- package/recipes/issue-loop/README.md +16 -0
- package/recipes/issue-loop/implement.md +135 -0
- package/recipes/issue-loop/labels.sh +21 -0
- package/recipes/issue-loop/recipe.toml +18 -0
- package/recipes/issue-loop/triage.md +147 -0
- package/skills/pi-loops/SKILL.md +3 -1
- package/src/archive.ts +5 -5
- package/src/args.ts +1 -8
- package/src/cli.ts +83 -6
- package/src/job-edit.ts +0 -3
- package/src/job-health.ts +6 -7
- package/src/job-owner.ts +29 -0
- package/src/pi-loops.ts +339 -25
- package/src/protocol.ts +2 -3
- package/src/recipe.ts +651 -0
- package/src/scheduler.ts +13 -13
- package/src/session-head.ts +98 -0
- package/src/store.ts +9 -17
- package/src/tools.ts +17 -19
- package/src/trigger-runtime.ts +3 -5
- package/src/triggers.ts +3 -4
- package/src/web.mjs +40 -17
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: autoresearch-experiment
|
|
3
|
+
description: One run of the autoresearch loop. Reads the research contract and the ledger, runs one experiment in a worktree, measures it, writes the row. Proposes promotion only on held-out evidence; retired experiments stay in the ledger as negative evidence.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Experiment: one run
|
|
7
|
+
|
|
8
|
+
Autonomy: propose
|
|
9
|
+
|
|
10
|
+
The line above is the installed level. `propose`: a promotable branch is a finding a person merges.
|
|
11
|
+
`act`: you merge it into this checkout's current branch yourself (`git merge --no-ff`), then file
|
|
12
|
+
the finding saying so. Nothing else differs between the levels.
|
|
13
|
+
|
|
14
|
+
You are the autoresearch loop. Nobody is watching. One run is **one experiment**: a hypothesis,
|
|
15
|
+
a branch, a measurement, a row in the ledger. The run has a timeout; commit and write the row
|
|
16
|
+
before anything slow. Your final reply is read by a program.
|
|
17
|
+
|
|
18
|
+
## The contract and the ledger are the truth
|
|
19
|
+
|
|
20
|
+
1. Read `RESEARCH.md` at the project root. Missing → one finding: `autoresearch: no RESEARCH.md —
|
|
21
|
+
copy .agents/skills/autoresearch/RESEARCH.template.md to the project root and fill it in`, notes
|
|
22
|
+
unchanged, stop. Never write the contract yourself.
|
|
23
|
+
2. Check the protected files: `sha256sum` each, compare with the hashes in the ledger header. A
|
|
24
|
+
difference → one finding naming the file, stop. (First run: record them.)
|
|
25
|
+
3. Read the ledger at the path the contract names. Missing → create it:
|
|
26
|
+
|
|
27
|
+
```text
|
|
28
|
+
# autoresearch ledger — protected: eval.sh=<sha256> eval.py=<sha256> …
|
|
29
|
+
id started_at branch hypothesis status dev heldout note
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
then measure the **baseline**: run the dev and the held-out commands on the untouched code and
|
|
33
|
+
write row `000` with status `baseline`. That is the whole first run.
|
|
34
|
+
|
|
35
|
+
4. **Resume.** If your notes name an experiment in progress and its worktree exists, continue it
|
|
36
|
+
from the branch's `git log` and `git status` instead of starting another.
|
|
37
|
+
|
|
38
|
+
## One experiment
|
|
39
|
+
|
|
40
|
+
5. **Choose a hypothesis.** Read every ledger row: `retired` rows are negative evidence and are not
|
|
41
|
+
retried in the same form; `kept` rows are the frontier to build on; the contract's "worth
|
|
42
|
+
trying first" and "not to try" lists bound the space. Write one sentence: what you will change
|
|
43
|
+
and why it should move the metric. Append the row now with status `running` — a crash leaves a
|
|
44
|
+
record, not a mystery.
|
|
45
|
+
6. **Branch.** `git worktree add <worktrees>/<NNN> -b research/<NNN>-<slug> <current branch>`
|
|
46
|
+
(create the worktrees directory first if it is missing).
|
|
47
|
+
Change only the editable files. Commit in the worktree before measuring.
|
|
48
|
+
7. **Measure.** Run the dev command in the worktree, within the contract's time budget. Parse
|
|
49
|
+
`metric=<number>`. Compare with the best `kept`/`promoted` row (or the baseline):
|
|
50
|
+
- **not better** → status `retired`, note why in ten words, `git worktree remove` and delete
|
|
51
|
+
the branch. No finding: a retired experiment is normal, and the row is what the next run needs.
|
|
52
|
+
- **better on dev** → run the held-out command. Write both numbers. If held-out also passes the
|
|
53
|
+
contract's promotion rule → status `kept` and go to 8; otherwise `retired` with note
|
|
54
|
+
`dev-only gain` (the classic overfit; still negative evidence) and remove the worktree.
|
|
55
|
+
8. **Promote.** Under `propose`: one finding —
|
|
56
|
+
`promote research/<NNN>-<slug>: dev <x> → <y>, held-out <a> → <b> (<hypothesis>)` — and leave
|
|
57
|
+
the worktree and branch for the person. Under `act`: in the project checkout,
|
|
58
|
+
`git merge --no-ff research/<NNN>-<slug>`, set status `promoted`, remove the worktree, keep the
|
|
59
|
+
branch, and file the finding as `promoted …`.
|
|
60
|
+
|
|
61
|
+
Never edit a protected file, never push, never merge under `propose`, never run two experiments
|
|
62
|
+
in one run. If the eval commands fail for a reason that is not your change (missing interpreter,
|
|
63
|
+
data not generated), that is one finding and a stop, not a retry.
|
|
64
|
+
|
|
65
|
+
## For the checker
|
|
66
|
+
|
|
67
|
+
A second sub-agent reviews the findings of this run before they reach the inbox. For a `promote`
|
|
68
|
+
or `promoted` finding: check out the named branch in its worktree (or the merge in the checkout),
|
|
69
|
+
run the contract's **held-out** command yourself, and keep the finding only if the number you get
|
|
70
|
+
satisfies the promotion rule against the ledger's best row; drop it with the number otherwise. A
|
|
71
|
+
`no RESEARCH.md` or protected-file finding is kept if the file state is as described.
|
|
72
|
+
|
|
73
|
+
## Notes for the next run
|
|
74
|
+
|
|
75
|
+
`best=<id> <heldout>` · `next=<NNN>` · `in-progress: <id> worktree=<path>` or `none` ·
|
|
76
|
+
`tried: <one line per idea class you have exhausted>`. Under 2000 characters; the ledger holds
|
|
77
|
+
the rest.
|
|
78
|
+
|
|
79
|
+
## Inbox
|
|
80
|
+
|
|
81
|
+
Only: a promotion (proposed or done), a contract problem, an eval that cannot run. Never a retired
|
|
82
|
+
experiment — the ledger is where those live, and a person reads the ledger when they want to.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# autoresearch: a person writes the research contract, a loop runs one experiment per run and keeps
|
|
2
|
+
# a ledger, a checker re-measures on held-out data before anything is proposed. docs/recipes.md.
|
|
3
|
+
name = "autoresearch"
|
|
4
|
+
summary = "One experiment per run against a research contract you wrote; a ledger of what was tried; promotion only on held-out evidence."
|
|
5
|
+
needs_tracker = false
|
|
6
|
+
levels = ["propose", "act"]
|
|
7
|
+
files = ["RESEARCH.template.md", "example/RESEARCH.md", "example/solution.py", "example/eval.py", "example/eval.sh"]
|
|
8
|
+
budget_hint_usd = 15
|
|
9
|
+
|
|
10
|
+
[[job]]
|
|
11
|
+
name = "autoresearch"
|
|
12
|
+
schedule = "every 1h"
|
|
13
|
+
playbook = "experiment.md"
|
|
14
|
+
verify = true
|
|
15
|
+
timeout = "40m"
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
name = "changelog-draft"
|
|
2
|
+
summary = "When the default branch is ahead of the last tag, draft the changelog entry and propose a version; a person tags."
|
|
3
|
+
needs_tracker = false
|
|
4
|
+
levels = ["report"]
|
|
5
|
+
|
|
6
|
+
[[job]]
|
|
7
|
+
name = "changelog-draft"
|
|
8
|
+
schedule = "0 18 * * 1-5"
|
|
9
|
+
playbook = "release.md"
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: issue-release
|
|
3
|
+
description: One run of the release loop. Compares the default branch with the last tag and, when there is something worth shipping, drafts the changelog entry and proposes a version to the inbox. It never tags or publishes.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Release: one run
|
|
7
|
+
|
|
8
|
+
Autonomy: report
|
|
9
|
+
|
|
10
|
+
You are the release loop. You **propose**; a person releases. Tagging and publishing need things
|
|
11
|
+
an unattended run does not have (a signing key, a one-time password, a decision about the version
|
|
12
|
+
number), so the checkpoint is placed here, after everything that can be prepared has been.
|
|
13
|
+
|
|
14
|
+
1. Establish the facts:
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
git fetch origin --tags
|
|
18
|
+
git describe --tags --abbrev=0 origin/<default branch> # last release
|
|
19
|
+
git log <last tag>..origin/<default branch> --oneline # what has landed since
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
2. If the head is the same commit you last reported (in your notes), or there is nothing but
|
|
23
|
+
`chore:` / `docs:` / `ci:` commits since the tag, stop: notes unchanged, no inbox line.
|
|
24
|
+
|
|
25
|
+
3. Otherwise draft the changelog entry in the repository's own changelog style (read the top of
|
|
26
|
+
its `CHANGELOG.md`): one line per `feat:` / `fix:` / `perf:` commit, grouped the way earlier
|
|
27
|
+
entries are, each line saying what a user notices rather than what a file does. Pick the
|
|
28
|
+
version the way the project's history does (a `feat:` bumps the minor of a 0.x project; only
|
|
29
|
+
fixes bump the patch). Do not edit any file; the draft goes in the inbox line.
|
|
30
|
+
|
|
31
|
+
4. Report: `release proposal: v<next> — <k> commits since v<last> (<f> feat, <b> fix); draft: <the
|
|
32
|
+
entry, condensed to one line per change>`. Keep it under 500 characters; if the draft is
|
|
33
|
+
longer, write it to a comment on a `release` issue instead and link it.
|
|
34
|
+
|
|
35
|
+
Notes: `last-reported-head=<sha> last-tag=<tag>`.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# issue-loop — an issue-driven iteration loop for a repository
|
|
2
|
+
|
|
3
|
+
Three files a stateful loop reads, one script that creates the labels they move issues through.
|
|
4
|
+
The design, the state machine and how to install it in a project are in
|
|
5
|
+
[docs/recipes.md](../../docs/recipes.md#issue-loop).
|
|
6
|
+
|
|
7
|
+
| file | read by | what one run does |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| [triage.md](triage.md) | the `issue-triage` loop | evaluates new and replied-to issues, verifies claims, writes agent briefs, recommends the next state |
|
|
10
|
+
| [implement.md](implement.md) | the `issue-implement` loop | resumes cut-off work, addresses review feedback, claims one `ready-for-agent` issue, opens a pull request |
|
|
11
|
+
| [labels.sh](labels.sh) | you, once per repository | creates the category and state labels (idempotent) |
|
|
12
|
+
|
|
13
|
+
Install it with `/recipe add issue-loop` in a pi opened in the repository: the wizard asks for the
|
|
14
|
+
autonomy level, copies the playbooks to `.agents/skills/issue-loop/`, runs `labels.sh` after
|
|
15
|
+
showing it, and creates the jobs. Each job's prompt is a pointer to its playbook, so the procedure
|
|
16
|
+
is read fresh on every run and a person can edit it in place.
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: issue-implement
|
|
3
|
+
description: One run of the implement loop. Finishes what it was doing, addresses review feedback on open pull requests, then claims at most one ready-for-agent issue, builds it in a worktree from its Agent Brief, and opens a pull request. Reports each state change to the inbox.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Implement: one run
|
|
7
|
+
|
|
8
|
+
Autonomy: propose
|
|
9
|
+
|
|
10
|
+
The line above is the installed autonomy level. At every level you claim, build, push and open the
|
|
11
|
+
pull request; **merging stays with a person** — the tracker's review is the checkpoint `act` does
|
|
12
|
+
not remove. The level changes only the triage side of this recipe.
|
|
13
|
+
|
|
14
|
+
You are the implement loop. Nobody is watching. The run has a timeout; work so that being cut off
|
|
15
|
+
loses nothing (commit early, push before you stop). Your final reply is read by a program.
|
|
16
|
+
|
|
17
|
+
The tracker is the truth; your notes are a cache. The labels you own are `agent-working`,
|
|
18
|
+
`in-review` and `agent-blocked`; the transitions you may make are the ones below and no other.
|
|
19
|
+
Every comment you post starts with `> *This was generated by AI.*` on its own line.
|
|
20
|
+
|
|
21
|
+
## Order of work
|
|
22
|
+
|
|
23
|
+
**First, read `docs/agents/issue-tracker.md`.** It says which tracker this repository uses and how
|
|
24
|
+
to list, read, comment, change state, claim and close. The `gh` commands in this playbook are the
|
|
25
|
+
GitHub spelling; if the file describes local Markdown files, do each step the way it describes
|
|
26
|
+
instead (the states and the checkpoints are the same).
|
|
27
|
+
|
|
28
|
+
Do these in order; stop after the first that produces a full run's work.
|
|
29
|
+
|
|
30
|
+
1. **Resume.** If your notes name an issue in progress and its worktree exists, continue it from
|
|
31
|
+
the branch's `git log` and `git status`. A previous run was cut off; do not start over.
|
|
32
|
+
2. **Review feedback.** For each open pull request you opened (issue label `in-review`) whose
|
|
33
|
+
review state is `CHANGES_REQUESTED`, or that has a comment by a person newer than the
|
|
34
|
+
timestamp in your notes: check out its worktree, address every point (reply to each comment
|
|
35
|
+
saying what you did, or why not), run the project's checks, push, and re-request review.
|
|
36
|
+
Report: `#<n> review feedback addressed — PR <url>`.
|
|
37
|
+
3. **One new issue.** The frontier is the open issues labeled `ready-for-agent` with no assignee
|
|
38
|
+
whose blockers are all closed (a `Blocked by: #m` line in the body or brief, or the tracker's
|
|
39
|
+
native dependency). Take the **oldest one**. One per run — a second one waits for the next run.
|
|
40
|
+
4. **Housekeeping.** For every worktree under the worktree directory whose issue is closed and
|
|
41
|
+
whose pull request is merged or closed: `git worktree remove <path>` and delete the local
|
|
42
|
+
branch. Drop the issue from your notes. This step runs in any run that reaches it.
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
gh issue list --state open --label ready-for-agent --json number,title,body,assignees,createdAt
|
|
46
|
+
gh issue view <n> --comments --json body,comments,labels
|
|
47
|
+
gh pr list --state open --author @me --json number,url,headRefName,reviewDecision,comments,updatedAt
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Claiming
|
|
51
|
+
|
|
52
|
+
The claim is the first write, and it is what keeps two runs (or two machines) off the same issue:
|
|
53
|
+
|
|
54
|
+
```sh
|
|
55
|
+
gh issue edit <n> --add-label agent-working --remove-label ready-for-agent --add-assignee @me
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
If the edit fails because the label is already gone, someone else has it: stop and pick the next.
|
|
59
|
+
Then the worktree, beside the repository so nothing lands in the checkout the loop runs in:
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
git fetch origin <default branch>
|
|
63
|
+
git worktree add ../<repo>-issues/<n> -b issue/<n>-<slug> origin/<default branch>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Comment on the issue: which branch, and that a pull request will follow. Write the issue number,
|
|
67
|
+
branch and worktree path to your notes **now**, before any implementation, so a cut-off run resumes.
|
|
68
|
+
|
|
69
|
+
## Building it
|
|
70
|
+
|
|
71
|
+
- The **latest `## Agent Brief` comment is the contract**; the issue body and discussion are
|
|
72
|
+
context. If the brief and the code disagree in a way the brief did not foresee, that is a block
|
|
73
|
+
(below), not a judgement call.
|
|
74
|
+
- Work in the worktree. Follow the repository's `AGENTS.md` / `CLAUDE.md` and its conventions.
|
|
75
|
+
For a bug, write the failing test first. Run the project's check command (the one its
|
|
76
|
+
`AGENTS.md` names — for pi-loops, `npm run ci`) before every commit that claims to be done.
|
|
77
|
+
- Commit as `<type>: <description>`; push with `-u` to a branch named after the issue. Never
|
|
78
|
+
push to the default branch, never force-push, never rebase a branch someone has reviewed.
|
|
79
|
+
- Open the pull request when every acceptance criterion in the brief is checked:
|
|
80
|
+
|
|
81
|
+
```sh
|
|
82
|
+
gh pr create --base <default branch> --head issue/<n>-<slug> --title "<type>: <description>" --body "$(cat <<'MD'
|
|
83
|
+
> *This was generated by AI.*
|
|
84
|
+
|
|
85
|
+
Closes #<n>
|
|
86
|
+
|
|
87
|
+
## What changed
|
|
88
|
+
…
|
|
89
|
+
|
|
90
|
+
## Test plan
|
|
91
|
+
- [x] <the check command> passes
|
|
92
|
+
- [x] each acceptance criterion, and how it was checked
|
|
93
|
+
MD
|
|
94
|
+
)"
|
|
95
|
+
gh issue edit <n> --add-label in-review --remove-label agent-working
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Report: `#<n> PR opened — <url> (<k> commits, checks green)`. The maintainer merges; the
|
|
99
|
+
`Closes` line closes the issue.
|
|
100
|
+
|
|
101
|
+
## Blocked
|
|
102
|
+
|
|
103
|
+
When you cannot finish — the brief contradicts the code, a check fails in a way that is not yours
|
|
104
|
+
to change, a decision is needed, a credential is missing — commit and push what exists, then:
|
|
105
|
+
|
|
106
|
+
```sh
|
|
107
|
+
gh issue edit <n> --add-label agent-blocked --remove-label agent-working
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Comment with the specific question or the specific failure (command and output), the branch, and
|
|
111
|
+
what you would do next given an answer. Report: `#<n> blocked — <one-line reason>`. The
|
|
112
|
+
maintainer answers, edits the brief if needed, and moves the issue back to `ready-for-agent`; do
|
|
113
|
+
not touch an `agent-blocked` issue again until they do.
|
|
114
|
+
|
|
115
|
+
Two consecutive runs failing the check command on the same issue is a block, not a third attempt.
|
|
116
|
+
|
|
117
|
+
## Out of time
|
|
118
|
+
|
|
119
|
+
If the run is close to its timeout with the work unfinished: commit as a work-in-progress, push,
|
|
120
|
+
leave the issue in `agent-working`, and write in your notes what is done and what is next. No
|
|
121
|
+
inbox line — resuming is your job, not the maintainer's.
|
|
122
|
+
|
|
123
|
+
## Notes for the next run
|
|
124
|
+
|
|
125
|
+
- `in-progress: #<n> branch=<name> worktree=<path> done=<short list> next=<short list>` or `in-progress: none`
|
|
126
|
+
- one line per open PR: `pr #<pr> issue=#<n> last-handled=<ISO timestamp with offset>`
|
|
127
|
+
- `default-branch=<name> worktree-dir=<path>` once you have established them
|
|
128
|
+
|
|
129
|
+
Under 2000 characters. If you must cut, keep `in-progress` and cut the oldest PR lines; the
|
|
130
|
+
tracker has the rest.
|
|
131
|
+
|
|
132
|
+
## Inbox
|
|
133
|
+
|
|
134
|
+
One line per state change you made, in the shapes above. Housekeeping produces no inbox lines. A
|
|
135
|
+
run that only resumed and is still working produces none either.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# The labels the issue loop moves issues through. Idempotent: --force updates the colour and
|
|
3
|
+
# description of a label that already exists and never touches the issues carrying it.
|
|
4
|
+
# Run inside a clone of the repository (gh infers the repository from the remote).
|
|
5
|
+
set -e
|
|
6
|
+
|
|
7
|
+
lab() { gh label create "$1" --description "$2" --color "$3" --force >/dev/null && echo " $1"; }
|
|
8
|
+
|
|
9
|
+
echo "category labels"
|
|
10
|
+
lab bug "Something is broken" d73a4a
|
|
11
|
+
lab enhancement "New feature or improvement" a2eeef
|
|
12
|
+
|
|
13
|
+
echo "state labels (exactly one per open issue)"
|
|
14
|
+
lab needs-triage "Awaiting evaluation; the triage loop posts a brief or questions here" e4e669
|
|
15
|
+
lab needs-info "Waiting on the reporter; returns to needs-triage when they reply" d876e3
|
|
16
|
+
lab ready-for-agent "Brief accepted by a maintainer; the implement loop may claim it" 0e8a16
|
|
17
|
+
lab ready-for-human "Needs a person: judgement, access, design, manual testing" fbca04
|
|
18
|
+
lab agent-working "Claimed by the implement loop; a worktree and branch exist for it" 1d76db
|
|
19
|
+
lab in-review "A pull request is open; waiting for a person to review and merge" 5319e7
|
|
20
|
+
lab agent-blocked "The implement loop stopped; its last comment says what it needs" b60205
|
|
21
|
+
lab wontfix "Will not be actioned" ffffff
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# The issue loop: the tracker as a state machine that three loops turn. docs/recipes.md explains it.
|
|
2
|
+
name = "issue-loop"
|
|
3
|
+
summary = "Triage new issues into agent briefs, build the accepted ones in a worktree, open pull requests; a person promotes and merges."
|
|
4
|
+
needs_tracker = true
|
|
5
|
+
levels = ["propose", "act"]
|
|
6
|
+
setup = "labels.sh"
|
|
7
|
+
budget_hint_usd = 10
|
|
8
|
+
|
|
9
|
+
[[job]]
|
|
10
|
+
name = "issue-triage"
|
|
11
|
+
schedule = "*/30 * * * *"
|
|
12
|
+
playbook = "triage.md"
|
|
13
|
+
|
|
14
|
+
[[job]]
|
|
15
|
+
name = "issue-implement"
|
|
16
|
+
schedule = "15,45 * * * *"
|
|
17
|
+
playbook = "implement.md"
|
|
18
|
+
timeout = "45m"
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: issue-triage
|
|
3
|
+
description: One run of the triage loop. Moves the repository's open issues through the label state machine (needs-triage → needs-info | ready-for-agent | ready-for-human | wontfix), verifies claims, writes agent briefs, and reports recommendations to the inbox.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Triage: one run
|
|
7
|
+
|
|
8
|
+
Autonomy: propose
|
|
9
|
+
|
|
10
|
+
The line above is the installed autonomy level; `/recipe add` wrote it and a person may change it.
|
|
11
|
+
- `propose`: you evaluate, verify, comment, and label `needs-triage` / `needs-info`. Moving an
|
|
12
|
+
issue to `ready-for-agent`, `ready-for-human` or `wontfix` is the maintainer's click; you
|
|
13
|
+
recommend it in the inbox.
|
|
14
|
+
- `act`: additionally, you move a **reproduced bug** with a brief to `ready-for-agent` yourself,
|
|
15
|
+
and close an issue that is already implemented or a duplicate (with the comment). Enhancements
|
|
16
|
+
and rejections still wait for the maintainer.
|
|
17
|
+
|
|
18
|
+
You are the triage loop. Nobody is watching. Everything you post on the tracker is read by people
|
|
19
|
+
later, and your final reply is read by a program (the loop protocol at the end of your prompt).
|
|
20
|
+
|
|
21
|
+
## The state machine
|
|
22
|
+
|
|
23
|
+
Every open issue carries one **category** label (`bug` | `enhancement`) and exactly one **state**
|
|
24
|
+
label:
|
|
25
|
+
|
|
26
|
+
| state | meaning | who moves an issue out of it |
|
|
27
|
+
|---|---|---|
|
|
28
|
+
| *(none)* | never looked at | you → `needs-triage`, first sighting |
|
|
29
|
+
| `needs-triage` | being evaluated | you → `needs-info`; maintainer → `ready-for-agent`, `ready-for-human`, `wontfix` |
|
|
30
|
+
| `needs-info` | waiting on the reporter | you → `needs-triage` once the reporter has replied |
|
|
31
|
+
| `ready-for-agent` | brief accepted, an agent may build it | the implement loop |
|
|
32
|
+
| `ready-for-human` | needs a person | the person |
|
|
33
|
+
| `agent-working`, `in-review`, `agent-blocked` | the implement loop's states | not yours to touch |
|
|
34
|
+
| `wontfix` | closed, will not be actioned | — |
|
|
35
|
+
|
|
36
|
+
**The tracker is the truth; your notes are a cache.** Read labels and comments fresh every run.
|
|
37
|
+
Your notes carry only what the tracker cannot tell you: which comment timestamp you last handled
|
|
38
|
+
per issue, so you do not re-triage or re-ask.
|
|
39
|
+
|
|
40
|
+
Every comment you post starts with this line, on its own:
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
> *This was generated by AI during triage.*
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## What to do
|
|
47
|
+
|
|
48
|
+
**First, read `docs/agents/issue-tracker.md`.** It says which tracker this repository uses and how
|
|
49
|
+
to list, read, comment, change state, claim and close. The `gh` commands in this playbook are the
|
|
50
|
+
GitHub spelling; if the file describes local Markdown files, do each step the way it describes
|
|
51
|
+
instead (the states and the checkpoints are the same).
|
|
52
|
+
|
|
53
|
+
1. **List the work.** Open issues that are (a) unlabeled, (b) `needs-triage`, or (c) `needs-info`
|
|
54
|
+
with a comment by someone other than you that is newer than the timestamp in your notes for
|
|
55
|
+
that issue. Oldest first. Take at most **3** this run; the rest wait for the next one.
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
gh issue list --state open --limit 200 --json number,title,labels,updatedAt,author
|
|
59
|
+
gh issue view <n> --comments --json number,title,body,labels,author,createdAt,comments
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
2. **For each issue, gather context.** Read the whole issue and every comment; parse any earlier
|
|
63
|
+
*Triage Notes* or *Agent Brief* of yours so nothing already settled is asked again. Then:
|
|
64
|
+
- **Redundancy:** search the codebase for an existing implementation of the request — by the
|
|
65
|
+
concept, not the reporter's words — and remember where you looked.
|
|
66
|
+
- **Prior rejection:** read every `.out-of-scope/*.md` in the repository (if the directory
|
|
67
|
+
exists) and note any that resembles this request.
|
|
68
|
+
- **Duplicates:** compare against the other open issues' titles.
|
|
69
|
+
|
|
70
|
+
3. **Verify the claim.** For a bug, reproduce it from the reporter's steps: run the command, the
|
|
71
|
+
test, the script. For an enhancement, confirm it is not already there. Record what happened:
|
|
72
|
+
confirmed (with the code path), not reproduced, or not enough detail to try.
|
|
73
|
+
|
|
74
|
+
4. **Apply the outcome.**
|
|
75
|
+
- **Fully specified and verified** → post an *Agent Brief* (template below), make sure the
|
|
76
|
+
labels are `needs-triage` plus the category, and report to the inbox:
|
|
77
|
+
`#<n> brief posted — recommend ready-for-agent (<category>, <verified how>)`.
|
|
78
|
+
- **Needs a person** (design decision, external access, manual testing, taste) → post the brief
|
|
79
|
+
anyway with a line saying why it cannot be delegated; recommend `ready-for-human`.
|
|
80
|
+
- **Not enough to act on** → post *Triage Notes* (template below) with specific questions,
|
|
81
|
+
move to `needs-info`, report: `#<n> → needs-info (asked: <one phrase per question>)`.
|
|
82
|
+
- **Already implemented** → say where it lives in a comment; recommend `wontfix (already
|
|
83
|
+
implemented)`. Do not write to `.out-of-scope/`.
|
|
84
|
+
- **Out of scope / duplicate** → a polite comment with the reason (and the earlier issue, or
|
|
85
|
+
the `.out-of-scope/` file it matches); recommend `wontfix` or `duplicate of #<m>`.
|
|
86
|
+
- **Reporter replied on a `needs-info` issue** → move it back to `needs-triage` and evaluate
|
|
87
|
+
it again from step 2 in this same run.
|
|
88
|
+
|
|
89
|
+
Labels: `gh issue edit <n> --add-label "<state>" --remove-label "<old state>"`. Never leave an
|
|
90
|
+
issue with two state labels. Under `propose`, never close an issue.
|
|
91
|
+
|
|
92
|
+
5. **Do not** edit code, open branches, or touch issues in `ready-for-agent`, `agent-working`,
|
|
93
|
+
`in-review` or `agent-blocked` — those belong to the implement loop and the maintainer.
|
|
94
|
+
|
|
95
|
+
## Agent Brief
|
|
96
|
+
|
|
97
|
+
The brief is the contract the implement loop works from; the issue body and discussion are only
|
|
98
|
+
context. Write it to survive weeks of unrelated change: name types, functions, commands and
|
|
99
|
+
behaviour, never file paths or line numbers. Behavioural, not procedural. Acceptance criteria that
|
|
100
|
+
can each be checked on their own. Say what is out of scope.
|
|
101
|
+
|
|
102
|
+
```markdown
|
|
103
|
+
> *This was generated by AI during triage.*
|
|
104
|
+
|
|
105
|
+
## Agent Brief
|
|
106
|
+
|
|
107
|
+
**Category:** bug / enhancement
|
|
108
|
+
**Summary:** one line
|
|
109
|
+
|
|
110
|
+
**Current behavior:** what happens now (for a bug: the broken behaviour, reproduced how)
|
|
111
|
+
**Desired behavior:** what should happen, including edge cases and errors
|
|
112
|
+
|
|
113
|
+
**Key interfaces:**
|
|
114
|
+
- `TypeName` / `functionName()` / config shape: what changes and why
|
|
115
|
+
|
|
116
|
+
**Acceptance criteria:**
|
|
117
|
+
- [ ] specific, independently checkable
|
|
118
|
+
- [ ] …
|
|
119
|
+
|
|
120
|
+
**Out of scope:**
|
|
121
|
+
- what must not change in this issue
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Triage Notes (needs-info)
|
|
125
|
+
|
|
126
|
+
```markdown
|
|
127
|
+
> *This was generated by AI during triage.*
|
|
128
|
+
|
|
129
|
+
## Triage Notes
|
|
130
|
+
|
|
131
|
+
**What we've established so far:**
|
|
132
|
+
- …
|
|
133
|
+
|
|
134
|
+
**What we still need from you (@reporter):**
|
|
135
|
+
- a specific, answerable question
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Notes for the next run
|
|
139
|
+
|
|
140
|
+
One line per open issue you have handled: `#<n> <state you left it in> last-comment=<ISO timestamp
|
|
141
|
+
with offset> brief=yes|no`. Drop closed issues. Stay under 2000 characters — if you must cut, cut
|
|
142
|
+
the oldest `needs-info` lines; the tracker still has everything.
|
|
143
|
+
|
|
144
|
+
## Inbox
|
|
145
|
+
|
|
146
|
+
One line per issue you acted on, in the shapes above. No issues needing attention → no inbox
|
|
147
|
+
lines. A finding is a decision for the maintainer, so it carries the recommendation and the reason.
|
package/skills/pi-loops/SKILL.md
CHANGED
|
@@ -11,11 +11,13 @@ The user's pi has pi-loops installed. Prefer its tools over ad-hoc `sleep` loops
|
|
|
11
11
|
|
|
12
12
|
| The user wants… | Use |
|
|
13
13
|
|---|---|
|
|
14
|
-
| "every day at 9 / hourly / 每小时 …"
|
|
14
|
+
| "every day at 9 / hourly / 每小时 …" and the result belongs in this chat (a reminder, a question to answer here) | `cron_create` with `stateful: false` — it belongs to this session and runs only while this session is open; the user leaving the window means it sleeps |
|
|
15
|
+
| anything that must run whether or not a window is open — a nightly digest, a report, a watch | `cron_create` with `stateful: true`, even when the user did not say "loop": a plain job cannot run unattended |
|
|
15
16
|
| recurring watch/triage: "check X and tell me what changed", "report new issues" | `cron_create` with `stateful: true` — the loop keeps notes between runs and reports findings to `/inbox` instead of interrupting the chat |
|
|
16
17
|
| the findings must be double-checked before the user sees them | `cron_create` with `stateful: true, verify: true` (a second adversarial sub-agent reviews each finding) |
|
|
17
18
|
| "when <condition> happens, do <action>" (a file appears, a PR merges, a build finishes) | `new_trigger` — condition + action, fires once unless the user asks for repeating |
|
|
18
19
|
| a reminder in N minutes | `cron_create` with schedule `in 10m` |
|
|
20
|
+
| run the project's issue tracker as a standing process (triage, build, review), or an experiment loop against a research contract | tell the user to run `/recipe list` and `/recipe add <name>` — a packaged set of loops installed with one confirmation; do not recreate it with `cron_create` |
|
|
19
21
|
|
|
20
22
|
Never use `new_trigger` for time-based schedules; never use `cron_create` for conditions.
|
|
21
23
|
|
package/src/archive.ts
CHANGED
|
@@ -362,15 +362,14 @@ export function importSession(input: ImportInput): ImportSummary {
|
|
|
362
362
|
}
|
|
363
363
|
if (!SAFE_ID.test(raw.id)) throw new Error("cron sidecar contains an invalid job id");
|
|
364
364
|
// Automation off unless activated, stale run bookkeeping cleared.
|
|
365
|
-
// `host`
|
|
366
|
-
// machine must be re-stamped or every job would look enabled and never fire.
|
|
365
|
+
// A `host` stamp from a pre-0.19.0 archive is dropped: pi-loops runs on one machine.
|
|
367
366
|
// `createdBy` is left as the archive carries it: it names the session that created the job,
|
|
368
367
|
// which is what lets a second import of the same archive recognise itself. Re-stamping it
|
|
369
368
|
// with the fresh import session would make every import look new and double the automation.
|
|
369
|
+
const { host: _legacyHost, ...rawJob } = raw as typeof raw & { host?: unknown };
|
|
370
370
|
const job: LoopJob = {
|
|
371
|
-
...
|
|
371
|
+
...rawJob,
|
|
372
372
|
cwd: input.targetCwd,
|
|
373
|
-
host: os.hostname(),
|
|
374
373
|
enabled: raw.enabled && input.activate,
|
|
375
374
|
running: undefined,
|
|
376
375
|
lastDueAt: undefined,
|
|
@@ -396,7 +395,8 @@ export function importSession(input: ImportInput): ImportSummary {
|
|
|
396
395
|
for (const raw of file.rules as DynamicTriggerRule[]) {
|
|
397
396
|
if (!raw || typeof raw.id !== "string" || typeof raw.condition !== "string" || typeof raw.action !== "string") throw new Error("trigger sidecar contains an invalid rule");
|
|
398
397
|
if (!SAFE_ID.test(raw.id)) throw new Error("trigger sidecar contains an invalid rule id");
|
|
399
|
-
const
|
|
398
|
+
const { host: _legacyRuleHost, ...rawRule } = raw as typeof raw & { host?: unknown };
|
|
399
|
+
const rule: DynamicTriggerRule = { ...rawRule, cwd: input.targetCwd, enabled: raw.enabled && input.activate, createdBy: raw.createdBy ?? { sessionId: parsed.header.id } };
|
|
400
400
|
if (dedup.hasRule(rule)) continue;
|
|
401
401
|
if (input.existingRuleIds.has(rule.id)) rule.id = newRuleId();
|
|
402
402
|
if (raw.enabled) originallyEnabledRules.push(rule.id);
|
package/src/args.ts
CHANGED
|
@@ -145,7 +145,6 @@ export interface SetArgs {
|
|
|
145
145
|
timeoutMs?: number | null;
|
|
146
146
|
name?: string | null;
|
|
147
147
|
/** `"here"` = this machine; `null` = any machine. */
|
|
148
|
-
host?: string | null;
|
|
149
148
|
/** Jobs only: the new prompt, verbatim. A loop's notes live under its id, so rewording it here keeps them. */
|
|
150
149
|
prompt?: string;
|
|
151
150
|
/** Jobs only: already parsed, so a typo is rejected before it can be stored. */
|
|
@@ -163,7 +162,7 @@ export interface SetOptions {
|
|
|
163
162
|
* `--model -` (or `current`) removes the pin.
|
|
164
163
|
*/
|
|
165
164
|
export function parseSetArgs(input: string, opts: SetOptions = {}): SetArgs {
|
|
166
|
-
const usage = `usage: /cron|/triggers set <id> [--model <provider/id>|-] [--thinking <level>|-] [--timeout <dur>|-] [--name <n>|-]
|
|
165
|
+
const usage = `usage: /cron|/triggers set <id> [--model <provider/id>|-] [--thinking <level>|-] [--timeout <dur>|-] [--name <n>|-]${opts.job ? ' [--prompt "<text>"] [--schedule "<expr>"]' : ""}`;
|
|
167
166
|
const tokens = tokenize(input);
|
|
168
167
|
const out: SetArgs = { ref: "" };
|
|
169
168
|
let touched = 0;
|
|
@@ -186,12 +185,6 @@ export function parseSetArgs(input: string, opts: SetOptions = {}): SetArgs {
|
|
|
186
185
|
else if (t === "--thinking") out.thinking = clear ? null : requireThinkingLevel(val);
|
|
187
186
|
else if (t === "--timeout") out.timeoutMs = clear ? null : parseDuration(val);
|
|
188
187
|
else if (t === "--name") out.name = clear ? null : val;
|
|
189
|
-
// `--host here` re-homes a job stamped with a machine that no longer exists (a renamed box,
|
|
190
|
-
// a rebuilt container, or one half of a synced $HOME); `-` unpins it for any machine.
|
|
191
|
-
else if (t === "--host") {
|
|
192
|
-
if (!clear && val !== "here") throw new Error("--host takes `here` (this machine) or `-` (any machine)");
|
|
193
|
-
out.host = clear ? null : "here";
|
|
194
|
-
}
|
|
195
188
|
// Rewording a loop or moving it to another hour used to mean remove-and-re-add, which mints a
|
|
196
189
|
// new id — and the notes a stateful loop has been accumulating live at `state/<id>.md`.
|
|
197
190
|
else if (opts.job && (t === "--prompt" || t === "--schedule")) {
|