Skip to content

Wire the agent's embedding service by construction instead of re-injecting it per engine swap #1328

Closed

Description

@malibio

Overview

The agent's GraphToolExecutor receives the embedding service by having it manually re-passed every time the inference engine is swapped. One construction site passed None, which disabled semantic search and skill-based tool routing in production until it was fixed. A residual startup race remains. This issue makes the executor read the current embedding service by construction so the whole class of bug cannot recur.

Problem Statement

In packages/daemon/src/services/local_agent_service.rs:

  • The embedding service is held in LocalAgentServiceInner (as Arc<RwLock<Option<Arc<NodeEmbeddingService>>>>).
  • It must be manually re-passed into a freshly-built GraphToolExecutor in replace_engine() each time the engine changes.
  • build_noop_service() and replace_engine() are two separate construction sites; one of them was passing embedding_service: None, which disabled search_semantic for the agent AND disabled skill-based select_tools routing (skill matching runs semantic search over skill nodes via the same embedding service). The full tool list was always sent instead of the skill-scoped list.
  • Residual race: the embedding model loads in a background task. If the chat model is loaded before embeddings finish, replace_engine() captures None and will not pick up the embedding service until the next engine swap.

Proposed Solution

Have the executor read the embedding service through the shared handle by construction rather than capturing a snapshot at swap time. Options:

  1. GraphToolExecutor holds the shared Arc<RwLock<Option<Arc<NodeEmbeddingService>>>> and reads it per call (so it always sees the current value, closing the race), or
  2. Re-run the executor wiring when embeddings become ready (a re-swap hook from the background loader), as a narrower alternative.

Option 1 is the durable fix. Note GraphToolExecutor lives in the agent crate (packages/agent/src/local_agent/tools.rs), so the shared handle type must be expressible there without a daemon dependency.

Acceptance Criteria

  • The executor can never be wired with a stale or None embedding service when one is (or becomes) available
  • The startup race (chat model loaded before embeddings finish) is closed: a turn run after embeddings load uses semantic search and skill routing without an engine swap
  • search_semantic and skill-based select_tools work in a daemon where the chat model was loaded first
  • No remaining construction site can pass embedding_service: None when the service exists
  • Code passes `bun run quality:fix`
  • Tests pass (`bun run test:all`)

Technical Specifications

Reference Files

  • Service wiring: `packages/daemon/src/services/local_agent_service.rs` (`replace_engine`, `build_noop_service`, the background embedding loader)
  • Executor struct: `packages/agent/src/local_agent/tools.rs` (`GraphToolExecutor`, `select_tools`, `exec_search_semantic`)

Context

This is the durable fix for the embedding_service: None class of bug. A point fix (passing the current service through in replace_engine) has already landed; this issue removes the need to remember to do that and closes the race.

Parallelization

Scoped to the daemon service wiring and the GraphToolExecutor struct. It does not change message types, the tool name registry, or prompt assembly, so it runs in parallel with the related issues. It shares the agent crate with the message-type issue but touches different files (tools.rs struct/methods vs agent_types.rs/inference.rs).

Activity

  1. malibio commented on Jun 5, 2026

    @malibio
    CollaboratorAuthor

    Frontend baseline: 3844 passed (134 files). Starting work in worktree issue-1328-wire-embedding-handle.

  2. self-assigned this
    on Jun 5, 2026
  3. added a commit that references this issue on Jun 5, 2026
    f2403ee
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions