Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
139 commits
Select commit Hold shift + click to select a range
10f5278
feat(spine): name the markers a model ends a turn silently with
LivXue Oct 22, 2026
2ba05fd
docs: define the silence marker
LivXue Oct 22, 2026
9614c76
fix(spine): let a runtime turn's marker carry edge punctuation too
LivXue Oct 22, 2026
2e6f8ec
feat(spine): carry a silent turn and a no-reply failure on the turn e…
LivXue Oct 22, 2026
e1a7ede
refactor(spine): name the empty failure no_output, apart from the NO_…
LivXue Oct 22, 2026
9aa0424
fix(providers): treat an anthropic 200 error body as an error and a m…
LivXue Oct 22, 2026
af6ced5
fix(providers): report codex incomplete, refusal and missing terminal…
LivXue Oct 22, 2026
8624cdc
fix(providers): report a missing azure finish as unknown and keep its…
LivXue Oct 22, 2026
150d3bf
fix(providers): report transport failure evidence that outlasts the r…
LivXue Oct 22, 2026
f4e56f3
fix(providers): report the finish the upstream sent, refusals included
LivXue Oct 22, 2026
e5af5d2
test(providers): pin the unparsed-thought split that the stream exit …
LivXue Oct 22, 2026
9abd27d
refactor(providers): drop a transient note from the thought-split hel…
LivXue Oct 22, 2026
28b86a2
refactor(providers): name the refusal finishes once
LivXue Oct 22, 2026
6809cc5
feat(agent): decide empty-reply recovery by reply kind
LivXue Oct 22, 2026
7dd05f9
feat(agent): resend, nudge, prefill and step down an empty reply by kind
LivXue Oct 22, 2026
cb3fdfe
feat(agent): fall back on a refusal, a spent empty reply and a stream…
LivXue Oct 22, 2026
f2f3c87
feat(agent): continue a streamed answer that was cut and stitch it back
LivXue Oct 22, 2026
3726e76
feat(agent): recover an empty reply the way Hermes does
LivXue Oct 22, 2026
95c9f9b
fix(agent): end an answered turn on an empty reply and say when a ret…
LivXue Oct 22, 2026
30d6140
fix(providers): a stream the reader already saw does not fall back ei…
LivXue Oct 22, 2026
a48158d
fix(agent): ask for low, not minimal, when summarizing a compacted head
LivXue Oct 22, 2026
badb71f
refactor(agent): drop the old step-down helpers nothing calls
LivXue Oct 22, 2026
392b952
fix(agent): step down only a reply that stopped in thinking, high the…
LivXue Oct 22, 2026
203453c
feat(providers): hand back the text of a stream that died after showi…
LivXue Oct 22, 2026
df09f96
feat(*): continue a streamed answer the connection cut
LivXue Oct 22, 2026
d6ee296
fix(agent): say a cut answer is partial even with recovery switched off
LivXue Oct 22, 2026
0ce376c
fix(agent): only the answering call takes a cut stream back
LivXue Oct 23, 2026
b8a5638
feat(agent): hold back a reply that may still turn out to be a silenc…
LivXue Oct 23, 2026
2bd862e
feat(agent): end the loop on a silence marker, unseen, and file its r…
LivXue Oct 23, 2026
c2221a7
feat(agent): send nothing for a silent turn, but let its hooks have t…
LivXue Oct 23, 2026
16bfd87
feat(agent): end a turn without a reply when the model says [SILENT]
LivXue Oct 23, 2026
3bd854e
test(agent): pin a silent turn's exit and that a preamble is shown be…
LivXue Oct 23, 2026
c3f99ee
fix(agent): hold a marker wrapped in punctuation until the reply is read
LivXue Oct 23, 2026
b1bb0e7
feat(channels): add end_turn so a channel can stop typing without a send
LivXue Oct 23, 2026
799a73e
feat(gateway): do not report a cron run that got no output to the chat
LivXue Oct 23, 2026
8a12b2f
feat(gateway): stop typing when a turn ends without a reply
LivXue Oct 23, 2026
62c208f
fix(gateway): end typing only for a turn a user started
LivXue Oct 23, 2026
7a84f83
feat(core): teach a scheduled task the silence marker and let a silen…
LivXue Oct 23, 2026
8728ce8
feat(core): let a scheduled task stay silent, and keep a no-reply run…
LivXue Oct 23, 2026
38e9c34
fix(core): a silent scheduled run is not a nudge the sentinel should …
LivXue Oct 23, 2026
ec1c63c
feat(agent): teach the host to answer a routine unprompted report wit…
LivXue Oct 23, 2026
63b64b7
feat(rpc): carry a silent turn's row and notice to clients
LivXue Oct 23, 2026
9be4dd6
fix(acp): do not replay a silent row as an answer
LivXue Oct 23, 2026
f6d4bbe
fix(session): keep a silent row out of the session list preview
LivXue Oct 23, 2026
06f7c62
feat(ui-tui): show a silent turn as one muted line
LivXue Oct 23, 2026
fc3788f
feat(ui-web): show a silent turn as one quiet line
LivXue Oct 23, 2026
36618be
test(ui-web): say what the silent notice helper hands finishTurn
LivXue Oct 23, 2026
c7926a1
fix(ui-web): a silent turn neither pings nor re-stamps the rail
LivXue Oct 23, 2026
7bdb09b
fix(agent): continue only a stream that died mid-answer, and deliver …
LivXue Oct 23, 2026
870fc2b
fix(session): keep a silent proactive turn out of the session list
LivXue Oct 23, 2026
bd5e79e
fix(agent): tell clients a marker after a message-tool reply is a sil…
LivXue Oct 23, 2026
3f2600f
fix(ui-web): keep an earlier unread mark when a parked turn ends silent
LivXue Oct 23, 2026
02f6744
fix(ui-tui): ring no bell for a silent turn
LivXue Oct 23, 2026
2216833
refactor(spine): name the proactive origins once
LivXue Oct 23, 2026
3544f78
test(*): retire the old meaning of a silent cron completion
LivXue Oct 23, 2026
9559fbe
docs(*): correct comments that said something false
LivXue Oct 23, 2026
e262d81
revert(agent): send no silent-turn notice after a message-tool reply
LivXue Oct 23, 2026
d85a9bf
docs(agent): say what a stream cut after output does with retries off
LivXue Oct 23, 2026
577f90c
docs(agent): say a raised stream error is never switched to a fallback
LivXue Oct 23, 2026
d1f0af2
fix(agent): drop the prefill rows on a streamed error's model switch
LivXue Oct 23, 2026
6341319
fix(agent): end a stream cut on the last iteration on what streamed
LivXue Oct 23, 2026
bc6da32
fix(agent): end a turn silent when its exhaustion wrap-up is a marker
LivXue Oct 23, 2026
a17bf4c
fix(*): return an A2A turn's reply verbatim, even as a silence marker
LivXue Oct 23, 2026
7cf631d
fix(agent): make the silence gate read each delta once and pass symbo…
LivXue Oct 23, 2026
8338e14
fix(agent): step a count-only thinking reply down without prefilling
LivXue Oct 23, 2026
269ef1e
docs(agent): name the step-down where the loop asks for wire keys
LivXue Oct 23, 2026
d0fe406
fix(agent): hand after_iteration hooks the joined answer after a cont…
LivXue Oct 23, 2026
452ac76
fix(agent): claim retries in a thinking-only failure only when some w…
LivXue Oct 23, 2026
a0ff050
fix(agent): count a truncation failure's continuations instead of the…
LivXue Oct 23, 2026
af34b70
fix(agent): word the output-limit continuation for a cut that left no…
LivXue Oct 23, 2026
8135b5f
test(agent): pin the reply_kind order for a marker and the reasoning-…
LivXue Oct 23, 2026
2be39ac
test(agent): pin that a fallback a streamed error switched to gets on…
LivXue Oct 23, 2026
e7ce41d
test(agent): pin that a refusal with no text quotes its reasoning
LivXue Oct 23, 2026
401c40e
fix(agent): end any turn that runs out with a continuation pending on…
LivXue Oct 23, 2026
4ad413c
docs(agent): say a thinking-only step-down asks only rungs below the …
LivXue Oct 23, 2026
9cab32a
docs(*): say a refusal with recovery off leaves no_output unset
LivXue Oct 23, 2026
57381ba
docs(providers): stop calling an empty reply silence
LivXue Oct 23, 2026
ebd4439
docs(providers): name the step-down where reasoning wire keys are read
LivXue Oct 23, 2026
769d2d3
docs(*): record the empty-reply rework in the changelog
LivXue Oct 23, 2026
36be658
fix(session): label a silent row in an export as not delivered
LivXue Oct 23, 2026
b1ea05b
fix(rpc): show no reply for a cron run that ended silent
LivXue Oct 23, 2026
324d280
fix(acp): leave a silent turn the runtime opened out of a replay
LivXue Oct 23, 2026
4b6934a
docs(rpc): describe a silent transcript row by what it holds
LivXue Oct 23, 2026
e46c336
fix(acp): keep a runtime turn the message tool answered in a replay
LivXue Oct 23, 2026
6a4fe61
docs(providers): state which stream failures come back as an error re…
LivXue Oct 23, 2026
fc8b04f
fix(ui-web): keep a stopped silent turn from landing the next turn si…
LivXue Oct 23, 2026
0842ebd
test(ui-tui): assert the thought a reloaded silent turn keeps
LivXue Oct 23, 2026
14cf2b1
refactor(ui-tui): drop a silent check the typed turn end never reaches
LivXue Oct 23, 2026
57a5979
docs(*): name the message-tool exception to live and reload parity
LivXue Oct 23, 2026
4eebe60
docs(*): describe silence and cut-answer endings as the code now runs…
LivXue Oct 23, 2026
7c3863d
docs(*): say which streamed errors reach a fallback model
LivXue Oct 23, 2026
463e55f
docs(*): say what a cut stream leads to on each call with retries off
LivXue Oct 23, 2026
73c5de7
fix(agent): prefill a thinking-only reply only when its thought has text
LivXue Oct 23, 2026
cc7cbbe
fix(agent): word truncation and thinking-only failures by their step'…
LivXue Oct 23, 2026
f0d9ae3
test(acp): pin that a silent runtime turn is dropped before the repla…
LivXue Oct 23, 2026
2ff2c38
docs(*): tidy four descriptions of silence and model switches
LivXue Oct 23, 2026
976c2cc
docs(*): say which thinking a prefill re-feeds
LivXue Oct 23, 2026
cd731ee
fix(session): keep a runtime turn the message tool answered
LivXue Oct 24, 2026
de7f023
chore(tests): let ruff format the new session list regression
LivXue Oct 24, 2026
7531edc
test(*): pin how a sub-agent and the turn nav meet this change
LivXue Oct 27, 2026
984be65
fix(agent): hold a continuation's doubled ceiling to the room left
LivXue Oct 27, 2026
c2d2fc3
docs(*): say how a continuation and a compaction summary read finishes
LivXue Oct 27, 2026
f1b8959
fix(agent): give a reply a cut answer ends on its window reading
LivXue Oct 27, 2026
148534b
fix(agent): honour only the taught marker on a person's turn
LivXue Oct 27, 2026
5c3bd21
fix(agent): file the reply a hook composes on a silent turn
LivXue Oct 27, 2026
d60f2a6
fix(agent): read a person's reply past its inline thinking in the gate
LivXue Oct 27, 2026
3665d15
fix(*): read the message tool's live verdict when a stored turn is read
LivXue Oct 27, 2026
5cbd2fe
fix(agent): read a cut stream's fragment as a cut before any marker
LivXue Oct 27, 2026
a8bb162
fix(agent): hold a continuation's doubled ceiling to the model's own
LivXue Oct 27, 2026
bb2bb37
test(agent): pin the step a fallback's answer opens after a refusal
LivXue Oct 27, 2026
8cd924b
fix(agent): keep a cut answer's words on the call its continuation made
LivXue Oct 27, 2026
44cddeb
test(agent): pin the wrap-up's silence check to its turn's own rule
LivXue Oct 27, 2026
500a4cc
fix(providers): keep a refusal's finish on a stream that dies after it
LivXue Oct 27, 2026
d94b5b8
refactor(*): seat the silence markers in agent context, not the kernel
LivXue Oct 27, 2026
5914063
refactor(spine): drop the silent flag from the turn-ended event
LivXue Oct 27, 2026
43ea4d1
fix(providers): read an OpenAI refusal where litellm files it
LivXue Oct 27, 2026
5e11a65
fix(gateway): stop the typing a merged message started when no reply …
LivXue Oct 27, 2026
ba2e9a5
refactor(providers): build the upstream transport failure in one place
LivXue Oct 27, 2026
daf8d9d
fix(ui-web): keep the narration a silent turn's wrap-up shares a step…
LivXue Oct 27, 2026
abd2a09
refactor(agent): pick every origin's silence rule in one place
LivXue Oct 27, 2026
1b713f6
docs(*): describe the silence and recovery rules as they now stand
LivXue Oct 27, 2026
d835d50
fix(agent): leave a cut stream's partial out of the budget-stop marker
LivXue Oct 27, 2026
d4df76e
test(ui-web): mark the message-tool verdict as unsent offline
LivXue Oct 27, 2026
2e60a09
fix(gateway): key a merged message's record as the turn's events are
LivXue Oct 27, 2026
4f53e67
fix(agent): read the reply a cut answer ends on for the marker
LivXue Oct 27, 2026
f9a0a7d
fix(*): file the message tool's verdict on a row the turn stores
LivXue Oct 27, 2026
b5c0b5b
test(agent): pin how the silence gate reads a think tag at its edges
LivXue Oct 27, 2026
6d107b9
docs(*): say what the kernel holds of silence and what the gate holds…
LivXue Oct 27, 2026
990aabd
docs(*): wrap the two glossary lines the last edits left wide
LivXue Oct 27, 2026
e742379
fix(agent): read a cut reply with its think blocks out of the whole
LivXue Oct 27, 2026
e380170
fix(agent): read a stray closing think tag as debris in the gate
LivXue Oct 27, 2026
47dc5cf
docs(*): say exactly which entry carries the message tool's verdict
LivXue Oct 27, 2026
6fd7a11
fix(agent): read a cut reply that joins into a bare thought as an unc…
LivXue Oct 27, 2026
c32f2f0
fix(agent): file a cut call's row with its words as they came
LivXue Oct 27, 2026
fb59af4
docs(*): say that only closed think blocks leave a cut reply
LivXue Oct 27, 2026
e1f8afd
test(agent): pin what a proactive turn shows after a thought is reread
LivXue Oct 27, 2026
09959ff
fix(agent): resend an empty streamed reply once without streaming
LivXue Oct 28, 2026
f55e814
test(agent): pin that a failed unstreamed resend shows none of its error
LivXue Oct 28, 2026
fab98f5
perf(agent): hold a run of blank deltas without reading each character
LivXue Oct 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 29 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,28 @@ All notable changes to Raven are documented here.

### Changed

- Empty and abnormal model replies are handled the way Hermes Agent handles
them. A reply that is only the silence marker `[SILENT]` ends the turn with
nothing sent; a scheduled run, a sub-agent's report or a Sentinel notice
may also spell it `SILENT`, `NO_REPLY` or `NO REPLY` and wrap it in a note,
but a reply to a person that is only "No reply." reaches them as written.
An A2A peer, which waits on the text, still gets the reply as written, and
the TUI and web UI draw one muted line, "Raven chose not to reply."; every
cron reminder teaches the marker, and a silent run counts as a success. An
empty reply is recovered by its kind: a plain one is resent unchanged up to
three times after a backoff (once when the call cost at least
`agents.defaults.emptyRetryCostThresholdUsd`, default 0.25 USD); one that
only thought is asked again and then at a lower reasoning effort; one cut
at the output limit is continued with more room; a refusal is never resent
but goes to a fallback model or fails with the model's explanation (one an
OpenAI-compatible model streams, which litellm drops from the stream, comes
back as an empty reply, and the first resend of an empty streamed reply asks
without streaming, where the refusal is read); and a turn left with nothing
fails in words that name what happened. A streamed answer whose connection
dies is continued where it stopped, then delivered marked as partial if it
stays cut. A compaction summary asks for reasoning effort `low` instead of
`minimal`, which Codex refuses.

- A complete install now carries the `everos-cloud-memory` plugin, so the
hosted EverOS is a memory backend a user can pick rather than a wheel
attached to the release with no install path: the one-line installer,
Expand Down Expand Up @@ -1291,8 +1313,13 @@ All notable changes to Raven are documented here.
hosting uses, waits out the same ladder after its one reconnect instead of
failing the turn on the second error. An unattended run may also ask, with
`agents.defaults.llmRetryAfterOutput`, for a streamed call that failed after
it had produced output to be asked again (the deck launcher does); off, the
turn fails as before.
it had produced output to be asked again (the deck launcher does). Off, the
turn's answering call now hands back the words that streamed: with
empty-response recovery on the turn continues them (see the entry on empty
and abnormal replies above), and with it off they are delivered as they
stand with a note that the reply is partial. A sub-agent run and the turn's
other model calls, such as the wrap-up of a turn that ran out of steps,
still treat the cut as before.
- The deck engine's second reader is bounded: one reading stops at 240 s and
names the pages it did not get to, a deck's readings together stop at 15 min,
a reading is recorded at the version of the pixels it read (the build's own
Expand Down
203 changes: 166 additions & 37 deletions CONTEXT.md

Large diffs are not rendered by default.

32 changes: 21 additions & 11 deletions docs-site/docs/proactivity-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,9 +202,10 @@ check-and-reserve transaction:
- The task-discovery menu is policy-gated. A user-selected option is a separate
execution path, not a second pass through ProactiveSpawn for every action.
- Cron is explicitly user-scheduled and bypasses `check()`. With a Sentinel
runner wired, successful fires update its shared counters and topic ledger.
This helps suppress overlapping Sentinel reminders; it is not a guarantee
of semantic deduplication across all messages.
runner wired, successful fires update its shared counters and topic ledger,
except a run the model ends with `[SILENT]`: it nudged no one, so it records
nothing. This helps suppress overlapping Sentinel reminders; it is not a
guarantee of semantic deduplication across all messages.

## State and feedback

Expand Down Expand Up @@ -282,10 +283,12 @@ broadcast. `raven/core/cron_stack.py` submits the work as a `CRON` turn in

Successful recurring fires increase `silent_fire_count`; matching user
activity resets it. Reaching `silent_fire_limit` (default `12`) disables the
job. This counts fires without user activity, not failures. Cron's feedback
record is marked neutral so it does not lower Sentinel's learned acceptance
rate. Fixed-delay `every` schedules compute their next run from completion,
not from the previous due time.
job. This counts fires without user activity, not failures. A run the model
ends with `[SILENT]` is not counted either: it sent the user nothing to
respond to, so a check that mostly stays quiet is not disabled for it. Cron's
feedback record is marked neutral so it does not lower Sentinel's learned
acceptance rate. Fixed-delay `every` schedules compute their next run from
completion, not from the previous due time.

HeartbeatService (`schedulers/heartbeat/service.py`) checks `HEARTBEAT.md`
with a structured model decision and runs agent work only on a `run` result.
Expand All @@ -294,10 +297,17 @@ while user work is busy. Wake drives Heartbeat, not Sentinel's tick loop.

A successful Cron turn that explicitly replies uses the Spine's delivery
path and does not enqueue a completion event or wake Heartbeat to repeat the
reminder. Silent completions and failures still wake Heartbeat, as do missed
reminders and automatic disabling. A successful retry clears its pending
failure event; an explicit reply also clears any earlier silent completion.
Unrelated events stay queued.
reminder. The reminder also teaches the `[SILENT]` marker. Cron is a
proactive origin, so a marker alone on the first or last line, or `[SILENT]`
opening the reply, counts too, and a note on why the model stayed quiet does
not defeat it. A run that ends this way is recorded as a success and delivers
nothing: it enqueues no event and wakes no Heartbeat. A run in which the model
produced no output after every retry is recorded as failed, delivers nothing,
and does not wake Heartbeat either. Every other failure still wakes
Heartbeat, as do a success that ends with neither a reply nor a marker,
missed reminders and automatic disabling. A successful retry clears its
pending failure event; an explicit reply or a `[SILENT]` run also clears any
earlier completion event. Unrelated events stay queued.

The Spine's Scheduler routes turns into per-conversation Lanes and origin
concurrency pools; DeliveryHub routes output to outlets. User-inbound and
Expand Down
17 changes: 12 additions & 5 deletions docs-site/docs/proactivity-design.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,8 @@ ProactiveSpawn 校验任务,以任务文字作为去重内容检查共享策
在空闲检查后直接投递,不重新检查策略,因此没有完整的发送时配额或免打扰保障。
- 任务发现菜单受策略约束。用户选择后的执行是独立路径,并非每种动作都再经 ProactiveSpawn。
- Cron 是用户显式安排的任务,绕过 `check()`。接入 Sentinel runner 时,成功触发会更新
共享计数与话题账本。这有助于抑制重叠的 Sentinel 提醒,但不能保证所有消息语义上绝不重复。
共享计数与话题账本;模型以 `[SILENT]` 结束的运行没有提醒任何人,不做记录。
这有助于抑制重叠的 Sentinel 提醒,但不能保证所有消息语义上绝不重复。

## 状态与反馈 { #state-and-feedback }

Expand Down Expand Up @@ -228,17 +229,23 @@ CronService(`schedulers/cron/service.py`)在文件锁下持久化任务、

周期任务成功触发会增加 `silent_fire_count`,匹配的用户活动会将其重置。
达到 `silent_fire_limit`(默认 `12`)后任务被禁用。这里计数的是没有用户活动的触发,
不是失败次数。Cron 的反馈记录记为中性,不会拉低 Sentinel 学习到的接受率。
不是失败次数。模型以 `[SILENT]` 结束的运行同样不计数:它没有给用户发任何内容,
用户无从回应,所以多数时候保持沉默的检查类任务不会因此被禁用。
Cron 的反馈记录记为中性,不会拉低 Sentinel 学习到的接受率。
固定延迟的 `every` 计划从完成时刻计算下一次运行时间,而不是从上一次应触发时刻计算。

HeartbeatService(`schedulers/heartbeat/service.py`)通过结构化模型决策检查
`HEARTBEAT.md`,仅在结果为 `run` 时执行 Agent 工作。`wake.py` 合并提前唤醒请求、
限制频率,并在用户工作繁忙时延后。Wake 驱动 Heartbeat,不驱动 Sentinel 的节拍循环。

已明确回复的成功 Cron 轮次通过 Spine 投递,不再生成完成事件或唤醒 Heartbeat 重复提醒。
成功但未回复的轮次、失败、错过的提醒以及自动禁用仍会唤醒 Heartbeat。
任务重试成功后会清除自身待处理的失败事件;明确回复后也会清除此前排队的静默完成事件。
其他事件仍保留在队列中。
提醒文本还会教模型使用 `[SILENT]` 标记。Cron 属于主动来源(proactive origin),
标记单独占第一行或最后一行、或回复以 `[SILENT]` 开头,同样算沉默,
所以模型附带的一句说明不会让标记失效。以标记结束的运行记为成功、不投递:
不生成事件,也不唤醒 Heartbeat。模型在所有重试后仍什么都没给的运行记为失败、不投递,
同样不唤醒 Heartbeat。其他失败、既没有回复也没有标记的成功轮次、错过的提醒以及自动禁用
仍会唤醒 Heartbeat。任务重试成功后会清除自身待处理的失败事件;明确回复或以 `[SILENT]`
结束后,也会清除此前排队的完成事件。其他事件仍保留在队列中。

Spine 的 Scheduler 将轮次路由到按会话划分的 Lane 与按来源划分的并发池;
DeliveryHub 将输出投递到出口。用户入站与响应修饰钩子区分真实用户轮次和系统来源工作。
Expand Down
3 changes: 2 additions & 1 deletion docs-site/docs/proactivity.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,8 @@ uv run raven sentinel config set --max-nudges-per-hour 1 --max-nudges-per-day 4

Restart afterward. These are Sentinel policy limits, not account-wide billing
caps. User-scheduled Cron jobs bypass the policy check; when the shared ledger
is wired, their fires still consume counters seen by Sentinel.
is wired, their fires still consume counters seen by Sentinel, except a run the
model ends with `[SILENT]`.

The policy is not a strict send-time barrier for queued messages. Injects are
checked and charged when queued; deferred reminders are checked at registration
Expand Down
3 changes: 2 additions & 1 deletion docs-site/docs/proactivity.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,8 @@ uv run raven sentinel config set --max-nudges-per-hour 1 --max-nudges-per-day 4
```

修改后需重启。这些是 Sentinel 的策略限制,不是账户级费用上限。用户安排的 Cron 任务绕过
策略检查;接入共享账本时,它们的触发仍会消耗 Sentinel 看到的计数。
策略检查;接入共享账本时,它们的触发仍会消耗 Sentinel 看到的计数,
模型以 `[SILENT]` 结束的运行除外。

对于已入队消息,策略并不是严格的发送时刻限制。追加提醒在入队时检查并计入配额;延迟提醒
在登记时检查,但目前缺少发送时重新检查和记录配额的回调。若要让 Sentinel 提醒避开免打扰
Expand Down
16 changes: 10 additions & 6 deletions docs/Proactivity-Implementation.md
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,9 @@ the dispatcher.
pops the pending messages for that session and appends them to the agent's
outgoing reply. The agent loop applies this in its `after_send` chain and skips
it for system-origin turns so a proactive reply does not get a nudge layered on
top of itself.
top of itself. A turn the model ends on a silence marker has no reply for it to
modify: the chain runs with no outbound text, the modifier is not called, and the
queued nudge waits for the next reply.

Constraints: a TTL drops stale entries, a per-session cap evicts the oldest
beyond it (FIFO), and the pending queue is persisted through the shared
Expand Down Expand Up @@ -515,12 +517,14 @@ A cron fire also writes the shared NudgePolicy ledger (a topic-tag fire plus a
dispatched record marked neutral) so the Sentinel suppresses its own proactive
nudge on the same topic within the dedup window, without dragging down the
acceptance rate the Sentinel learns from (a cron is user-initiated, not the
Sentinel's own proposal). A recurring job that fires repeatedly with no user
Sentinel's own proposal). A run the model ends on a silence marker nudged no
one, so it writes nothing. A recurring job that fires repeatedly with no user
response is automatically disabled when `silent_fire_count` reaches
`silent_fire_limit` (default 12). Successful fires increment this count; user
activity for the matching channel and recipient resets it. This counts fires
without user activity, not execution failures, and contains runaway "every few
minutes forever" jobs.
`silent_fire_limit` (default 12). Successful fires increment this count, except
a run the model ends on a silence marker, which sent the user nothing to respond
to; user activity for the matching channel and recipient resets it. This counts
fires without user activity, not execution failures, and contains runaway "every
few minutes forever" jobs.

---

Expand Down
2 changes: 1 addition & 1 deletion docs/specs/2026-08-21-acp-agent-compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ build is validated against it.
| `tool_call_update` status | **Supported** | `completed` or `failed`, from the emit site's verdict: the loop derives it from the registry's own failure convention (the model-facing text of a failed tool starts with `Error`), which is the same rule the retry-hint logic uses. A tool that reports a friendly `Error: ...` message is therefore classified as failed here too -- breaking the convention mislabels the transcript row as well. |
| `ToolCallContent` `diff` | **Supported** | Structured `{path, newText, oldText?}` carried from the write tools, which hold both versions of the file. `oldText` is omitted only for a file that did not exist -- the schema defines its absence as "new file", so omitting it for a file whose previous content is merely unavailable would render every line of a rewrite as an addition. |
| `ToolCallContent` `terminal` | **Not supported** | See `terminal/*` below. |
| `stopReason` | `end_turn`, `cancelled`, `max_turn_requests`, `max_tokens` | A turn that would otherwise end `end_turn` is reported `max_turn_requests` when the agent loop's own budget ended it (the tool-call cap, the wall clock or a stalled tool) and a wrap-up answered in its place, and `max_tokens` when generation stopped at the model's output ceiling. The budget is read first. Either way the reply reads complete and is not, and the stop reason is the one place a delegating client can learn that. |
| `stopReason` | `end_turn`, `cancelled`, `max_turn_requests`, `max_tokens` | A turn that would otherwise end `end_turn` is reported `max_turn_requests` when the agent loop's own budget ended it (the tool-call cap, the wall clock or a stalled tool) and a wrap-up, or the cut answer it left no room to continue, answered in its place, and `max_tokens` when generation stopped at the model's output ceiling. The budget is read first. A stream cut mid-answer and delivered as a partial spent no budget and stays `end_turn`; its reply says it is partial. Either way the reply reads complete and is not, and the stop reason is the one place a delegating client can learn that. |
| `refusal` as a stop reason | **Not sent** | It would come from the runtime's `action_blocked` notice, and that notice carries no turn id: `_run_turn` has none to give it, and the runtime shares a lane with the client's turn (see `_owns_lane`), so a refusal seen on this session cannot be shown to belong to this prompt. Latching it anyway made a *foreign* turn's block the reported outcome of a turn that completed normally, which is a false statement about this turn; reporting `end_turn` is merely a less specific true one. The refusal itself is not lost -- it is delivered as message content, which is what the person reads. Restoring it needs a turn id on the notice, which means threading one through the agent loop. |
| A failed turn | **Explained, then ended** | A prompt is never answered with a JSON-RPC error. Measured from the other direction on codex-acp: an error in reply to a turn-shaped request makes clients tear down the whole turn. The failure is surfaced as message content and the turn ends with a stop reason. |
| `plan` | **Not sent** | `PlanEntry.priority` is required (`high`/`medium`/`low`) and Raven's DAG has no source for it. A plan would have to be invented. |
Expand Down
4 changes: 4 additions & 0 deletions i18n/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -2531,6 +2531,10 @@
"en": "A question went unanswered, so the turn continued without an answer.",
"zh": "有提问没有得到回答,这一回合在没有答案的情况下继续了。"
},
"gui.notice.silent_turn": {
"en": "Raven chose not to reply.",
"zh": "Raven 选择不回复。"
},
"gui.hub.auto_use": {
"zh": "技能由 Raven 按需自动使用,暂不支持单独停用",
"en": "Skills are picked up by Raven on demand; per-skill toggling is not supported yet"
Expand Down
10 changes: 7 additions & 3 deletions raven/a2a/runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -297,8 +297,12 @@ async def _run_one_shot_turn(
carries a `SentinelExtras` payload this call has none of) -- it is a
proactive, system-origin turn a peer agent asked for, which is exactly what
`HEARTBEAT` already means elsewhere (`raven/cli/gateway_commands.py`'s
`on_heartbeat_execute`). `HEARTBEAT`'s only special case is scheduler lane
pooling, irrelevant here since this path never reaches the Scheduler.
`on_heartbeat_execute`). Two things ride a proactive origin: scheduler lane
pooling, irrelevant here since this path never reaches the Scheduler, and
the silence protocol, under which a reply that is or closes on a Silence
Marker is withheld. The peer is waiting on the text, so the turn is run
with `reply_awaited` and takes no part in that protocol: the reply comes
back as the model wrote it.

`conversation_id` is minted by the caller (`RavenAgentExecutor.execute`), not
here: an A2A `SendMessage` carries no conversation of its own (unlike a
Expand Down Expand Up @@ -352,7 +356,7 @@ def drain() -> list[TurnRequest]:
return []

text_sink: dict[str, str] = {}
await agent_loop.run_turn(req, emit, drain, stream=False, text_sink=text_sink)
await agent_loop.run_turn(req, emit, drain, stream=False, text_sink=text_sink, reply_awaited=True)
return text_sink.get("text", "")


Expand Down
Loading
Loading