Documentation
¶
Overview ¶
Package workflow owns the solo dynamic-workflow task board: a dependency graph of tasks the root agent plans at runtime and an append-only session log that makes the board restart-safe. The wf_task_* tools mutate it as the "root" actor; the in-process dispatch engine (internal/agent) mutates it as the "system" actor, executing structure the root already declared — the writer matrix in store.go is the load-bearing wall.
The package is deliberately free of agent imports so downstream embedders can reuse the board with their own dispatch loop. See docs/roadmap/PRD/solo-dynamic-workflow.md for the design; the execution model is the swarm DWF wave's (docs/roadmap/PRD/swarm-dynamic-workflow.md) ported to solo.
Index ¶
- Constants
- Variables
- func Names() []tools.ToolName
- type Actor
- type AgentTypesLookup
- type Comment
- type CreateTool
- type Dispatcher
- type DispatcherLookup
- type GetTool
- type ListTool
- type Patch
- type Status
- type Store
- func (s *Store) Close()
- func (s *Store) CompleteWork(id, result string, failed bool) (Task, error)
- func (s *Store) Counts() map[Status]int
- func (s *Store) Create(t Task) (Task, error)
- func (s *Store) Delete(id string) error
- func (s *Store) Dependents(id string) []string
- func (s *Store) Dispatch(id, owner string) (Task, error)
- func (s *Store) Dispatchable() []Task
- func (s *Store) Domain() string
- func (s *Store) Get(id string) (Task, bool)
- func (s *Store) List() []Task
- func (s *Store) ResetLostRunning(alive func(owner string) bool) []Task
- func (s *Store) SetPersistence(dir string)
- func (s *Store) SetSession(id string) error
- func (s *Store) Transition(id string, to Status, actor Actor, note string) (Task, error)
- func (s *Store) UnblockDependents(completedID string) []Task
- func (s *Store) UnmetDeps(id string) []string
- func (s *Store) Update(id string, p Patch) (Task, error)
- type Task
- type UpdateTool
- type Verify
- type VerifyTool
- type WorkerSpec
Constants ¶
const Domain = "workflow"
Domain is the observable.Change.Domain value every board change carries. Consumers switch on this string and type-assert Payload to []workflow.Task.
Variables ¶
var ( ErrNotFound = errors.New("task not found") ErrDepNotFound = errors.New("dependency not found") )
Sentinel errors callers branch on. Detail is wrapped with %w.
Functions ¶
Types ¶
type Actor ¶
type Actor string
Actor identifies who is holding the pen for a transition. Root is the judgment writer (the wf_task_* tools); System is the engine performing strictly mechanical edges. There is no worker actor — solo workers have no board tools; their terminal report arrives through the engine.
type AgentTypesLookup ¶
type AgentTypesLookup func() []string
AgentTypesLookup resolves the live subagent-type list for worker validation at create time, or nil when unknown (validation then defers to dispatch).
type Comment ¶
Comment is one append-only audit line: force-unblocks, verdicts, worker-lost resets, and root notes all land here.
type CreateTool ¶
type CreateTool struct {
// contains filtered or unexported fields
}
func NewCreate ¶
func NewCreate(s *Store, dispatch DispatcherLookup, agentTypes AgentTypesLookup) *CreateTool
func (*CreateTool) Description ¶
func (t *CreateTool) Description() string
func (*CreateTool) Execute ¶
func (t *CreateTool) Execute(_ context.Context, logger *slog.Logger, input json.RawMessage) (tools.Result, error)
func (*CreateTool) Name ¶
func (t *CreateTool) Name() string
func (*CreateTool) Schema ¶
func (t *CreateTool) Schema() json.RawMessage
type Dispatcher ¶
type Dispatcher interface {
Sweep()
}
Dispatcher is the engine seam: after a readiness-changing mutation the tools poke it so ready tasks dispatch on the same tool call that made them ready. Late-bound through a lookup because the engine wires up after the toolset is built; a nil Dispatcher (tests, embedders with their own loop) leaves the board fully functional minus auto-dispatch.
type DispatcherLookup ¶
type DispatcherLookup func() Dispatcher
DispatcherLookup resolves the current Dispatcher, or nil.
type GetTool ¶
type GetTool struct {
// contains filtered or unexported fields
}
func (*GetTool) Description ¶
func (*GetTool) Schema ¶
func (t *GetTool) Schema() json.RawMessage
type ListTool ¶
type ListTool struct {
// contains filtered or unexported fields
}
func (*ListTool) Description ¶
func (*ListTool) Schema ¶
func (t *ListTool) Schema() json.RawMessage
type Patch ¶
Patch carries the root's field edits for Update. nil pointers leave the field untouched; Note (non-empty) appends an audit comment.
type Status ¶
type Status string
Status enumerates the board's task states. `blocked` is birth-only (a task created with incomplete dependencies) plus the force-unblock escape; `verifying` holds a worker's recorded result awaiting judgment. There is no suspended and no failed — a worker failure lands in verifying with WorkerFailed set, and abandonment is deletion or simply never verifying.
type Store ¶
type Store struct {
observable.Observable
// contains filtered or unexported fields
}
Store is the per-agent board: tasks, their dependency edges, and the append-only session log that rebuilds them on resume.
Mutations are guarded by mu; Notify fires after the lock is released so observers may freely call back into the store. Safe for concurrent access — the wf_task_* tools (root actor) and the dispatch engine (system actor) share one instance via ToolState.
func NewStore ¶
func NewStore() *Store
NewStore returns an empty, in-memory board. Call SetPersistence + SetSession to attach it to a session log.
func (*Store) Close ¶
func (s *Store) Close()
Close releases the session log handle. The board stays readable.
func (*Store) CompleteWork ¶
CompleteWork is the worker's terminal report arriving through the engine: result recorded and running → verifying in one mutation. failed marks a crash/kill instead of a natural report — the matrix then refuses to auto-complete regardless of the verify policy.
func (*Store) Create ¶
Create validates and inserts a task. The store assigns the id and the birth status: blocked iff any dependency is incomplete, else pending (a dependency on an already-completed task is satisfied at birth). Dependencies must reference existing tasks — with edges immutable after creation this makes the graph acyclic by construction.
func (*Store) Delete ¶
Delete removes a task created in error. Guarded: a running task has a live worker (stop it first), and a depended-on task would orphan its dependents' edges — both are refused with the reason.
func (*Store) Dependents ¶
Dependents lists ids of tasks that name id in their DependsOn — the cascade a completion may unblock, in creation order.
func (*Store) Dispatch ¶
Dispatch is the engine's pending → running edge plus the owner stamp in one mutation: the task records which worker daemon carries it.
func (*Store) Dispatchable ¶
Dispatchable returns, in creation order, every pending engine-managed task. Pending implies dependencies are satisfied (blocked birth + the unblock edge) or the root force-unblocked — either way the engine may dispatch. The concurrency cap is the engine's business, not the store's.
func (*Store) Domain ¶
Domain identifies this store on the change stream. Required by the observable.Store interface.
func (*Store) ResetLostRunning ¶
ResetLostRunning re-queues running tasks whose worker did not survive a restart: alive reports whether an owner daemon id is still live. Each reset task returns to pending with an audit comment; the caller follows with a dispatch sweep. The restart-recovery half of the engine.
func (*Store) SetPersistence ¶
SetPersistence sets the directory session logs live under (<dir>/<session>.jsonl). Takes effect at the next SetSession.
func (*Store) SetSession ¶
SetSession rotates the board to the given session id: the in-memory state is reset and, when a persistence dir is configured, an existing session log is replayed (a fresh id starts an empty board). The log file itself is created lazily on the first mutation, so a session that never touches the board leaves nothing on disk. Mirrors checkpoint.Manager.SetSession — called at boot, on resume, and on /clear.
func (*Store) Transition ¶
Transition moves a task along one edge of the writer matrix, appending note (when non-empty) to the audit comments. Owner and WorkerFailed are cleared on any edge back to pending so a re-dispatch starts clean.
func (*Store) UnblockDependents ¶
UnblockDependents flips every blocked dependent of completedID whose dependencies are now all complete to pending (the system edge). Returns the flipped tasks. Idempotent — a second call finds nothing blocked. Transition runs this automatically on every edge into completed; the exported form exists for defensive resweeps.
type Task ¶
type Task struct {
ID string `json:"id"`
Subject string `json:"subject"`
Description string `json:"description,omitempty"`
ActiveForm string `json:"active_form,omitempty"`
Status Status `json:"status"`
Verify Verify `json:"verify"`
Worker *WorkerSpec `json:"worker,omitempty"`
DependsOn []string `json:"depends_on,omitempty"`
Owner string `json:"owner,omitempty"` // daemon id of the running worker
Result string `json:"result,omitempty"`
// WorkerFailed marks a verifying task whose worker crashed or was
// killed instead of reporting; the store refuses to auto-complete it
// regardless of the verify policy.
WorkerFailed bool `json:"worker_failed,omitempty"`
Comments []Comment `json:"comments,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
Task is one node of the board graph.
DependsOn edges may only reference tasks that already exist and are immutable after creation, so the graph is acyclic by construction — no cycle detection exists anywhere (the DWF rule).
func (Task) EngineManaged ¶
EngineManaged reports whether the engine dispatches this task (it carries a worker spec). Self-tasks are root-managed on every edge.
type UpdateTool ¶
type UpdateTool struct {
// contains filtered or unexported fields
}
func NewUpdate ¶
func NewUpdate(s *Store, dispatch DispatcherLookup) *UpdateTool
func (*UpdateTool) Description ¶
func (t *UpdateTool) Description() string
func (*UpdateTool) Execute ¶
func (t *UpdateTool) Execute(_ context.Context, logger *slog.Logger, input json.RawMessage) (tools.Result, error)
func (*UpdateTool) Name ¶
func (t *UpdateTool) Name() string
func (*UpdateTool) Schema ¶
func (t *UpdateTool) Schema() json.RawMessage
type Verify ¶
type Verify string
Verify is the per-task verification policy, fixed at creation. "leader" (the default) folds the worker's result into the root conversation for judgment; "auto" lets the engine complete the task mechanically and cascade dependents with zero root wakes. Stored as text so a future machine-evidence policy ("checks") slots in without a schema change.
type VerifyTool ¶
type VerifyTool struct {
// contains filtered or unexported fields
}
func NewVerify ¶
func NewVerify(s *Store, dispatch DispatcherLookup) *VerifyTool
func (*VerifyTool) Description ¶
func (t *VerifyTool) Description() string
func (*VerifyTool) Execute ¶
func (t *VerifyTool) Execute(_ context.Context, logger *slog.Logger, input json.RawMessage) (tools.Result, error)
func (*VerifyTool) Name ¶
func (t *VerifyTool) Name() string
func (*VerifyTool) Schema ¶
func (t *VerifyTool) Schema() json.RawMessage
type WorkerSpec ¶
type WorkerSpec struct {
AgentType string `json:"agent_type"`
Isolation string `json:"isolation,omitempty"`
Level int `json:"level,omitempty"`
}
WorkerSpec is the spawn spec fixed at creation for an engine-managed task: which subagent kind to dispatch, whether it runs in an isolated git worktree, and the model capability tier (the AGENT tool's level). A task without a WorkerSpec is a self-task — a step the root agent does itself, tracked on the same graph.