1use roder_api::inference::{
2 InstructionBundle, ModelHarnessProfile, ModelInstructionOverlay, RuntimeProfile,
3};
4
5pub const RODER_INSTRUCTIONS: &str = r#"You are Roder, a Rust-native coding agent running inside a terminal TUI on a user's computer.
6
7Roder is inspired by OpenAI Codex and the original Gode agent harness. Within this context, "Roder" refers to this open-source coding-agent harness and TUI, not a language model.
8
9## How You Work
10
11- Be precise, safe, and helpful.
12- Keep responses concise and direct unless the user asks for detail.
13- Prefer actionable guidance and concrete next steps.
14- Continue working until the user's coding task is genuinely handled.
15- Use the tools provided by the harness to inspect files, search the workspace, and make progress.
16
17## Workspace And Tools
18
19- Treat the current workspace as the user's repository.
20- When searching for text or files, prefer fast targeted search. If a search tool is available, use it before broad manual inspection.
21- Read relevant files before making assumptions about the codebase.
22- Keep edits scoped to the user's request and consistent with existing project patterns.
23- The available tool set depends on how this Roder thread is configured. Do not claim access to tools that are not exposed in the current turn.
24- Roder exposes tools through its Responses namespace and tool search. Use the available tool names directly; do not assume a legacy functions namespace or that only the most recently used tool is available.
25- When discovery tools are available, use `discovery.list`, `discovery.search`, or `discovery.read` before using unfamiliar tools, MCP servers, skills, commands, plugins, subagents, or file-backed artifact surfaces. Reading a discovery item promotes its detailed schema or instructions for the thread.
26
27## Editing Constraints
28
29- Default to ASCII when editing or creating files. Only introduce non-ASCII when clearly justified or when the file already uses it.
30- Add succinct comments only when they clarify non-obvious logic.
31- Prefer dedicated file-editing tools (`apply_patch`, `edit`, `multi_edit`, or `write_file`) for source changes when they are available. Use shell commands for editing only when the requested edit cannot be expressed with the available editing tools or those tools fail.
32- Do not revert changes you did not make unless the user explicitly asks.
33- You may be in a dirty git worktree. Ignore unrelated work from other agents or the user.
34- Do not use destructive operations such as hard resets or deleting user work unless explicitly requested.
35
36## Validation
37
38- When you change code, run the most relevant tests or build commands available for the touched area.
39- Start with focused checks, then broaden when confidence increases.
40- If you cannot run a useful validation command, say exactly what was not verified and why.
41
42## Communication
43
44- Before making tool calls, send a brief preamble message to the user explaining what you are about to do.
45- Group related tool calls under one concise preamble instead of narrating every trivial read separately.
46- Keep preambles to 1-2 sentences focused on immediate, tangible next steps; for quick updates, aim for 8-12 words.
47- Build on prior context in later preambles so the user can follow progress and understand the next action.
48- Explain what changed and why in plain engineering language.
49- If an operation fails, surface the key error and the likely next debugging step.
50- Avoid dumping large files or logs into the response; summarize and reference paths where useful."#;
51
52pub fn default_instructions() -> InstructionBundle {
53 let mut system = RODER_INSTRUCTIONS.to_string();
54 if cfg!(target_os = "windows") {
55 system.push_str(WINDOWS_SYSTEM_INSTRUCTIONS);
56 }
57 InstructionBundle {
58 system: Some(system),
59 developer: None,
60 developer_context: None,
61 }
62}
63
64const WINDOWS_SYSTEM_INSTRUCTIONS: &str = r#"
65
66## Windows Runtime
67
68- You are running on Windows.
69- Prefer PowerShell commands and PowerShell syntax for shell operations. This shell guidance does not supersede the instruction to prefer dedicated file-editing tools for source changes.
70- Use Windows paths and commands when referring to files, processes, environment variables, and filesystem operations."#;
71
72const NON_INTERACTIVE_INSTRUCTIONS: &str = r#"## Runtime Profile
73
74This turn is running in a non-interactive profile. Do not wait for unavailable user clarification. Assume reasonable defaults, state assumptions briefly when needed, and continue to a concrete final result."#;
75
76const EVAL_INSTRUCTIONS: &str = r#"## Eval Runtime Profile
77
78This turn is running in eval mode. Do not wait for user clarification unless explicit fixture answers are available. Assume reasonable defaults, keep progress observable through tools and events, and reach a final answer only after the task has been handled."#;
79
80const TASK_LEDGER_REQUIRED_INSTRUCTIONS: &str = r#"## Task Ledger Required
81
82This eval task is decomposed work. The first tool call must be `task_ledger.update`; do not call shell, search, web, file, or edit tools before the ledger exists. Keep exactly one item in progress and include evidence when marking items completed."#;
83
84const PLAN_MODE_INSTRUCTIONS: &str = r#"## Plan Mode
85
86You are in plan mode. Do not make file changes or run implementation commands yet. Inspect and discuss as needed, then present a concrete implementation plan to the user.
87
88When the plan is ready for approval, call `exit_plan_mode` with:
89- `summary`: the user-visible plan, in Markdown.
90- `next_steps`: concise implementation steps.
91- `target_mode`: `default` unless the user explicitly asked for a more permissive mode.
92
93After the user approves, the harness exits plan mode and the turn may continue with implementation. If the user rejects, keep discussing and revise the plan."#;
94
95const LITERAL_TOOL_OUTPUTS_OVERLAY: &str = r#"## Model Harness Profile
96
97Tool outputs are literal evidence from the harness. Prefer exact filenames, command output, and structured tool results over inferred state."#;
98
99const INTUITIVE_CONTEXT_OVERLAY: &str = r#"## Model Harness Profile
100
101Use the provided context as the current working set. Ask for or inspect missing files before assuming project structure outside the visible evidence."#;
102
103pub fn apply_thread_developer_instructions(
107 mut instructions: InstructionBundle,
108 addition: &str,
109) -> InstructionBundle {
110 let addition = addition.trim();
111 if addition.is_empty() {
112 return instructions;
113 }
114 instructions.developer = Some(match instructions.developer {
115 Some(existing) if !existing.trim().is_empty() => format!("{addition}\n\n{existing}"),
116 _ => addition.to_string(),
117 });
118 instructions
119}
120
121pub fn apply_turn_developer_context(
130 mut instructions: InstructionBundle,
131 context: &str,
132) -> InstructionBundle {
133 let context = context.trim();
134 if context.is_empty() {
135 return instructions;
136 }
137 instructions.developer_context = Some(context.to_string());
138 instructions
139}
140
141pub fn apply_runtime_profile(
142 mut instructions: InstructionBundle,
143 profile: RuntimeProfile,
144) -> InstructionBundle {
145 let addition = match profile {
146 RuntimeProfile::Interactive => return instructions,
147 RuntimeProfile::NonInteractive => NON_INTERACTIVE_INSTRUCTIONS,
148 RuntimeProfile::Eval => EVAL_INSTRUCTIONS,
149 };
150 instructions.developer = Some(match instructions.developer {
151 Some(existing) if !existing.trim().is_empty() => format!("{existing}\n\n{addition}"),
152 _ => addition.to_string(),
153 });
154 instructions
155}
156
157pub fn apply_task_ledger_required(mut instructions: InstructionBundle) -> InstructionBundle {
158 instructions.developer = Some(match instructions.developer {
159 Some(existing) if !existing.trim().is_empty() => {
160 format!("{existing}\n\n{TASK_LEDGER_REQUIRED_INSTRUCTIONS}")
161 }
162 _ => TASK_LEDGER_REQUIRED_INSTRUCTIONS.to_string(),
163 });
164 instructions
165}
166
167pub fn apply_plan_mode(mut instructions: InstructionBundle) -> InstructionBundle {
168 instructions.developer = Some(match instructions.developer {
169 Some(existing) if !existing.trim().is_empty() => {
170 format!("{existing}\n\n{PLAN_MODE_INSTRUCTIONS}")
171 }
172 _ => PLAN_MODE_INSTRUCTIONS.to_string(),
173 });
174 instructions
175}
176
177pub fn apply_model_instruction_overlay(
178 mut instructions: InstructionBundle,
179 profile: &ModelHarnessProfile,
180) -> InstructionBundle {
181 let addition = match profile.instruction_overlay {
182 ModelInstructionOverlay::Standard => return instructions,
183 ModelInstructionOverlay::LiteralToolOutputs => LITERAL_TOOL_OUTPUTS_OVERLAY,
184 ModelInstructionOverlay::IntuitiveContext => INTUITIVE_CONTEXT_OVERLAY,
185 };
186 instructions.developer = Some(match instructions.developer {
187 Some(existing) if !existing.trim().is_empty() => format!("{existing}\n\n{addition}"),
188 _ => addition.to_string(),
189 });
190 instructions
191}
192
193#[cfg(test)]
194mod tests {
195 use super::*;
196
197 #[test]
198 fn base_instructions_name_lazy_discovery_tools() {
199 let instructions = default_instructions();
200 let system = instructions.system.expect("system instructions");
201 assert!(system.contains("discovery.list"));
202 assert!(system.contains("discovery.search"));
203 assert!(system.contains("discovery.read"));
204 assert!(system.contains("promotes its detailed schema"));
205 }
206
207 #[test]
208 fn windows_instructions_match_host_platform() {
209 let instructions = default_instructions();
210 let system = instructions.system.expect("system instructions");
211 assert_eq!(
212 system.contains("You are running on Windows."),
213 cfg!(target_os = "windows")
214 );
215 assert_eq!(
216 system.contains("Prefer PowerShell commands"),
217 cfg!(target_os = "windows")
218 );
219 assert_eq!(
220 system.contains(
221 "does not supersede the instruction to prefer dedicated file-editing tools"
222 ),
223 cfg!(target_os = "windows")
224 );
225 }
226
227 #[test]
228 fn base_instructions_prefer_edit_tools_for_source_changes() {
229 let instructions = default_instructions();
230 let system = instructions.system.expect("system instructions");
231 assert!(system.contains("Prefer dedicated file-editing tools"));
232 assert!(system.contains("apply_patch"));
233 }
234
235 #[test]
236 fn plan_mode_instructions_tell_model_to_request_approval() {
237 let instructions = apply_plan_mode(InstructionBundle::default());
238 let developer = instructions.developer.expect("developer instructions");
239 assert!(developer.contains("You are in plan mode"));
240 assert!(developer.contains("exit_plan_mode"));
241 assert!(developer.contains("After the user approves"));
242 }
243
244 #[test]
245 fn thread_developer_instructions_prepend_to_developer_slot() {
246 let instructions = apply_runtime_profile(
247 apply_thread_developer_instructions(
248 default_instructions(),
249 "You are embedded in a host app.",
250 ),
251 RuntimeProfile::NonInteractive,
252 );
253 let developer = instructions.developer.expect("developer instructions");
254 assert!(developer.starts_with("You are embedded in a host app."));
255 assert!(developer.contains("non-interactive profile"));
256 assert!(
257 instructions
258 .system
259 .expect("system")
260 .starts_with("You are Roder")
261 );
262
263 let unchanged = apply_thread_developer_instructions(default_instructions(), " ");
264 assert_eq!(unchanged.developer, None);
265 }
266
267 #[test]
268 fn turn_developer_context_fills_dedicated_slot_after_thread_instructions() {
269 let instructions = apply_turn_developer_context(
270 apply_runtime_profile(
271 apply_thread_developer_instructions(
272 default_instructions(),
273 "You are embedded in a host app.",
274 ),
275 RuntimeProfile::NonInteractive,
276 ),
277 "Connected accounts: example-service.",
278 );
279 let developer = instructions.developer.expect("developer instructions");
280 assert!(developer.starts_with("You are embedded in a host app."));
281 assert!(!developer.contains("Connected accounts"));
282 assert_eq!(
283 instructions.developer_context.as_deref(),
284 Some("Connected accounts: example-service.")
285 );
286
287 let unchanged = apply_turn_developer_context(default_instructions(), " ");
288 assert_eq!(unchanged.developer_context, None);
289 }
290
291 #[test]
292 fn base_instructions_include_intermediary_message_guidance() {
293 let instructions = default_instructions();
294 let system = instructions.system.expect("system instructions");
295 assert!(system.starts_with("You are Roder"));
296 assert!(system.contains("Before making tool calls, send a brief preamble message"));
297 assert!(system.contains("Group related tool calls under one concise preamble"));
298 assert!(system.contains("Build on prior context in later preambles"));
299 }
300}