feat: add message-search fallback in dialectic tools for short sessions (#362)

This commit is contained in:
doria 2026-02-03 11:17:24 -05:00 committed by GitHub
parent c9dee51fb9
commit 44577e048c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 54 additions and 5 deletions

View File

@ -65,7 +65,7 @@ Honcho has four storage primitives that work together:
- **Workspaces** - Top-level containers that isolate different applications or environments
- **Peers** - Any entity that persists but changes over time (users, agents, objects, and more)
- **Sessions** - Interaction threads between peers with temporal boundaries
- **Messages** - Units of data that trigger reasoning (conversations, events, activity, documents, and more)
- **Messages** - Units of data that trigger reasoning (conversations, events, activity, documents, and more)
When you write messages to Honcho, they're stored and processed in the background. Custom reasoning models perform formal logical [_reasoning_](/v3/documentation/core-concepts/reasoning) to generate conclusions about each peer. These conclusions are stored as [_representations_](/v3/documentation/core-concepts/representation) that you can query to provide rich context for your agents.
@ -100,4 +100,4 @@ Welcome to Honcho. We're excited to have you at the frontier of AI with us 🫡.
<Card title="Reasoning" icon="gears" href="/v3/documentation/core-concepts/reasoning">
Learn how Honcho reasons about data to build memory
</Card>
</CardGroup>
</CardGroup>

View File

@ -1129,7 +1129,34 @@ async def _handle_search_memory(ctx: ToolContext, tool_input: dict[str, Any]) ->
mem = Representation.from_documents(documents)
total_count = mem.len()
if total_count == 0:
return f"No observations found for query '{tool_input['query']}'"
# fallback behavior: if the memory is *empty*, that means we're quite
# early in a workspace/peer/session -- in order to give good answers in
# this stage, and be efficient with tool calls, and make sure the model
# doesn't short-circuit and think there's nothing here, we
# automatically search the message history for relevant information.
query = tool_input["query"]
if ctx.agent_type == "dialectic":
limit = min(tool_input.get("top_k", 20), 20)
snippets = await crud.search_messages(
ctx.db,
workspace_name=ctx.workspace_name,
session_name=ctx.session_name,
query=query,
limit=limit,
context_window=0,
)
if snippets:
message_output = _format_message_snippets(
snippets, f"for query '{query}'"
)
return (
f"No observations yet. Message search results:\n\n{message_output}"
)
return (
f"No observations found for query '{query}', and no messages found in "
"history. Try a different phrasing or use grep_messages for exact text."
)
return f"No observations found for query '{query}'"
mem_str = mem.str_with_ids() if ctx.include_observation_ids else str(mem)
return f"Found {total_count} observations for query '{tool_input['query']}':\n\n{mem_str}"
@ -1696,12 +1723,14 @@ async def create_tool_executor(
Returns:
String result describing what was done
"""
logger.info(f"[tool call] {tool_name}")
logger.info(f"[tool call] {tool_name} {tool_input}")
try:
handler = _TOOL_HANDLERS.get(tool_name)
if handler:
return await handler(ctx, tool_input)
result = await handler(ctx, tool_input)
logger.info(f"[tool result] {tool_name} {result}")
return result
return f"Unknown tool: {tool_name}"
except ValueError as e:

View File

@ -706,6 +706,7 @@ async def _execute_tool_loop(
total_output_tokens = 0
total_cache_creation_tokens = 0
total_cache_read_tokens = 0
empty_response_retries = 0
# Track effective tool_choice - switches from "required" to "auto" after first iteration
effective_tool_choice = tool_choice
@ -778,6 +779,25 @@ async def _execute_tool_loop(
# No tool calls, return final response
logger.debug("No tool calls in response, finishing")
if (
isinstance(response.content, str)
and not response.content.strip()
and empty_response_retries < 1
and iteration < max_tool_iterations - 1
):
empty_response_retries += 1
conversation_messages.append(
{
"role": "user",
"content": (
"Your last response was empty. Provide a concise answer "
"to the original query using the available context."
),
}
)
iteration += 1
continue
if stream_final:
# Stream the final response with metadata from tool execution
stream = _stream_final_response(