fix: add wing param to diary_write/diary_read, derive from transcript path (#659)
* fix: add wing param to diary_write/diary_read, derive from transcript path
Without a wing override, all diary entries from the stop hook land in
wing_session-hook regardless of which project the session is in, making
per-project diary search impossible.
- tool_diary_write(): add optional `wing` param; sanitize and use it when
provided, fall back to wing_{agent_name} when omitted
- tool_diary_read(): add optional `wing` param for filtering by target wing
- TOOLS dict: expose `wing` in input_schema for both diary tools
- hooks_cli: add _wing_from_transcript_path() helper that extracts the
project name from Claude Code paths like
~/.claude/projects/-home-jp-Projects-kiyo-xhci-fix/... → kiyo-xhci-fix
- hook_stop: derive project wing and append wing= hint to block reason so
Claude writes diary entries to the correct per-project wing
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: sanitize wing param, cross-platform paths, tighten test assertions
Addresses Copilot review feedback on #659.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: wing_ prefix + agent filter on diary_read
Addresses bensig's 2-issue review on this PR.
1. _wing_from_transcript_path() was returning bare project names
(e.g. "myproject") while all existing wings follow the wing_*
convention from AAAK_SPEC. Entries landed in wing="myproject"
while diary_read defaulted to wing="wing_<agent_name>" —
orphaning every diary entry written by the stop hook. Now
returns "wing_<project>" and falls back to "wing_sessions".
2. tool_diary_read() did not include agent_name in the ChromaDB
where filter when a custom wing was provided — any caller with
a shared wing could read entries written by other agents.
Add {"agent": agent_name} to the $and clause. Also flagged by
Qudo and left unresolved until now.
Tests updated to expect the wing_ prefix (6 tests).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+32
-7
@@ -918,10 +918,10 @@ def tool_kg_stats():
|
||||
# ==================== AGENT DIARY ====================
|
||||
|
||||
|
||||
def tool_diary_write(agent_name: str, entry: str, topic: str = "general"):
|
||||
def tool_diary_write(agent_name: str, entry: str, topic: str = "general", wing: str = ""):
|
||||
"""
|
||||
Write a diary entry for this agent. Each agent gets its own wing
|
||||
with a diary room. Entries are timestamped and accumulate over time.
|
||||
Write a diary entry for this agent. Entries are timestamped and
|
||||
accumulate over time in a diary room.
|
||||
|
||||
This is the agent's personal journal — observations, thoughts,
|
||||
what it worked on, what it noticed, what it thinks matters.
|
||||
@@ -932,7 +932,10 @@ def tool_diary_write(agent_name: str, entry: str, topic: str = "general"):
|
||||
except ValueError as e:
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
wing = f"wing_{agent_name.lower().replace(' ', '_')}"
|
||||
if wing:
|
||||
wing = sanitize_name(wing)
|
||||
else:
|
||||
wing = f"wing_{agent_name.lower().replace(' ', '_')}"
|
||||
room = "diary"
|
||||
col = _get_collection(create=True)
|
||||
if not col:
|
||||
@@ -987,24 +990,38 @@ def tool_diary_write(agent_name: str, entry: str, topic: str = "general"):
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
|
||||
def tool_diary_read(agent_name: str, last_n: int = 10):
|
||||
def tool_diary_read(agent_name: str, last_n: int = 10, wing: str = ""):
|
||||
"""
|
||||
Read an agent's recent diary entries. Returns the last N entries
|
||||
in chronological order — the agent's personal journal.
|
||||
|
||||
When ``wing`` is provided, reads from that wing instead of the
|
||||
agent's default ``wing_<agent_name>`` wing. This lets hooks
|
||||
direct diary reads to a project-specific wing derived from
|
||||
the transcript path.
|
||||
"""
|
||||
try:
|
||||
agent_name = sanitize_name(agent_name, "agent_name")
|
||||
if wing:
|
||||
wing = sanitize_name(wing)
|
||||
except ValueError as e:
|
||||
return {"error": str(e)}
|
||||
last_n = max(1, min(last_n, 100))
|
||||
wing = f"wing_{agent_name.lower().replace(' ', '_')}"
|
||||
if not wing:
|
||||
wing = f"wing_{agent_name.lower().replace(' ', '_')}"
|
||||
col = _get_collection()
|
||||
if not col:
|
||||
return _no_palace()
|
||||
|
||||
try:
|
||||
results = col.get(
|
||||
where={"$and": [{"wing": wing}, {"room": "diary"}]},
|
||||
where={
|
||||
"$and": [
|
||||
{"wing": wing},
|
||||
{"room": "diary"},
|
||||
{"agent": agent_name},
|
||||
]
|
||||
},
|
||||
include=["documents", "metadatas"],
|
||||
limit=10000,
|
||||
)
|
||||
@@ -1497,6 +1514,10 @@ TOOLS = {
|
||||
"type": "string",
|
||||
"description": "Topic tag (optional, default: general)",
|
||||
},
|
||||
"wing": {
|
||||
"type": "string",
|
||||
"description": "Target wing for this diary entry (optional). If omitted, uses wing_{agent_name}. Use this to write diary entries to a project wing instead of an agent-specific wing.",
|
||||
},
|
||||
},
|
||||
"required": ["agent_name", "entry"],
|
||||
},
|
||||
@@ -1515,6 +1536,10 @@ TOOLS = {
|
||||
"type": "integer",
|
||||
"description": "Number of recent entries to read (default: 10)",
|
||||
},
|
||||
"wing": {
|
||||
"type": "string",
|
||||
"description": "Wing to read diary entries from (optional). If omitted, reads from wing_{agent_name}.",
|
||||
},
|
||||
},
|
||||
"required": ["agent_name"],
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user