Skip to content

Hidden Roles and Plans

refact-planner edited this page Jun 7, 2026 · 1 revision

Hidden Roles and Plans

The chat system uses hidden message roles plus plan tools to carry structured state without exposing internal wire formats to the model.

Hidden roles

The engine stores some internal messages as hidden roles in the chat stream. The prompt called out three main shapes:

Hidden role Stored shape Purpose
event extra.event = { subkind, source, ... } Internal event stream for system/activity markers
plan extra.plan = { ... } Full plan payload preserved as a structured message
event(plan_delta) role = event, extra.event.subkind = "plan_delta", payload in event data Append-only plan updates without replacing the whole plan

These are wire/internal representations; the model-facing output must not use literal hidden roles.

Event subkinds

compression_exemption.rs and related chat-history tests show event subkind handling. The prompt’s list includes:

  • mode_switch
  • tool_decision
  • ide_callback
  • process_completed
  • cron_fire
  • tick
  • summarization_marker
  • verifier_report
  • cancellation_note
  • system_notice
  • plan_delta

Compression behavior depends on subkind. For example, chat-history logic treats plan_delta as never compressible, while tick/mode_switch are dropped on age and process_completed/cron_fire are kept for recent history.

Compression rules for hidden events

crates/refact-chat-history/src/compression_exemption.rs contains the gating logic:

  • role == "plan"CompressionExemption::Never
  • event.subkind == "plan_delta"Never
  • tick / mode_switchDropOnAge
  • process_completed / cron_fireKeepRecentN

This is why plans and plan deltas survive history compression, while some other system events can be trimmed.

Plan tools

The hidden plan system is exposed through three tools:

  • set_plan
  • update_plan
  • get_plan

The implementation lives in src/tools/tool_set_plan.rs, tool_update_plan.rs, and tool_get_plan.rs.

Behavior

  • set_plan creates or replaces the current plan state.
  • update_plan appends a delta/update instead of rewriting the whole plan.
  • get_plan retrieves the current plan for display or tool use.

The render path maps plan data into user-context blocks such as <plan> and <plan-update> rather than exposing literal event/plan roles to the model.

Size caps

The prompt specified these caps:

  • plan body: 96 KB
  • plan delta: 16 KB

Those caps keep plan state bounded while still allowing append-only updates.

Wire mapping

Hidden plan state is rendered into user-visible context blocks that the model can read safely.

  • plan<plan> ... </plan>
  • plan delta<plan-update> ... </plan-update>
  • hidden event roles remain internal and are not sent as literal event/plan roles
flowchart LR
  Tools[set_plan / update_plan / get_plan] --> Hidden[hidden plan state]
  Hidden --> Render[<plan> / <plan-update> blocks]
  Render --> Model[LLM context]
Loading

Why this matters

Hidden roles let the engine keep durable structured state, while the wire mapping keeps model input stable and policy-compliant. Plan deltas also survive compression, so long-lived task state is not destroyed by history trimming.

See also Chat System.

Clone this wiki locally