primeWorkspaceMCPCache populates workspaceMCPToolsCache after the create_workspace or start_workspace tool finishes waiting for the workspace agent to become reachable. By the time it runs the agent is already Ready, so a single ListMCPTools call usually succeeds. When the agent's MCP server is stil
( ctx context.Context, logger slog.Logger, chatID uuid.UUID, workspaceCtx *turnWorkspaceContext, )
| 626 | // the budget. The next user turn re-runs top-of-turn discovery from |
| 627 | // scratch. |
| 628 | func (p *Server) primeWorkspaceMCPCache( |
| 629 | ctx context.Context, |
| 630 | logger slog.Logger, |
| 631 | chatID uuid.UUID, |
| 632 | workspaceCtx *turnWorkspaceContext, |
| 633 | ) { |
| 634 | deadline := p.clock.Now().Add(workspaceMCPPrimeMaxWait) |
| 635 | attempt := 0 |
| 636 | for { |
| 637 | attempt++ |
| 638 | tools := p.discoverWorkspaceMCPTools(ctx, logger, chatID, workspaceCtx) |
| 639 | if len(tools) > 0 { |
| 640 | logger.Debug(ctx, "primed workspace MCP cache", |
| 641 | slog.F("chat_id", chatID), |
| 642 | slog.F("tool_count", len(tools)), |
| 643 | slog.F("attempts", attempt), |
| 644 | ) |
| 645 | return |
| 646 | } |
| 647 | if ctx.Err() != nil { |
| 648 | return |
| 649 | } |
| 650 | if !p.clock.Now().Before(deadline) { |
| 651 | logger.Debug(ctx, |
| 652 | "workspace MCP cache primer gave up waiting for tools", |
| 653 | slog.F("chat_id", chatID), |
| 654 | slog.F("attempts", attempt), |
| 655 | ) |
| 656 | return |
| 657 | } |
| 658 | timer := p.clock.NewTimer(workspaceMCPPrimeRetryInterval, "chatd", "workspace-mcp-prime") |
| 659 | select { |
| 660 | case <-timer.C: |
| 661 | case <-ctx.Done(): |
| 662 | timer.Stop() |
| 663 | return |
| 664 | } |
| 665 | } |
| 666 | } |
| 667 | |
| 668 | type turnWorkspaceContext struct { |
| 669 | server *Server |