@@ -940,3 +940,95 @@ func (c *coordinator) refreshApiKeyTemplate(ctx context.Context, providerCfg con
941 return nil
941 return nil
944+// runSubAgent runs a sub-agent and handles session management and cost accumulation.
945+// It creates a sub-session, runs the agent with the given prompt, and propagates
946+// the cost to the parent session.
947+func (c *coordinator) runSubAgent(
948+ ctx context.Context,
950+ sessionID, agentMessageID, toolCallID string,
952+ sessionTitle string,
953+) (fantasy.ToolResponse, error) {
954+ return c.runSubAgentWithOptions(ctx, agent, sessionID, agentMessageID, toolCallID, prompt, sessionTitle, nil)
957+// runSubAgentWithOptions runs a sub-agent with additional session configuration options.
958+// The sessionSetup function is called after session creation but before agent execution.
959+func (c *coordinator) runSubAgentWithOptions(
960+ ctx context.Context,
962+ sessionID, agentMessageID, toolCallID string,
964+ sessionTitle string,
965+ sessionSetup func(sessionID string),
966+) (fantasy.ToolResponse, error) {
967+ // Create sub-session
968+ agentToolSessionID := c.sessions.CreateAgentToolSessionID(agentMessageID, toolCallID)
969+ session, err := c.sessions.CreateTaskSession(ctx, agentToolSessionID, sessionID, sessionTitle)
971+ return fantasy.ToolResponse{}, fmt.Errorf("create session: %w", err)
974+ // Call session setup function if provided
975+ if sessionSetup != nil {
976+ sessionSetup(session.ID)
979+ // Get model configuration
980+ model := agent.Model()
981+ maxTokens := model.CatwalkCfg.DefaultMaxTokens
982+ if model.ModelCfg.MaxTokens != 0 {
983+ maxTokens = model.ModelCfg.MaxTokens
986+ providerCfg, ok := c.cfg.Providers.Get(model.ModelCfg.Provider)
988+ return fantasy.ToolResponse{}, errors.New("model provider not configured")
992+ result, err := agent.Run(ctx, SessionAgentCall{
993+ SessionID: session.ID,
995+ MaxOutputTokens: maxTokens,
996+ ProviderOptions: getProviderOptions(model, providerCfg),
997+ Temperature: model.ModelCfg.Temperature,
998+ TopP: model.ModelCfg.TopP,
999+ TopK: model.ModelCfg.TopK,
1000+ FrequencyPenalty: model.ModelCfg.FrequencyPenalty,
1001+ PresencePenalty: model.ModelCfg.PresencePenalty,
1004+ return fantasy.NewTextErrorResponse("error generating response"), nil
1007+ // Update parent session cost
1008+ if err := c.updateParentSessionCost(ctx, session.ID, sessionID); err != nil {
1009+ return fantasy.ToolResponse{}, err
1012+ return fantasy.NewTextResponse(result.Response.Content.Text()), nil
1015+// updateParentSessionCost accumulates the cost from a child session to its parent session.
1016+func (c *coordinator) updateParentSessionCost(ctx context.Context, childSessionID, parentSessionID string) error {
1017+ childSession, err := c.sessions.Get(ctx, childSessionID)
1019+ return fmt.Errorf("get child session: %w", err)
1022+ parentSession, err := c.sessions.Get(ctx, parentSessionID)
1024+ return fmt.Errorf("get parent session: %w", err)
1027+ parentSession.Cost += childSession.Cost
1029+ if _, err := c.sessions.Save(ctx, parentSession); err != nil {
1030+ return fmt.Errorf("save parent session: %w", err)