agents: neighborhood placeholder, organize prompt, weight-set command
Add {{neighborhood}} placeholder for agent prompts: full seed node
content + ranked neighbors (score = link_strength * node_weight) with
smooth cutoff, minimum 10, cap 25, plus cross-links between included
neighbors.
Rewrite organize.agent prompt to focus on structural graph work:
merging duplicates, superseding junk, calibrating weights, creating
concept hubs.
Add weight-set CLI command for direct node weight manipulation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
5ef9098deb
commit
34e74ca2c5
5 changed files with 106 additions and 49 deletions
|
|
@ -239,6 +239,7 @@ fn resolve(
|
|||
"siblings" | "neighborhood" => {
|
||||
let mut out = String::new();
|
||||
let mut all_keys: Vec<String> = Vec::new();
|
||||
const MAX_NEIGHBORS: usize = 25;
|
||||
|
||||
for key in keys {
|
||||
let Some(node) = store.nodes.get(key.as_str()) else { continue };
|
||||
|
|
@ -248,35 +249,74 @@ fn resolve(
|
|||
out.push_str(&format!("## {} (seed)\n\n{}\n\n", key, node.content));
|
||||
all_keys.push(key.clone());
|
||||
|
||||
// All neighbors with full content and link strength
|
||||
if !neighbors.is_empty() {
|
||||
out.push_str("### Neighbors\n\n");
|
||||
for (nbr, strength) in &neighbors {
|
||||
// Rank neighbors by link_strength * node_weight
|
||||
// Include all if <= 10, otherwise take top MAX_NEIGHBORS
|
||||
let mut ranked: Vec<(String, f32, f32)> = neighbors.iter()
|
||||
.filter_map(|(nbr, strength)| {
|
||||
store.nodes.get(nbr.as_str()).map(|n| {
|
||||
let node_weight = n.weight.max(0.01);
|
||||
let score = strength * node_weight;
|
||||
(nbr.to_string(), *strength, score)
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
ranked.sort_by(|a, b| b.2.total_cmp(&a.2));
|
||||
|
||||
let total = ranked.len();
|
||||
let included: Vec<_> = if total <= 10 {
|
||||
ranked
|
||||
} else {
|
||||
// Smooth cutoff: threshold scales with neighborhood size
|
||||
// Generous — err on including too much so the agent can
|
||||
// see and clean up junk. 20 → top 75%, 50 → top 30%
|
||||
let top_score = ranked.first().map(|(_, _, s)| *s).unwrap_or(0.0);
|
||||
let ratio = (15.0 / total as f32).min(1.0);
|
||||
let threshold = top_score * ratio;
|
||||
ranked.into_iter()
|
||||
.enumerate()
|
||||
.take_while(|(i, (_, _, score))| *i < 10 || *score >= threshold)
|
||||
.take(MAX_NEIGHBORS)
|
||||
.map(|(_, item)| item)
|
||||
.collect()
|
||||
};
|
||||
|
||||
if !included.is_empty() {
|
||||
if total > included.len() {
|
||||
out.push_str(&format!("### Neighbors (top {} of {}, ranked by importance)\n\n",
|
||||
included.len(), total));
|
||||
} else {
|
||||
out.push_str("### Neighbors\n\n");
|
||||
}
|
||||
let included_keys: std::collections::HashSet<&str> = included.iter()
|
||||
.map(|(k, _, _)| k.as_str()).collect();
|
||||
|
||||
for (nbr, strength, _score) in &included {
|
||||
if let Some(n) = store.nodes.get(nbr.as_str()) {
|
||||
out.push_str(&format!("#### {} (link: {:.2})\n\n{}\n\n",
|
||||
nbr, strength, n.content));
|
||||
all_keys.push(nbr.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cross-links between neighbors (local subgraph structure)
|
||||
let nbr_set: std::collections::HashSet<&str> = neighbors.iter()
|
||||
.map(|(k, _)| k.as_str()).collect();
|
||||
let mut cross_links = Vec::new();
|
||||
for (nbr, _) in &neighbors {
|
||||
for (nbr2, strength) in graph.neighbors(nbr) {
|
||||
if nbr2.as_str() != key && nbr_set.contains(nbr2.as_str()) && nbr.as_str() < nbr2.as_str() {
|
||||
cross_links.push((nbr.clone(), nbr2, strength));
|
||||
// Cross-links between included neighbors
|
||||
let mut cross_links = Vec::new();
|
||||
for (nbr, _, _) in &included {
|
||||
for (nbr2, strength) in graph.neighbors(nbr) {
|
||||
if nbr2.as_str() != key
|
||||
&& included_keys.contains(nbr2.as_str())
|
||||
&& nbr.as_str() < nbr2.as_str()
|
||||
{
|
||||
cross_links.push((nbr.clone(), nbr2, strength));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !cross_links.is_empty() {
|
||||
out.push_str("### Cross-links between neighbors\n\n");
|
||||
for (a, b, s) in &cross_links {
|
||||
out.push_str(&format!(" {} ↔ {} ({:.2})\n", a, b, s));
|
||||
if !cross_links.is_empty() {
|
||||
out.push_str("### Cross-links between neighbors\n\n");
|
||||
for (a, b, s) in &cross_links {
|
||||
out.push_str(&format!(" {} ↔ {} ({:.2})\n", a, b, s));
|
||||
}
|
||||
out.push_str("\n");
|
||||
}
|
||||
out.push_str("\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -83,6 +83,22 @@ pub fn cmd_not_useful(key: &str) -> Result<(), String> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn cmd_weight_set(key: &str, weight: f32) -> Result<(), String> {
|
||||
super::check_dry_run();
|
||||
let mut store = store::Store::load()?;
|
||||
let resolved = store.resolve_key(key)?;
|
||||
let weight = weight.clamp(0.01, 1.0);
|
||||
if let Some(node) = store.nodes.get_mut(&resolved) {
|
||||
let old = node.weight;
|
||||
node.weight = weight;
|
||||
println!("Weight: {} {:.2} → {:.2}", resolved, old, weight);
|
||||
store.save()?;
|
||||
} else {
|
||||
return Err(format!("Node not found: {}", resolved));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn cmd_gap(description: &[String]) -> Result<(), String> {
|
||||
if description.is_empty() {
|
||||
return Err("gap requires a description".into());
|
||||
|
|
|
|||
|
|
@ -160,6 +160,14 @@ EXAMPLES:
|
|||
/// Node key
|
||||
key: String,
|
||||
},
|
||||
/// Set a node's weight directly
|
||||
#[command(name = "weight-set")]
|
||||
WeightSet {
|
||||
/// Node key
|
||||
key: String,
|
||||
/// Weight (0.01 to 1.0)
|
||||
weight: f32,
|
||||
},
|
||||
/// Record a gap in memory coverage
|
||||
Gap {
|
||||
/// Gap description
|
||||
|
|
@ -769,6 +777,7 @@ fn main() {
|
|||
Command::Wrong { key, context } => cli::node::cmd_wrong(&key, &context),
|
||||
Command::NotRelevant { key } => cli::node::cmd_not_relevant(&key),
|
||||
Command::NotUseful { key } => cli::node::cmd_not_useful(&key),
|
||||
Command::WeightSet { key, weight } => cli::node::cmd_weight_set(&key, weight),
|
||||
Command::Gap { description } => cli::node::cmd_gap(&description),
|
||||
|
||||
// Node
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue