Skip to content

feat(cognition,#1411): tool_embedding PR-1 — pure types + cosine_similarity + threshold#1413

Merged
joelteply merged 1 commit into
canaryfrom
feat/oxidizer-tool-embedding-pr1
May 18, 2026
Merged

feat(cognition,#1411): tool_embedding PR-1 — pure types + cosine_similarity + threshold#1413
joelteply merged 1 commit into
canaryfrom
feat/oxidizer-tool-embedding-pr1

Conversation

@joelteply
Copy link
Copy Markdown
Contributor

Summary

Oxidizer for ToolRegistry.generateToolEmbeddings + ToolRegistry.semanticSearchTools (TS, see src/system/tools/server/ToolRegistry.ts:421-511). Sibling to closed oxidizers #1375 check_redundancy + #1385 generate_response. Part of #1248 "TS-as-thin-glue" arc.

What this ships (PR-1)

  • Wire types (ts-rs): ToolDescription, ToolEmbedding, EmbedToolsRequest, EmbedToolsResponse, SemanticSearchToolsRequest, SemanticSearchResult
  • cosine_similarity(a, b) -> f32 — pure. Defensive 0.0 on length mismatch + zero magnitude.
  • extract_category(tool_name) -> &str — pure. First slash-segment or "root".
  • round_similarity(sim) -> f32 — pure. 3-decimal rounding (mirrors TS Math.round(s * 1000) / 1000).
  • Constants matching TS literals: SIMILARITY_THRESHOLD=0.3, TOOL_EMBEDDING_MODEL="nomic-embed-text", DEFAULT_SEARCH_LIMIT=10.

NOT in this PR

  • PR-2: cache (LazyLock<Mutex<...>>) + async embed_tools / semantic_search_tools + IPC handlers tools/embed + tools/semantic-search.
  • PR-3: TS shim — ToolRegistryclient.toolsEmbed / client.toolsSemanticSearch.
  • PR-4: Delete dead TS (inline cosineSimilarity, toolEmbeddings Map, AIProviderDaemon.createEmbedding call sites).

Discipline

  • f64 accumulation in cosine_similarity prevents catastrophic cancellation on long vectors; final cast to f32 matches wire shape.
  • Mismatched vector lengths → 0.0 (TS parity).
  • Zero-magnitude → 0.0 (avoids NaN from divide-by-zero).
  • All defaults match TS literals so PR-3 shim is byte-equivalent.

Tests (22 — 16 logic + 6 ts-rs export)

cosine_similarity (8): identical→1.0, orthogonal→0.0, opposite→-1.0, mismatched lengths→0.0, zero-magnitude→0.0 (both sides + both), empty→0.0, known pythagorean (3,4)·(4,3)=0.96, 1000-dim precision preserved.

extract_category (3): no slash→"root", standard category/tool→first segment, leading-slash boundary.

round_similarity (2): 3-decimal positive + negative.

constants (3): threshold, model, limit all match TS literal.

ts-rs exports (6).

Full cognition regression: 368/368 pass.

Refs

🤖 Generated with Claude Code

…larity + threshold

Oxidizer for ToolRegistry.generateToolEmbeddings +
ToolRegistry.semanticSearchTools (TS, see
src/system/tools/server/ToolRegistry.ts:421-511). Sibling to closed
oxidizers #1375 check_redundancy + #1385 generate_response. Part of
#1248 "TS-as-thin-glue" arc.

## What this ships (PR-1 scope — pure, atomic)

- Wire types (ts-rs):
  - `ToolDescription { name, description }`
  - `ToolEmbedding { tool_name, vector }`
  - `EmbedToolsRequest { tools, model? }`
  - `EmbedToolsResponse { embeddings, model, generated_at_ms }`
  - `SemanticSearchToolsRequest { query, model?, limit?, threshold? }`
  - `SemanticSearchResult { name, description, category, similarity }`
- `cosine_similarity(a, b) -> f32` — pure. Mirrors TS impl with
  defensive 0.0 returns on length mismatch + zero magnitude.
- `extract_category(tool_name) -> &str` — pure. First slash-segment
  or "root" (matches TS ternary).
- `round_similarity(sim) -> f32` — pure. 3-decimal-place rounding
  (mirrors TS `Math.round(s * 1000) / 1000`).
- Constants (all match TS literal values):
  - `SIMILARITY_THRESHOLD: f32 = 0.3`
  - `TOOL_EMBEDDING_MODEL: &str = "nomic-embed-text"`
  - `DEFAULT_SEARCH_LIMIT: u32 = 10`

## NOT in this PR

- **PR-2**: cache (LazyLock<Mutex<ToolEmbeddingCache>>) + async
  embed_tools + semantic_search_tools + IPC handlers tools/embed +
  tools/semantic-search.
- **PR-3**: TS shim — ToolRegistry calls client.toolsEmbed /
  client.toolsSemanticSearch.
- **PR-4**: Delete dead TS (inline cosineSimilarity, toolEmbeddings
  Map, AIProviderDaemon.createEmbedding call sites).

## Discipline

- f64 accumulation in cosine_similarity prevents catastrophic
  cancellation on long vectors; final cast to f32 matches wire shape.
- Mismatched vector lengths -> 0.0 (TS parity).
- Zero-magnitude -> 0.0 (avoids NaN from divide-by-zero).
- All defaults match TS literals so PR-3 shim is byte-equivalent.

## Tests (22 — 16 logic + 6 ts-rs export)

cosine_similarity (8):
- identical vectors -> ~1.0
- orthogonal -> 0.0
- opposite -> ~-1.0
- mismatched lengths -> 0.0
- zero magnitude -> 0.0 (both sides + both)
- empty vectors -> 0.0
- known pythagorean case (3,4) . (4,3) = 0.96
- long vector (1000-dim) precision preserved

extract_category (3):
- no slash -> "root"
- standard category/tool -> first segment
- leading slash boundary

round_similarity (2):
- 3-decimal rounding
- negative values

constants (3):
- threshold, model, limit all match TS

Full cognition regression: 368/368 pass.

Ref: #1411 oxidizer card, #1248 umbrella.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@joelteply joelteply merged commit da455c4 into canary May 18, 2026
@joelteply joelteply deleted the feat/oxidizer-tool-embedding-pr1 branch May 18, 2026 18:42
joelteply added a commit that referenced this pull request May 18, 2026
…bed/search + IPC handlers (#1416)

Stacks on PR-1 #1413 (MERGED). Wires the async path: process-wide
LazyLock<Mutex<ToolEmbeddingCache>> + async embed_tools (replaces TS
ToolRegistry.generateToolEmbeddings) + async semantic_search_tools
(replaces TS ToolRegistry.semanticSearchTools) + cognition/embed-tools
+ cognition/semantic-search-tools IPC handlers.

## What this ships

- ToolEmbeddingCache (process-singleton) — Vec<ToolEmbedding> +
  parallel Vec<ToolDescription> + model. Replaces TS
  ToolRegistry.toolEmbeddings: Map<string, Float32Array>.
- ToolEmbeddingError — typed: NoAdapter, EmbeddingFailed, CacheEmpty,
  EmbeddingCountMismatch.
- embed_tools(EmbedToolsRequest) -> Result<EmbedToolsResponse, _> —
  async. Routes via global_registry → adapter.create_embedding,
  validates count parity, replaces (not merges) cache atomically
  under one lock acquire.
- semantic_search_tools(SemanticSearchToolsRequest) -> Result<Vec<SemanticSearchResult>, _>
  — async. Reads cached embeddings (CacheEmpty if absent), embeds the
  query through the cache's model (no silent space-mixing), computes
  cosine via PR-1 pure fn, filters by threshold, sorts descending,
  truncates to limit.
- IPC command arms: cognition/embed-tools + cognition/semantic-search-tools.
- Test scaffolding (_clear_cache_for_tests + _install_cache_for_tests)
  for cache-state tests without requiring a real adapter.

## NOT in this PR

- PR-3: TS shim — ToolRegistry.generateToolEmbeddings +
  semanticSearchTools delegate to client.cognitionEmbedTools +
  client.cognitionSemanticSearchTools.
- PR-4: Delete dead TS — inline cosineSimilarity, toolEmbeddings Map,
  AIProviderDaemon.createEmbedding call sites.

## Discipline

- No silent default-on-error. Provider failure / count mismatch /
  empty cache surface as typed Result.
- semantic_search_tools uses the cache's model unless explicitly
  overridden — never silently mixes embedding spaces (different
  models = meaningless cosine).
- Cache replacement atomic under one Mutex acquire — no
  read-modify-write window for partial state.
- expect("mutex poisoned") panics rather than swallowing — by design.
- generated_at_ms intentionally NOT retained on cache struct (no
  internal reader yet; EmbedToolsResponse already carries it for
  caller observability; a future cache-state IPC can re-add).

## Tests (5 new — full module now 27 passing)

- Error Display: NoAdapter carries provider+model, CacheEmpty gives
  actionable hint, EmbeddingCountMismatch includes both numbers.
- semantic_search_empty_cache_errors pins CacheEmpty before any
  adapter lookup.
- cache_install_and_clear_for_tests pins scaffolding contract.

Full cognition regression: 373/373 pass. Clippy held at 157 baseline.

Ref: #1411 PR-1 (MERGED #1413), #1248 umbrella.

Co-authored-by: Test <test@test.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
joelteply added a commit that referenced this pull request May 18, 2026
… (PR-4 folded) (#1418)

* feat(cognition,#1411): tool_embedding PR-2 — process cache + async embed/search + IPC handlers

Stacks on PR-1 #1413 (MERGED). Wires the async path: process-wide
LazyLock<Mutex<ToolEmbeddingCache>> + async embed_tools (replaces TS
ToolRegistry.generateToolEmbeddings) + async semantic_search_tools
(replaces TS ToolRegistry.semanticSearchTools) + cognition/embed-tools
+ cognition/semantic-search-tools IPC handlers.

## What this ships

- ToolEmbeddingCache (process-singleton) — Vec<ToolEmbedding> +
  parallel Vec<ToolDescription> + model. Replaces TS
  ToolRegistry.toolEmbeddings: Map<string, Float32Array>.
- ToolEmbeddingError — typed: NoAdapter, EmbeddingFailed, CacheEmpty,
  EmbeddingCountMismatch.
- embed_tools(EmbedToolsRequest) -> Result<EmbedToolsResponse, _> —
  async. Routes via global_registry → adapter.create_embedding,
  validates count parity, replaces (not merges) cache atomically
  under one lock acquire.
- semantic_search_tools(SemanticSearchToolsRequest) -> Result<Vec<SemanticSearchResult>, _>
  — async. Reads cached embeddings (CacheEmpty if absent), embeds the
  query through the cache's model (no silent space-mixing), computes
  cosine via PR-1 pure fn, filters by threshold, sorts descending,
  truncates to limit.
- IPC command arms: cognition/embed-tools + cognition/semantic-search-tools.
- Test scaffolding (_clear_cache_for_tests + _install_cache_for_tests)
  for cache-state tests without requiring a real adapter.

## NOT in this PR

- PR-3: TS shim — ToolRegistry.generateToolEmbeddings +
  semanticSearchTools delegate to client.cognitionEmbedTools +
  client.cognitionSemanticSearchTools.
- PR-4: Delete dead TS — inline cosineSimilarity, toolEmbeddings Map,
  AIProviderDaemon.createEmbedding call sites.

## Discipline

- No silent default-on-error. Provider failure / count mismatch /
  empty cache surface as typed Result.
- semantic_search_tools uses the cache's model unless explicitly
  overridden — never silently mixes embedding spaces (different
  models = meaningless cosine).
- Cache replacement atomic under one Mutex acquire — no
  read-modify-write window for partial state.
- expect("mutex poisoned") panics rather than swallowing — by design.
- generated_at_ms intentionally NOT retained on cache struct (no
  internal reader yet; EmbedToolsResponse already carries it for
  caller observability; a future cache-state IPC can re-add).

## Tests (5 new — full module now 27 passing)

- Error Display: NoAdapter carries provider+model, CacheEmpty gives
  actionable hint, EmbeddingCountMismatch includes both numbers.
- semantic_search_empty_cache_errors pins CacheEmpty before any
  adapter lookup.
- cache_install_and_clear_for_tests pins scaffolding contract.

Full cognition regression: 373/373 pass. Clippy held at 157 baseline.

Ref: #1411 PR-1 (MERGED #1413), #1248 umbrella.

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

* feat(cognition,#1411): tool_embedding PR-3 — TS shim + delete dead TS (PR-4 folded)

Stacks on PR-2 #1416. ToolRegistry.generateToolEmbeddings (inline
AIProviderDaemon.createEmbedding) + semanticSearchTools (inline
cosineSimilarity + manual sort) now delegate to
RustCoreIPCClient.cognitionEmbedTools +
RustCoreIPCClient.cognitionSemanticSearchTools. Mirrors codex's
check_redundancy PR-3 #1383 shape (PR-4 dead-code delete folded in).

## What this ships

- `ToolRegistry.populateRustEmbeddingCache` — calls
  client.cognitionEmbedTools with all registered tools. Rust populates
  the process-wide cache.
- `ToolRegistry.ensureToolEmbeddings` simplified — one-shot guard
  + concurrent-call dedup. TTL gone (Rust cache persists for the
  process lifetime; a future "tools changed" event will re-trigger).
- `ToolRegistry.semanticSearchTools` thin shim — call
  client.cognitionSemanticSearchTools(query, limit), map descriptions
  through cleanDescription for chat UX presentation.
- TS cognition mixin adds `cognitionEmbedTools` +
  `cognitionSemanticSearchTools` binding methods.
- New ts-rs barrel re-exports: EmbedToolsRequest, EmbedToolsResponse,
  SemanticSearchToolsRequest, SemanticSearchResult.

## Dead TS deleted (PR-4 folded in)

- `private toolEmbeddings: Map<string, number[]>` cache state — Rust
  owns the cache now.
- `private embeddingsGeneratedAt: number` + `EMBEDDINGS_TTL_MS` — TTL
  belongs to Rust if reintroduced.
- `private cosineSimilarity(a, b)` — Rust's pure cosine_similarity
  (PR-1) is the source of truth.
- `import { AIProviderDaemon }` from
  '../../../daemons/ai-provider-daemon/shared/AIProviderDaemon' —
  unused after both call sites moved to IPC.
- Inline embedding request construction + Math.round +
  threshold-comparison loop — all in Rust now.

Net diff: -136 LOC TS, +51 LOC mixin (which lives in the bindings
layer next to other cognition delegates). Net cognition-TS deletion
in the ratchet-watched dirs.

## Discipline

- ensureToolEmbeddings cache flag scoped to TS singleton — no global
  state outside the registry instance.
- Concurrent-call dedup retained (multiple callers hitting
  semanticSearchTools at boot won't trigger N parallel embed_tools
  IPC calls — TS pipes them through one promise).
- cleanDescription stays TS — that's pure UI/presentation; Rust
  returns the raw description.
- Error handling: IPC failures throw (no fail-open default), matches
  the pattern in cognitionGenerateResponse + cognitionCheckRedundancy.

## Stack progress

- #1411 PR-1 (pure types + cosine + threshold): #1413 MERGED
- #1411 PR-2 (cache + async + IPC handlers): #1416 OPEN
- #1411 PR-3 (TS shim + dead-TS delete): **this PR**
- #1411 PR-4 (dead-TS delete): **folded into this PR**

After merge: `ToolRegistry.ts` semantic-search surface is a 40-LOC
shim. AIProviderDaemon dependency gone from this file.

## Refs

- #1411 sub-card
- #1413 PR-1 (MERGED)
- #1416 PR-2 (in flight)
- #1383 codex's check_redundancy PR-3 — same shape, folded-PR-4 pattern
- #1248 umbrella

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

---------

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant