Skip to content
review Visibility internal Owner erik@uvilo.com Approver _ Created 2026-07-26 Updated 2026-07-26

Project Automation Plan 13

Scope: Implement permanent agent handoff so that when Forge Chat hands off to Project Runner (or any target via Agent Handoff edges), the conversation’s active agent switches for all subsequent turns — without a LibreChat server patch.


Task 1 — Add switch_agent tool to Forge Chat’s MCP server (forge-spawn)

Context: Vision R5 (Smooth Agent Handoffs) — “Handoff between agent runs must be seamless.” Research §4 — Agent Handoff Design, Option A (Phase-file orchestration with consistent naming and persisted reports).

  • Add a switch_agent tool to Forge/Configs/MCP_Servers/forge-spawn/src/index.ts (the existing spawn MCP server)
  • The tool accepts two arguments:
    • agent_id (string, e.g. "agent_yG6v4v0wfi5EEHQ8htTXH") — the target agent to switch to
    • conversation_id (string) — the conversation whose agent should be changed
  • The tool’s implementation:
    1. Connect to LibreChat’s MongoDB using a new MongodbClient class (see steps below) and update the conversation: db.collection('conversations').updateOne({ conversationId }, { $set: { agent_id: agent_id } })
    2. Return a structured JSON response: { "action": "switch_agent", "agent_id": "<id>", "conversation_id": "<id>", "status": "success" }
    3. If the MongoDB update fails or no conversation is found, return { "action": "switch_agent", "agent_id": "<id>", "conversation_id": "<id>", "status": "error", "message": "<reason>" }
  • To add MongoDB access:
    1. Run cd Forge/Configs/MCP_Servers/forge-spawn && npm install mongodb
    2. Create src/mongo.ts with a MongodbClient class:
      • Constructor reads MONGO_URI from env (same MongoDB instance LibreChat uses)
      • async connect() — establishes connection
      • async updateConversationAgent(conversationId: string, agentId: string) — performs the updateOne
      • async close() — cleans up
    3. In src/index.ts, instantiate MongodbClient in initializeServices(), call connect() after DB init
    4. Pass the MongodbClient instance to the switch_agent tool handler (module-level variable, same pattern as db)
  • The tool description must make clear to the LLM: “Call this tool when you want to permanently transfer the user to a different agent. After calling this tool, the next user message will be handled by the target agent. This is for permanent handoff — use the Agent Handoff edge for single-turn delegation. Requires both agent_id and conversation_id parameters.”

Task 2 — Update Forge Chat prompt with routing rules for switch_agent

Context: Vision R5 (Smooth Agent Handoffs) — “each transition must leave the project in a state the next agent can pick up without confusion.”

  • Add a “Permanent Handoff” section to Forge/Configs/Agents/Forge_Chat_Prompt.md (after the existing PRE-WORK section):
    • When to use switch_agent: After you have already handed off to an agent via the Agent Handoff edge and the user continues the conversation about the same topic. Also use it when the user explicitly asks to switch to a specific agent by name.
    • How to use: Call the switch_agent tool with the target agent’s agent_id and the current conversation_id. Then use the Agent Handoff edge to transfer the current turn. Both calls together ensure the current turn goes to the right agent AND the next turn does too.
    • When NOT to use: For one-off tool calls or single-turn delegation where the user will return to general chat. For those, use only the Agent Handoff edge.
  • Do NOT hardcode agent IDs in the prompt. Instead, reference agent names (e.g., “Project Runner Master”, “Task Runner Sonnet”) and add a lookup table in a new file Forge/Configs/Agents/agent_ids.json:
    {
      "project_runner_master": "agent_yG6v4v0wfi5EEHQ8htTXH",
      "project_runner_sonnet": "agent_PvysxnUr1KLvpQqBSswQs",
      "task_runner_master": "agent_-uZUR-rTgMhfMZRbwmFAd",
      "task_runner_sonnet": "agent_M3dH1zdGmWGBAMB3uSpID"
    }
  • Add a brief note in the prompt: “Agent IDs are in Forge/Configs/Agents/agent_ids.json — read this file to resolve names to IDs when calling switch_agent.”
  • Keep the new section ≤120 words to stay within the Chat context budget

Task 3 — Update the “project” routing rule to use both mechanisms

Context: Vision R5 (Smooth Agent Handoffs). Spec §4 (Agent Handoffs) — routing table for phase dispatching.

  • Modify PRE-WORK rule 1 in Forge/Configs/Agents/Forge_Chat_Prompt.md:
    • Change from: “you must transfer to the Project Runner agent immediately — do not answer directly.”
    • Change to: “you must (1) call switch_agent with the Project Runner Master agent_id (from agent_ids.json) and the current conversation_id, then (2) transfer to the Project Runner agent via the Agent Handoff edge — do not answer directly.”
  • This ensures that when the word “project” triggers a handoff, it’s permanent from the start — no need for the user to mention “project” again.

Task 4 — Handle the overwrite race in chatCompletion finally block

Context: Vision R5 (Smooth Agent Handoffs) — transitions must be seamless. This task addresses the known race condition where LibreChat’s own saveConvo overwrites the agent_id that switch_agent set during the same turn.

After the graph run completes, LibreChat’s saveConvo writes agent_id from getSaveOptions(), which returns the original primary agent. This overwrites whatever switch_agent wrote to MongoDB during the run. The tool output returning { "action": "switch_agent" } is the signal to the client to fix this.

Step 4a — Verify LibreChat’s existing conversation update endpoint

  • Check if LibreChat has an existing PATCH /api/convos/:conversationId endpoint that accepts { agent_id } by reading the LibreChat source code (check api/server/routes/convos.js or equivalent)
  • If it exists: use it directly — skip Step 4c
  • If it doesn’t exist: proceed to Step 4c to create a minimal custom endpoint

Step 4b — Frontend SSE detection of switch_agent tool result

  • In the Uvilo frontend SSE message handler (wherever EventSource messages are processed), add detection for switch_agent tool results:
    • Listen for SSE events of type tool_call or message that contain tool result content
    • Parse the tool result JSON; if it contains "action": "switch_agent" and "status": "success", store the agent_id and conversation_id from the response
    • After the SSE stream closes (detected by the [DONE] event or stream termination), trigger the PATCH call from Step 4a/4c with the stored values

Step 4c — Custom PATCH endpoint (only if LibreChat doesn’t have one)

  • Create a minimal API route in the Uvilo frontend (same server that serves the UI):
    • Route: PATCH /api/convos/:conversationId
    • Body: { "agent_id": "<target_agent_id>" }
    • Implementation: connect to the same MongoDB instance, run db.collection('conversations').updateOne({ conversationId }, { $set: { agent_id } })
    • Auth: require the same session cookie / JWT that the main UI uses
    • Return: { "success": true } or { "success": false, "error": "<reason>" }
  • The exact file location depends on the Uvilo frontend framework — locate the existing API route directory and add this route alongside any existing conversation-related endpoints

Task 5 — Build, commit, push

Context: Spec §2 (Persisted Review and Evaluation Reports) — all changes must be committed and pushed.

  • Build the forge-spawn MCP server: cd Forge/Configs/MCP_Servers/forge-spawn && npm run build
  • Verify the build succeeds with no errors
  • Stage all changes, commit with message Project Automation: Plan 13 — Permanent agent handoff via switch_agent tool, and push to dev