Commit Graph

5 Commits

Author SHA1 Message Date
Rohit Ghumare d626b4ea60 perf(hooks): fire-and-forget telemetry hooks (#573) (#688)
* perf(hooks): fire-and-forget telemetry hooks (closes #573)

Telemetry hooks (notification, post-tool-failure, post-tool-use,
prompt-submit, stop, session-end, subagent-start, subagent-stop,
task-completed) previously `await fetch(..., AbortSignal.timeout(N))`
inside a try/catch. The await kept the hook process alive until the
response arrived — up to N ms per request — which blocks Claude Code's
next-prompt boundary on every assistant turn.

Switch to fire-and-forget:

  fetch(url, { signal: AbortSignal.timeout(N) }).catch(() => {});
  setTimeout(() => process.exit(0), 500).unref();

The unawaited fetch dispatches the request; the unref'd setTimeout
force-exits the process after the request has been flushed to the
local daemon's socket buffer (~500ms is enough). Without the
setTimeout Node keeps the event loop alive waiting for any in-flight
fetch to settle, which means the hook still blocks Claude Code's
next-prompt boundary for up to the AbortSignal duration.

Context-injecting hooks (pre-tool-use, pre-compact, session-start)
still use `await fetch` because Claude Code reads their stdout for
context injection — left untouched.

AGENTS.md updated with the two-pattern guidance.

* chore(hooks): drop verbose comments on fire-and-forget hooks

* fix(hooks): bump stop+session-end exit delay to 1500ms

Multi-request hooks (stop fires 2, session-end up to 4) need more
than 500ms to initiate all fetches when AGENTMEMORY_URL points to a
remote daemon — DNS + TCP + TLS handshakes can eat the budget before
the second/third fetch is even dispatched. Bump to 1500ms on those
two hooks only; single-request hooks keep 500ms.

AGENTS.md updated with the multi-request exception.
2026-05-27 20:51:23 +01:00
Rohit Ghumare 0468407249 fix(hooks): send repo basename as project, not full path (#474) (#687)
* fix(hooks): send repo basename as project, not full path (closes #474)

Hooks were sending `data.cwd` (an absolute filesystem path) as the
`project` field on every observe/session/start call. Native sessions,
replay-import, and manual memory_lesson_save calls all use the repo
basename. The mismatch caused auto-injected context to filter out the
bulk of relevant lessons because the path never matches the stored
project name.

Add shared `resolveProject(cwd)` helper:
  1. AGENTMEMORY_PROJECT_NAME env (per-repo escape hatch)
  2. basename of `git rev-parse --show-toplevel` (handles subdirs)
  3. basename of cwd (final fallback when not in a git repo)

Applied to 9 hooks: notification, post-tool-use, post-tool-failure,
prompt-submit, session-start, subagent-start, subagent-stop,
task-completed, pre-compact.

Build: split hook entries into per-entry tsdown configs so each hook
bundles into a fully self-contained .mjs. Previous shared config
hoisted helpers into hashed chunks that changed on every rebuild.

* chore(hooks): drop issue-number ref from resolveProject comment

* chore: trim verbose comments on _project.ts + tsdown.config
2026-05-27 20:32:54 +01:00
Rohit Ghumare 51bcb09104 address CodeRabbit review on #187 + fix CI
Findings verified against current code on this branch; all four valid.

1. config.ts loadFallbackConfig (L281) — user could set
   FALLBACK_PROVIDERS=agent-sdk and bypass the AGENTMEMORY_ALLOW_AGENT_SDK
   gate added to detectProvider. Filter it out at the fallback layer too,
   with the same warning pointing at the opt-in flag.

2. summarize.ts (L87-92) — the empty_provider_response branch returned
   without recording failure metrics or a diagnostic log, unlike the
   parse/validation paths. Record the same metricsStore failure event and
   log provider name, prompt size, system size, and observation count so
   empty responses are visible in telemetry.

3. providers/agent-sdk.ts (L14-45) — setting
   process.env.AGENTMEMORY_SDK_CHILD = '1' without restoring it caused
   every subsequent .query() in the same parent process to hit the
   short-circuit guard and return '' (classified as a SDK child it is
   not). Capture prev, set in try, restore in finally (delete if prev
   was undefined). Child processes spawned during the for-await loop
   still inherit the marker because env is inherited at spawn time; we
   only restore after the loop completes.

4. plugin/scripts/sdk-guard-DI1NUOS9.mjs — tsdown extracted the shared
   guard helper into a hashed chunk. Hash rotates on every rebuild and
   churns the diff. Stopped using the shared module from hooks entirely
   and inlined the 6-line guard function into each hook .ts file
   instead. sdk-guard.ts stays in the tree because the unit tests cover
   it directly. Deleted the tracked hashed .mjs and confirmed no new
   chunk is emitted.

Also applied the CI two-step install (npm install --package-lock-only
then npm ci) on this branch, matching #184. Without it, npm ci fails
because lockfiles are gitignored.

Tests: 74 files / 819 tests pass.
2026-04-22 11:44:05 +01:00
Rohit Ghumare 5e63846b29 fix(hooks): break Stop-hook infinite recursion via agent-sdk fallback
Reported: a user with no provider API key and AGENTMEMORY_AUTO_COMPRESS=false
(which they believed protected them) hit unbounded recursion — Stop hook
POSTs /agentmemory/summarize, handler calls provider.summarize(), agent-sdk
provider spawns @anthropic-ai/claude-agent-sdk query(), which creates a full
CC-style child session that reads ~/.claude/settings.json, registers the
same plugin hooks, and fires its own Stop -> another child -> loop. ~579
ghost 'entrypoint: sdk-ts' sessions accumulated in a few minutes, draining
Claude Pro tokens.

#149 only added a stderr warning. AGENTMEMORY_AUTO_COMPRESS gated /compress
but never /summarize, so users who followed the warning's implied guidance
still got hit. Fix the loop at every layer:

1. config.ts detectProvider
   - Treat empty-string provider keys (ANTHROPIC_API_KEY=) as unset; they
     previously passed the truthiness check identically to a real key.
   - Stop defaulting to agent-sdk. When no key is set, return a 'noop'
     provider config and warn. Agent-sdk fallback now requires an explicit
     AGENTMEMORY_ALLOW_AGENT_SDK=true opt-in with a loud second warning.

2. providers/noop.ts (new) + providers/index.ts
   - NoopProvider implements MemoryProvider and returns empty strings for
     compress and summarize so callers can detect .name === 'noop' and
     short-circuit without spawning anything.
   - Add ProviderType 'noop' and wire it through createBaseProvider.

3. providers/agent-sdk.ts
   - Before spawning query(), check process.env.AGENTMEMORY_SDK_CHILD === '1'
     and return '' instead of recursing. Set the env var to '1' before the
     spawn so any child process (including the Agent SDK session's hooks)
     inherits it.

4. hooks/sdk-guard.ts (new) + all 12 hook scripts
   - Shared isSdkChildContext(payload) checks both AGENTMEMORY_SDK_CHILD=1
     and payload.entrypoint === 'sdk-ts' (CC writes this into the stdin
     jsonl for SDK-spawned sessions). Every hook script now bails early
     when that returns true, so even if one guard layer fails the others
     break the loop.

5. functions/summarize.ts
   - Short-circuit with {success:false, error:'no_provider'} when
     provider.name === 'noop' — never reach .summarize().
   - Treat an empty provider response as empty_provider_response instead
     of trying to parse it.

Tests: 74 files / 819 tests pass (+7 new in stop-hook-recursion-guard.test.ts).
Defense in depth means any ONE of the five layers breaks the loop.
2026-04-22 10:57:57 +01:00
Rohit Ghumare 45795b6033 feat: v0.2.0 -- full memory upgrade with 12 hooks, MCP tools, skills, and intelligence
New hooks (7):
- PreToolUse: inject file history before edits (Edit/Write/Read/Glob/Grep)
- PostToolUseFailure: capture error patterns for learning
- PreCompact: preserve memory context through compaction
- SubagentStart/SubagentStop: track multi-agent workflows
- Notification: capture permission prompts (tool preferences)
- TaskCompleted: track team task completions

New functions (4):
- mem::file-context: file-centric memory index for PreToolUse
- mem::consolidate: merge observations into long-term memories via LLM
- mem::patterns + mem::generate-rules: detect co-change patterns and recurring errors
- mem::remember + mem::forget: explicit save/delete for long-term memory

MCP server (2 endpoints):
- GET /agentmemory/mcp/tools: list 5 MCP tools (recall, save, file_history, patterns, sessions)
- POST /agentmemory/mcp/call: dispatch tool calls to iii functions

Skills (4):
- /recall [query]: search past observations
- /remember [insight]: save to long-term memory
- /session-history: show past session timeline
- /forget [target]: delete specific memory data

New API endpoints (7):
- POST /agentmemory/file-context
- POST /agentmemory/remember
- POST /agentmemory/forget
- POST /agentmemory/consolidate
- POST /agentmemory/patterns
- POST /agentmemory/generate-rules
- GET/POST /agentmemory/mcp/*

Updated: types (7 new HookTypes, 3 new ObservationTypes), version 0.2.0
2026-02-27 11:33:48 +05:30