Skip to content

Commit 1d6956c

Browse files
committed
Sequential Worflow
1 parent fce5af9 commit 1d6956c

File tree

15 files changed

+655
-6
lines changed

15 files changed

+655
-6
lines changed

cagent-schema.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@
2828
"metadata": {
2929
"$ref": "#/definitions/Metadata",
3030
"description": "Configuration metadata"
31+
},
32+
"workflow": {
33+
"type": "array",
34+
"description": "Sequential workflow steps",
35+
"items": {
36+
"$ref": "#/definitions/WorkflowStep"
37+
}
3138
}
3239
},
3340
"additionalProperties": false,
@@ -413,6 +420,23 @@
413420
},
414421
"required": ["path", "cmd"],
415422
"additionalProperties": false
423+
},
424+
"WorkflowStep": {
425+
"type": "object",
426+
"description": "A single step in a workflow",
427+
"properties": {
428+
"type": {
429+
"type": "string",
430+
"description": "Type of workflow step",
431+
"enum": ["agent"]
432+
},
433+
"name": {
434+
"type": "string",
435+
"description": "Name of the agent to run"
436+
}
437+
},
438+
"required": ["type", "name"],
439+
"additionalProperties": false
416440
}
417441
}
418442
}

cmd/root/run.go

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"github.com/docker/cagent/pkg/app"
2424
"github.com/docker/cagent/pkg/chat"
2525
"github.com/docker/cagent/pkg/config"
26+
v2 "github.com/docker/cagent/pkg/config/v2"
2627
"github.com/docker/cagent/pkg/content"
2728
"github.com/docker/cagent/pkg/evaluation"
2829
"github.com/docker/cagent/pkg/remote"
@@ -32,6 +33,7 @@ import (
3233
"github.com/docker/cagent/pkg/teamloader"
3334
"github.com/docker/cagent/pkg/telemetry"
3435
"github.com/docker/cagent/pkg/tui"
36+
"github.com/docker/cagent/pkg/workflow"
3537
)
3638

3739
var (
@@ -205,6 +207,26 @@ func doRunCommand(ctx context.Context, args []string, exec bool) error {
205207
slog.Error("Failed to stop tool sets", "error", err)
206208
}
207209
}()
210+
211+
// Check if this is a workflow by loading the config
212+
dir := filepath.Dir(agentFilename)
213+
fs, err := os.OpenRoot(dir)
214+
if err == nil {
215+
defer fs.Close()
216+
cfg, err := config.LoadConfig(filepath.Base(agentFilename), fs)
217+
if err == nil && len(cfg.Workflow) > 0 {
218+
// This is a workflow - check if TUI should be used
219+
slog.Info("Detected workflow configuration", "steps", len(cfg.Workflow))
220+
221+
// For exec mode or --tui=false, run without TUI
222+
if exec || !useTUI {
223+
return runWorkflowCLI(ctx, cfg, agents)
224+
}
225+
226+
// Default: use TUI for workflow
227+
return runWorkflowTUI(ctx, agentFilename, cfg, agents)
228+
}
229+
}
208230
} else {
209231
// For remote runtime, just store the original agent filename
210232
// The remote server will handle agent loading
@@ -882,3 +904,170 @@ func printAvailableCommands(agentName string, cmds map[string]string) {
882904
fmt.Printf(" - %s: %s\n", n, cmds[n])
883905
}
884906
}
907+
908+
// runWorkflowCLI executes a sequential workflow in CLI mode (--tui=false)
909+
func runWorkflowCLI(ctx context.Context, cfg *v2.Config, agents *team.Team) error {
910+
executor := workflow.New(cfg, agents)
911+
912+
// Create an events channel
913+
events := make(chan runtime.Event, 128)
914+
915+
// Run the workflow in a goroutine
916+
go func() {
917+
defer close(events)
918+
if err := executor.Execute(ctx, events); err != nil {
919+
events <- runtime.Error(err.Error())
920+
}
921+
}()
922+
923+
var lastErr error
924+
currentAgent := ""
925+
llmIsTyping := false
926+
927+
for event := range events {
928+
switch e := event.(type) {
929+
case *runtime.WorkflowStepStartedEvent:
930+
if llmIsTyping {
931+
fmt.Println()
932+
llmIsTyping = false
933+
}
934+
currentAgent = e.AgentName
935+
fmt.Printf("\n%s\n", bold(fmt.Sprintf("⏳ Step %d: Running agent '%s'...", e.StepIndex+1, e.AgentName)))
936+
937+
case *runtime.WorkflowStepCompletedEvent:
938+
if llmIsTyping {
939+
fmt.Println()
940+
llmIsTyping = false
941+
}
942+
fmt.Printf("%s\n", bold(fmt.Sprintf("✅ Step %d completed", e.StepIndex+1)))
943+
944+
case *runtime.WorkflowStepFailedEvent:
945+
if llmIsTyping {
946+
fmt.Println()
947+
llmIsTyping = false
948+
}
949+
fmt.Printf("%s\n", red("❌ Step %d failed: %s", e.StepIndex+1, e.Error))
950+
lastErr = fmt.Errorf("workflow step %d failed: %s", e.StepIndex+1, e.Error)
951+
952+
case *runtime.WorkflowCompletedEvent:
953+
if llmIsTyping {
954+
fmt.Println()
955+
llmIsTyping = false
956+
}
957+
fmt.Printf("\n%s\n", bold("🎉 Workflow completed successfully!"))
958+
fmt.Printf("\n%s\n", bold("Final Output:"))
959+
fmt.Printf("%s\n", e.FinalOutput)
960+
961+
case *runtime.AgentChoiceEvent:
962+
if e.AgentName != currentAgent {
963+
if llmIsTyping {
964+
fmt.Println()
965+
}
966+
currentAgent = e.AgentName
967+
printAgentName(e.AgentName)
968+
llmIsTyping = false
969+
}
970+
if !llmIsTyping {
971+
fmt.Println()
972+
llmIsTyping = true
973+
}
974+
fmt.Print(e.Content)
975+
976+
case *runtime.ToolCallEvent:
977+
if llmIsTyping {
978+
fmt.Println()
979+
llmIsTyping = false
980+
}
981+
printToolCall(e.ToolCall)
982+
983+
case *runtime.ToolCallResponseEvent:
984+
if llmIsTyping {
985+
fmt.Println()
986+
llmIsTyping = false
987+
}
988+
printToolCallResponse(e.ToolCall, e.Response)
989+
990+
case *runtime.ErrorEvent:
991+
if llmIsTyping {
992+
fmt.Println()
993+
llmIsTyping = false
994+
}
995+
lastErr = fmt.Errorf("%s", e.Error)
996+
printError(lastErr)
997+
}
998+
}
999+
1000+
if llmIsTyping {
1001+
fmt.Println()
1002+
}
1003+
1004+
if lastErr != nil {
1005+
return lastErr
1006+
}
1007+
1008+
return nil
1009+
}
1010+
1011+
// runWorkflowTUI executes a sequential workflow in TUI mode (default)
1012+
func runWorkflowTUI(ctx context.Context, agentFilename string, cfg *v2.Config, agents *team.Team) error {
1013+
// Create a dummy session for workflow tracking
1014+
sess := session.New()
1015+
sess.Title = "Workflow: " + filepath.Base(agentFilename)
1016+
1017+
// Get the first workflow step's agent for runtime (just for app compatibility)
1018+
var firstAgentName string
1019+
if len(cfg.Workflow) > 0 {
1020+
firstAgentName = cfg.Workflow[0].Name
1021+
} else {
1022+
return fmt.Errorf("no workflow steps defined")
1023+
}
1024+
1025+
// Create a minimal runtime (we won't actually use it for execution, just for app compatibility)
1026+
rt, err := runtime.New(agents,
1027+
runtime.WithCurrentAgent(firstAgentName),
1028+
runtime.WithSessionCompaction(false),
1029+
)
1030+
if err != nil {
1031+
return fmt.Errorf("failed to create runtime: %w", err)
1032+
}
1033+
1034+
// Create the app which will handle event forwarding to TUI
1035+
a := app.New("cagent", agentFilename, rt, agents, sess, nil)
1036+
m := tui.New(a)
1037+
1038+
progOpts := []tea.ProgramOption{
1039+
tea.WithAltScreen(),
1040+
tea.WithContext(ctx),
1041+
tea.WithFilter(tui.MouseEventFilter),
1042+
tea.WithMouseCellMotion(),
1043+
tea.WithMouseAllMotion(),
1044+
}
1045+
1046+
p := tea.NewProgram(m, progOpts...)
1047+
1048+
// Start the event subscription in the background
1049+
go a.Subscribe(ctx, p)
1050+
1051+
// Run the workflow executor and forward events to the app's event channel
1052+
go func() {
1053+
executor := workflow.New(cfg, agents)
1054+
events := make(chan runtime.Event, 128)
1055+
1056+
// Execute workflow
1057+
go func() {
1058+
defer close(events)
1059+
if err := executor.Execute(ctx, events); err != nil {
1060+
events <- runtime.Error(err.Error())
1061+
}
1062+
}()
1063+
1064+
// Forward all workflow events to the app's event channel (which TUI will receive)
1065+
for event := range events {
1066+
a.SendEvent(event)
1067+
}
1068+
}()
1069+
1070+
// Run the TUI
1071+
_, err = p.Run()
1072+
return err
1073+
}

examples/README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,24 @@ These are more advanced examples, most of them involve some sort of MCP server t
4444
| [couchbase_agent.yaml](couchbase_agent.yaml) | Run Database commands using MCP tools | | | | | | docker,[couchbase](https://hub.docker.com/mcp/server/couchbase/overview) | |
4545
| [notion-expert.yaml](notion-expert.yaml) | Notion documentation expert using OAuth | | | | | | [notion](https://mcp.notion.com) (uses OAuth) | |
4646

47+
## **Workflow Configurations**
48+
49+
These examples demonstrate sequential workflow execution where multiple agents are chained together. Each agent processes the output from the previous agent, creating a pipeline of transformations. Workflows don't require a root agent - they execute agents in the order defined in the workflow section.
50+
51+
See [WORKFLOW_README.md](WORKFLOW_README.md) for detailed documentation.
52+
53+
| Name | Description/Purpose | Steps | Models Used |
54+
|------------------------------------------------------------------|-------------------------------------------|-------|-------------|
55+
| [story_workflow.yaml](story_workflow.yaml) | Creative writing workflow | 3 | OpenAI GPT-4o |
56+
| [product_description_workflow.yaml](product_description_workflow.yaml) | Marketing content generation | 3 | OpenAI GPT-4o |
57+
| [joke_workflow.yaml](joke_workflow.yaml) | Joke creation, translation, and formatting | 3 | OpenAI GPT-4o |
58+
59+
**How workflows work:**
60+
- Agents execute sequentially in the order defined
61+
- Output from each agent becomes input for the next
62+
- No root agent required
63+
- Run with: `./bin/cagent run examples/story_workflow.yaml`
64+
4765
## **Multi-Agent Configurations**
4866

4967
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.

examples/WORKFLOW_README.md

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
# Sequential Workflow Execution
2+
3+
This directory contains examples of sequential workflow execution in cagent. Workflows allow you to chain multiple agents together, where each agent processes the output from the previous agent.
4+
5+
## Examples
6+
7+
### Story Generation Workflow
8+
9+
The `story_workflow.yaml` file demonstrates a creative writing workflow with three agents:
10+
11+
1. **story_starter** - Writes the opening paragraph of a story about a robot learning to cook
12+
2. **add_dialogue** - Continues the story by adding dialogue between the robot and a chef
13+
3. **add_ending** - Completes the story with a satisfying conclusion
14+
15+
```bash
16+
./bin/cagent run examples/story_workflow.yaml
17+
```
18+
19+
### Product Description Workflow
20+
21+
The `product_description_workflow.yaml` file shows a marketing content workflow:
22+
23+
1. **draft_writer** - Creates an initial product description for a smart water bottle
24+
2. **make_exciting** - Rewrites the description with more engaging language
25+
3. **add_cta** - Adds a compelling call-to-action
26+
27+
```bash
28+
./bin/cagent run examples/product_description_workflow.yaml
29+
```
30+
31+
### Joke Workflow
32+
33+
The `joke_workflow.yaml` demonstrates a simple two-step comedy workflow:
34+
35+
1. **joke_writer** - Creates an original joke
36+
2. **joke_improver** - Enhances the joke with better timing or punchline
37+
38+
```bash
39+
./bin/cagent run examples/joke_workflow.yaml
40+
```
41+
42+
## How It Works
43+
44+
The `run` command automatically detects workflows by checking if the configuration file contains a `workflow` section. No special command is needed!
45+
46+
## Workflow Configuration
47+
48+
### Basic Structure
49+
50+
```yaml
51+
version: "2"
52+
53+
agents:
54+
agent_name:
55+
model: openai/gpt-4o
56+
instruction: |
57+
Your agent instructions here
58+
59+
workflow:
60+
- type: agent
61+
name: agent_name
62+
- type: agent
63+
name: next_agent
64+
```
65+
66+
### Key Features
67+
68+
1. **Sequential Execution**: Agents run in the order defined in the workflow
69+
2. **Data Piping**: The output of each agent becomes the input for the next agent
70+
3. **Automatic Context**: The first agent receives instructions to generate initial content, subsequent agents receive the previous output as input
71+
4. **No Root Agent Required**: Workflows don't need a "root" agent - just define the agents used in your workflow steps
72+
73+
### Example Flow
74+
75+
```
76+
Step 1: story_starter
77+
→ Output: "RoboChef-42 had never encountered a kitchen before..."
78+
79+
Step 2: add_dialogue (receives previous output)
80+
→ Output: "...Chef Lucia approached with curiosity..."
81+
82+
Step 3: add_ending (receives previous output)
83+
→ Output: "...a bright future in the culinary world."
84+
```
85+
86+
## Command Options
87+
88+
Workflows support the same runtime configuration flags as regular agent runs:
89+
90+
### Running without TUI (CLI mode)
91+
92+
```bash
93+
./bin/cagent run examples/story_workflow.yaml --tui=false
94+
```
95+
96+
### Model Overrides
97+
98+
Override specific agent models:
99+
100+
```bash
101+
./bin/cagent run examples/story_workflow.yaml \
102+
--model story_starter=anthropic/claude-sonnet-4-0 \
103+
--model add_dialogue=openai/gpt-4o
104+
```
105+
106+
### Debug Mode
107+
108+
```bash
109+
./bin/cagent run examples/story_workflow.yaml --debug
110+
```
111+
112+
## Notes
113+
114+
- Each agent's output is passed as text to the next agent
115+
- The workflow stops immediately if any agent fails
116+
- Model overrides can be specified per agent using `--model agent_name=provider/model`
117+
- Currently only supports `type: agent` workflow steps (future: conditions, parallel execution)
118+
- The final output of the workflow is the output from the last agent in the sequence

0 commit comments

Comments
 (0)