Architecture¶
Technical overview of goflow's internal architecture and execution model.
Current CLI status
The codebase contains both sequential and parallel orchestrator implementations, shared-memory building blocks, and truncation helpers. The current goflow run command uses the parallel orchestrator path (RunParallel), but does not automatically wire shared memory and does not automatically apply truncation during normal execution.
System Overview¶
┌─────────────────────────────────────────────────────────────────┐
│ goflow CLI │
│ ┌───────────┐ ┌───────────┐ ┌────────────┐ ┌────────────┐ │
│ │ Parser │→ │ DAG │→ │Orchestrator│→ │ Reporter │ │
│ │ │ │ Builder │ │ │ │ │ │
│ └───────────┘ └───────────┘ └────────────┘ └────────────┘ │
│ ↓ ↓ ↓ │
│ ┌───────────┐ ┌───────────┐ ┌────────────┐ ┌────────────┐ │
│ │ Agent │ │ Template │ │ Executor │ │ Audit │ │
│ │ Loader │ │ Engine │ │ │ │ Logger │ │
│ └───────────┘ └───────────┘ └────────────┘ └────────────┘ │
│ │ │
│ ┌───────┴───────┐ │
│ │ Session │ │
│ │ Monitor │ │
│ └───────┬───────┘ │
│ │ │
│ ↓ │
│ ┌────────────┐ │
│ │ Copilot │ │
│ │ SDK │ │
│ └────────────┘ │
└─────────────────────────────────────────────────────────────────┘
Component Responsibilities¶
Parser (pkg/workflow/parser.go)¶
- Reads and validates workflow YAML files
- Resolves agent file references
- Validates schema compliance
- Reports configuration errors
Agent Loader (pkg/agents/loader.go)¶
- Discovers agent files in standard paths
- Parses
.agent.mdfrontmatter - Extracts system prompts from markdown body
- Validates agent configurations
DAG Builder (pkg/workflow/dag.go)¶
- Constructs dependency graph from steps
- Performs topological sort
- Detects circular dependencies
- Groups steps into parallel execution levels
Orchestrator (pkg/orchestrator/orchestrator.go)¶
- Coordinates step execution order
- Contains both sequential and parallel execution implementations
- Handles condition evaluation
- Tracks step results
Executor (pkg/executor/)¶
- Executes individual steps via the Copilot SDK (default) or CLI subprocess (
--clifallback) - Creates isolated sessions per step
- Uses event-based session monitoring for completion detection
- Applies templates
- Captures outputs
- Routes BYOK provider configuration to the SDK
Session Monitor (pkg/executor/monitor.go)¶
- Tracks session state and progress via SDK events
- Handles 67+ event types (
session.idle,tool.execution_start,assistant.message_delta, etc.) - Provides real-time progress callbacks for verbose output
- Eliminates timeout requirements for long-running sessions
Template Engine (pkg/workflow/template.go)¶
- Resolves
{{inputs.X}}templates - Resolves
{{steps.Y.output}}templates - Contains truncation helpers, though the normal CLI path does not currently invoke them automatically
Reporter (pkg/reporter/reporter.go)¶
- Formats final output
- Selects output steps and formats markdown/json/plain text
- Supports markdown/json/plain formats
Audit Logger (pkg/audit/logger.go)¶
- Creates run directories
- Records step prompts and outputs
- Saves workflow metadata
- Manages retention policy
Execution Flow¶
Phase 1: Initialization¶
1. Parse workflow YAML
2. Validate schema
3. Load referenced agent files
4. Resolve input values (CLI + defaults)
5. Create audit directory
Phase 2: Planning¶
1. Build dependency graph
2. Detect circular dependencies → error if found
3. Topological sort into execution levels
4. Level 0: steps with no dependencies
5. Level N: steps whose dependencies are all in levels 0..N-1
Phase 3: Execution¶
For each level L in [0, 1, 2, ...]:
For each step S in level L (parallel):
1. Evaluate condition → skip if not met
2. Resolve templates in prompt
3. Create SDK session (or CLI subprocess with --cli)
4. Send prompt to AI
5. Capture output
6. Write to audit trail
Wait for all level L steps to complete
Phase 4: Output¶
1. Collect step outputs per output.steps
2. Apply truncation
3. Format as markdown/json/plain
4. Write to stdout and audit trail
Parallel Execution Model¶
DAG Levels¶
Steps are grouped by dependency depth:
steps:
- id: A # Level 0 (no deps)
- id: B # Level 0 (no deps)
- id: C # Level 1 (deps: [A])
depends_on: [A]
- id: D # Level 1 (deps: [B])
depends_on: [B]
- id: E # Level 2 (deps: [C, D])
depends_on: [C, D]
Execution:
Goroutine Model¶
func ExecuteLevel(steps []Step) {
var wg sync.WaitGroup
results := make(chan StepResult, len(steps))
for _, step := range steps {
wg.Add(1)
go func(s Step) {
defer wg.Done()
result := execute(s)
results <- result
}(step)
}
wg.Wait()
close(results)
}
Synchronization¶
- sync.WaitGroup — Wait for all parallel steps
- Thread-safe results map — Store step outputs
- No blocking across levels — Next level starts only after current completes
SDK & CLI Integration¶
goflow ships two executor backends. The Copilot SDK executor is the default;
the legacy CLI subprocess executor is available via --cli.
| Backend | Flag | Module | How it talks to Copilot |
|---|---|---|---|
| SDK (default) | (none) | pkg/executor/copilot_sdk.go |
JSON-RPC over stdio to a single managed CLI process |
| CLI fallback | --cli |
pkg/executor/copilot_cli.go |
Spawns a new copilot subprocess per Send() call |
Both backends require the Copilot CLI binary on $PATH (or at ~/.copilot/copilot).
The SDK is a Go library (github.com/github/copilot-sdk/go) compiled into the goflow
binary — users do not install it separately.
Session Per Step¶
Each step creates an isolated SDK session:
config := &copilot.SessionConfig{
Model: step.Model,
SystemMessage: &copilot.SystemMessageConfig{Content: step.SystemPrompt},
AvailableTools: step.Tools,
Provider: providerConfig, // nil = GitHub Models, non-nil = BYOK
}
session, _ := client.CreateSession(ctx, config)
result, _ := session.SendAndWait(ctx, copilot.MessageOptions{Content: prompt})
Why Session Per Step?¶
- Deterministic agent selection — Explicit agent assignment per step
- Clean context — No cross-contamination between steps
- Parallel execution — Independent sessions can run concurrently
- Audit clarity — Each step has isolated transcript
- BYOK support — Each session can route to a custom provider
Tool Exposure¶
Tools are configured on the SDK session:
The underlying Copilot CLI runtime handles tool execution regardless of which executor backend is used.
Shared Memory Implementation¶
Storage¶
Shared memory is a simple in-memory string with mutex protection:
type SharedMemory struct {
mu sync.RWMutex
content string
}
func (m *SharedMemory) Read() string {
m.mu.RLock()
defer m.mu.RUnlock()
return m.content
}
func (m *SharedMemory) Write(content string, mode string) {
m.mu.Lock()
defer m.mu.Unlock()
if mode == "append" {
m.content += "\n" + content
} else {
m.content = content
}
}
Tool Integration¶
Shared memory tools are registered with the SDK:
session.RegisterTool("shared_memory_read", func() string {
return memory.Read()
})
session.RegisterTool("shared_memory_write", func(content, mode string) {
memory.Write(content, mode)
})
Prompt Injection¶
When inject_into_prompt: true:
func buildPrompt(originalPrompt string, memory *SharedMemory) string {
return fmt.Sprintf(`## Shared Memory (Current State)
%s
---
%s`, memory.Read(), originalPrompt)
}
Audit Trail Structure¶
Directory Layout¶
.workflow-runs/
└── YYYY-MM-DDTHH-MM-SS_workflow-name/
├── workflow.meta.json # Run metadata
├── workflow.yaml # Workflow snapshot
├── final_output.md # Formatted output
├── memory.md # Shared memory state
└── steps/
├── 00_step-a/
│ ├── step.meta.json # Step metadata
│ ├── prompt.md # Resolved prompt
│ ├── output.md # AI response (final)
│ └── stream.jsonl # Real-time streaming log
└── 01_step-b/
└── ...
Stream Recording (stream.jsonl)¶
When streaming is enabled (--streaming flag), each step records all LLM events
to stream.jsonl in JSON Lines format. This provides a complete audit trail of
the LLM's "thought process" and enables several use cases:
Format: One JSON object per line, appended in real-time.
{"ts":"2026-03-30T14:32:05.001Z","type":"assistant.turn_start"}
{"ts":"2026-03-30T14:32:05.050Z","type":"assistant.message_delta","data":"I'll analyze"}
{"ts":"2026-03-30T14:32:05.080Z","type":"assistant.message_delta","data":" the code"}
{"ts":"2026-03-30T14:32:05.200Z","type":"tool.execution_start","data":{"tool":"grep","args":"{\"query\":\"password\"}"}}
{"ts":"2026-03-30T14:32:06.500Z","type":"tool.execution_complete","data":{"tool":"grep","status":"completed"}}
{"ts":"2026-03-30T14:32:07.000Z","type":"assistant.turn_end"}
{"ts":"2026-03-30T14:32:07.100Z","type":"session.idle"}
Event Types:
| Type | Description | Data |
|---|---|---|
assistant.turn_start |
LLM turn begins | — |
assistant.turn_end |
LLM turn ends | — |
assistant.message_delta |
Streaming text chunk | String (the text) |
tool.execution_start |
Tool call begins | {tool, args} |
tool.execution_complete |
Tool call ends | {tool, status} |
session.idle |
Session completed | — |
session.error |
Session error | Error message |
user.input_requested |
LLM asks for input | {prompt, choices} |
user.input_response |
User's response | String (the answer) |
Use Cases:
- Debugging — See exactly what the LLM was doing before an error
- TUI/CLI — Display real-time streaming output or switch between step streams
- Interactive mode — Show accumulated context when LLM asks for user input
- Audit compliance — Full transparency into LLM behavior
- Replay — Reconstruct the LLM's decision-making process
Live Monitoring:
# Tail a single step's stream
tail -f .workflow-runs/.../steps/01_analyze/stream.jsonl
# Watch all steps (in parallel execution)
tail -f .workflow-runs/.../steps/*/stream.jsonl
Metadata Files¶
workflow.meta.json:
{
"name": "code-review",
"started_at": "2026-03-26T10:00:00Z",
"completed_at": "2026-03-26T10:02:30Z",
"status": "completed",
"duration_ms": 150000,
"inputs": {
"files": "src/*.go"
}
}
step.meta.json:
{
"id": "analyze",
"agent": "code-analyzer",
"model": "gpt-4o",
"started_at": "2026-03-26T10:00:05Z",
"completed_at": "2026-03-26T10:00:45Z",
"duration_ms": 40000,
"status": "completed",
"condition_met": true
}
MCP Server Integration¶
Configuration¶
MCP servers are defined in agent files:
mcp-servers:
db-tools:
command: docker
args: ["run", "--rm", "db-mcp:latest"]
env:
DB_HOST: localhost
Lifecycle¶
- Start — MCP server process started when agent session begins
- Communication — SDK communicates via stdio
- Stop — Process terminated when step completes
Tool Registration¶
MCP server tools are registered dynamically:
Error Handling¶
Validation Errors¶
Caught during Phase 1 (Initialization):
- Invalid YAML syntax
- Missing required fields
- Unknown agent references
- Missing required inputs
Execution Errors¶
Caught during Phase 3 (Execution):
- SDK/CLI failures
- Tool execution errors
- Timeout violations
- Model errors
Error Propagation¶
Partial results are saved to audit trail before exit.
Configuration Precedence¶
Settings are resolved in this order (later wins):
- Built-in defaults
- Workflow config section
- Agent file settings
- Step-level overrides
- CLI flags
Example (model selection):
Default: provider default
← Workflow: config.model: "gpt-4o"
← Agent: model: "claude-3-opus"
← Step: model: "gpt-4-turbo"
← CLI: --model gpt-4o-mini
See Also¶
- Workflow Schema — Configuration reference
- Agent Format — Agent file structure
- CLI Reference — Command-line options