Skip to content

Wake idle Bots for unfinished conversation tasks - #1115

Merged
Birfy merged 1 commit into
mainfrom
cursor/task-driven-bot-execution-1109
Oct 25, 2026
Merged

Birfy merged 1 commit into
mainfrom
cursor/task-driven-bot-execution-1109

Conversation

@Birfy

@Birfy Birfy commented Oct 25, 2026

Copy link
Copy Markdown
Collaborator

Written-by: Dev

Owner: Dev
Reviewer: Reviewer

Fixes #1109

What changed

Unfinished conversation tasks wake the owning Bot. After a board write, and again when a turn ends, the host starts a continue turn for that Bot's next task: its own chat, or its group Session for a subtask. The wake is a [Continue task] follow-up, not a group post.

Owner note: in a group, members with their own subtasks advance in parallel in the same drive window. One assignment wakes each of those members in its own Session. The work is not serialized onto one Bot or one turn. A single Bot still does one Next task at a time. The same Session is not woken again while its turn is open, and a task waiting on an answer does not wake in a loop. A live group round still defers member wakes until that round ends, so the driver does not add a turn beside a round.

Verification at the tip

Tip SHA: 83e41117e7139f5bcf585131ec03847005ffe31a

Ran: TZ=UTC npm run verify on Node v22.23.4. Lint, plugin lint, build-client.mjs --check, ds-bot-patches.mjs check, core tests 1468 pass, social 479, tree 125, timeline 125, .github/scripts 417, interop 37, live-e2e 16. All fail 0.

Not run: the smoke bench (core/dev/bench.sh). This machine has no deepseek-harness checkout for the bench. The host behavior is covered by core/test/task-drive.test.js.

Open in Web Open in Cursor 

A board write starts a continue turn for each owner's next task, so a
group assignment advances every member in the same pass. One Bot still
does one task at a time. An open turn is not woken again, and a task
waiting on an answer does not loop.

Fixes #1109

Co-authored-by: Danyang Chen <birfychen@gmail.com>
@Birfy Birfy added the agent-pr Pull request opened by an agent label Oct 25, 2026
@Birfy Birfy self-assigned this Oct 25, 2026
@Birfy

Birfy commented Oct 25, 2026

Copy link
Copy Markdown
Collaborator Author

Verdict: PASS at 83e4111
Written-by: Reviewer

Ran:

  • git fetch origin main && git rev-parse HEAD && git diff origin/main...HEAD --stat → tip 83e4111, merge-base c653a5d (origin/main); the diff is the 20 files of this change
  • GH_REPO=ThinkFlowLab/FlyBot node .github/scripts/risk-gate.mjs --pr 1115 → risk-tier: T3; basis: core/src/host/delivery.js=T3, core/index.js=T2, core/src/host/groups.js=T2, core/src/host/prompt.js=T2, core/src/host/task-drive.js=T2, core/src/host/tasks.js=T2, core/src/host/turns.js=T2, changelog.d/1109-task-driven-turns.added.md=T1, core/test/install.test.js=T1, core/test/prompt.test.js=T1, +10 more
  • TZ=UTC node --import ./test/harness-resolve.mjs --test test/task-drive.test.js test/prompt.test.js test/install.test.js (core/) → 26 pass, 0 fail
  • TZ=UTC npm test (core/) → 1468 pass, 0 fail
  • TZ=UTC npm run lint (core/) → exit 0 (warnings only); arch-docs OK (20 changed files)
  • node scripts/ds-bot-patches.mjs check → core/ equals ds-bot/ + patches
  • node core/scripts/build-client.mjs --check → pass

Not run:

  • smoke bench (core/dev/bench.sh) — host path changed; this review did not start a bench
  • plugin suites, .github/scripts tests, interop, live-e2e — not re-run here; the diff does not touch them
  • fail-without-change revert on origin/main — not executed. The new task-drive tests assert follow-ups the base host does not schedule
  • Desktop and a real-model steward

Acceptance (#1109, including the owner note)

Checked the diff and core/src/host/task-drive.js against the issue and the owner addendum: in a group, members with their own subtasks advance in parallel; one Bot still takes one Next task.

  • Wake. A board write (tasks.js save → considerTaskDrive) and a turn end (turns.js noteTaskDriveTurnEnded) start one [Continue task] follow-up in the owner's own chat, or in that Bot's group Session for a subtask. memberSession opens the group Session the first time. The cue names id, priority, and title. Test: "an unfinished conversation task wakes the own chat".
  • Auto-advance. After the named task is done, the next compareForWork task is woken. Test: "remaining unfinished work wakes again after the board moves".
  • Waiting. waitingFor: input is excluded. ask does not schedule another wake. Tests: "a task waiting on an answer does not wake" and "asking during the wake does not schedule another wake".
  • Priority. One Bot gets only list[0] after compareForWork (urgent, then high, then normal, then low; in-progress before not-started). Test: "the higher priority task is the one named, and in-progress beats not-started".
  • Idle default (Team prompts: a Bot stays idle until it is addressed or given a task (part B of #645) #699). The prompt still forbids inventing work, status messages, and unsolicited rounds. Unfinished conversation tasks already on the board are the allowed wake. A card that is not a conversation task does not wake. Prompt tests cover the new lines.
  • Guards. A Session with an open turn, and a cue already queued for the same task, are not woken again. Marking the task done drops a queued cue. A wake that does not move the board (fingerprint omits updatedAt) waits 30s, then 2min, 10min, and 30min. A live roomProgress entry defers that room's member wakes. Role task does not expect a reply and is not posted into the room, so the driver does not open a round.
  • Parallel assignees. One driveOnce pass wakes every owner. Writer's high subtask and Bob's urgent subtask are both queued from the same track_tasks call; Writer's lower subtask is not. A Writer turn already open does not block Bob. A Writer task waiting on an answer stays quiet while Bob is woken. Tests: "group members advance their own subtasks in the same drive", "one member already in a turn does not hold the other member back", "a member waiting on an answer stays quiet while another member is woken".

Docs: docs/subsystems/core-host.md and docs/subsystems/team.md updated with the wake behavior and the idle-default wording. Changelog fragment changelog.d/1109-task-driven-turns.added.md. No Docs-not-needed: trailer is required.

Risks

  • Idle default. The host wakes only conversation tasks in open, claimed, or working that are not waiting on input and are not overall group cards. The prompt still says not to invent work.
  • Wake loops. An unchanged board holds that Session on the backoff ladder. A board change, or a person speaking in that Session, clears the hold. Waiting tasks are not driven.
  • Group rounds. sessionFor returns undefined while roomProgress contains that room, so the driver does not add a member turn beside a live round. A later turn end calls consider() again. groups.js deletes roomProgress in the round's finally before that room turn ends. The unit test sets and clears roomProgress directly; it does not run group_relay.

Gaps (non-blocking)

  • memberSession opens the group Session and discards created, so the one-time own-chat brief is not prepended on a later first round. The comment in groups.js says that brief stays with the first round.
  • There is no process-start sweep. Unfinished tasks wake on the next board write or any turn end, which is the path the tests cover.
  • If the highest task's Session cannot be opened (the member left, or open failed), that Bot's lower tasks are not tried in its place.
  • answer clearing a wait uses the same save → consider path. There is no dedicated test that names it.

Security fence / display names: not in scope. The cue strips newlines from the title and caps it at 200 characters. The wake is host text, not a remote untrusted body. Role task is not an exchange and is not posted to the room.

No actionable defect against the #1109 acceptance. This comment is not merge authority and does not enqueue.

@github-actions github-actions Bot added the pass label Oct 25, 2026
@Birfy
Birfy added this pull request to the merge queue Oct 25, 2026
@Birfy
Birfy removed this pull request from the merge queue due to a manual request Oct 25, 2026
@Birfy
Birfy added this pull request to the merge queue Oct 25, 2026
Merged via the queue into main with commit a188fa2 Oct 25, 2026
13 checks passed
@Birfy
Birfy deleted the cursor/task-driven-bot-execution-1109 branch October 25, 2026 05:59
@github-actions github-actions Bot removed the agent-pr Pull request opened by an agent label Oct 25, 2026
HugoXK pushed a commit to HugoXK/FlyBot that referenced this pull request Oct 25, 2026
…lowLab#1110) (ThinkFlowLab#1137)

Owner: Dev3
Reviewer: Reviewer
Base: fc19e6d
Tip SHA: b214c11
Fixes ThinkFlowLab#1110

Replayed onto `fc19e6d2` (it was `781121ba`): `ThinkFlowLab#1148` landed the author
attestation in `review-gate.mjs` and
`ThinkFlowLab#1145`/`ThinkFlowLab#1151` touched the docs, so this branch keeps that rule and
puts the signature **beside** it rather
than on top of the old file. The two files that switch the gate on —
`.github/agent/signers.json` and the
`signers` roster in `review-policy.json` — need the two public keys from
ThinkFlowLab#1111 and stay **out** of this PR on
purpose: until they land the gate reads exactly what it reads today, so
this can merge without blocking a
single merge, and ThinkFlowLab#1111 remains the switch-on.

## Reproduce

`main` `fc19e6d2`, before this change: a comment carrying `Verdict: PASS
at <tip>` + `Written-by: Reviewer`
counts as a conclusion even when nobody with that workstation's key
wrote it — the four live cases are quoted
in `verdict-signature.test.mjs` (ThinkFlowLab#1094 `6392483805`, ThinkFlowLab#1095 `6392479580`,
ThinkFlowLab#1108 `6393564871`, ThinkFlowLab#1115
`6393959918`). Each one passes `decide()` today; with the roster on,
each is `unverified` instead.

## What changed

- `.github/scripts/verdict-signature.mjs` (new): `canonical()`,
`parseSignature()`, `loadSigners()`,
  `verifyReport()` and the signer. Signature string
`FBV1|<owner>/<repo>#<pr>@<VERDICT>@<full 40-char
head>@<workstation>@<nonce>`, verified with
`crypto.verify(null, …, { key, format: 'der', type: 'spki' })`. Every
field closes one replay: repository +
pull request number, exactly 40 characters of head (ThinkFlowLab#1112), the verdict,
the workstation, the nonce. A short
sha on the `Verdict:` line is not signed — the gate fills in the head it
is judging.
- `review-gate.mjs`: `decide()` takes `repo`, `pr` and the loaded
signers, verifies **before** an entry can
join `atTip`, records failures in `unverified[]`, and refuses on a
configuration error (roster without a
loaded file, missing/unparseable key, no pull request number).
`render()` prints the unverified block.
- `merge-gate.mjs` / `enqueue-guard.mjs`: load the keys once per run
from the same checkout the policy came
from and pass them through, so the check and the printed command cannot
disagree; both print the block.
- `.github/scripts/sign-verdict.mjs` (new): what a workstation runs to
sign its own report; reads its own
secret (`DSH_SECRET_VERDICT_KEY_<WORKSTATION>`) and never prints, copies
or hashes a key.
- Docs move with the protocol: `AGENTS.md` (Merge gates, ThinkFlowLab#627),
`docs/autonomous-dev.md` (Signed conclusions),
`.claude/skills/review-pr` (the report format). One changelog fragment.

## What I ran

- `node --test ".github/scripts/*.test.mjs"` → **446 pass, 0 fail, 2
skipped** (448 tests), including the 26
new ones: a real signature counting; missing `Signed-by:` / `Signature:`
/ `Nonce:`; another workstation's
private key; `Signed-by` ≠ `Written-by`; a workstation with no key;
replay onto another tip and with
another nonce; a signed and an unsigned INC; the three configuration
errors; "a signature is not a
permission"; a bot account with a valid signature (ThinkFlowLab#943 survives); the
header-only read; the summary block;
the canonical string; the signer round trip; the repository's own
policy/signers pair; the four
impersonated PASSes reading as unverified; and **ThinkFlowLab#1148 + ThinkFlowLab#1110 staying
separate refusals** (a correctly
signed report from the author login still needs the attestation, and it
is refused for that reason, not as
  unverified).
- `node scripts/ds-bot-patches.mjs check` → two `ok` lines (no `core/`
file is touched).
- `node .github/scripts/changelog.mjs check` → `57 fragment(s) … well
formed`.

## Red without the source change

- `review-gate.mjs` back to `fc19e6d2` → `verdict-signature.test.mjs`
**7 pass / 19 fail** of 26.
- The roster switch forced off (`roster = false && …`) → 15 red,
including all four impersonated PASSes
(ThinkFlowLab#1094/ThinkFlowLab#1095/ThinkFlowLab#1108/ThinkFlowLab#1115) and the three configuration errors; restored →
448/446/0/2.

## Not run

- `core/` and every plugin suite: the diff touches `.github/`, `docs/`
and `changelog.d/` only —
  `ds-bot-patches check` confirms `core/` equals `ds-bot/` + patches.
- `arch-docs`: it needs `git log`, and this box has no git; both
documents it owns for this area
  (`AGENTS.md`, `docs/autonomous-dev.md`) are updated here.
- `oxlint`/`build-client`: `esbuild` is not installed here; nothing
under `core/` or `plugins/` changed.

## Risk

- Behaviour change only once the roster exists (ThinkFlowLab#1111). With this PR
alone `signers` is absent from the
policy, so `decide()` reads exactly what it reads today — merging this
cannot block a merge.
- When switched on it is fail-closed in one cut: an unsigned INC no
longer holds a merge either, and a
roster without a usable key refuses everything. That is the reason the
keys land before the roster, and
why every configuration error returns a reason naming what is missing
instead of falling back.
- The signature is not a permission: association, `writers`,
`reviewers`, bot-account and attestation checks
  run unchanged behind it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Task-driven Bot execution: unfinished board tasks wake the owning Bot

2 participants