// tools/journal.rs — Native journal tool // // Appends entries directly to the journal file without spawning a // shell. The entry is persisted to disk immediately; // build_context_window() picks it up on the next compaction. // // This tool is "ephemeral" — after the API processes the tool call // and result, the agent strips them from the conversation history. // The journal file is the durable store; keeping the tool call in // context would just waste tokens on something already persisted. use anyhow::{Context, Result}; use serde_json::json; use crate::agent::types::ToolDef; /// Tool name — used by the agent to identify ephemeral tool calls. pub const TOOL_NAME: &str = "journal"; pub fn definition() -> ToolDef { ToolDef::new( TOOL_NAME, "Write a journal entry. The entry is appended to your journal file \ with an automatic timestamp. Use this for experiences, reflections, \ observations — anything worth remembering across sessions. \ This tool has zero context cost: entries are persisted to disk \ and loaded by the context manager, not kept in conversation history.", json!({ "type": "object", "properties": { "entry": { "type": "string", "description": "The journal entry text. Write naturally — \ experiences, not task logs." } }, "required": ["entry"] }), ) } pub fn write_entry(args: &serde_json::Value) -> Result { let entry = args["entry"] .as_str() .context("entry is required")?; let journal_path = crate::agent::journal::default_journal_path(); // Ensure parent directory exists if let Some(parent) = journal_path.parent() { std::fs::create_dir_all(parent).ok(); } let timestamp = chrono::Utc::now().format("%Y-%m-%dT%H:%M"); // Append with the same format as poc-journal write use std::io::Write; let mut file = std::fs::OpenOptions::new() .create(true) .append(true) .open(&journal_path) .with_context(|| format!("Failed to open {}", journal_path.display()))?; writeln!(file, "\n## {}\n\n{}", timestamp, entry) .with_context(|| "Failed to write journal entry")?; Ok("Logged.".to_string()) }