feat: PM-Agent also need to look at PR comments
gate / gate (pull_request) Successful in 2m58s
images / images (pull_request) Successful in 1m6s
gate / conformance (pull_request) Has been skipped

From Gitea issue #116.

Authored by the PM agent's worker; the merge gate is the approver. Refs #116.

Agent: pm-agent (issue #116, mode revise)
This commit is contained in:
pm-agent committed 2026-11-07 06:02:58 +00:00
1 parent 2fdb95393c
commit e8e9de8611
7 files changed
+471 -189

No files matched your search

+45 -16
View File
@@ -169,16 +169,17 @@ the live Gitea — see `decisions/2026-11-07-pm-agent-pr-mentions.md`.
With it on, the dispatcher runs a second, independent pass each poll. A mention of the bot
account on a **pull request** makes the PM read that pull request — its description, its
changed files, its diff, the thread it was mentioned in and the rest of the conversation —
and **reply in that same thread**. Both places count: the pull request's conversation, and
inline review threads against a file and a line.
and **reply where it was mentioned**. Three places count: the pull request's conversation, a
review's own body (Gitea renders it in the conversation, so the reply goes there), and an
inline review comment against a file and a line.
```
@pm-agent in a PR comment ─▶ discover (notifications, else open PRs)
or an inline review thread │
├─▶ read /issues/{n}/comments, /pulls/{n}/reviews,
or in a review body │
or on a line inline ├─▶ read /issues/{n}/comments, /pulls/{n}/reviews,
│ .../reviews/{id}/comments
├─▶ decide: self-authored? already answered? mentioned,
│ or a follow-up in an already-mentioned thread?
├─▶ decide, per comment: self-authored? already
│ answered? does it name the bot?
├─▶ assemble the PR + diff + thread, every field fenced
│ and labelled UNTRUSTED
├─▶ ask the model: `comment` or `decline`, and nothing else
@@ -191,13 +192,37 @@ ask for work in a shape this program would accept. Nothing on the path calls `bu
The change-authoring half of issue #116 — revise a branch an agent owns, versus open a fresh
branch and PR against `main` for somebody else's — is a separate issue and a separate PR.
**"The same thread."** Inline review comments have a thread identity in Gitea (comments on
the same file and diff position are one conversation). Pull-request conversation comments do
not — they are a flat list — so the issue path's existing convention is reused: the
conversation is **one** thread, and from the first mention in a thread onward, every later
non-bot comment in it continues the conversation without a new mention. A comment made
*before* the mention is not pulled in, and continuation is per thread, not per pull request.
That is a policy choice, not a Gitea fact; it is flagged on issue #116.
**An explicit mention is the only trigger.** A comment is answered if and only if it is not
the bot's own, it spells out `@`-the-bot-account, and no earlier reply of the bot's says it
was already answered. Nothing else is actionable: a comment with no mention produces **no
reply and no model call**, however recently the bot was mentioned or replied nearby. So a
mention is a discrete, self-limiting request — one `@` gets one reply in the place it was
written, and the rest of the pull request is read as *context* rather than as a queue of
questions.
That is the operator's decision on issue #116, taken once it was established that pull-request
conversation comments are a flat list rather than a hierarchy: *"since there is no threads and
the PR comments are flat, then the pm-agent should be looking for explicit mentions rather than
a single mention and continued scrapping of comments within the PR in perpetuity"*. It replaced
an earlier rule where the first mention in a thread armed replies to every later non-bot
comment in it. It is applied uniformly, including to inline review comments — those *are*
genuinely threaded in Gitea, but keeping continuation for them alone would be a second rule
nobody asked for. The fixtures that used to assert continuation are **inverted, not deleted**,
so the old behaviour cannot creep back unnoticed.
Two practical consequences. A mention is **repeatable**: ask again by tagging again, and each
tag gets its own reply. And a mention is **not a conversation** — the PM is told to answer as
if it is the last word it will see, and to ask you to tag it again if it needs something back.
What remains, unavoidably, is that *anyone* who writes the handle gets a reply — the operator's
answer (3) is "anyone can talk and have the agent work" — and Gitea cannot tell a quoted
mention (`> @pm-agent what do you think?`) from a fresh one, so re-quoting an old mention in a
new comment costs one more reply.
**Thread identity is now only about where the reply goes.** Inline review comments are grouped
by (path, position), which is Gitea's own grouping and is what makes the reply land in the
thread that was spoken to rather than beside it; conversation comments and review bodies are
answered in the conversation. None of that is part of the trigger any more.
**Loop prevention.** There is no author gate — anyone who can comment can start a run, which
is the operator's decision (issue #116, answer 3: *"anyone can talk and have the agent work.
@@ -223,9 +248,13 @@ bot's *own* comments — pasting it into yours does not silence a mention.
The operator asked for "a way to check for '@'s for the bot account instead of scouring the
entire pull request list and associated threads". That is Gitea's notification list: a
mention subscribes the mentioned account to the thread, and the subscription then raises a
notification for **every later comment on it** — which answers discovery and thread
continuation with one bounded call.
mention subscribes the mentioned account to the thread, so one bounded call names the pull
requests worth reading.
It is a **hint, never the decision** — the trigger is re-derived by reading that pull
request's comments. That split is why the endpoint over-delivering is harmless: the
subscription keeps raising notifications for later comments on a pull request the bot was
once mentioned on, and since those comments carry no mention, they resolve to nothing.
**It will be refused on today's token.** `/api/v1/notifications` sits behind Gitea's
`notification` scope category, and the bot's token is minted
+12 -7
View File
@@ -131,11 +131,16 @@ data:
# --- mentions on a pull request (issue #116) --------------------------------
# OFF. Turning it on makes the dispatcher also look at PULL REQUESTS: a mention of the bot
# account in a pull request's conversation, or inside an inline review thread, makes the PM
# read that pull request and reply in that same thread, and follow-up comments in a thread
# that already holds a mention continue the conversation with no new mention. It can ONLY
# reply — a mention cannot start, revise or push a branch; that is the second half of
# issue #116 and has its own issue.
# account in a pull request's conversation, in a review's body, or on a line inside an
# inline review thread, makes the PM read that pull request and reply where it was
# mentioned. It can ONLY reply — a mention cannot start, revise or push a branch; that is
# the second half of issue #116 and has its own issue.
#
# AN EXPLICIT MENTION IS THE ONLY TRIGGER, and a mention is self-limiting: one `@` gets one
# reply, and no other comment on the pull request is picked up. A comment that does not
# name the bot costs nothing — no reply and no model call — however recently the bot was
# mentioned or replied nearby. So the spend on this path is bounded by how often somebody
# types the handle, not by how busy the pull request is.
#
# WHY IT SHIPS OFF. Not one call on this path has been made against the live Gitea — the
# branch that wrote it held no Gitea credential and no cluster — and the cheap mention
@@ -178,8 +183,8 @@ data:
# Plan mode for the pull-request path: discover, assemble the payload, PRINT it to the
# dispatcher's stdout, ask no model and post nothing. Setting it also enables the path, so
# it is one knob to look before leaping. PM_DRY_RUN above implies it, because on this path
# the posted reply IS the action under test and also the marker that changes future
# behaviour — posting it would make a dry run not dry.
# the posted reply IS the action under test, and it also carries the idempotency marker —
# posting it would make a dry run not dry.
PM_PLAN: "false"
# If the gate refuses a worker's change, the branch is NOT pushed by default and the issue