Skip to content

feat: pre-tool-call recall (trigger / preToolHook)#12

Open
sgonz-xtrace wants to merge 2 commits into
mainfrom
feat/trigger-pre-tool-hook
Open

feat: pre-tool-call recall (trigger / preToolHook)#12
sgonz-xtrace wants to merge 2 commits into
mainfrom
feat/trigger-pre-tool-hook

Conversation

@sgonz-xtrace

Copy link
Copy Markdown
Contributor

Summary

Adds a client for the new POST /v1/memories/trigger endpoint, which recalls the lesson / procedure insights past sessions recorded about the symbols an in-flight tool call is touching. It's purpose-built for a pre-tool-call hook: recall fires on the symbols the agent is about to act on (not a semantic query), and the endpoint is not metered against the monthly quota, so it's safe to call before every tool use.

This PR is purely additive — no existing type, method, or behavior changes.

What's new

memories.trigger(body, ctx?) — thin wrapper over the endpoint. Throws on HTTP errors, symmetric with search().

memories.preToolHook(signal, opts) — ergonomic sugar for the hook hot path:

  • Fails soft. A network error, timeout, or abort returns { context: "", memories: [], degraded: true } so a recall hiccup can never stall the agent's tool loop (the endpoint itself also fails soft server-side).
  • Fails loud on caller bugs. Missing scope axis or missing firing signal throw synchronously, before any network call — those are misconfiguration, not transient failures.
  • mode: "compose" (default) returns the server-assembled, inject-ready block in context; mode: "retrieve" returns raw matched rows and renders the block client-side.
  • Accepts the in-flight tool call ({ tool, args, output? }) or pre-extracted { entities }.
  • Optional timeoutMs for a hard client-side cap (opt-in — the server already bounds recall and fails soft).

renderLessonProcedurePrompt(rows) — deterministic, no-LLM renderer for the retrieve-mode block.

TypesLessonProcedureType, LessonProcedureDetails, LessonMemory, ProcedureMemory, ActionContext, TriggerRequest, TriggerResponse, all exported.

Usage

// Before the agent runs a tool, pull what past sessions learned about it.
const { context } = await client.memories.preToolHook(
  { tool: "Edit", args: { file_path: "tool_loop.py" } },
  { user_id: "alice", task: "fix finalize-on-abort in the tool loop" },
);
if (context) systemPrompt += `\n\n${context}`;

Design notes

  • The lesson / procedure row types reuse the shared memory shape but deliberately do not join the Memory union — they're only ever returned by this endpoint, so keeping them separate leaves search / list / recall type narrowing untouched.
  • preToolHook is the recommended entry point for hooks; trigger is the lower-level escape hatch when you want raw errors and full control over the request.

Testing

Adds src/trigger.test.ts (9 tests) covering both modes, the compose context pass-through, retrieve-mode client rendering, fail-soft on error, the two validation throws, and aborted-signal handling. Full suite (55 tests) and tsc --noEmit pass.

Wrap the new POST /v1/memories/trigger endpoint, which recalls the
lesson/procedure insights past sessions recorded about the symbols an
in-flight tool call touches. Purpose-built for a pre-tool-call hook:
no query, and not metered against the monthly quota, so it's safe to
call before every tool use.

- `memories.trigger(body)` — thin endpoint wrapper (throws like search).
- `memories.preToolHook(signal, opts)` — hot-path sugar that fails soft:
  a network error / timeout / abort returns an empty, degraded result so
  a recall hiccup can never stall the agent's tool loop. Throws only on
  caller misconfiguration (no scope axis / no firing signal), before any
  network call. `compose` (default) returns the server-assembled block;
  `retrieve` returns raw rows rendered client-side. Optional `timeoutMs`.
- `renderLessonProcedurePrompt(rows)` — deterministic block renderer for
  retrieve mode.
- Types: LessonProcedureType, LessonProcedureDetails, LessonMemory,
  ProcedureMemory, ActionContext, TriggerRequest, TriggerResponse.

Purely additive — no existing types, methods, or behavior change. The
lesson/procedure row types reuse the shared memory shape without joining
the `Memory` union, so search/list/recall narrowing is untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@xtrace-memhub-staging xtrace-memhub-staging Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MemHub Code Review — 1 issue(s) found

MemHub Code Review found 1 issue(s): 1 medium.

Advisory — informational only; this check does not block merge.

Comment thread src/memories.ts
When a client-side timeout was combined with a caller-supplied signal,
the `abort` listener added to `opts.signal` was never removed on normal
completion (`{ once: true }` only fires on abort). A long-lived caller
signal reused across a busy tool loop would accumulate one dangling
listener per call. Store the handler and remove it in `finally`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant