Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 34 additions & 3 deletions agent-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://github.com/docker/docker-agent/blob/main/agent-schema.json",
"title": "Docker Agent Configuration",
"description": "Configuration schema for Docker Agent v8",
"description": "Configuration schema for Docker Agent v9",
"type": "object",
"properties": {
"version": {
Expand All @@ -17,7 +17,8 @@
"5",
"6",
"7",
"8"
"8",
"9"
],
"examples": [
"0",
Expand All @@ -28,7 +29,8 @@
"5",
"6",
"7",
"8"
"8",
"9"
]
},
"providers": {
Expand Down Expand Up @@ -415,6 +417,35 @@
"type": "string"
}
},
"subagents": {
"type": "array",
"description": "Schema v9: runtime-managed child agents that this agent can start dynamically via the subagent_start tool. Lives next to `sub_agents:` (which keeps driving the legacy transfer_task flow); both fields may co-exist. Each entry is either a string (agent name or external reference) or an object {agent, description}. Local references must match a name from the top-level `agents:` section; external references follow the same OCI image / URL / 'name:reference' rules as `sub_agents:`.",
"items": {
"oneOf": [
{
"type": "string",
"description": "Shorthand: agent name reference or external reference."
},
{
"type": "object",
"properties": {
"agent": {
"type": "string",
"description": "Agent name reference or external reference (OCI image / URL / 'name:reference')."
},
"description": {
"type": "string",
"description": "Optional hint shown to the parent model about when to start this subagent."
}
},
"required": [
"agent"
],
"additionalProperties": false
}
]
}
},
"handoffs": {
"type": "array",
"description": "List of agents this agent can hand off the conversation to. Can be names of agents defined in this config, external references (OCI images like 'namespace/repo' or URLs), or named external references using 'name:reference' syntax (e.g. 'reviewer:agentcatalog/review-pr'). External agents without an explicit name are named after their last path segment.",
Expand Down
40 changes: 40 additions & 0 deletions examples/subagents.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Schema v9 introduces a new top-level `subagents:` field on agents that
# drives the upcoming runtime-managed subagent feature. It lives next to
# `sub_agents:` (which keeps powering the legacy `transfer_task` flow) so
# existing configurations keep working unchanged.
#
# Each entry under `subagents:` accepts either:
# - a string shorthand (an agent name from this config, or an external
# OCI / URL reference, e.g. "agentcatalog/pirate")
# - the full object form `{ agent: <ref>, description: <hint> }` when
# you want to give the parent model a custom hint for when to start
# this subagent
#
# This example wires a coordinator that can start two child agents at
# runtime. Future PRs will land the runtime side that consumes this
# field; today this file exercises the parse + validate path.

agents:
coordinator:
model: openai/gpt-4o
description: "Coordinator that delegates to runtime-managed subagents"
instruction: |
You are a coordinator. Plan the work, then delegate specialist tasks
to your subagents instead of doing everything yourself.
subagents:
- researcher
- agent: writer
description: "Drafts the final document from research notes"

researcher:
model: openai/gpt-4o
description: "Web research helper"
instruction: |
You are a research helper. When a task arrives, gather the relevant
facts and summarise them in a short bullet list.

writer:
model: openai/gpt-4o
description: "Long-form writer"
instruction: |
You are a writer. Turn supplied research notes into polished prose.
24 changes: 18 additions & 6 deletions pkg/agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type Agent struct {
fallbackCooldown time.Duration // Duration to stick with fallback after non-retryable error
modelOverrides atomic.Pointer[[]provider.Provider] // Optional model override(s) set at runtime (supports alloy)
subAgents []*Agent
runtimeSubagents []*Agent
handoffs []*Agent
parents []*Agent
addDate bool
Expand Down Expand Up @@ -125,26 +126,37 @@ func (a *Agent) WelcomeMessage() string {
return a.welcomeMessage
}

// SubAgents returns the list of sub-agents
// SubAgents returns the list of legacy delegation sub-agents (transfer_task).
func (a *Agent) SubAgents() []*Agent {
return a.subAgents
}

// RuntimeSubagents returns the list of runtime-managed subagents (v9 subagents: key).
// These are distinct from the legacy sub_agents / transfer_task list.
func (a *Agent) RuntimeSubagents() []*Agent {
return a.runtimeSubagents
}

// Handoffs returns the list of handoff agents
func (a *Agent) Handoffs() []*Agent {
return a.handoffs
}

// HasSubAgents checks if the agent has legacy delegation sub-agents
func (a *Agent) HasSubAgents() bool {
return len(a.subAgents) > 0
}

// HasRuntimeSubagents reports whether the agent has runtime-managed subagents.
func (a *Agent) HasRuntimeSubagents() bool {
return len(a.runtimeSubagents) > 0
}

// Parents returns the list of parent agent names
func (a *Agent) Parents() []*Agent {
return a.parents
}

// HasSubAgents checks if the agent has sub-agents
func (a *Agent) HasSubAgents() bool {
return len(a.subAgents) > 0
}

// Model returns the model to use for this agent.
// If model override(s) are set, it returns one of the overrides (randomly for alloy).
// Otherwise, it returns a random model from the available models.
Expand Down
12 changes: 12 additions & 0 deletions pkg/agent/opts.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,18 @@ func WithSubAgents(subAgents ...*Agent) Opt {
}
}

// WithRuntimeSubagents configures runtime-managed subagents declared via the
// v9 `subagents:` config key. These are distinct from legacy transfer_task
// delegation targets configured via `sub_agents:`.
func WithRuntimeSubagents(subAgents ...*Agent) Opt {
return func(a *Agent) {
a.runtimeSubagents = subAgents
for _, subAgent := range subAgents {
subAgent.parents = append(subAgent.parents, a)
}
}
}

func WithHandoffs(handoffs ...*Agent) Opt {
return func(a *Agent) {
a.handoffs = handoffs
Expand Down
50 changes: 50 additions & 0 deletions pkg/api/types.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package api

import (
"encoding/json"
"time"

"github.com/docker/docker-agent/pkg/chat"
Expand Down Expand Up @@ -270,3 +271,52 @@ type SessionStatusResponse struct {
OutputTokens int64 `json:"output_tokens"`
NumMessages int `json:"num_messages"`
}

// ---------------------------------------------------------------------------
// Live-session observability DTOs (PR 5)
// ---------------------------------------------------------------------------

// LiveSessionNode represents a node in the currently-live agent/subagent tree.
// The tree is encoded recursively via the Children field.
type LiveSessionNode struct {
ID string `json:"id"`
ParentID string `json:"parent_id,omitempty"`
AgentName string `json:"agent_name"`
Status string `json:"status"`
Title string `json:"title,omitempty"`
Depth int `json:"depth,omitempty"`
LastPreview string `json:"last_preview,omitempty"`
CreatedAt time.Time `json:"created_at,omitzero"`
LastUpdateAt time.Time `json:"last_update_at,omitzero"`
Children []LiveSessionNode `json:"children,omitempty"`
}

// LiveSessionTreeResponse describes the current live tree for a root session.
// Nodes is a flat-plus-recursive slice; the first element is always the root.
type LiveSessionTreeResponse struct {
Nodes []LiveSessionNode `json:"nodes"`
}

// LiveSessionResponse describes a single live session (root or descendant).
type LiveSessionResponse struct {
ID string `json:"id"`
AgentName string `json:"agent_name"`
Status string `json:"status"`
}

// LiveSessionSnapshotResponse is returned by GET /live-sessions/:id/snapshot.
// Events contains the full event history of the session as raw JSON objects so
// a remote client can reconstruct the transcript without a separate /session
// lookup. Each element is a verbatim event JSON blob (same wire format the SSE
// stream emits).
type LiveSessionSnapshotResponse struct {
SessionID string `json:"session_id"`
Events []json.RawMessage `json:"events"`
}

// LiveSessionControlResponse is returned by control endpoints:
// steer, followup, close, interrupt, stop.
type LiveSessionControlResponse struct {
OK bool `json:"ok"`
Message string `json:"message"`
}
Loading