From 294a16ada06cb8b8e49da649d4017a222a3fe015 Mon Sep 17 00:00:00 2001 From: ciregenz Date: Sun, 10 May 2026 19:09:49 -0700 Subject: [PATCH] [eric] system prompt: direct mcp__openswarm-web__WebSearch/WebFetch hint when web MCP is active (fixes 2-minute ToolSearch thrash on small Ollama models) --- backend/CLAUDE.md | 40 ++++++++++++++++++++++++ backend/apps/agents/agent_manager.py | 46 ++++++++++++++++++++++++++++ electron/CLAUDE.md | 24 +++++++++++++++ frontend/CLAUDE.md | 28 +++++++++++++++++ 4 files changed, 138 insertions(+) create mode 100644 backend/CLAUDE.md create mode 100644 electron/CLAUDE.md create mode 100644 frontend/CLAUDE.md diff --git a/backend/CLAUDE.md b/backend/CLAUDE.md new file mode 100644 index 00000000..edd44a1a --- /dev/null +++ b/backend/CLAUDE.md @@ -0,0 +1,40 @@ +# backend/CLAUDE.md + +FastAPI orchestrator. Entry: `backend/main.py` (uvicorn `:8324`, REST `/api/*`, WS `/ws/*`, Swagger `/docs`). See root `CLAUDE.md` for repo-wide constraints. + +## Run / test + +- Dev: `bash backend/run.sh` (creates `.venv/`, installs `requirements.txt`, runs uvicorn with `--reload`). +- Tests: `pip install -r requirements-dev.txt && pytest tests/`. +- `requirements-dev.txt` is deliberately kept out of `requirements.txt` so `pytest` etc. don't ship in the production DMG. Sync both files when adding deps that need to exist in either place. + +## Layout + +- `apps/agents/` — agent orchestration, WS manager, MCP plumbing. + - `providers/registry.py` — resolves primary + aux models across Anthropic / OpenAI / Google / OpenRouter / custom OpenAI-compatible providers. **Always go through here; never hardcode a model ID.** + - `mcp_preflight.py` — vague-prompt classifier that surfaces the one-click MCP-connect modal. + - `mcp_meta_server.py`, `mcp_registry.py` — MCP discovery + registry. + - `9router_gpt5_patch.js` — patch loaded into 9router to translate OpenAI `max_tokens` semantics. +- `apps/nine_router.py` — supervises the 9router subprocess on `:20128`. +- `apps/subscription/router.py` — OAuth + Stripe callbacks for openswarm-pro signup. +- `apps/outputs/` — view renderer (HTML/JS/CSS iframes, sandboxed Python execution). +- `auth.py` — per-install bearer token. **Generated BEFORE the HTTP bind** so the Electron shell can read it from disk; don't reorder. + +## MCP gate + +- Dispatch flows through `_build_mcp_servers`. This is the only place MCP tools become reachable. +- `session.active_mcps` defaults to empty; the user opts in via `MCPSearch` + `MCPActivate` (HITL). +- New MCP-related code path? It must respect this gate. Don't add side channels. +- Suggestions surfaced to users must come from the vetted/default set — not the full upstream registry. + +## Providers / models + +- Primary model: per-session user choice, resolved by `providers.registry`. +- Aux model (preflight, classifier, summarizers): pick the **cheap tier of the user's configured provider** — Haiku for Anthropic, GPT-5-mini for OpenAI, Gemini Flash for Google, etc. Never hardcode Haiku. + +## Common pitfalls + +- New endpoint? Use a pydantic request/response model and `@typechecked`. +- Pinning matters — `requirements.txt` is fully pinned for reproducibility. +- Token middleware already scrubs bearer tokens from logs; don't re-add raw logging. +- MCP bundles in `mcp-bundles/` are esbuild output; regenerate via the bundle script rather than editing. diff --git a/backend/apps/agents/agent_manager.py b/backend/apps/agents/agent_manager.py index b2c569ac..43a06031 100644 --- a/backend/apps/agents/agent_manager.py +++ b/backend/apps/agents/agent_manager.py @@ -1815,6 +1815,52 @@ class AgentManager: if _bt not in effective_disallowed: effective_disallowed.append(_bt) + # Tell the model directly which web tools work for this session. + # The Claude Code CLI's deferred-tool registry still advertises bare + # `WebSearch` and `WebFetch` even when we've stripped them above — + # frontier models (Claude/GPT-5/Gemini Pro) intuit the namespaced + # MCP variant from context, but smaller open-source models (gpt-oss + # via Ollama, smaller Llama/Qwen, etc.) thrash on the deferred-tool + # handshake (saw 2+ minutes of repeated `ToolSearch(select:WebSearch)` + # → empty matches → retry). Naming the working tool here cuts that + # to a single direct call. Only injected when (a) we registered the + # web MCP, AND (b) the user hasn't disabled the policy — matches + # the same gate the MCP allowlist uses, so disabling WebSearch in + # Settings still wins. + _web_tools_available = _need_web_mcp and ( + "mcp__openswarm-web__WebSearch" in effective_allowed + or "mcp__openswarm-web__WebFetch" in effective_allowed + ) + if _web_tools_available: + _hint_lines = [""] + _hint_lines.append( + "This session does NOT have the built-in `WebSearch` / " + "`WebFetch` tools (they delegate to Anthropic Haiku, which " + "isn't reachable on this primary). Use the MCP-backed " + "equivalents instead — call them DIRECTLY, no ToolSearch " + "step needed:" + ) + if "mcp__openswarm-web__WebSearch" in effective_allowed: + _hint_lines.append( + "- `mcp__openswarm-web__WebSearch(query: str, " + "num_results?: int)` — DuckDuckGo search." + ) + if "mcp__openswarm-web__WebFetch" in effective_allowed: + _hint_lines.append( + "- `mcp__openswarm-web__WebFetch(url: str, prompt?: " + "str)` — fetch a URL and return readable text." + ) + _hint_lines.append( + "Do not call `ToolSearch(select:WebSearch)` — bare " + "`WebSearch` is unavailable on this session and that path " + "will return empty matches." + ) + _hint_lines.append("") + _web_hint = "\n".join(_hint_lines) + composed_prompt = ( + f"{composed_prompt}\n\n{_web_hint}" if composed_prompt else _web_hint + ) + # Log effective tool lists google_allowed = [t for t in effective_allowed if "google-workspace" in t] reddit_allowed = [t for t in effective_allowed if "reddit" in t] diff --git a/electron/CLAUDE.md b/electron/CLAUDE.md new file mode 100644 index 00000000..7ddf0cf4 --- /dev/null +++ b/electron/CLAUDE.md @@ -0,0 +1,24 @@ +# electron/CLAUDE.md + +Electron 40.x (CastLabs DRM build) desktop shell + auto-updater via GitHub Releases. Entry: `main.js`. Version is in `package.json`. See root `CLAUDE.md` for repo-wide constraints. + +## Build / release + +- Local build: `npm run build` → produces `build-staging/` containing the frontend dist, backend bundle, standalone Python 3.13, and the 9router binary. Build artifacts are ephemeral; not git-tracked. +- macOS release: requires Apple ID, app-specific password, and team ID env vars. App is signed + notarized. +- Windows release: signed via Azure code signing in CI (`.github/workflows/release-windows.yml`); triggers on `v*` tags. + +## Bundling + +- Python 3.13 is bundled via python-build-standalone — users do not need a system Python. +- 9router binary is pulled at build time by `scripts/fetch-router.sh` / `fetch-router.ps1`. The version pin (`0.3.60`) is load-bearing for cross-provider WebSearch — see root `CLAUDE.md`. + +## Versioning + +- Source of truth: `electron/package.json` `version`. Bump alongside any user-facing release; CI tags off it. +- Bump only when cutting a release — coordinate with the publish flow rather than landing version bumps speculatively. + +## Pitfalls + +- `build-staging/` is regenerated on every build; never commit it. +- Auto-updater reads the latest release feed from GitHub; staging/test builds should use a separate channel to avoid pushing unsigned bits to users. diff --git a/frontend/CLAUDE.md b/frontend/CLAUDE.md new file mode 100644 index 00000000..e4c81bee --- /dev/null +++ b/frontend/CLAUDE.md @@ -0,0 +1,28 @@ +# frontend/CLAUDE.md + +React 18 + TypeScript + webpack 5 + Redux. Entry: `src/app/Main.tsx`. Dev server on `:3000` proxies REST and WebSocket to backend on `:8324`. See root `CLAUDE.md` for repo-wide constraints. + +## Run + +- Dev (full stack): `bash run.sh`. +- Dev (frontend only): `bash frontend/run.sh` — runs `npm install` then `npm run dev`. +- No JS/TS test runner is wired up. Changes must be manually exercised in the running app before merging. + +## Key concepts + +- **Spatial dashboard** — agents are draggable nodes on a canvas; layout + selection state lives in Redux. +- **Settings draft persistence** — `AppSettings.dismissed_mcp_suggestions` is a map of MCP id → ISO timestamp; preserve this shape when modifying settings serialization. +- **Onboarding wizard** (`src/app/pages/Onboarding/`) — 8-step agentic cursor walkthrough. Cursor offsets, fit-to-view, AC popup timing, and group-meta dedup were each delicate to land; verify visually after touching this code. +- **Custom providers** — `AppSettings.custom_providers: CustomProvider[]` supports any OpenAI-compatible endpoint (e.g. LM Studio). + +## Conventions + +- TS only; no PropTypes. +- No eslint/prettier config — match nearby files. +- Onboarding-copy placeholders shaped like real API keys (`sk-ant-api03-…`) are already allowlisted in `.gitleaks.toml`. Reuse the existing placeholder rather than introducing new "example" tokens. +- MCP suggestion UI must surface only the vetted/default set — never expose the full upstream registry to users. + +## Pitfalls + +- Direct LLM calls from the frontend bypass the backend's provider routing and MCP gate. Don't add them; route through `/api/*` instead. +- Webpack-dev-server hot reload occasionally loses WS state — full page reload after backend restarts.