flow

package
v0.9.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 6, 2026 License: MIT Imports: 17 Imported by: 0

Documentation

Overview

Package flow implements jcode's dynamic-workflow engine: user- or agent-authored JavaScript orchestration scripts that fan work out across many subagents, run them in parallel/pipeline, and collect structured results — the plan lives in code (à la Claude Code Dynamic Workflows / Qoder CLI Workflows).

The engine is a thin goja (JavaScript) control-flow shell over a hand-written Go execution core. The script itself does no I/O (no shell/filesystem/network); every side effect happens through the agent() primitive, which spawns a subagent that goes through jcode's normal tools, permissions, and hooks.

Design doc: internal-doc/dynamic-workflow-design.md

Index

Constants

View Source
const (
	DefaultMaxConcurrent = 16   // concurrent agents
	DefaultMaxAgents     = 1000 // hard total per run (runaway backstop)
	DefaultTimeout       = 30 * time.Minute
)

Default caps, matching Claude Code's dynamic workflows.

Variables

This section is empty.

Functions

func RunIDFromContext

func RunIDFromContext(ctx context.Context) string

RunIDFromContext returns the run ID tagged on ctx, or "".

func SlashRunPrompt added in v0.9.3

func SlashRunPrompt(name, userInput string) string

SlashRunPrompt is the agent instruction a "/<workflow>" slash command expands to on every frontend (TUI / Web / ACP). It tells the agent to run the named saved workflow via the workflow_run tool rather than authoring an inline script; any text the user typed after the slash is handed over so the agent can shape it into the workflow's `args` object. Keeping the wording here means all frontends stay in lockstep.

func Validate

func Validate(source string) error

Validate pre-flights a workflow source WITHOUT running it: it checks the `meta` block parses and that the full script compiles as JavaScript. Use it to reject an agent-generated or user-supplied script with a clear error before spawning any agents. (Engine.Run compiles too, so a bad script never runs; Validate just moves that check up-front with a friendlier message.)

func WithRunID

func WithRunID(ctx context.Context, runID string) context.Context

WithRunID tags a context with the current run ID (used by spawn for journaling scope and logging).

Types

type AgentEvent

type AgentEvent struct {
	ID     string // stable within a run, e.g. "agent_7"
	Label  string
	Phase  string
	Status string // "running" | "done" | "failed"
	Tokens int64
	Detail string // last tool / short note
	Err    string
}

AgentEvent carries per-agent progress to the sink.

type AgentResult

type AgentResult struct {
	Text       string
	Structured interface{}
	Tokens     int64
}

AgentResult is what a single agent() call returns. When Schema was set and the subagent produced valid structured output, Structured is non-nil and is what the script sees; otherwise the script sees Text.

type AgentSpec

type AgentSpec struct {
	Prompt    string      `json:"-"`
	Label     string      `json:"label"`
	Phase     string      `json:"phase"`
	Model     string      `json:"model"`     // "provider/model" override; "" = session default
	AgentType string      `json:"agentType"` // explore|general|coordinator; "" = explore
	Schema    interface{} `json:"schema"`    // JSON Schema (as a JS object) → structured output
	Isolation string      `json:"isolation"` // "worktree" (recognised; isolation is a later phase)
}

AgentSpec describes a single agent() call from the script. Prompt is the first positional argument; the rest come from the opts object (mapped by json tag).

type Engine

type Engine struct {
	// contains filtered or unexported fields
}

Engine runs workflows. It depends only on a SpawnFunc and an EventSink, so it never imports the tools/model packages — the real spawn adapter is wired by the command layer, and tests inject a fake. One Engine can run many workflows.

func New

func New(spawn SpawnFunc, sink EventSink, opts ...Option) *Engine

New builds an Engine. sink may be nil (defaults to NopSink).

func (*Engine) Run

func (e *Engine) Run(ctx context.Context, wf Workflow, opts RunOptions) (interface{}, error)

Run executes a workflow to completion and returns its final value (whatever the script `return`s) or an error. It blocks until the run finishes, the context is cancelled, or the timeout fires. Safe to call concurrently for different runs.

type EventSink

type EventSink interface {
	OnRunStart(r RunInfo)
	OnPhase(runID, title, detail string)
	OnAgentStart(runID string, a AgentEvent)
	OnAgentDone(runID string, a AgentEvent)
	OnLog(runID, level, msg string)
	OnRunDone(runID string, result interface{}, err error)
}

EventSink receives run progress. It is frontend-agnostic: the headless CLI, TUI, Web, and ACP each provide an implementation. All methods must be safe for concurrent use — the engine calls them from the loop goroutine, but the sink may fan out to other goroutines. Sinks must not block for long (they run on the loop).

type FuncSink

type FuncSink struct {
	RunStart   func(RunInfo)
	Phase      func(runID, title, detail string)
	AgentStart func(runID string, a AgentEvent)
	AgentDone  func(runID string, a AgentEvent)
	Log        func(runID, level, msg string)
	RunDone    func(runID string, result interface{}, err error)
}

FuncSink adapts individual callbacks into an EventSink; unset fields are no-ops. Handy for the CLI and tests.

func (FuncSink) OnAgentDone

func (s FuncSink) OnAgentDone(runID string, a AgentEvent)

func (FuncSink) OnAgentStart

func (s FuncSink) OnAgentStart(runID string, a AgentEvent)

func (FuncSink) OnLog

func (s FuncSink) OnLog(runID, level, msg string)

func (FuncSink) OnPhase

func (s FuncSink) OnPhase(runID, title, detail string)

func (FuncSink) OnRunDone

func (s FuncSink) OnRunDone(runID string, result interface{}, err error)

func (FuncSink) OnRunStart

func (s FuncSink) OnRunStart(r RunInfo)

type Loader

type Loader struct {
	// contains filtered or unexported fields
}

Loader discovers workflows from builtin (embedded), the user dir (~/.jcode/workflows), and the project dir (<project>/.jcode/workflows). On name collision, project wins over user wins over builtin (mirrors skills.Loader).

func NewLoader

func NewLoader() *Loader

NewLoader creates a Loader pre-populated with builtins and the user dir.

func (*Loader) All

func (l *Loader) All() []Workflow

All returns all workflows sorted by name.

func (*Loader) Get

func (l *Loader) Get(name string) (Workflow, bool)

Get returns a workflow by name.

func (*Loader) GetBySlash added in v0.9.3

func (l *Loader) GetBySlash(slash string) (Workflow, bool)

GetBySlash returns the workflow whose auto-generated "/<name>" trigger matches slash. Workflows only expose auto-generated slashes (see SlashCommands), so this is a name lookup with the leading "/" stripped.

func (*Loader) LoadProject

func (l *Loader) LoadProject(projectDir string)

LoadProject scans a project's .jcode/workflows dir (project overrides user).

func (*Loader) Resolve

func (l *Loader) Resolve(name string) (Workflow, bool)

Resolve is a convenience matching Engine's WithResolver signature.

func (*Loader) SaveUser

func (l *Loader) SaveUser(name, src string) (string, error)

SaveUser writes a workflow source to the user dir as <name>.js and registers it. Used by "save this run as a command".

func (*Loader) SlashCommands

func (l *Loader) SlashCommands() []SlashCommand

SlashCommands returns a "/name" trigger for each workflow, sorted.

type Meta

type Meta struct {
	Name        string  `json:"name"`
	Description string  `json:"description"`
	WhenToUse   string  `json:"whenToUse"`
	Phases      []Phase `json:"phases"`
}

Meta is the exported `meta` block at the top of a workflow script. It must be a pure literal object (no computed values) so it can be parsed without running the body. Name becomes the /<name> slash command.

func ParseMeta

func ParseMeta(src string) (Meta, error)

ParseMeta extracts the `meta` object literal from a workflow's source and evaluates it in a throwaway VM (without running the body). The meta block must be a pure literal (no computed values, no host calls) — this is enforced by running it in a bare runtime with no globals injected.

type NopSink

type NopSink struct{}

NopSink is an EventSink that discards everything.

func (NopSink) OnAgentDone

func (NopSink) OnAgentDone(string, AgentEvent)

func (NopSink) OnAgentStart

func (NopSink) OnAgentStart(string, AgentEvent)

func (NopSink) OnLog

func (NopSink) OnLog(string, string, string)

func (NopSink) OnPhase

func (NopSink) OnPhase(string, string, string)

func (NopSink) OnRunDone

func (NopSink) OnRunDone(string, interface{}, error)

func (NopSink) OnRunStart

func (NopSink) OnRunStart(RunInfo)

type Option

type Option func(*Engine)

Option configures an Engine.

func WithConcurrency

func WithConcurrency(n int) Option

WithConcurrency sets the max number of concurrent agents (default 16).

func WithMaxAgents

func WithMaxAgents(n int) Option

WithMaxAgents sets the hard per-run agent cap (default 1000).

func WithResolver

func WithResolver(fn func(name string) (Workflow, bool)) Option

WithResolver enables workflow() nesting by resolving a name to a Workflow.

func WithTimeout

func WithTimeout(d time.Duration) Option

WithTimeout sets the per-run wall-clock timeout (default 30m). Zero disables it.

type Phase

type Phase struct {
	Title  string `json:"title"`
	Detail string `json:"detail"`
}

Phase is one entry in meta.phases — a titled progress group.

type RunInfo

type RunInfo struct {
	RunID string
	Name  string
	Meta  Meta
}

RunInfo identifies a workflow run for the event sink.

type RunOptions

type RunOptions struct {
	RunID       string      // stable id; generated if empty
	Args        interface{} // becomes the `args` global (JSON value)
	BudgetTotal int64       // token target for budget.total; 0 = unset
}

RunOptions carry per-run inputs.

type Scope

type Scope string

Scope describes where a workflow was loaded from. Project wins over user wins over builtin on name collisions (mirrors skills.Loader precedence).

const (
	ScopeBuiltin Scope = "builtin"
	ScopeUser    Scope = "user"
	ScopeProject Scope = "project"
	ScopeInline  Scope = "inline" // generated on the fly, not saved
)

type SlashCommand

type SlashCommand struct {
	Slash       string
	Name        string
	Description string
	WhenToUse   string
}

SlashCommand is a "/name" trigger derived from a workflow.

type SpawnFunc

type SpawnFunc func(ctx context.Context, spec AgentSpec) (AgentResult, error)

SpawnFunc runs one subagent to completion and returns its result. The engine is built around this seam so it never imports the tools/model packages directly: the real implementation (spawn.go) reuses jcode's adk agent machinery, and tests inject a deterministic fake. Implementations must honour ctx cancellation.

type Workflow

type Workflow struct {
	Meta   Meta
	Source string // full .js source (including the meta block)
	Path   string // filesystem path ("" for inline/builtin)
	Scope  Scope
}

Workflow is a loaded workflow definition.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL