Skip to content

emrg: the batch-call reminder is a system turn, not the host's voice - #2694

Merged
argszero merged 1 commit into
masterfrom
fix/batch-call-reminder-is-a-system-turn
Nov 3, 2026
Merged

argszero merged 1 commit into
masterfrom
fix/batch-call-reminder-is-a-system-turn

Conversation

@argszero

@argszero argszero commented Nov 3, 2026

Copy link
Copy Markdown
Owner

Closes #2693

What this changes

_with_batch_call_nudge (emrg/server/daemon.py) appends the batching reminder to the request that follows a round dispatching exactly one tool call. That entry was {"role": "user", "content": "batch call"}; it is now {"role": "system", "content": "batch call"}.

The content is what the model reads; the role is what every other reader reads. A user turn is the host's voice — the role a client renders as the host's bubble, the role a transcript attributes to the host, and the role a compaction summarises as something the host said. The reminder is the daemon's own instruction to the model, so a user role told all of those readers that the host had typed it. The host reported exactly that on 2026-11-03.

Nothing else about the mechanism moves: still one entry, still the last one, still only after a round with exactly one tool call, still derived on a copy so nothing reaches session history, llm.jsonl or a rebuilt request.

Why a trailing system entry is safe here

Measured on 2026-11-03 against the configured provider (https://atp-dev.shenbiai.net/v1, model deepseek-flash): a request whose messages end …, {"role": "user", "content": "Say ok."}, {"role": "system", "content": "batch call"} answers normally, and the same request without the trailing entry answers the same way. The probe is a one-off reading, not a guard: it costs tokens and depends on a provider being reachable.

Verification

  • uv run --no-sync pytest tests/test_batch_call_nudge.py -q — 7 passed. The expectations in that file carry the role: NUDGE is the exact dict the request must end with, and _nudges counts by the role actually sent.
  • uv run --no-sync pytest tests/ -q — 6110 passed, 36 skipped (4m25s) on this host.
  • CI: pending at the time of opening. No new guard is added for this change; the existing seven readings already assert the entry's exact shape, so they redden on the old role.

Handled by this PR; the issue it closes is #2693.

A round that dispatched exactly one tool call is reminded at the tail of the
next request, and that reminder was appended as a user message. A user turn is
the host's voice: the role a client renders as the host's bubble, the role a
transcript attributes to the host, and the role a compaction summarises as
something the host said. The reminder is the daemon's instruction, so every one
of those readers was told the host said something it never typed.

It is now a system message. Measured against the configured provider: a
{"role": "system"} entry after the user turn is accepted and answers normally.

Closes #2693

@argszero argszero left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM — cycle cyc20261103-205542

Landing tree reviewed: 99a23aee46bf, the tree this merges onto master 485b433 — 2 paths, emrg/server/daemon.py (+11 −1) and tests/test_batch_call_nudge.py (+7 −4). This head is FRESH (master's tip is its merge base) and its run 41637665080 is green on both legs (test 2m51s, test-windows 4m33s).

The defect is real and pointable: the "batch call" reminder the daemon appends to the request that follows a round dispatching exactly one tool call was appended as a user turn, so every reader that keys off the role — a client rendering the host's bubble, a transcript attributing the line to the host, a compaction summarising what the host said — reported a daemon instruction as something the host typed. The reminder is the daemon's instruction to the model, so the role is system; the content is unchanged, and a trailing system turn is accepted by the configured provider.

The test reads the role off the messages list the provider was actually handed, not off the code that built it, and its three readings move together in this diff — the expected turn, the docstring and the counter helper all read system.

@argszero argszero left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM — cycle cyc20261103-213055

Landing tree reviewed: f580595197d5, the tree this merges onto master 6522ecf. check-merge-landing-diff.py 2694 reports it changes two paths and only two — emrg/server/daemon.py (+11 −1) and tests/test_batch_call_nudge.py (+7 −4). The other nine paths in diff(master, head) are master's own later commits (the 0.3.35 bump and the AppImage permission fix), which this head does not contain, so they appear there as reversals this PR does not make.

Measured on that landing tree: check-merge-plan-suite.py 2694 passes the full suite — 6109 passed, 37 skipped in 275 s. The head is unchanged at 26e32e9 and its own run 41637665080 was green on both legs (test 2m51s, test-windows 4m33s); check-merge-freshness.py reads that verdict stale only because master has moved twice since, which is why this vote is about the landing tree above rather than the CI base.

The change itself: the "batch call" reminder the daemon appends after a round that dispatched exactly one tool call was written as a user turn. A user turn is the host's voice — the role a client renders as the host's bubble, a transcript attributes to the host, and a compaction summarises as something the host said — so every one of those readers reported a daemon instruction as something the host typed. The reminder is the daemon's instruction to the model, so the role becomes system; the content is untouched, and a trailing system turn is accepted by the configured provider.

The test reads the role off the messages list the provider was actually handed rather than off the code that built it, and its three readings move together in this diff — the expected turn, the docstring and the counting helper all read system.

@argszero argszero left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM — cycle cyc20261103-214759

Landing tree reviewed: f580595197d5, the tree this merges onto master 6522ecf. check-merge-landing-diff.py 2694 reports it changes two paths and only two — emrg/server/daemon.py (+11 −1) and tests/test_batch_call_nudge.py (+7 −4). The other nine paths in diff(master, head) are master's own later commits (the 0.3.35 bump and the AppImage permission fix), which this head does not contain, so they appear there as reversals this PR does not make.

Measured on that landing tree this cycle: check-merge-plan-suite.py 2694 passes the full suite — 6109 passed, 37 skipped in 280 s. The head is unchanged at 26e32e9 and its own run 41637665080 was green on both legs (test 2m51s, test-windows 4m33s); freshness reads that verdict stale only because master moved twice since its base, which is why this vote is about the landing tree rather than the CI base.

What the change is: the "batch call" reminder the daemon appends after a round that dispatched exactly one tool call was written as a user turn. A user turn is the host's voice — the role a client renders as the host's bubble, the role a transcript attributes to the host, and the role a compaction summarises as something the host said — so each of those readers reported a daemon instruction as something the host typed. The reminder is the daemon's instruction to the model, so its role becomes system; the content is untouched, and a trailing system turn is accepted by the configured provider.

The test reads the role off the messages list the provider was actually handed rather than off the code that built it, and its three readings move together in this diff — the expected turn, the docstring and the counting helper all read system.

@argszero
argszero merged commit f8797d2 into master Nov 3, 2026
2 checks passed
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.

emrg: the batch-call reminder is the daemon instruction, so it is a system message

1 participant