pub mod checkpoint;
pub mod land;
pub mod tools;
pub use checkpoint::{due, state};
pub use land::{LandOutcome, land};
use super::{Error, subagent};
use brazen::Tool;
use serde_json::json;
use std::path::Path;
pub const COMPACTOR_ROLE: &str = "compactor";
pub fn builtin_tool_schemas() -> Vec<Tool> {
vec![
Tool::Custom {
name: tools::WRITE_SUMMARY.to_string(),
description: Some(
"Write a signal-preserving summary to the next summary/<NNN>.md \
on this branch."
.to_string(),
),
input_schema: json!({
"type": "object",
"properties": {
"content": {
"type": "string",
"description": "The summary body, written verbatim."
}
},
"required": ["content"],
"additionalProperties": false
}),
strict: None,
},
Tool::Custom {
name: tools::MARK_FOR_DELETION.to_string(),
description: Some(
"Nominate a branch-relative path for removal (deletion-only: this \
can remove, never rewrite)."
.to_string(),
),
input_schema: json!({
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "Branch-relative path to remove, e.g. messages/003-user.md."
}
},
"required": ["path"],
"additionalProperties": false
}),
strict: None,
},
]
}
pub fn injected(role: &str) -> &'static [&'static str] {
if role == COMPACTOR_ROLE {
&[tools::WRITE_SUMMARY, tools::MARK_FOR_DELETION]
} else {
&[]
}
}
pub fn compactor_goal(parent_worktree: &Path, parent_branch: &str) -> Result<String, Error> {
let parent_goal = std::fs::read_to_string(parent_worktree.join(subagent::GOAL_FILE))?;
Ok(format!(
"You are the compactor for branch `{parent_branch}`.\n\
\n\
In your context is that branch's transcript and its prior summaries\n\
under `summary/`. Read them and produce a signal-preserving, minimal\n\
view of the branch's history using the `write_summary` tool. The\n\
harness writes it to the next `summary/<NNN>.md` on this branch. Carry\n\
a prior summary's signal forward into what you write before you\n\
nominate it for deletion: once deleted, its content is gone from the\n\
branch's context for good.\n\
\n\
Use `mark_for_deletion` to nominate superseded files — stale transcript\n\
entries under `messages/`, a prior `summary/` you are replacing, spent\n\
`skills/` bodies the transcript shows loaded and finished with. Your\n\
toolset is deletion-only: you can remove and summarize, never rewrite,\n\
so the worst case is lost information, never corrupted\n\
information. A work product the live branch has rewritten\n\
since you forked is kept regardless of your nomination (live-branch-wins).\n\
\n\
Judge relevance against the dispatching branch's own goal, not your own\n\
preferences:\n\
\n\
<dispatching-branch-goal>\n\
{parent_goal}\n\
</dispatching-branch-goal>\n"
))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn compactor_goal_names_the_branch_and_both_tools() {
let dir = tempfile::tempdir().unwrap();
std::fs::write(dir.path().join("goal.md"), "ship the widget").unwrap();
let g = compactor_goal(dir.path(), "20260101-p1").unwrap();
assert!(g.contains("`20260101-p1`"), "{g}");
assert!(g.contains("write_summary"));
assert!(g.contains("mark_for_deletion"));
assert!(g.contains("summary/<NNN>.md"));
assert!(g.contains("live-branch-wins"));
}
#[test]
fn compactor_goal_quotes_the_dispatching_branchs_goal_and_names_only_reachable_sources() {
let dir = tempfile::tempdir().unwrap();
std::fs::write(dir.path().join("goal.md"), "ship the widget").unwrap();
let g = compactor_goal(dir.path(), "20260101-p1").unwrap();
assert!(
g.contains("<dispatching-branch-goal>\nship the widget\n</dispatching-branch-goal>"),
"{g}"
);
assert!(g.contains("summary/"));
assert!(g.contains("messages/"));
assert!(!g.contains("work products"), "{g}");
}
#[test]
fn compactor_goal_declines_a_dispatching_branch_with_no_goal() {
let dir = tempfile::tempdir().unwrap();
let err = compactor_goal(dir.path(), "20260101-p1").unwrap_err();
assert!(matches!(err, Error::Io(_)), "{err:?}");
}
#[test]
fn compactor_role_is_the_soul_key() {
assert_eq!(COMPACTOR_ROLE, "compactor");
}
#[test]
fn builtin_tool_schemas_are_the_two_named_tools_with_required_inputs() {
let schemas = builtin_tool_schemas();
let custom = |t: &Tool| match t {
Tool::Custom {
name,
description,
input_schema,
..
} => (name.clone(), description.clone(), input_schema.clone()),
_ => panic!("builtin tools are Custom"),
};
let parts: Vec<_> = schemas.iter().map(custom).collect();
let names: Vec<&str> = parts.iter().map(|(n, _, _)| n.as_str()).collect();
assert_eq!(names, vec![tools::WRITE_SUMMARY, tools::MARK_FOR_DELETION]);
assert!(parts.iter().all(|(_, d, _)| d.is_some()));
assert_eq!(parts[0].2["required"][0], "content");
assert_eq!(parts[1].2["required"][0], "path");
}
#[test]
fn the_injected_names_are_the_injected_schemas() {
let schemas = builtin_tool_schemas();
let schema_names: Vec<&str> = schemas
.iter()
.map(|t| match t {
Tool::Custom { name, .. } | Tool::Provider { name, .. } => name.as_str(),
})
.collect();
assert_eq!(injected(COMPACTOR_ROLE), schema_names.as_slice());
}
#[test]
fn no_other_role_has_an_injected_toolset() {
assert!(injected("worker").is_empty());
assert!(injected("verifier").is_empty());
}
}