Skip to content

A pull request is titled with a name for its work, never with the prompt - #1621

Merged
suleimansh merged 3 commits into
mainfrom
pr-title-from-agent
Aug 21, 2026
Merged

A pull request is titled with a name for its work, never with the prompt#1621
suleimansh merged 3 commits into
mainfrom
pr-title-from-agent

Conversation

@suleimansh

@suleimansh suleimansh commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

A session that emits no session name had its PR titled intent.slice(0, 72) — the instruction it was given, cut wherever 72 characters landed. Squash-merge inherits the PR title, so it becomes a permanent commit subject. This one is real history on the #1334 dogfood repo:

Open TODO_AGENTS.md and work on the FIRST open entry only. When the work (fix #1)

It reads like a title while being neither a description of the change nor a whole sentence. A drain preset's first line is identical across every run it fires, so repeated firings produce near-identical subjects saying nothing about what each one changed — and it lands hardest exactly where nobody is watching, on unattended work whose PRs merge without a human reading the title.

The block reads like a commit message

The issue offered a narrow fix (drop the middle rung) and a better one: an agent that can be asked to describe its PR can be asked to name it. This is the better one. #1612 moved PR opening onto the path where the framework composes the title, and #1567 already has the agent writing the body in an open-pr block, so the block gains a title line:

```open-pr
Keep the queued state across a reload

The reader re-read the file on mount, so …
```

The three rungs are now all names for the work: the agent's own title, else the session's name, else Session <id> — which says little, but says it honestly. The prompt is not among them.

A first line longer than 100 characters is not a name for the work, it is the opening of a paragraph: it is taken as body text and the title falls back, rather than being cut to fit. Truncating a sentence into a title is the bug, not the fix.

Two things that fall out of it

Plan agents defuse both halves now. The title is the agent's own prose since this change, so a closing phrase in it would ride the squash-merge subject straight into the issue — which is what #1560 was. The body has been defused since then; the title had never carried prose before.

The event is open-pr, not pull-request-description. It carries a title as well as a description, and the old name was one character from the pull-request event that records the PR the framework actually opened.

Prompt change

prompts/protocols/signal.md now asks for the title line and says why it is worth a sentence (the squash merge makes it permanent). Flagged because it changes what every session is told, whatever else it was started for.

Closes #1618

`quotaBoundaryStatus` takes a model, filters the account's `week-model`
windows down to that model's own week, and measures it against the same
boundary as the account's week — documented, unit-tested, and never once
called with a model in production. `dashboard/quota.ts` was the only caller
and passes none, which is right for the panel it feeds: the bar is about the
account. But nothing else computed a boundary at all, so both self-starting
paths — auto PM and the CI-watch fix — gated on that same model-blind reading.

Live on this account today: the week is 54% used, the Fable week is 100% used,
and the gate said start. Every run it started died at its first API call.

The fix is a second question rather than an argument on the first.
`QuotaSource` grows `boundaryFor(model)` beside `read()`, both measured off
the same reading and the same slider position, so #960's "one source" promise
holds — the panel and the gate cannot disagree about the account, only about a
question the panel never asks. Making `read()` take a model would instead have
left the panel's call site looking like it had simply forgotten to pass one,
which is exactly how this went unnoticed.

The daemon resolves the model the way it resolves everything else about a
background start: `resolveProjectAgentOptions`, the same two tiers the start
itself uses, so the gate cannot measure a different model than the one that
runs. That makes it per project rather than per sweep — the model is a project
setting — so auto PM's `quota` seam now takes the project. It costs no extra
reading: the poller's last good windows are simply measured again.

A model with no window of its own, and a project with no model set, are gated
on the account's week exactly as before: with nothing chosen the driver picks,
and a window nobody can tie to the work must never stop it (#879).

Blocked work now stands down instead of dying at the API. Auto PM already
names the window that stopped it ("Current week (Fable) is 100% used"), which
reaches the dashboard's own line under the toggle. The CI-fix half said
nothing at all, so it now logs its stand-down once per failing head commit —
the same re-arm rule its attempt cap uses, rather than a line per tick for as
long as the PR stays red.

The end-to-end test is the half that was missing here, so it uses the real
poller and the real `pollerQuotaSource` over a stubbed driver read: a fake
would have answered whatever the test wanted, which is how an argument only
tests ever passed stayed unwired. Verified against the live reading too — the
account's own label is `Current week (Fable)`, byte for byte the fixture's.

Closes #1619
A session that emitted no session name got its PR titled `intent.slice(0, 72)`
— the instruction it was given, cut wherever 72 characters landed. The squash
merge inherits the PR title, so that becomes a permanent commit subject. One
from the #1334 dogfood is now real history on the scratch repo:

  Open TODO_AGENTS.md and work on the FIRST open entry only. When the work
  (fix #1)

It reads like a title while being neither a description of the change nor a
whole sentence, and a drain preset's first line is identical across every run
it fires — so repeated firings produce near-identical subjects that say nothing
about what each one changed. It lands hardest where nobody is watching:
unattended work, whose PRs merge without a human ever reading the title.

The middle rung is gone. What replaces it is the better half of #1618: an agent
that can be asked to describe its PR can be asked to name it. #1612 moved PR
opening onto the path where the framework composes the title, and #1567 already
has the agent writing the body in an `open-pr` block — so the block now reads
like a commit message. First line the title, the rest the body:

    ```open-pr
    Keep the queued state across a reload

    The reader re-read the file on mount, so …
    ```

A first line longer than 100 characters is not a name for the work, it is the
opening of a paragraph, and it is taken as body text rather than cut to fit —
truncating a sentence into a title is the bug, not the fix. The title then
falls back to the session's name, and past that to `Session <id>`, which says
little but misleads nobody.

Both halves of the block are defused for a plan agent now, not just the body:
since the title is the agent's own prose, a closing phrase there would ride the
squash-merge subject straight into the issue — which is what #1560 was.

The event that carried the block is `open-pr` rather than
`pull-request-description`: it carries a title as well as a description, and
the old name is also one character from the `pull-request` event that records
the PR the framework actually opened.

Closes #1618
@suleimansh

Copy link
Copy Markdown
Contributor Author

🤖 agent comment

Dogfooded on the real daemon, and the run happened to be the exact case this is about: the session emitted no set-session-name block at all. Before this change that is precisely the run whose PR would have been titled with 72 characters of its own prompt.

Built the branch, restarted the daemon on it, and started one unattended run against a scratch repo — "Add a CONTRIBUTING.md at the repository root: one short paragraph on how to run index.js, and a two-line note on how to open a pull request."

What the framework opened (suleimansh/tf-1334-dogfood#3):

Add CONTRIBUTING.md with run and pull request instructions

Adds a CONTRIBUTING.md at the repository root covering two things:

  • Running the project — one paragraph noting the repo is a single dependency-free Node.js script…
  • Opening a pull request — a two-line note: branch, commit, push to origin, then open a PR against main

The agent's record for that run: sessionName = None, handoffReport = done, pr = #3.

Three things this actually shows:

  • The agent followed the new instruction first time, without being told anything beyond the protocol block — first line a title, rest the body, blank line between.
  • The title is a description of the change, not of the request. On main after a squash that reads as a commit subject, which is the whole point.
  • The fallback ladder was never reached. A nameless session used to fall to the prompt rung; it now has a rung above it that the agent filled, so Session <id> stayed unused even in the case it exists for.

For contrast, the same repo's history from the run that found this — a real commit subject on its main:

Open TODO_AGENTS.md and work on the FIRST open entry only. When the work (fix #1)

@suleimansh

Copy link
Copy Markdown
Contributor Author

@brillout flagging this one because it changes the signal protocol every session is given, not just code.

prompts/protocols/signal.md now asks for the open-pr block to be written like a commit message — first line the title, the rest the body:

```open-pr
Keep the queued state across a reload

The reader re-read the file on mount, so …
```

The reason for touching the prompt rather than only the code: a session that emitted no session name had its PR titled with 72 characters of its own prompt, cut mid-sentence, and the squash merge made that a permanent commit subject on main. #1618 has one from the dogfood repo — "Open TODO_AGENTS.md and work on the FIRST open entry only. When the work (fix #1)". Dropping that rung alone would have left nameless sessions titled Session <id>; asking the agent for a name gets a real one, and #1612 had already moved PR opening onto the path where we compose the title.

Tested end to end on the real daemon: the run I drove emitted no session name at all — exactly the case — and titled its PR "Add CONTRIBUTING.md with run and pull request instructions". Evidence in the comment above.

Two decisions in here worth your veto:

@brillout

Copy link
Copy Markdown
Contributor

The prompt changes are okay 👍 (a little bit too babbling at one place, but it's okay).

… constraint

Rom's read of #1621: the prompt change is fine, one place babbles. It did — the same rule was
stated three times over. The placeholder repeated the failure mode the sentence above it already
implies, and a sentence about squash merges making the title a permanent commit subject was
rationale for us, not an instruction for the session reading it.

Every session is given this file, so a sentence that only explains ourselves to ourselves is
paid for on every run.
@suleimansh
suleimansh merged commit 558209e into main Aug 21, 2026
2 checks passed
@suleimansh
suleimansh deleted the pr-title-from-agent branch August 21, 2026 16:14
suleimansh added a commit that referenced this pull request Aug 22, 2026
…mpt (#1621)

* The quota gate is asked which model the work will run on

`quotaBoundaryStatus` takes a model, filters the account's `week-model`
windows down to that model's own week, and measures it against the same
boundary as the account's week — documented, unit-tested, and never once
called with a model in production. `dashboard/quota.ts` was the only caller
and passes none, which is right for the panel it feeds: the bar is about the
account. But nothing else computed a boundary at all, so both self-starting
paths — auto PM and the CI-watch fix — gated on that same model-blind reading.

Live on this account today: the week is 54% used, the Fable week is 100% used,
and the gate said start. Every run it started died at its first API call.

The fix is a second question rather than an argument on the first.
`QuotaSource` grows `boundaryFor(model)` beside `read()`, both measured off
the same reading and the same slider position, so #960's "one source" promise
holds — the panel and the gate cannot disagree about the account, only about a
question the panel never asks. Making `read()` take a model would instead have
left the panel's call site looking like it had simply forgotten to pass one,
which is exactly how this went unnoticed.

The daemon resolves the model the way it resolves everything else about a
background start: `resolveProjectAgentOptions`, the same two tiers the start
itself uses, so the gate cannot measure a different model than the one that
runs. That makes it per project rather than per sweep — the model is a project
setting — so auto PM's `quota` seam now takes the project. It costs no extra
reading: the poller's last good windows are simply measured again.

A model with no window of its own, and a project with no model set, are gated
on the account's week exactly as before: with nothing chosen the driver picks,
and a window nobody can tie to the work must never stop it (#879).

Blocked work now stands down instead of dying at the API. Auto PM already
names the window that stopped it ("Current week (Fable) is 100% used"), which
reaches the dashboard's own line under the toggle. The CI-fix half said
nothing at all, so it now logs its stand-down once per failing head commit —
the same re-arm rule its attempt cap uses, rather than a line per tick for as
long as the PR stays red.

The end-to-end test is the half that was missing here, so it uses the real
poller and the real `pollerQuotaSource` over a stubbed driver read: a fake
would have answered whatever the test wanted, which is how an argument only
tests ever passed stayed unwired. Verified against the live reading too — the
account's own label is `Current week (Fable)`, byte for byte the fixture's.

Closes #1619

* A pull request is titled with a name for its work, never with the prompt

A session that emitted no session name got its PR titled `intent.slice(0, 72)`
— the instruction it was given, cut wherever 72 characters landed. The squash
merge inherits the PR title, so that becomes a permanent commit subject. One
from the #1334 dogfood is now real history on the scratch repo:

  Open TODO_AGENTS.md and work on the FIRST open entry only. When the work
  (fix #1)

It reads like a title while being neither a description of the change nor a
whole sentence, and a drain preset's first line is identical across every run
it fires — so repeated firings produce near-identical subjects that say nothing
about what each one changed. It lands hardest where nobody is watching:
unattended work, whose PRs merge without a human ever reading the title.

The middle rung is gone. What replaces it is the better half of #1618: an agent
that can be asked to describe its PR can be asked to name it. #1612 moved PR
opening onto the path where the framework composes the title, and #1567 already
has the agent writing the body in an `open-pr` block — so the block now reads
like a commit message. First line the title, the rest the body:

    ```open-pr
    Keep the queued state across a reload

    The reader re-read the file on mount, so …
    ```

A first line longer than 100 characters is not a name for the work, it is the
opening of a paragraph, and it is taken as body text rather than cut to fit —
truncating a sentence into a title is the bug, not the fix. The title then
falls back to the session's name, and past that to `Session <id>`, which says
little but misleads nobody.

Both halves of the block are defused for a plan agent now, not just the body:
since the title is the agent's own prose, a closing phrase there would ride the
squash-merge subject straight into the issue — which is what #1560 was.

The event that carried the block is `open-pr` rather than
`pull-request-description`: it carries a title as well as a description, and
the old name is also one character from the `pull-request` event that records
the PR the framework actually opened.

Closes #1618

* Say it once: the open-pr instruction loses its aside and its restated constraint

Rom's read of #1621: the prompt change is fine, one place babbles. It did — the same rule was
stated three times over. The placeholder repeated the failure mode the sentence above it already
implies, and a sentence about squash merges making the title a permanent commit subject was
rationale for us, not an instruction for the session reading it.

Every session is given this file, so a sentence that only explains ourselves to ourselves is
paid for on every run.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A nameless session's PR is titled with its raw prompt, truncated mid-sentence — and the squash makes it permanent

2 participants