Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 26 additions & 2 deletions src/agents/hermes/templates/plugin_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,18 @@ def _resolve_auxiliary_client(agent=None):
"tracedecay_lcm_preflight",
))

STANDARD_HERMES_LCM_PROVIDER = "cursor"

LCM_PROVIDER_LOCAL_TOOL_NAMES = frozenset((
"tracedecay_lcm_compress",
"tracedecay_lcm_describe",
"tracedecay_lcm_doctor",
"tracedecay_lcm_expand",
"tracedecay_lcm_expand_query",
"tracedecay_lcm_preflight",
"tracedecay_lcm_session_boundary",
))

# Direct duplicates of the memory provider's own tool surface
# (fact_store / fact_feedback / memory_status). Skipped at register() time
# when tracedecay is the active memory.provider so the same store is not
Expand Down Expand Up @@ -1858,7 +1870,9 @@ def _synthesize_expand_query_payload(retrieval, agent=None, **kwargs):
def _handle_lcm_expand_query(args, **kwargs) -> str:
kwargs = dict(kwargs)
agent = kwargs.pop("agent", None)
retrieval = call_tracedecay_json("tracedecay_lcm_expand_query", args or {}, **kwargs)
args = dict(args or {})
args.setdefault("provider", STANDARD_HERMES_LCM_PROVIDER)
retrieval = call_tracedecay_json("tracedecay_lcm_expand_query", args, **kwargs)
payload = _synthesize_expand_query_payload(retrieval, agent=agent, **kwargs)
return json.dumps(payload)

Expand Down Expand Up @@ -2219,6 +2233,7 @@ def _report_compression_boundary(self, session_id, bound_session_id, kwargs):
return
args = _storage_args(self.project_root, self.hermes_home)
args.update({
"provider": STANDARD_HERMES_LCM_PROVIDER,
"session_id": session_id,
"old_session_id": old_session_id,
"boundary_reason": boundary_reason,
Expand Down Expand Up @@ -2261,6 +2276,7 @@ def _preflight_probe(self, messages, current_tokens=None, **kwargs):
)
)
args.update({
"provider": STANDARD_HERMES_LCM_PROVIDER,
"session_id": self.active_session_id,
"messages": messages,
"current_tokens": current_tokens,
Expand Down Expand Up @@ -2469,6 +2485,8 @@ def handle_tool_call(self, name, arguments=None, **kwargs) -> str:
storage_args = _storage_args(self.project_root, self.hermes_home)
for key, value in storage_args.items():
tool_args.setdefault(key, value)
if tracedecay_name in LCM_PROVIDER_LOCAL_TOOL_NAMES:
tool_args.setdefault("provider", STANDARD_HERMES_LCM_PROVIDER)
if tracedecay_name == "tracedecay_lcm_compress" and self.project_root:
tool_args.setdefault("response_handle_project_root", self.project_root)
if native_name in ("lcm_status", "lcm_doctor"):
Expand All @@ -2485,6 +2503,7 @@ def handle_tool_call(self, name, arguments=None, **kwargs) -> str:
def expand_query(self, prompt, query=None, node_ids=None, **kwargs):
kwargs = dict(kwargs)
args = self._tool_args(kwargs.pop("session_id", None))
args["provider"] = STANDARD_HERMES_LCM_PROVIDER
args["prompt"] = prompt
if query is not None:
args["query"] = query
Expand Down Expand Up @@ -2903,6 +2922,7 @@ def _compress_to_result(self, messages, current_tokens=None, focus_topic=None, *
)
)
args.update({
"provider": STANDARD_HERMES_LCM_PROVIDER,
"messages": messages,
"current_tokens": current_tokens,
"focus_topic": focus_topic,
Expand Down Expand Up @@ -3199,7 +3219,11 @@ def sync_turn(self, user_content, assistant_content, *, session_id="", messages=
role = str(entry.get("role") or "user")
entry["id"] = f"tracedecay_sync_{batch_id}_{timestamp_ns}_{idx}_{role}"
args = _storage_args(self.project_root, self.hermes_home)
args.update({"session_id": sid, "messages": turn_messages})
args.update({
"provider": STANDARD_HERMES_LCM_PROVIDER,
"session_id": sid,
"messages": turn_messages,
})
try:
tools.call_tracedecay_tool(
"tracedecay_lcm_preflight",
Expand Down
34 changes: 18 additions & 16 deletions src/mcp/tools/definitions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2392,7 +2392,7 @@ fn def_lcm_status() -> ToolDefinition {
"properties": {
"provider": {
"type": "string",
"description": "Provider id to inspect (default: cursor)."
"description": "Optional provider id. Omit or use 'all' to inspect all providers."
},
"session_id": {
"type": "string",
Expand Down Expand Up @@ -2420,7 +2420,7 @@ fn def_lcm_doctor() -> ToolDefinition {
"properties": {
"provider": {
"type": "string",
"description": "Provider id to inspect (default: cursor)."
"description": "Specific provider id to inspect or repair. Required; 'all' is not accepted for this lifecycle tool."
},
"session_id": {
"type": "string",
Expand Down Expand Up @@ -2463,7 +2463,8 @@ fn def_lcm_doctor() -> ToolDefinition {
"storage_scope": lcm_storage_scope_schema(),
"hermes_home": lcm_hermes_home_schema()
},
"allOf": lcm_storage_scope_requires_hermes_home()
"allOf": lcm_storage_scope_requires_hermes_home(),
"required": ["provider"]
}),
)
}
Expand All @@ -2478,7 +2479,7 @@ fn def_lcm_load_session() -> ToolDefinition {
"properties": {
"provider": {
"type": "string",
"description": "Provider id, default cursor."
"description": "Optional provider id. Omit or use 'all' to load messages for this session id across all providers."
},
"session_id": {
"type": "string",
Expand Down Expand Up @@ -2616,7 +2617,7 @@ fn def_lcm_describe() -> ToolDefinition {
"properties": {
"provider": {
"type": "string",
"description": "Provider id, default cursor."
"description": "Specific provider id. Required because describe targets are provider-local."
},
"session_id": {
"type": "string",
Expand Down Expand Up @@ -2644,7 +2645,7 @@ fn def_lcm_describe() -> ToolDefinition {
"hermes_home": lcm_hermes_home_schema()
},
"allOf": lcm_storage_scope_requires_hermes_home(),
"required": ["session_id"]
"required": ["provider", "session_id"]
}),
)
}
Expand All @@ -2659,7 +2660,7 @@ fn def_lcm_expand() -> ToolDefinition {
"properties": {
"provider": {
"type": "string",
"description": "Provider id, default cursor."
"description": "Specific provider id. Required because expansion targets are provider-local."
},
"session_id": {
"type": "string",
Expand Down Expand Up @@ -2714,7 +2715,7 @@ fn def_lcm_expand() -> ToolDefinition {
"hermes_home": lcm_hermes_home_schema()
},
"allOf": lcm_storage_scope_requires_hermes_home(),
"required": ["session_id", "target"]
"required": ["provider", "session_id", "target"]
}),
)
}
Expand All @@ -2729,7 +2730,7 @@ fn def_lcm_expand_query() -> ToolDefinition {
"properties": {
"provider": {
"type": "string",
"description": "Provider id, default cursor."
"description": "Specific provider id. Required because retrieval context is provider-local."
},
"session_id": {
"type": "string",
Expand Down Expand Up @@ -2775,7 +2776,7 @@ fn def_lcm_expand_query() -> ToolDefinition {
"hermes_home": lcm_hermes_home_schema()
},
"allOf": lcm_storage_scope_requires_hermes_home(),
"required": ["session_id", "prompt"]
"required": ["provider", "session_id", "prompt"]
}),
)
}
Expand All @@ -2790,7 +2791,7 @@ fn def_lcm_preflight() -> ToolDefinition {
"properties": {
"provider": {
"type": "string",
"description": "Provider id, default cursor."
"description": "Specific provider id. Required for compression lifecycle operations."
},
"session_id": {
"type": "string",
Expand Down Expand Up @@ -2865,7 +2866,8 @@ fn def_lcm_preflight() -> ToolDefinition {
"storage_scope": lcm_storage_scope_schema(),
"hermes_home": lcm_hermes_home_schema()
},
"allOf": lcm_storage_scope_requires_hermes_home()
"allOf": lcm_storage_scope_requires_hermes_home(),
"required": ["provider", "session_id"]
}),
)
}
Expand All @@ -2880,7 +2882,7 @@ fn def_lcm_compress() -> ToolDefinition {
"properties": {
"provider": {
"type": "string",
"description": "Provider id, default cursor."
"description": "Specific provider id. Required for compression lifecycle operations."
},
"session_id": {
"type": "string",
Expand Down Expand Up @@ -2978,7 +2980,7 @@ fn def_lcm_compress() -> ToolDefinition {
"hermes_home": lcm_hermes_home_schema()
},
"allOf": lcm_storage_scope_requires_hermes_home(),
"required": ["session_id"]
"required": ["provider", "session_id"]
}),
)
}
Expand All @@ -2993,7 +2995,7 @@ fn def_lcm_session_boundary() -> ToolDefinition {
"properties": {
"provider": {
"type": "string",
"description": "Provider id, default cursor."
"description": "Specific provider id. Required for compression lifecycle operations."
},
"session_id": {
"type": "string",
Expand All @@ -3015,7 +3017,7 @@ fn def_lcm_session_boundary() -> ToolDefinition {
"hermes_home": lcm_hermes_home_schema()
},
"allOf": lcm_storage_scope_requires_hermes_home(),
"required": ["session_id"]
"required": ["provider", "session_id"]
}),
)
}
Expand Down
41 changes: 23 additions & 18 deletions src/mcp/tools/handlers/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -811,8 +811,18 @@ fn timestamp_argument_error(name: &str) -> TraceDecayError {
))
}

fn provider_arg(args: &Value) -> &str {
string_arg(args, "provider").unwrap_or("cursor")
fn provider_or_all_arg(args: &Value) -> &str {
optional_search_provider_arg(args).unwrap_or("all")
}

fn required_specific_provider_arg(args: &Value) -> Result<&str> {
match string_arg(args, "provider") {
Some("all") => Err(argument_error(
"provider must name a specific provider for this tool",
)),
Some(provider) => Ok(provider),
None => Err(argument_error("provider is required for this tool")),
}
}

fn optional_search_provider_arg(args: &Value) -> Option<&str> {
Expand Down Expand Up @@ -1270,15 +1280,10 @@ fn parse_lcm_scope(args: &Value) -> Result<LcmScope> {
}
}

fn lcm_grep_provider_arg(args: &Value, scope: LcmScope) -> &str {
fn lcm_grep_provider_arg(args: &Value) -> &str {
if let Some(provider) = optional_search_provider_arg(args) {
return provider;
}
if matches!(scope, LcmScope::Current | LcmScope::Session)
&& string_arg(args, "provider").is_none()
{
return provider_arg(args);
}
"all"
}

Expand Down Expand Up @@ -1507,7 +1512,7 @@ pub(super) async fn handle_lcm_status(
context: LcmHandlerContext<'_>,
args: Value,
) -> Result<ToolResult> {
let provider = provider_arg(&args);
let provider = provider_or_all_arg(&args);
let session_id = string_arg(&args, "session_id");
let deep = bool_arg(&args, "deep")?.unwrap_or(false);
let gc_config = lcm_gc_config(&args)?;
Expand All @@ -1534,7 +1539,7 @@ pub(super) async fn handle_lcm_doctor(
context: LcmHandlerContext<'_>,
args: Value,
) -> Result<ToolResult> {
let provider = provider_arg(&args);
let provider = required_specific_provider_arg(&args)?;
let session_id = string_arg(&args, "session_id");
let mode = lcm_doctor_mode(&args)?;
let apply = args.get("apply").and_then(Value::as_bool).unwrap_or(false);
Expand Down Expand Up @@ -1629,7 +1634,7 @@ pub(super) async fn handle_lcm_load_session(
context: LcmHandlerContext<'_>,
args: Value,
) -> Result<ToolResult> {
let provider = provider_arg(&args);
let provider = provider_or_all_arg(&args);
let session_id = required_string_arg(&args, "session_id")?;
let (content_slice, content_limit_clamped_from) = lcm_load_content_slice(&args)?;
let storage = lcm_open_storage_ro!(context, &args);
Expand Down Expand Up @@ -1682,7 +1687,7 @@ pub(super) async fn handle_lcm_grep(
// Validate scope before opening storage so argument errors are reported
// even when the sessions DB does not exist yet.
let scope = parse_lcm_scope(&args)?;
let provider = lcm_grep_provider_arg(&args, scope);
let provider = lcm_grep_provider_arg(&args);
let storage = lcm_open_storage_ro!(context, &args);
let hits = storage
.db
Expand Down Expand Up @@ -1721,7 +1726,7 @@ pub(super) async fn handle_lcm_describe(
context: LcmHandlerContext<'_>,
args: Value,
) -> Result<ToolResult> {
let provider = provider_arg(&args);
let provider = required_specific_provider_arg(&args)?;
let session_id = required_string_arg(&args, "session_id")?;
// Validate target before opening storage so argument errors are reported
// even when the sessions DB does not exist yet.
Expand Down Expand Up @@ -1751,7 +1756,7 @@ pub(super) async fn handle_lcm_expand(
context: LcmHandlerContext<'_>,
args: Value,
) -> Result<ToolResult> {
let provider = provider_arg(&args);
let provider = required_specific_provider_arg(&args)?;
let session_id = required_string_arg(&args, "session_id")?;
let target = parse_lcm_expand_target(&args)?;
let storage = lcm_open_storage_ro!(context, &args);
Expand Down Expand Up @@ -1782,7 +1787,7 @@ pub(super) async fn handle_lcm_expand_query(
context: LcmHandlerContext<'_>,
args: Value,
) -> Result<ToolResult> {
let provider = provider_arg(&args);
let provider = required_specific_provider_arg(&args)?;
let session_id = required_string_arg(&args, "session_id")?;
let prompt = required_string_arg(&args, "prompt")?;
let max_results =
Expand Down Expand Up @@ -1835,7 +1840,7 @@ pub(super) async fn handle_lcm_session_boundary(
context: LcmHandlerContext<'_>,
args: Value,
) -> Result<ToolResult> {
let provider = provider_arg(&args);
let provider = required_specific_provider_arg(&args)?;
let session_id = required_string_arg(&args, "session_id")?;
let storage = lcm_open_storage!(context, &args);
let response = storage
Expand Down Expand Up @@ -1866,7 +1871,7 @@ pub(super) async fn handle_lcm_preflight(
context: LcmHandlerContext<'_>,
args: Value,
) -> Result<ToolResult> {
let provider = provider_arg(&args);
let provider = required_specific_provider_arg(&args)?;
let session_id = required_string_arg(&args, "session_id")?;
let storage = lcm_open_storage!(context, &args);
let response = storage
Expand Down Expand Up @@ -1907,7 +1912,7 @@ pub(super) async fn handle_lcm_compress(
context: LcmHandlerContext<'_>,
args: Value,
) -> Result<ToolResult> {
let provider = provider_arg(&args);
let provider = required_specific_provider_arg(&args)?;
let session_id = required_string_arg(&args, "session_id")?;
let response_handle_root = lcm_response_handle_root(context.project_root, &args);
let storage = lcm_open_storage!(context, &args);
Expand Down
3 changes: 2 additions & 1 deletion src/sessions/lcm/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ pub(crate) async fn load_session(
let limit = clamp_limit(request.limit);
let fetch_limit = limit.saturating_add(1);
let mut values = vec![
Value::Text(request.provider.clone()),
Value::Text(request.provider.clone()),
Value::Text(request.session_id.clone()),
Value::Integer(request.after_store_id.unwrap_or(0)),
Expand All @@ -124,7 +125,7 @@ pub(crate) async fn load_session(
timestamp, content, content_hash, storage_kind, payload_ref,
snippet_text, legacy_source, legacy_truncated, metadata_json
FROM lcm_raw_messages
WHERE provider = ?
WHERE (? = 'all' OR provider = ?)
AND session_id = ?
AND store_id > ?
{role_clause}
Expand Down
2 changes: 1 addition & 1 deletion tests/agent_suite/agent_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1152,7 +1152,7 @@ fn test_hermes_plugin_init_snapshot_matches_embedded_asset() {
hasher.update(body.as_bytes());
assert_eq!(
hex::encode(hasher.finalize()),
"a622c197db57fd32c5375c2345cf61dda52863b741b32d1921c59777812310c7",
"fe9f53b0721f9080ceb0fd0b16227efd66f550e1fca11a07b25abf8489e8435c",
"templates/plugin_init.py payload hash changed — verify the edit is intentional and update this snapshot"
);
}
Expand Down
Loading
Loading