Files
waggle-os/docs/redesign-warm-hive/pr4-recon/05-agent-pick-search.md
Oleg Maslov 0c3e2ead3b
Some checks failed
Installer Smoke / installer-smoke (push) Has been cancelled
moving
2026-09-02 10:10:29 +02:00

16 KiB
Raw Blame History

PR4 Recon — Slice 5: Agent-Search / Agent-Pick

Screen 09 Marketplace · Variation A "agent-suggestion box" "Describe what you want to do…" → agent recommends a connector + skill + tool, each with a "why" reason and an install button.

Branch: feature/warm-hive-pr4 · READ-ONLY recon · cited file:line as of 2026-06-16.


TL;DR

The agent-pick engine already exists and is good: searchCapabilities() in packages/agent/src/capability-acquisition.ts:179 takes a natural-language need and returns a ranked, deduped AcquisitionProposal of native tools / installed skills / starter-pack skills / marketplace packages — each candidate already carries a matchReason ("why") and an installAction plus a trust assessment. It fuses keyword scoring (no embeddings) across local sources with pre-fetched marketplace FTS5 candidates.

The gap is entirely the surface, not the brain. This engine is reachable only as an agent tool (acquire_capability, packages/agent/src/skill-tools.ts:406) invoked inside the chat loop — there is no HTTP route that the Screen-09 agent-search bar could POST a need to and render the three-up suggestion box. PR4 must add a thin REST endpoint over the existing searchCapabilities() (plus its deps assembly that today lives inline in local/index.ts:620) and a new MarketplaceApp agent-search UI. The matching scoring is single-recommendation today (proposal.recommendation is ONE candidate); the design wants one-of-each-kind (connector+skill+tool), which is a selection/grouping change on top of the existing ranked candidates[], not new matching.


What exists today (verified)

1. The matching brain — searchCapabilities() (the agent-pick core)

packages/agent/src/capability-acquisition.ts

  • Input (SearchCapabilitiesInput, :170): { need, installedSkills[], starterSkillsDir, nativeToolNames[], marketplaceCandidates[] }. Marketplace candidates are pre-fetched and passed insearchCapabilities itself does no IO except reading starter-skill .md files from disk (loadStarterSkillsMeta, :142).
  • Algorithm (no embeddings — pure keyword): extractKeywords() (:64, stop-word filtered) → scoreMatch() (:74, name-hit ×2 / content-hit ×1, normalized 01) across four source lanes:
    1. native tools (scored vs NATIVE_TOOL_HINTS map, :113);
    2. installed/active skills (:218);
    3. starter-pack skills not yet installed (:241);
    4. marketplace candidates, merging the FTS score when present via Math.max(keywordScore, mkt.score) (:270).
  • Output (AcquisitionProposal, :39): { need, gapDetected, summary, candidates[]≤8, recommendation, alreadyHandled }. Each CapabilityCandidate (:27) already has the exact fields Screen-09 needs: name, type ('native'|'skill'|'plugin'|'mcp'|'connector'|'marketplace'), availability, description, matchReason (the "why", built by buildMatchReason :99), installAction (string|null), and trust.
  • Recommendation is SINGLE (:307314): picks the best installable (or best active if already-handled). It does not group into connector+skill+tool. The full ranked candidates[] is there to do that, but the grouping logic does not exist yet.
  • summary is a markdown string built for the chat surface (buildProposalSummary, :330): it even emits the inline-install marker (<!--waggle:capability_request {...}-->, :385) verbatim for the chat card. This is debug/chat-grade prose — a UI agent-suggestion box would consume the structured candidates/recommendation, not summary.

2. How it's invoked today — the agent tool acquire_capability

packages/agent/src/skill-tools.ts:404478

  • Tool acquire_capability (param: need) gathers deps: getInstalledSkills(), starterSkillsDir, nativeToolNames, and calls deps.searchMarketplace(need) (graceful try/catch) to pre-fetch marketplace candidates, then calls searchCapabilities(...) and returns proposal.summary (the markdown string) to the model. Audit event recorded on gap (:461).
  • Companion tool install_capability (:482) installs starter-pack skills only (validated by validateInstallCandidate, capability-acquisition.ts:424 — rejects any source ≠ starter-pack).
  • The deps are wired in packages/server/src/local/index.ts:620 (createSkillTools({...})):
    • nativeToolNames = union of mind/system/plan/git/document tool names (:624);
    • getInstalledSkills = live server.agentState.skills (:631, hot-reloadable);
    • searchMarketplace = marketplaceDb.search({ query, limit: 10 }) mapped to MarketplaceCandidate[] (:641656) — note score is hardcoded undefined (FTS rank not surfaced through the API).

3. The marketplace search it sits on

packages/server/src/local/routes/marketplace.ts:57GET /api/marketplace/searchMarketplaceDB.search() (packages/marketplace/src/db.ts:86).

  • FTS5 over the packages table. Critically, db.search already tolerates a verbose NL need: toFtsMatchQuery() (db.ts:9199) relaxes the raw string into an OR-of-prefixes and falls back to an unfiltered listing rather than throwing — so the agent's natural-language need works as-is.
  • SearchResult (types.ts:233): { packages[], total, facets{types,categories,sources}, installedCount }. Each MarketplacePackage carries waggle_install_type ('skill'|'connector'|'mcp') → the kind badge, package_type, description, downloads (install count), and (route-annotated at marketplace.ts:105) installed, scanStatus. installedCount is the natural source for the "N in this workspace" count bar (currently catalog-wide, not workspace-scoped — see Gaps).

4. The parallel, narrower "recommend" path (skills-only) — DO NOT confuse with agent-pick

packages/agent/src/skill-recommender.ts (SkillRecommender.recommend(context, topN), class at :118).

  • Multi-signal keyword + bigram + synonym-cluster matcher over installed skills only (no marketplace, no connectors, no MCP). Returns SkillRecommendation[] = {skillName, reason, relevanceScore} — also a "why" (reason), but skills-only.
  • Exposed over HTTP at GET /api/skills/suggestions?context=&topN= (packages/server/src/local/routes/skills.ts:393406). This is the only existing HTTP surface that returns "what should I use" with a reason — but it's the wrong shelf (skills only, already installed) for Screen-09's connector+skill+tool suggestion box. Useful as a prior-art pattern for shaping the new route.

5. The router (a third matcher) — for completeness

packages/agent/src/capability-router.ts (CapabilityRouter.resolve(query), :58). Maps a query to ranked routes across native/connector/skill/plugin/mcp/subagent with confidences. Resolution, not recommendation — returns "where could this be handled" not "install this". Connector lane (:85) knows connected status and emits a suggestion when not connected. Not currently HTTP-exposed; a secondary input if PR4 wants live-connector awareness in the suggestion box.

6. The inline-card render path (the "sync" downstream, shared with the grid)

  • Parser: apps/web/src/components/os/apps/chat-blocks/capability-request-parser.tssegmentText() splits agent text on the <!--waggle:capability_request {name,source,reason}--> marker (and a legacy phrasing) into install-card segments, deduped by source::name.
  • Card: apps/web/src/components/os/apps/chat-blocks/CapabilityRequestCard.tsx — renders the pending→installing→installed/failed micro-states (:17,:107149) with a "why" line from request.reason (:103). Install routing already branches by source: marketplace → adapter.searchMarketplace then adapter.installMarketplacePackage(pkg.id) (:4854, tier-gated, 403 → UpgradeModal); starter-pack → adapter.installPack (:69). This is exactly the type-aware one-click flow §09 asks for, already built for the inline-chat variation (Variation B).

7. Adapter methods already present (the install actions)

apps/web/src/lib/adapter.ts: searchMarketplace(query,limit) (:1334), installMarketplacePackage(id) (:1338), installPack(skillId) (:1292), connectConnector(id,creds) (:1971), installMcp(mcpId) (:2017). All three install kinds in the §09 sync spec (skill Add / connector Connect / MCP Enable) have adapter coverage.


Gaps vs the Screen-09 contract

  1. No HTTP route for agent-pick. searchCapabilities() is reachable ONLY inside the chat agent loop via the acquire_capability tool. The Screen-09 centered agent-search bar ("Ask the agent") needs a POST /api/marketplace/agent-search (or similar) that runs searchCapabilities and returns structured candidates (not the chat summary markdown). Must be built on top of the existing engine.

  2. Returns chat-grade summary, not a structured suggestion box. The tool returns proposal.summary (markdown for the model). The UI needs the raw candidates[]/recommendation JSON. The data is computed (proposal object) but discarded at skill-tools.ts:476 — a route would return the object directly.

  3. Single recommendation, not one-of-each-kind. proposal.recommendation is ONE candidate (capability-acquisition.ts:307). §09 wants connector + skill + tool (three, one per kind) each with its own why + install. Needs a small grouping pass over the ranked candidates[] (top per type/availability) — new logic, but trivial given candidates already carry type.

  4. Marketplace deps assembly lives inline in local/index.ts, not reusable. The getInstalledSkills / nativeToolNames / searchMarketplace closure that feeds the tool is constructed once at server boot (index.ts:620). A new route needs the same deps — either lift this into a shared helper or have the route reconstruct it. (Native tool names, in particular, are only assembled in that closure.)

  5. "N in this workspace" count bar is catalog-wide, not workspace-scoped. installedCount (db.search → types.ts:242) counts all installed packages globally; the marketplace DB is not workspace-partitioned. §09's per-workspace chip count has no backing field today.

  6. No "example chips" / suggested-need seeding. Pure UI; no backend. Can be static or derived from persona suggestedCommands — out of agent-pick scope.

  7. No shared install-state store on the frontend. §09's CRITICAL "sync" (one store powers grid + agent-pick + inline card, installing in any view reflects in all) — the current MarketplaceApp.tsx (no agent-search at all, install state local to each card) and CapabilityRequestCard (local useState phase) have independent state. This is the headline PR4 frontend build; agent-pick is one of the three consumers of that store. (Owned by the "shared install store" slice — flagged here as the integration boundary.)

  8. MarketplaceApp.tsx has no agent-search UI. Verified: the 417-line component (apps/web/src/components/os/apps/MarketplaceApp.tsx) contains no "Ask the agent" / suggestion / acquire-style references — only grid install/uninstall via installMarketplacePackage (:228). Variation A's centered bar + suggestion box must be built net-new.

  9. Marketplace FTS rank not surfaced. searchMarketplace hardcodes score: undefined (index.ts:651), so marketplace candidates rank purely by keyword re-scoring inside searchCapabilities. Acceptable, but means FTS relevance is currently dropped on the floor for the agent-pick path.


Exact integration points a PR4 build would touch

  • Reuse (engine): searchCapabilities(input): AcquisitionProposalpackages/agent/src/capability-acquisition.ts:179. Exported from @waggle/agent (packages/agent/src/index.ts:326). Types CapabilityCandidate / AcquisitionProposal / MarketplaceCandidate / SearchCapabilitiesInput are all exported.
  • Build (route): new POST /api/marketplace/agent-search (body { need }) in the marketplace route file packages/server/src/local/routes/marketplace.ts — runs searchCapabilities with deps assembled like local/index.ts:620657, returns structured { candidates, recommendation, groupedByKind }. Pattern to mirror for shape/contract: GET /api/skills/suggestions (packages/server/src/local/routes/skills.ts:393).
  • Reuse (deps): marketplace search MarketplaceDB.search({query,limit}) (packages/marketplace/src/db.ts:86); installed-skills source server.agentState.skills; native tool-name union (currently only assembled at local/index.ts:624 — lift if reused).
  • Add (grouping): a pickOnePerKind(candidates) helper (new) to satisfy connector+skill+tool — trivial reduce over candidate.type.
  • Install actions (already present, reuse): adapter.installMarketplacePackage / adapter.installPack / adapter.connectConnector / adapter.installMcp (apps/web/src/lib/adapter.ts:1338/1292/1971/2017). Marker/card render reuse: segmentText + CapabilityRequestCard (chat-blocks/).
  • Count bar: SearchResult.installedCount (packages/marketplace/src/types.ts:242) via /api/marketplace/search; needs workspace-scoping if §09's per-workspace count is taken literally.
  • Audit (existing, ride along): fastify.auditStore?.record(...) already called on gap in skill-tools.ts:461 — a route should record proposals the same way.

Risks / watch-outs

  • Two parallel matchers + a router (searchCapabilities vs SkillRecommender vs CapabilityRouter) — building a new route on the wrong one (e.g. SkillRecommender, which is skills-only and HTTP-exposed already) would silently drop connectors/MCP. Use searchCapabilities.
  • summary vs structured-candidates confusion — the tool's return value is markdown; do not parse it for the UI. Return the proposal object from the new route.
  • Keyword-only matching — no embeddings; verbose/synonym-heavy needs may under-match. Acceptable for v1 but the suggestion box may look thin on phrasing mismatch. (SkillRecommender has synonym expansion; searchCapabilities does not.)
  • Tier gating asymmetry — marketplace/MCP install is PRO-gated (marketplace.ts:181 requireTier), starter-pack/skill is free. The suggestion box must reflect this (the inline card already 403→Upgrade, CapabilityRequestCard.tsx:55).
  • Workspace scoping of installs — marketplace DB is global; the "N in this workspace" framing may over-promise isolation that the substrate doesn't provide.
  • Native tool-name list is closure-local (index.ts:624) — a route reconstructing deps must not drift from the real registered tool set, or agent-pick "already have a tool" answers go stale.

Open questions for the founder/lead

  1. Route shape: dedicated POST /api/marketplace/agent-search, or extend GET /api/marketplace/search with an agentPick=true mode? (The former is cleaner given the verbose NL body + structured proposal response.)
  2. One-of-each-kind vs top-N: §09 shows exactly connector+skill+tool (3). When a kind has no match (e.g. no relevant connector), show 2? Show an empty-kind hint? Define the grouping contract.
  3. Count bar semantics: is "N in this workspace" literally per-workspace (needs new workspace-scoped install tracking) or is catalog-wide installedCount acceptable for v1?
  4. Suggestion-box "why": use the engine's matchReason (keyword-hit-grade, e.g. "name matches: risk") as-is, or have the route pass candidates to the LLM for a one-line natural "why"? The former is free + deterministic; the latter is prettier but adds a model call.
  5. Shared install store ownership: confirm the frontend "sync" store is a separate PR4 slice that agent-pick plugs into (this recon treats it as the integration boundary, not part of Slice 5).