-
-
Notifications
You must be signed in to change notification settings - Fork 4
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.
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.
compression_exemption.rs and related chat-history tests show event subkind handling. The prompt’s list includes:
mode_switchtool_decisionide_callbackprocess_completedcron_fireticksummarization_markerverifier_reportcancellation_notesystem_noticeplan_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.
crates/refact-chat-history/src/compression_exemption.rs contains the gating logic:
-
role == "plan"→CompressionExemption::Never -
event.subkind == "plan_delta"→Never -
tick/mode_switch→DropOnAge -
process_completed/cron_fire→KeepRecentN
This is why plans and plan deltas survive history compression, while some other system events can be trimmed.
The hidden plan system is exposed through three tools:
set_planupdate_planget_plan
The implementation lives in src/tools/tool_set_plan.rs, tool_update_plan.rs, and tool_get_plan.rs.
-
set_plancreates or replaces the current plan state. -
update_planappends a delta/update instead of rewriting the whole plan. -
get_planretrieves 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.
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.
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]
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.
Refact on GitHub: https://github.com/JegernOUTT/refact
- Agent Modes
- Agent Tools
- Task Planner & Cards
- Worktrees
- Subagents
- Memory & Knowledge
- Hidden Roles & Plans
- Context Compression
- Scheduler & Cron
- Processes & PTY
- Buddy
- MCP
- Skills, Commands & Hooks
- Marketplace
- Chat System
- Providers
- Caps & Models
- Code Completion (FIM)
- AST
- VecDB
- Exec Runtime
- HTTP API
- Checkpoints & Git
- Voice