Introspection
Inspect and debug orchestration plans before running them.
Explain
Explain() returns a human-readable representation of the execution plan
showing levels, parallelism, dependencies, and guards.
plan := orchestrator.NewPlan(client)
health := plan.TaskFunc("check-health", healthFn)
hostname := plan.TaskFunc("get-hostname",
func(
ctx context.Context,
c *client.Client,
) (*orchestrator.Result, error) {
resp, err := c.Node.Hostname(ctx, "_any")
if err != nil {
return nil, err
}
return orchestrator.CollectionResult(
resp.Data,
func(r client.HostnameResult) orchestrator.HostResult {
return orchestrator.HostResult{
Hostname: r.Hostname,
Changed: r.Changed,
Error: r.Error,
}
},
), nil
},
)
hostname.DependsOn(health)
fmt.Println(plan.Explain())
Output:
Plan: 2 tasks, 2 levels
Level 0:
check-health [fn]
Level 1:
get-hostname [fn] <- check-health
Tasks are annotated with their type (fn for functional tasks), dependency
edges (<-), and any active guards (only-if-changed, when).
Levels
Levels() returns the levelized DAG — tasks grouped into execution levels where
all tasks in a level can run concurrently. Returns an error if the plan fails
validation.
levels, err := plan.Levels()
if err != nil {
log.Fatal(err)
}
for i, level := range levels {
fmt.Printf("Level %d: %d tasks\n", i, len(level))
}
Validate
Validate() checks the plan for errors without executing it. It detects
duplicate task names and dependency cycles.
if err := plan.Validate(); err != nil {
log.Fatal(err) // e.g., "duplicate task name: "foo""
// "cycle detected: "a" depends on "b""
}
Run() calls Validate() internally, so explicit validation is only needed
when you want to catch errors before execution — for example, during plan
construction or in tests.