Simplify system prompt: remove coding style and parallel tool call sections
- Remove IMPORTANT FOR CODING section (~1,500 chars of coding guidelines) - Remove <use_parallel_tool_calls> block (~500 chars) - Remove unused const_format dependency from g3-core - Simplify get_system_prompt_for_native() to just return base prompt - Response Guidelines now cleanly ends the static prompt Prompt reduced from ~8,500 to ~6,500 characters.
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -1425,7 +1425,6 @@ dependencies = [
|
||||
"async-trait",
|
||||
"base64 0.22.1",
|
||||
"chrono",
|
||||
"const_format",
|
||||
"futures-util",
|
||||
"g3-computer-control",
|
||||
"g3-config",
|
||||
|
||||
@@ -43,7 +43,6 @@ tree-sitter-scheme = "0.24"
|
||||
streaming-iterator = "0.1"
|
||||
walkdir = "2.4"
|
||||
|
||||
const_format = "0.2"
|
||||
base64 = "0.22.1"
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
@@ -1,29 +1,3 @@
|
||||
use const_format::concatcp;
|
||||
const CODING_STYLE: &'static str = "# IMPORTANT FOR CODING:
|
||||
It is very important that you adhere to these principles when writing code. I will use a code quality tool to assess the code you have generated.
|
||||
|
||||
### Most important for coding: Specific guideline for code design:
|
||||
|
||||
- Functions and methods should be short - at most 80 lines, ideally under 40.
|
||||
- Classes should be modular and composable. They should not have more than 20 methods.
|
||||
- Do not write deeply nested (above 6 levels deep) ‘if’, ‘match’ or ‘case’ statements, rather refactor into separate logical sections or functions.
|
||||
- Code should be written such that it is maintainable and testable.
|
||||
- For Rust code write *ALL* test code into a ‘tests’ directory that is a peer to the ‘src’ of each crate, and is for testing code in that crate.
|
||||
- For Python code write *ALL* test code into a top level ‘tests’ directory.
|
||||
- Each non-trivial function should have test coverage. DO NOT WRITE TESTS FOR INDIVIDUAL FUNCTIONS / METHODS / CLASSES unless they are large and important. Instead write something
|
||||
at a higher level of abstraction, closer to an integration test.
|
||||
- Write tests in separate files, where the filename should match the main implementation and adding a “_test” suffix.
|
||||
|
||||
### Important for coding: General guidelines for code design:
|
||||
|
||||
Keep the code as simple as possible, with few if any external dependencies.
|
||||
DRY (Don’t repeat yourself) - each small piece code may only occur exactly once in the entire system.
|
||||
KISS (Keep it simple, stupid!) - keep each small piece of software simple and unnecessary complexity should be avoided.
|
||||
YAGNI (You ain’t gonna need it) - Always implement things when you actually need them never implements things before you need them.
|
||||
|
||||
Use Descriptive Names for Code Elements. - As a rule of thumb, use more descriptive names for larger scopes. e.g., name a loop counter variable “i” is good when the scope of the loop is a single line. But don’t name some class field or method parameter “i”.
|
||||
";
|
||||
|
||||
const SYSTEM_NATIVE_TOOL_CALLS: &'static str =
|
||||
"You are G3, an AI programming agent of the same skill level as a seasoned engineer at a major technology company. You analyze given tasks and write code to achieve goals.
|
||||
|
||||
@@ -126,6 +100,39 @@ IMPORTANT: If the user asks you to just respond with text (like \"just say hello
|
||||
|
||||
Do not explain what you're going to do - just do it by calling the tools.
|
||||
|
||||
# Project Memory
|
||||
|
||||
Project memory (if available) is automatically loaded at startup alongside README.md and AGENTS.md. It contains feature locations, patterns, and entry points discovered in previous sessions.
|
||||
|
||||
**IMPORTANT**: After completing a task where you discovered code locations, call the **`remember`** tool to save them. This helps avoid re-discovering the same code in future sessions.
|
||||
|
||||
## Memory Format
|
||||
|
||||
Use this format when calling `remember`:
|
||||
|
||||
### <Feature Name>
|
||||
- `<file_path>` [<start>..<end>] - `<function_name>()`, `<StructName>`
|
||||
|
||||
## Patterns
|
||||
|
||||
### <Pattern Name>
|
||||
1. Step one
|
||||
2. Step two
|
||||
|
||||
## When to Remember
|
||||
|
||||
Call `remember` at the end of your turn IF you discovered:
|
||||
- A feature's location (file + char range + function/struct names)
|
||||
- A useful pattern or workflow
|
||||
- An entry point for a subsystem
|
||||
|
||||
Do NOT save duplicates - check the Project Memory section (loaded at startup) to see what's already known.
|
||||
|
||||
## Example
|
||||
|
||||
After discovering where WebDriver tools live:
|
||||
|
||||
{\"tool\": \"remember\", \"args\": {\"notes\": \"## Features\\n\\n### WebDriver Browser Automation\\n- crates/g3-core/src/tools/webdriver.rs [0..21750] - execute_webdriver_start(), execute_webdriver_navigate(), WebDriverSession\"}}
|
||||
|
||||
# Response Guidelines
|
||||
|
||||
@@ -134,21 +141,11 @@ Do not explain what you're going to do - just do it by calling the tools.
|
||||
- Use quick and clever humor when appropriate.
|
||||
";
|
||||
|
||||
pub const SYSTEM_PROMPT_FOR_NATIVE_TOOL_USE: &'static str =
|
||||
concatcp!(SYSTEM_NATIVE_TOOL_CALLS, CODING_STYLE);
|
||||
pub const SYSTEM_PROMPT_FOR_NATIVE_TOOL_USE: &'static str = SYSTEM_NATIVE_TOOL_CALLS;
|
||||
|
||||
/// Generate system prompt based on whether multiple tool calls are allowed
|
||||
pub fn get_system_prompt_for_native() -> String {
|
||||
// Always allow multiple tool calls - they are processed sequentially after stream ends
|
||||
let base = SYSTEM_PROMPT_FOR_NATIVE_TOOL_USE.to_string();
|
||||
base.replace(
|
||||
"2. Call the appropriate tool with the required parameters",
|
||||
"2. Call the appropriate tool(s) with the required parameters - you may call multiple tools in parallel when appropriate.
|
||||
<use_parallel_tool_calls>
|
||||
Whenever you perform multiple independent operations, invoke all relevant tools simultaneously rather than sequentially. Prioritize calling tools in parallel whenever possible. For example, when reading 3 files, run 3 tool calls in parallel to read all 3 files into context at the same time. When running multiple read-only commands like `ls` or `list_dir`, always run all of the commands in parallel. Err on the side of maximizing parallel tool calls rather than running too many tools sequentially.
|
||||
</use_parallel_tool_calls>
|
||||
"
|
||||
)
|
||||
SYSTEM_PROMPT_FOR_NATIVE_TOOL_USE.to_string()
|
||||
}
|
||||
|
||||
const SYSTEM_NON_NATIVE_TOOL_USE: &'static str =
|
||||
@@ -229,7 +226,7 @@ Short description for providers without native calling specs:
|
||||
For reading files, prioritize use of code_search tool use with multiple search requests per call instead of read_file, if it makes sense.
|
||||
|
||||
Exception to using ONE tool at a time:
|
||||
If all you’re doing is WRITING files, and you don’t need to do anything else between each step.
|
||||
If all you're doing is WRITING files, and you don't need to do anything else between each step.
|
||||
You can issue MULTIPLE write_file tool calls in a request, however you may ONLY make a SINGLE write_file call for any file in that request.
|
||||
For example you may call:
|
||||
[START OF REQUEST]
|
||||
@@ -318,15 +315,13 @@ Skip TODO tools for simple single-step tasks:
|
||||
|
||||
If you can complete it with 1-2 tool calls, skip TODO.
|
||||
|
||||
|
||||
# Response Guidelines
|
||||
|
||||
- Use Markdown formatting for all responses except tool calls.
|
||||
- Whenever taking actions, use the pronoun 'I'
|
||||
";
|
||||
|
||||
pub const SYSTEM_PROMPT_FOR_NON_NATIVE_TOOL_USE: &'static str =
|
||||
concatcp!(SYSTEM_NON_NATIVE_TOOL_USE, CODING_STYLE);
|
||||
pub const SYSTEM_PROMPT_FOR_NON_NATIVE_TOOL_USE: &'static str = SYSTEM_NON_NATIVE_TOOL_USE;
|
||||
|
||||
/// The G3 identity line that gets replaced in agent mode
|
||||
const G3_IDENTITY_LINE: &str = "You are G3, an AI programming agent of the same skill level as a seasoned engineer at a major technology company. You analyze given tasks and write code to achieve goals.";
|
||||
|
||||
@@ -272,6 +272,22 @@ fn create_core_tools(exclude_research: bool) -> Vec<Tool> {
|
||||
});
|
||||
}
|
||||
|
||||
// Project memory tool (memory is auto-loaded at startup, only remember is needed)
|
||||
tools.push(Tool {
|
||||
name: "remember".to_string(),
|
||||
description: "Update the project memory with new discoveries. Call this at the END of your turn (before your summary) if you discovered something worth noting. Provide your notes in markdown format - they will be merged with existing memory.".to_string(),
|
||||
input_schema: json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"notes": {
|
||||
"type": "string",
|
||||
"description": "New discoveries to add to memory in markdown format. Use the format:\n### Feature Name\n- `file/path.rs` [start..end] - `function_name()`, `StructName`\n\nDo not include content already in memory."
|
||||
}
|
||||
},
|
||||
"required": ["notes"]
|
||||
}),
|
||||
});
|
||||
|
||||
tools
|
||||
}
|
||||
|
||||
@@ -477,8 +493,9 @@ mod tests {
|
||||
let tools = create_core_tools(false);
|
||||
// Should have the core tools: shell, background_process, read_file, read_image,
|
||||
// write_file, str_replace, take_screenshot,
|
||||
// todo_read, todo_write, code_coverage, code_search, research (12 total)
|
||||
assert_eq!(tools.len(), 12);
|
||||
// todo_read, todo_write, code_coverage, code_search, research, remember
|
||||
// (13 total - memory is auto-loaded, only remember tool needed)
|
||||
assert_eq!(tools.len(), 13);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -492,15 +509,15 @@ mod tests {
|
||||
fn test_create_tool_definitions_core_only() {
|
||||
let config = ToolConfig::default();
|
||||
let tools = create_tool_definitions(config);
|
||||
assert_eq!(tools.len(), 12);
|
||||
assert_eq!(tools.len(), 13);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_tool_definitions_all_enabled() {
|
||||
let config = ToolConfig::new(true, true);
|
||||
let tools = create_tool_definitions(config);
|
||||
// 12 core + 15 webdriver = 27
|
||||
assert_eq!(tools.len(), 27);
|
||||
// 13 core + 15 webdriver = 28
|
||||
assert_eq!(tools.len(), 28);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -518,8 +535,8 @@ mod tests {
|
||||
let tools_with_research = create_core_tools(false);
|
||||
let tools_without_research = create_core_tools(true);
|
||||
|
||||
assert_eq!(tools_with_research.len(), 12);
|
||||
assert_eq!(tools_without_research.len(), 11);
|
||||
assert_eq!(tools_with_research.len(), 13);
|
||||
assert_eq!(tools_without_research.len(), 12);
|
||||
|
||||
assert!(tools_with_research.iter().any(|t| t.name == "research"));
|
||||
assert!(!tools_without_research.iter().any(|t| t.name == "research"));
|
||||
|
||||
@@ -7,7 +7,7 @@ use anyhow::Result;
|
||||
use tracing::{debug, warn};
|
||||
|
||||
use crate::tools::executor::ToolContext;
|
||||
use crate::tools::{file_ops, misc, research, shell, todo, webdriver};
|
||||
use crate::tools::{file_ops, memory, misc, research, shell, todo, webdriver};
|
||||
use crate::ui_writer::UiWriter;
|
||||
use crate::ToolCall;
|
||||
|
||||
@@ -44,6 +44,9 @@ pub async fn dispatch_tool<W: UiWriter>(
|
||||
// Research tool
|
||||
"research" => research::execute_research(tool_call, ctx).await,
|
||||
|
||||
// Project memory tools
|
||||
"remember" => memory::execute_remember(tool_call, ctx).await,
|
||||
|
||||
// WebDriver tools
|
||||
"webdriver_start" => webdriver::execute_webdriver_start(tool_call, ctx).await,
|
||||
"webdriver_navigate" => webdriver::execute_webdriver_navigate(tool_call, ctx).await,
|
||||
|
||||
178
crates/g3-core/src/tools/memory.rs
Normal file
178
crates/g3-core/src/tools/memory.rs
Normal file
@@ -0,0 +1,178 @@
|
||||
//! Project memory tool: remember.
|
||||
//!
|
||||
//! These tools provide a persistent "working memory" for the project,
|
||||
//! storing feature locations, patterns, and entry points discovered
|
||||
//! during g3 sessions.
|
||||
|
||||
use anyhow::Result;
|
||||
use chrono::Utc;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::ui_writer::UiWriter;
|
||||
use crate::ToolCall;
|
||||
|
||||
use super::executor::ToolContext;
|
||||
|
||||
/// Get the path to the memory file.
|
||||
/// Memory is stored at `.g3/memory.md` in the working directory.
|
||||
fn get_memory_path(working_dir: Option<&str>) -> PathBuf {
|
||||
let base = working_dir
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|| std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")));
|
||||
base.join(".g3").join("memory.md")
|
||||
}
|
||||
|
||||
/// Format the file size in a human-readable way.
|
||||
fn format_size(chars: usize) -> String {
|
||||
if chars < 1000 {
|
||||
format!("{} chars", chars)
|
||||
} else {
|
||||
format!("{:.1}k chars", chars as f64 / 1000.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Execute the remember tool.
|
||||
/// Merges new notes with existing memory and saves to file.
|
||||
pub async fn execute_remember<W: UiWriter>(
|
||||
tool_call: &ToolCall,
|
||||
ctx: &mut ToolContext<'_, W>,
|
||||
) -> Result<String> {
|
||||
let notes = tool_call
|
||||
.args
|
||||
.get("notes")
|
||||
.and_then(|v| v.as_str())
|
||||
.ok_or_else(|| anyhow::anyhow!("Missing required 'notes' parameter"))?;
|
||||
|
||||
let memory_path = get_memory_path(ctx.working_dir);
|
||||
|
||||
// Ensure .g3 directory exists
|
||||
if let Some(parent) = memory_path.parent() {
|
||||
std::fs::create_dir_all(parent)?;
|
||||
}
|
||||
|
||||
// Read existing memory or create new
|
||||
let existing = if memory_path.exists() {
|
||||
std::fs::read_to_string(&memory_path)?
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
// Merge notes with existing memory
|
||||
let updated = merge_memory(&existing, notes);
|
||||
|
||||
// Add/update header with timestamp and size
|
||||
let timestamp = Utc::now().format("%Y-%m-%dT%H:%M:%SZ").to_string();
|
||||
let size = format_size(updated.len());
|
||||
let final_content = update_header(&updated, ×tamp, &size);
|
||||
|
||||
// Write back
|
||||
std::fs::write(&memory_path, &final_content)?;
|
||||
|
||||
ctx.ui_writer
|
||||
.println(&format!("💾 Memory updated ({})", format_size(final_content.len())));
|
||||
|
||||
Ok(format!("Memory updated. Size: {}", format_size(final_content.len())))
|
||||
}
|
||||
|
||||
/// Merge new notes into existing memory.
|
||||
/// Appends new notes to the appropriate sections or creates new sections.
|
||||
fn merge_memory(existing: &str, new_notes: &str) -> String {
|
||||
if existing.is_empty() {
|
||||
// Start fresh with just the notes
|
||||
return new_notes.trim().to_string();
|
||||
}
|
||||
|
||||
// Simple merge strategy: append new notes to the end
|
||||
// The LLM is responsible for providing well-formatted notes
|
||||
// and avoiding duplicates (as instructed in the prompt)
|
||||
let existing_trimmed = existing.trim();
|
||||
let new_trimmed = new_notes.trim();
|
||||
|
||||
// Remove the header line if present (we'll re-add it)
|
||||
let existing_body = remove_header(existing_trimmed);
|
||||
|
||||
format!("{}\n\n{}", existing_body.trim(), new_trimmed)
|
||||
}
|
||||
|
||||
/// Remove the header line (# Project Memory and > Updated: ...) from content.
|
||||
fn remove_header(content: &str) -> String {
|
||||
let mut lines: Vec<&str> = content.lines().collect();
|
||||
|
||||
// Remove "# Project Memory" if first line
|
||||
if !lines.is_empty() && lines[0].starts_with("# Project Memory") {
|
||||
lines.remove(0);
|
||||
}
|
||||
|
||||
// Remove "> Updated: ..." line if present at start
|
||||
if !lines.is_empty() && lines[0].starts_with("> Updated:") {
|
||||
lines.remove(0);
|
||||
}
|
||||
|
||||
// Remove leading empty lines
|
||||
while !lines.is_empty() && lines[0].trim().is_empty() {
|
||||
lines.remove(0);
|
||||
}
|
||||
|
||||
lines.join("\n")
|
||||
}
|
||||
|
||||
/// Update or add the header with timestamp and size.
|
||||
fn update_header(content: &str, timestamp: &str, size: &str) -> String {
|
||||
let body = remove_header(content);
|
||||
format!(
|
||||
"# Project Memory\n> Updated: {} | Size: {}\n\n{}",
|
||||
timestamp,
|
||||
size,
|
||||
body.trim()
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_format_size() {
|
||||
assert_eq!(format_size(500), "500 chars");
|
||||
assert_eq!(format_size(999), "999 chars");
|
||||
assert_eq!(format_size(1000), "1.0k chars");
|
||||
assert_eq!(format_size(2500), "2.5k chars");
|
||||
assert_eq!(format_size(10000), "10.0k chars");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_merge_memory_empty() {
|
||||
let result = merge_memory("", "### New Feature\n- `file.rs` [0..100] - `func()`");
|
||||
assert_eq!(result, "### New Feature\n- `file.rs` [0..100] - `func()`");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_merge_memory_append() {
|
||||
let existing = "# Project Memory\n> Updated: 2025-01-10 | Size: 1k\n\n### Feature A\n- `a.rs` [0..50]";
|
||||
let new_notes = "### Feature B\n- `b.rs` [0..100]";
|
||||
let result = merge_memory(existing, new_notes);
|
||||
|
||||
assert!(result.contains("### Feature A"));
|
||||
assert!(result.contains("### Feature B"));
|
||||
assert!(!result.contains("# Project Memory")); // Header removed for re-adding
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove_header() {
|
||||
let content = "# Project Memory\n> Updated: 2025-01-10 | Size: 1k\n\n### Feature\n- details";
|
||||
let result = remove_header(content);
|
||||
assert!(!result.contains("# Project Memory"));
|
||||
assert!(!result.contains("> Updated:"));
|
||||
assert!(result.contains("### Feature"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_update_header() {
|
||||
let content = "### Feature\n- details";
|
||||
let result = update_header(content, "2025-01-10T12:00:00Z", "500 chars");
|
||||
|
||||
assert!(result.starts_with("# Project Memory"));
|
||||
assert!(result.contains("> Updated: 2025-01-10T12:00:00Z | Size: 500 chars"));
|
||||
assert!(result.contains("### Feature"));
|
||||
}
|
||||
}
|
||||
@@ -8,9 +8,11 @@
|
||||
//! - `webdriver` - Browser automation via WebDriver
|
||||
//! - `misc` - Other tools (screenshots, code search, etc.)
|
||||
//! - `research` - Web research via scout agent
|
||||
//! - `memory` - Project memory (read_memory, remember)
|
||||
|
||||
pub mod executor;
|
||||
pub mod file_ops;
|
||||
pub mod memory;
|
||||
pub mod misc;
|
||||
pub mod research;
|
||||
pub mod shell;
|
||||
|
||||
Reference in New Issue
Block a user