Skip to content

Commit db5a5d9

Browse files
fabio-rovaiclaude
andcommitted
feat: daemon monitor command + PostCompact hook (closes #3)
New daemon command: {"cmd":"monitor","text":"...","wing":"claude-session"} Returns: {"ok":true,"contradictions":N,"stored":true} Decomposes text into triples, checks each against palace for contradictions, stores the parsed fact. Lighter than verify-doc — designed for continuous session monitoring. CLI: `tardy monitor "text" [wing]` Hooks: - targy-compact-hook.sh: PostCompact sends conversation summary to daemon via socket (socat) or CLI fallback - targy-postool-hook.sh: updated to use daemon monitor instead of binary verify-doc (faster, no temp files) - Removed `timeout` commands (not available on macOS, Claude Code hook timeout setting handles this) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 3789114 commit db5a5d9

4 files changed

Lines changed: 95 additions & 36 deletions

File tree

hooks/targy-compact-hook.sh

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#!/usr/bin/env bash
2+
# Hook: PostCompact → feed conversation summary to tardygrada daemon via monitor command
3+
# This is the key hook that closes the output verification gap.
4+
# Compaction summaries contain the full conversation condensed — tardygrada
5+
# verifies it against the palace and stores it for future contradiction detection.
6+
7+
TARDY="${TARDY_BIN:-tardygrada}"
8+
SOCK="/tmp/tardygrada.sock"
9+
10+
# Bail silently if deps missing (PostCompact is not blocking)
11+
command -v jq >/dev/null 2>&1 || exit 0
12+
command -v socat >/dev/null 2>&1 || { command -v "$TARDY" >/dev/null 2>&1 || exit 0; }
13+
14+
# Extract compaction summary from stdin JSON
15+
# PostCompact sends: {"tool_response":{"summary":"condensed conversation..."}}
16+
SUMMARY=$(jq -r '.tool_response.summary // empty' 2>/dev/null)
17+
18+
if [ -z "$SUMMARY" ]; then
19+
exit 0
20+
fi
21+
22+
# Truncate to 4KB for verification
23+
SUMMARY="${SUMMARY:0:4096}"
24+
25+
# Escape for JSON: newlines, quotes, backslashes, tabs
26+
ESCAPED=$(printf '%s' "$SUMMARY" | jq -Rs '.')
27+
# jq -Rs wraps in quotes, strip them for embedding
28+
ESCAPED="${ESCAPED:1:${#ESCAPED}-2}"
29+
30+
# Try daemon socket first (fast path)
31+
if [ -S "$SOCK" ]; then
32+
RESPONSE=$(printf '{"cmd":"monitor","text":"%s","wing":"claude-session"}\n' "$ESCAPED" | \
33+
socat -t5 - UNIX-CONNECT:"$SOCK" 2>/dev/null)
34+
35+
if [ -n "$RESPONSE" ]; then
36+
CONTRADICTIONS=$(echo "$RESPONSE" | jq -r '.contradictions // 0' 2>/dev/null)
37+
if [ "$CONTRADICTIONS" -gt 0 ] 2>/dev/null; then
38+
jq -n --arg ctx "[TARDYGRADA] Compaction summary has $CONTRADICTIONS contradiction(s) with session history. Review the conversation for inconsistencies." \
39+
'{hookSpecificOutput:{hookEventName:"PostCompact",additionalContext:$ctx}}'
40+
fi
41+
exit 0
42+
fi
43+
fi
44+
45+
# Fallback: use CLI monitor command
46+
if command -v "$TARDY" >/dev/null 2>&1; then
47+
RESPONSE=$("$TARDY" monitor "$SUMMARY" 2>/dev/null)
48+
CONTRADICTIONS=$(echo "$RESPONSE" | jq -r '.contradictions // 0' 2>/dev/null)
49+
if [ "$CONTRADICTIONS" -gt 0 ] 2>/dev/null; then
50+
jq -n --arg ctx "[TARDYGRADA] Compaction summary has $CONTRADICTIONS contradiction(s) with session history." \
51+
'{hookSpecificOutput:{hookEventName:"PostCompact",additionalContext:$ctx}}'
52+
fi
53+
fi

hooks/targy-input-hook.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ TMPFILE=$(mktemp "$TMP/input.XXXXXX.md")
5050
# Use full message up to 4KB for verification (not 500 chars)
5151
printf '%s\n' "${MESSAGE:0:4096}" > "$TMPFILE"
5252

53-
RESULT=$(timeout 5 "$TARDY" verify-doc "$TMPFILE" 2>/dev/null) || true
53+
RESULT=$("$TARDY" verify-doc "$TMPFILE" 2>/dev/null) || true
5454
CONFLICTS=$(echo "$RESULT" | grep -c "CONFLICT" || true)
5555

5656
# Store in palace (truncated for storage, not for verification)

hooks/targy-monitor.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ case "$1" in
1212
input)
1313
TMPFILE=$(mktemp "$TMP/input.XXXXXX.md")
1414
printf '%s\n' "$2" > "$TMPFILE"
15-
RESULT=$(timeout 5 "$TARDY" verify-doc "$TMPFILE" 2>/dev/null) || true
15+
RESULT=$("$TARDY" verify-doc "$TMPFILE" 2>/dev/null) || true
1616
CONFLICTS=$(echo "$RESULT" | grep -c "CONFLICT" || true)
1717
if [ "$CONFLICTS" -gt 0 ]; then
1818
echo "[targy] INPUT has $CONFLICTS contradiction(s) with session history:"
@@ -24,7 +24,7 @@ case "$1" in
2424
output)
2525
TMPFILE=$(mktemp "$TMP/output.XXXXXX.md")
2626
printf '%s\n' "$2" > "$TMPFILE"
27-
RESULT=$(timeout 5 "$TARDY" verify-doc "$TMPFILE" 2>/dev/null) || true
27+
RESULT=$("$TARDY" verify-doc "$TMPFILE" 2>/dev/null) || true
2828
CONFLICTS=$(echo "$RESULT" | grep -c "CONFLICT" || true)
2929
if [ "$CONFLICTS" -gt 0 ]; then
3030
echo "[targy] OUTPUT has $CONFLICTS internal contradiction(s):"

hooks/targy-postool-hook.sh

Lines changed: 39 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
#!/usr/bin/env bash
2-
# Hook: PostToolUse → feed tool outputs to tardygrada palace (async)
3-
# Fixes: C2 (JSON injection), H1 (jq check), H2 (race conditions), H3 (cleanup),
4-
# H5 (unquoted TARDY), M4 (Read/Grep metadata), M5 (timeouts)
2+
# Hook: PostToolUse → feed tool outputs to tardygrada via daemon monitor command (async)
3+
# Uses socket when daemon is running (fast), falls back to CLI binary.
54

65
TARDY="${TARDY_BIN:-tardygrada}"
7-
TMP="/tmp/targy-monitor"
8-
mkdir -p "$TMP"
6+
SOCK="/tmp/tardygrada.sock"
97

108
# Bail silently if dependencies missing (this hook is async, don't block)
119
command -v jq >/dev/null 2>&1 || exit 0
12-
command -v "$TARDY" >/dev/null 2>&1 || exit 0
1310

1411
# Extract tool name and response from stdin JSON
1512
INPUT=$(cat)
@@ -23,39 +20,48 @@ case "$TOOL_NAME" in
2320
;;
2421
esac
2522

26-
# For Read/Grep/Glob: store metadata only (filename, match count), not full content
23+
# For Read/Grep/Glob: store metadata only, not full content
2724
case "$TOOL_NAME" in
2825
Read|Grep|Glob)
29-
# Extract just the file path or pattern, not the full content
3026
TOOL_INPUT=$(echo "$INPUT" | jq -r '(.tool_input | if type == "object" then tostring else . end) // empty' 2>/dev/null)
31-
SUMMARY="[$TOOL_NAME] input: ${TOOL_INPUT:0:200}"
32-
"$TARDY" remember claude-session -- "$SUMMARY" >/dev/null 2>&1 || true
33-
exit 0
27+
MONITOR_TEXT="[$TOOL_NAME] input: ${TOOL_INPUT:0:200}"
28+
;;
29+
*)
30+
if [ -z "$TOOL_RESPONSE" ] || [ "$TOOL_RESPONSE" = "null" ]; then
31+
exit 0
32+
fi
33+
# Don't prefix with tool name — it confuses triple decomposition
34+
MONITOR_TEXT="${TOOL_RESPONSE:0:2048}"
3435
;;
3536
esac
3637

37-
if [ -z "$TOOL_RESPONSE" ] || [ "$TOOL_RESPONSE" = "null" ]; then
38-
exit 0
38+
# Escape text for JSON embedding
39+
ESCAPED=$(printf '%s' "$MONITOR_TEXT" | jq -Rs '.')
40+
ESCAPED="${ESCAPED:1:${#ESCAPED}-2}"
41+
42+
# Fast path: daemon socket
43+
if [ -S "$SOCK" ] && command -v socat >/dev/null 2>&1; then
44+
RESPONSE=$(printf '{"cmd":"monitor","text":"%s","wing":"claude-session"}\n' "$ESCAPED" | \
45+
socat -t3 - UNIX-CONNECT:"$SOCK" 2>/dev/null) || true
46+
47+
if [ -n "$RESPONSE" ]; then
48+
CONTRADICTIONS=$(echo "$RESPONSE" | jq -r '.contradictions // 0' 2>/dev/null)
49+
if [ "$CONTRADICTIONS" -gt 0 ] 2>/dev/null; then
50+
jq -n --arg ctx "[TARDYGRADA] Tool output from $TOOL_NAME contradicts session history ($CONTRADICTIONS conflict(s))" \
51+
'{hookSpecificOutput:{hookEventName:"PostToolUse",additionalContext:$ctx}}'
52+
fi
53+
exit 0
54+
fi
3955
fi
4056

41-
# Use up to 2KB for verification, 500 chars for palace storage
42-
VERIFY_TEXT="${TOOL_RESPONSE:0:2048}"
43-
STORE_TEXT="${TOOL_RESPONSE:0:500}"
44-
45-
# Verify tool output against palace (unique temp file)
46-
TMPFILE=$(mktemp "$TMP/tool.XXXXXX.md")
47-
printf '[%s] %s\n' "$TOOL_NAME" "$VERIFY_TEXT" > "$TMPFILE"
48-
RESULT=$(timeout 5 "$TARDY" verify-doc "$TMPFILE" 2>/dev/null) || true
49-
CONFLICTS=$(echo "$RESULT" | grep -c "CONFLICT" || true)
50-
rm -f "$TMPFILE"
51-
52-
# Store in palace
53-
"$TARDY" remember claude-session -- "[$TOOL_NAME] $STORE_TEXT" >/dev/null 2>&1 || true
54-
55-
if [ "$CONFLICTS" -gt 0 ]; then
56-
DETAILS=$(echo "$RESULT" | grep "CONFLICT\|->.*conflict" | head -3)
57-
# Use jq for safe JSON construction
58-
jq -n --arg ctx "[TARDYGRADA] Tool output from $TOOL_NAME contradicts session history ($CONFLICTS conflict(s)):
59-
$DETAILS" \
60-
'{hookSpecificOutput:{hookEventName:"PostToolUse",additionalContext:$ctx}}'
57+
# Fallback: CLI binary
58+
if command -v "$TARDY" >/dev/null 2>&1; then
59+
RESPONSE=$("$TARDY" monitor "$MONITOR_TEXT" 2>/dev/null) || true
60+
if [ -n "$RESPONSE" ]; then
61+
CONTRADICTIONS=$(echo "$RESPONSE" | jq -r '.contradictions // 0' 2>/dev/null)
62+
if [ "$CONTRADICTIONS" -gt 0 ] 2>/dev/null; then
63+
jq -n --arg ctx "[TARDYGRADA] Tool output from $TOOL_NAME contradicts session history ($CONTRADICTIONS conflict(s))" \
64+
'{hookSpecificOutput:{hookEventName:"PostToolUse",additionalContext:$ctx}}'
65+
fi
66+
fi
6167
fi

0 commit comments

Comments
 (0)