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
45 changes: 45 additions & 0 deletions cagent-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@
"metadata": {
"$ref": "#/definitions/Metadata",
"description": "Configuration metadata"
},
"workflow": {
"type": "array",
"description": "Sequential workflow steps",
"items": {
"$ref": "#/definitions/WorkflowStep"
}
}
},
"additionalProperties": false,
Expand Down Expand Up @@ -526,6 +533,44 @@
"cmd"
],
"additionalProperties": false
},
"WorkflowStep": {
"type": "object",
"description": "A single step in a workflow",
"properties": {
"type": {
"type": "string",
"description": "Type of workflow step",
"enum": ["agent", "parallel"]
},
"name": {
"type": "string",
"description": "Name of the agent to run (for type 'agent')"
},
"steps": {
"type": "array",
"description": "List of agent names to run in parallel (for type 'parallel')",
"items": {
"type": "string"
}
}
},
"required": ["type"],
"additionalProperties": false,
"oneOf": [
{
"properties": {
"type": { "const": "agent" }
},
"required": ["name"]
},
{
"properties": {
"type": { "const": "parallel" }
},
"required": ["steps"]
}
]
}
}
}
189 changes: 189 additions & 0 deletions cmd/root/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/docker/cagent/pkg/app"
"github.com/docker/cagent/pkg/chat"
"github.com/docker/cagent/pkg/config"
v2 "github.com/docker/cagent/pkg/config/v2"
"github.com/docker/cagent/pkg/content"
"github.com/docker/cagent/pkg/evaluation"
"github.com/docker/cagent/pkg/remote"
Expand All @@ -32,6 +33,7 @@ import (
"github.com/docker/cagent/pkg/teamloader"
"github.com/docker/cagent/pkg/telemetry"
"github.com/docker/cagent/pkg/tui"
"github.com/docker/cagent/pkg/workflow"
)

var (
Expand Down Expand Up @@ -205,6 +207,26 @@ func doRunCommand(ctx context.Context, args []string, exec bool) error {
slog.Error("Failed to stop tool sets", "error", err)
}
}()

// Check if this is a workflow by loading the config
dir := filepath.Dir(agentFilename)
fs, err := os.OpenRoot(dir)
if err == nil {
defer fs.Close()
cfg, err := config.LoadConfig(filepath.Base(agentFilename), fs)
if err == nil && len(cfg.Workflow) > 0 {
// This is a workflow - check if TUI should be used
slog.Info("Detected workflow configuration", "steps", len(cfg.Workflow))

// For exec mode or --tui=false, run without TUI
if exec || !useTUI {
return runWorkflowCLI(ctx, cfg, agents)
}

// Default: use TUI for workflow
return runWorkflowTUI(ctx, agentFilename, cfg, agents)
}
}
} else {
// For remote runtime, just store the original agent filename
// The remote server will handle agent loading
Expand Down Expand Up @@ -882,3 +904,170 @@ func printAvailableCommands(agentName string, cmds map[string]string) {
fmt.Printf(" - %s: %s\n", n, cmds[n])
}
}

// runWorkflowCLI executes a sequential workflow in CLI mode (--tui=false)
func runWorkflowCLI(ctx context.Context, cfg *v2.Config, agents *team.Team) error {
executor := workflow.New(cfg, agents)

// Create an events channel
events := make(chan runtime.Event, 128)

// Run the workflow in a goroutine
go func() {
defer close(events)
if err := executor.Execute(ctx, events); err != nil {
events <- runtime.Error(err.Error())
}
}()

var lastErr error
currentAgent := ""
llmIsTyping := false

for event := range events {
switch e := event.(type) {
case *runtime.WorkflowStepStartedEvent:
if llmIsTyping {
fmt.Println()
llmIsTyping = false
}
currentAgent = e.AgentName
fmt.Printf("\n%s\n", bold(fmt.Sprintf("⏳ Step %d: Running agent '%s'...", e.StepIndex+1, e.AgentName)))

case *runtime.WorkflowStepCompletedEvent:
if llmIsTyping {
fmt.Println()
llmIsTyping = false
}
fmt.Printf("%s\n", bold(fmt.Sprintf("✅ Step %d completed", e.StepIndex+1)))

case *runtime.WorkflowStepFailedEvent:
if llmIsTyping {
fmt.Println()
llmIsTyping = false
}
fmt.Printf("%s\n", red("❌ Step %d failed: %s", e.StepIndex+1, e.Error))
lastErr = fmt.Errorf("workflow step %d failed: %s", e.StepIndex+1, e.Error)

case *runtime.WorkflowCompletedEvent:
if llmIsTyping {
fmt.Println()
llmIsTyping = false
}
fmt.Printf("\n%s\n", bold("🎉 Workflow completed successfully!"))
fmt.Printf("\n%s\n", bold("Final Output:"))
fmt.Printf("%s\n", e.FinalOutput)

case *runtime.AgentChoiceEvent:
if e.AgentName != currentAgent {
if llmIsTyping {
fmt.Println()
}
currentAgent = e.AgentName
printAgentName(e.AgentName)
llmIsTyping = false
}
if !llmIsTyping {
fmt.Println()
llmIsTyping = true
}
fmt.Print(e.Content)

case *runtime.ToolCallEvent:
if llmIsTyping {
fmt.Println()
llmIsTyping = false
}
printToolCall(e.ToolCall)

case *runtime.ToolCallResponseEvent:
if llmIsTyping {
fmt.Println()
llmIsTyping = false
}
printToolCallResponse(e.ToolCall, e.Response)

case *runtime.ErrorEvent:
if llmIsTyping {
fmt.Println()
llmIsTyping = false
}
lastErr = fmt.Errorf("%s", e.Error)
printError(lastErr)
}
}

if llmIsTyping {
fmt.Println()
}

if lastErr != nil {
return lastErr
}

return nil
}

// runWorkflowTUI executes a sequential workflow in TUI mode (default)
func runWorkflowTUI(ctx context.Context, agentFilename string, cfg *v2.Config, agents *team.Team) error {
// Create a dummy session for workflow tracking
sess := session.New()
sess.Title = "Workflow: " + filepath.Base(agentFilename)

// Get the first workflow step's agent for runtime (just for app compatibility)
var firstAgentName string
if len(cfg.Workflow) > 0 {
firstAgentName = cfg.Workflow[0].Name
} else {
return fmt.Errorf("no workflow steps defined")
}

// Create a minimal runtime (we won't actually use it for execution, just for app compatibility)
rt, err := runtime.New(agents,
runtime.WithCurrentAgent(firstAgentName),
runtime.WithSessionCompaction(false),
)
if err != nil {
return fmt.Errorf("failed to create runtime: %w", err)
}

// Create the app which will handle event forwarding to TUI
a := app.New("cagent", agentFilename, rt, agents, sess, nil)
m := tui.New(a)

progOpts := []tea.ProgramOption{
tea.WithAltScreen(),
tea.WithContext(ctx),
tea.WithFilter(tui.MouseEventFilter),
tea.WithMouseCellMotion(),
tea.WithMouseAllMotion(),
}

p := tea.NewProgram(m, progOpts...)

// Start the event subscription in the background
go a.Subscribe(ctx, p)

// Run the workflow executor and forward events to the app's event channel
go func() {
executor := workflow.New(cfg, agents)
events := make(chan runtime.Event, 128)

// Execute workflow
go func() {
defer close(events)
if err := executor.Execute(ctx, events); err != nil {
events <- runtime.Error(err.Error())
}
}()

// Forward all workflow events to the app's event channel (which TUI will receive)
for event := range events {
a.SendEvent(event)
}
}()

// Run the TUI
_, err = p.Run()
return err
}
21 changes: 21 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,27 @@ These are more advanced examples, most of them involve some sort of MCP server t
| [couchbase_agent.yaml](couchbase_agent.yaml) | Run Database commands using MCP tools | | | | | | docker,[couchbase](https://hub.docker.com/mcp/server/couchbase/overview) | |
| [notion-expert.yaml](notion-expert.yaml) | Notion documentation expert using OAuth | | | | | | [notion](https://mcp.notion.com) (uses OAuth) | |

## **Workflow Configurations**

These examples demonstrate workflow execution where multiple agents are chained together in sequential or parallel patterns. Workflows support both sequential execution (one agent after another) and parallel execution (multiple agents running concurrently). Workflows don't require a root agent - they execute agents in the order defined in the workflow section.

See [WORKFLOW_README.md](WORKFLOW_README.md) for detailed documentation.

| Name | Description/Purpose | Steps | Execution Pattern | Models Used |
|------------------------------------------------------------------|-------------------------------------------|-------|-------------------|-------------|
| [story_workflow.yaml](story_workflow.yaml) | Creative writing workflow | 3 | Sequential | OpenAI GPT-4o |
| [product_description_workflow.yaml](product_description_workflow.yaml) | Marketing content generation | 3 | Sequential | OpenAI GPT-4o |
| [joke_workflow.yaml](joke_workflow.yaml) | Joke creation, translation, and formatting | 3 | Sequential | OpenAI GPT-4o |
| [parallel_translation_workflow.yaml](parallel_translation_workflow.yaml) | Multi-language translation in parallel | 3 (1+3+1) | Mixed (Sequential + Parallel) | OpenAI GPT-4o |
| [parallel_sorting_workflow.yaml](parallel_sorting_workflow.yaml) | Compare sorting algorithms concurrently | 3 (1+4+1) | Mixed (Sequential + Parallel) | OpenAI GPT-4o |

**How workflows work:**
- **Sequential**: Agents execute one after another in order defined
- **Parallel**: Multiple agents run concurrently, processing the same input
- Output from each step becomes input for the next step
- No root agent required
- Run with: `./bin/cagent run examples/story_workflow.yaml`

## **Multi-Agent Configurations**

These examples are groups of agents working together. Each of them is specialized for a given task, and usually has some tools assigned to fulfill these tasks.
Expand Down
Loading
Loading