"""Reaction instructions tool — read / update the per-user Synapse reaction rules.
The instructions are the standing contract between the user and the reaction
system: which events to react to, which profile to route them to, what to do.
Navi may refine this document through this tool (self-improvement); every edit
is recorded with edited_by='navi' in the version history.
"""
from navi.synapse.settings_store import SynapseSettingsStore
from navi.tools._internal.base import Tool, ToolContext, ToolResult, current_user_id
class SynapseInstructionsTool(Tool):
name = "synapse_instructions"
description = (
"Read or update the user's Synapse reaction instructions — the standing rules "
"describing which platform events Navi reacts to and how. Read them before "
"acting on Synapse events; update them only when a change is clearly wanted "
"and explained (the user reviews the edit history)."
)
parameters = {
"type": "object",
"properties": {
"op": {
"type": "string",
"enum": ["read", "update"],
"description": "read: return the current document. update: replace it with `content`.",
},
"content": {
"type": "string",
"description": "update only: the FULL new text of the instructions document.",
},
"reason": {
"type": "string",
"description": "update only: one sentence explaining why you are changing the rules.",
},
},
"required": ["op"],
}
def __init__(self, pool_provider=None) -> None:
self._pool_provider = pool_provider
async def execute(self, params: dict, ctx: ToolContext | None = None) -> ToolResult:
user_id = ctx.user_id if ctx is not None else current_user_id.get(None)
if user_id is None:
return ToolResult(
success=False, output="",
error="No user context — reaction instructions are per-user.",
)
if not callable(self._pool_provider):
return ToolResult(
success=False, output="",
error="Reaction settings storage is not available (no pool).",
)
store = SynapseSettingsStore(self._pool_provider())
settings_row = await store.get(user_id)
op = params.get("op", "read")
if op == "read":
body = settings_row.instructions.strip()
if not body:
return ToolResult(
success=True,
output="The reaction instructions document is empty — no reactions are configured yet.",
)
return ToolResult(success=True, output=body)
if op != "update":
return ToolResult(success=False, output="", error=f"Unknown op '{op}'.")
content = (params.get("content") or "").strip()
if not content:
return ToolResult(
success=False, output="",
error="update requires non-empty `content` — use the full new text of the document.",
)
reason = (params.get("reason") or "").strip()
await store.save_instructions(
user_id, content, edited_by="navi", reason=reason or None,
)
return ToolResult(
success=True,
output=(
"Instructions updated and recorded in the edit history"
+ (f" ({reason})" if reason else "")
+ ". The change applies to future Synapse reactions."
),
)