Repository navigation
Consequential-action gate: external writes need a user request; persona 'proactive' = notice (#685) - #687
Merged
Conversation
rockfordlhotka
force-pushed
the
issue-683/synthesis-evaluator-evidence
branch
from
October 10, 2026 20:54
d00c67d to
3881144
Compare
rockfordlhotka
force-pushed
the
issue-685/consequential-action-gate
branch
from
October 10, 2026 20:54
c8fad44 to
daf86d3
Compare
rockfordlhotka
force-pushed
the
issue-683/synthesis-evaluator-evidence
branch
from
October 10, 2026 21:03
3881144 to
80d9b3f
Compare
Base automatically changed from
issue-683/synthesis-evaluator-evidence
to
main
October 10, 2026 21:08
A context-only user message ("the talk doesn't exist yet") led a subagent and
its wisps to create seven real calendar events. The persona told the agent to
act proactively, and nothing in the framework separated noticing from changing
an external system.
Gate (framework)
- ConsequentialActions: a call is consequential when ToolSideEffects says it
writes AND it reaches an MCP server (mcp_invoke_tool by tool_name, or a typed
{server}__{tool} wrapper), or it creates/cancels the agent's scheduled tasks,
or it is listed in AgentHost:ConsequentialActionGate:ExternalTools. file_*,
memory, task list, skills, rules, scripts and delegation stay agent-local.
- ActionGateScope(RunOrigin, UserAskedFor), bound per async flow by RunAsync
(ActionGateContext). UserTurn / SubagentOfUserTurn need an instruction;
Scheduled, A2A and Unknown origins keep acting. RunAsync(actionGate:) is set
by UserMessageHandler, UserFeedbackHandler, ScheduledTaskHandler, the A2A
inbound handlers and the subagent path; nested runs (wisp LLM steps,
workers) inherit. A user turn uses ClassifyUserRequest with
followsAgentMessage, so "yes" after a proposal is an instruction.
- SubagentManager captures the spawning scope (SubagentEntry.ActionGate);
SubagentRunner runs under it and carries RunOrigin/UserAskedFor on
SubagentResultMessage; the synthesis turn runs under the same scope.
- Checked at every dispatch site: RockBotFunctionInvokingChatClient, both
text-loop sites, WispExecutor direct steps. A refused call is not run; it
returns "Not run: <tool> would change <server> but the user did not ask for
that. Propose it ...", is recorded as failed, logged and counted
(rockbot.agent.consequential_action.gated).
- AgentHost:ConsequentialActionGate:Enabled (default true) turns it off.
Evaluator backstop
- For an information-only request in a user-turn lineage, successful external
changes in the loop's or relayed subagents' calls are listed under "External
changes the user did not ask for" and are grounds for INCOMPLETE; the
re-prompt says to tell the user plainly what changed so they can keep or
undo it. The follow-up evaluator no longer suggests unrequested external
changes.
Persona (seed files; PVC copies need refreshing)
- soul.md, directives.md: proactive = notice and surface; acting on your own
initiative is limited to memory, working memory and drafts/; external
changes need a request, offered in one sentence.
- subagent-directives.md, worker-directives.md: on a "Not run" refusal, don't
retry or reroute; report the proposal.
- common-directives.md: use convert_time for cross-zone times.
Time conversion
- convert_time(datetime, from_timezone, to_timezone) registry tool
(RockBot.Tools.TimeConversion, AddTimeTools) with the DST rules of the date:
09:45 Europe/Amsterdam on 2026-10-28 is 03:45 America/Chicago.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
rockfordlhotka
force-pushed
the
issue-685/consequential-action-gate
branch
from
October 10, 2026 21:08
daf86d3 to
d026734
Compare
This was referenced Oct 10, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #685. Part of #671. Stacked on #684 (#683) → #682 (#665) → #675 (#666). The design was approved by the repo owner.
Problem
A context-only message (a talk abstract and "the talk doesn't exist yet") led RockBot to create calendar events on the user's real
marimer-workcalendar. The persona directives say to anticipate needs and "do it immediately… do not ask permission", and nothing separated noticing from acting on external systems.Changes
ConsequentialActionGate.cs).ToolSideEffects) that targets an external system. That means any MCP write (mcp_invoke_tooljudged by itstool_name, or a typed{server}__{tool}),schedule_task/cancel_scheduled_task(otherwise a scheduled task could act later without a request), and anything listed inExternalTools.ClassifyUserRequest; "yes" after a proposal counts as an instruction. Subagents inherit the user's classification, so their own task description can't launder an information-only message. Wisps and workers inherit the enclosing scope. Scheduled, A2A-inbound and Unknown origins are allowed (unchanged behaviour).rockbot.agent.consequential_action.gated.AgentHost:ConsequentialActionGate:Enabled(default true).soul.mdanddirectives.md: "Proactive Behaviors" becomes "Proactive Noticing". Notice and surface freely; acting on your own initiative is limited to the agent's own state. Unrequested external changes get a one-sentence offer. "Response Endings" is scoped to match.subagent-directives.mdandworker-directives.md: on a "Not run" refusal, report the proposal; don't retry or reroute.convert_timetool. Converts between IANA zones and shows both UTC offsets. Amsterdam 09:45 on 2026-10-28 → Chicago 03:45; a model had computed 2:45. A line incommon-directives.mdpoints to it.Deploy note
Refresh these PVC copies by hand:
soul.md,directives.md,common-directives.md,subagent-directives.md,worker-directives.md.Known gaps (follow-ups)
ToolSideEffectsvariant (PR Thread-aware tier routing and mid-turn escalation (#663) #674) when both land; the combined preview branch already does this.Tests
ConsequentialActionGateTests(45): classifier, scope, native and text runs, the subagent and nested cases, allowed origins, evaluator backstop.WispConsequentialActionGateTests(3).TimeConversionTests(7).SubagentManagerTests(+2).Full suite: about 4,515 passed, 0 failed.
🤖 Generated with Claude Code