RunSkillFork executes a `context: fork` skill as an isolated sub-session. The expanded SKILL.md body becomes the child's first user message; the agent's own system prompt is preserved. Shared by the run_skill tool and the App's slash-command path.
(ctx context.Context, sess *session.Session, args skills.RunSkillArgs, evts EventSink)
| 30 | // agent's own system prompt is preserved. Shared by the run_skill tool |
| 31 | // and the App's slash-command path. |
| 32 | func (r *LocalRuntime) RunSkillFork(ctx context.Context, sess *session.Session, args skills.RunSkillArgs, evts EventSink) (*tools.ToolCallResult, error) { |
| 33 | st := r.CurrentAgentSkillsToolset() |
| 34 | if st == nil { |
| 35 | return tools.ResultError("no skills are available for the current agent"), nil |
| 36 | } |
| 37 | |
| 38 | prepared, errResult := st.PrepareForkSubSession(ctx, args) |
| 39 | if errResult != nil { |
| 40 | return errResult, nil |
| 41 | } |
| 42 | |
| 43 | ca := r.currentAgentName() |
| 44 | |
| 45 | // Open the span before any pre-delegation work so model resolution |
| 46 | // (inside WithAgentModel) is recorded under runtime.run_skill rather |
| 47 | // than the parent session span. |
| 48 | // |
| 49 | // Skills are workflow-shaped (a coordinated process the agent |
| 50 | // orchestrates), so the GenAI semconv `invoke_workflow` operation |
| 51 | // applies. Emit it via gen_ai.* attrs alongside the legacy keys |
| 52 | // for back-compat. |
| 53 | skillAttrs := []attribute.KeyValue{ |
| 54 | attribute.String(genai.AttrOperationName, genai.OperationInvokeWorkflow), |
| 55 | attribute.String(genai.AttrWorkflowName, prepared.SkillName), |
| 56 | attribute.String(genai.AttrAgentNameRuntime, ca), |
| 57 | attribute.String(genai.AttrConversationID, sess.ID), |
| 58 | } |
| 59 | if genai.EmitLegacyAttributes() { |
| 60 | skillAttrs = append(skillAttrs, |
| 61 | attribute.String("agent", ca), |
| 62 | attribute.String("skill", prepared.SkillName), |
| 63 | attribute.String("session.id", sess.ID), |
| 64 | ) |
| 65 | } |
| 66 | // Span name follows the GenAI agent semconv pattern |
| 67 | // `invoke_workflow {workflow.name}` so spec-aware backends |
| 68 | // classify the span as a workflow invocation. SpanKindInternal is |
| 69 | // passed explicitly per spec rather than relying on the SDK |
| 70 | // default — keeps intent clear and immune to default changes. |
| 71 | spanName := genai.OperationInvokeWorkflow |
| 72 | if prepared.SkillName != "" { |
| 73 | spanName = genai.OperationInvokeWorkflow + " " + prepared.SkillName |
| 74 | } |
| 75 | ctx, span := r.startSpan(ctx, spanName, |
| 76 | trace.WithSpanKind(trace.SpanKindInternal), |
| 77 | trace.WithAttributes(skillAttrs...), |
| 78 | ) |
| 79 | defer span.End() |
| 80 | |
| 81 | slog.DebugContext(ctx, "Running skill as sub-agent", |
| 82 | "agent", ca, |
| 83 | "skill", prepared.SkillName, |
| 84 | "task", prepared.Task, |
| 85 | ) |
| 86 | |
| 87 | // Apply the skill's optional model override for the sub-session. |
| 88 | // On failure we log and fall back to the agent's current model; |
| 89 | // restore is CAS-safe and always non-nil. |
no test coverage detected