taskgraph

package
v0.24.0 Latest Latest
Warning

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

Go to latest
Published: Nov 6, 2026 License: MIT Imports: 8 Imported by: 0

Documentation

Overview

Package taskgraph is a plan the model keeps for itself: a DAG of tasks a person can review, shared by a main agent and the sub-agents it hands parts to. It holds the nodes, the edges they wait on, the rules for a status change, the one answer to "can this start", an opaque owner per node, the `plan` tool (Tool), and what each write changed and for whom (Diff, Apply).

It is optional, like files and loopdetector: a host mounts it; agent does not import it. It imports llm (the tool definition) and stdlib only.

Where the line is. A plan the model keeps decides nothing about which session runs, when, or why, so it is not orchestration. Everything that is stays with the host: storage (Store), what an owner is and which session it maps to, who may change what (WithMayChange), approval or a plan mode, delegation (spawning the agent a part is assigned to), scheduling, and how a change reaches an owner (WithOnChange, Apply).

Index

Constants

View Source
const ToolName = "plan"

ToolName is the model's one tool for the plan.

Variables

This section is empty.

Functions

func Clip

func Clip(s string, max int) string

Clip cuts s to max runes, with an ellipsis; "" when max is not positive.

func Tool

func Tool(st Store, actor string, opts ...Option) (llm.ToolDef, func(ctx context.Context, args string) string)

Tool is the plan tool for actor, over st: its definition and its handler. actor is whom the calls are for ("" when the host has none to tell apart); a node an actor adds is owned by it.

Types

type Change

type Change struct {
	ID string
	// Before is the node as it was; nil when the write added it.
	Before *Node
	// After is the node as it is now; nil when the write removed it (nothing
	// in this package removes a node, but an fn given to Apply may).
	After *Node
	// Ready: the node could not start before the write, and can now.
	Ready bool
	// Tell is the owners that should hear of it, in a stable order, without
	// the actor. "" is an owner like any other: what it means is the host's.
	Tell []string
}

Change is one node a write changed.

func Apply

func Apply(ctx context.Context, st Store, actor string, fn func(g *Graph) error) ([]Change, error)

Apply runs fn on the plan in st as actor and returns what it changed. It is the one write path: the plan tool goes through it, and so should every write the host makes itself (a person's edit on a screen, an assignment), or those changes are never told to the owners they touch.

The changes are computed inside Update, from the plan Update hands fn, and returned only if Update succeeds: a write the store refused or failed to save tells no one.

func Diff

func Diff(before, after Graph, actor string) []Change

Diff is the changes from before to after, made by actor, in node order (removed nodes last). Fields that did not change make no Change.

type Graph

type Graph struct {
	Nodes []*Node `json:"nodes"`
	// Seq numbers the next node; ids are never reused.
	Seq int `json:"seq,omitempty"`
}

Graph is the whole plan.

func (*Graph) Add

func (g *Graph) Add(n Node) (*Node, error)

Add makes a node from n's Title, Goal, Needs, Owner, DoneWhen, Notes, Files and Replaces. It starts as todo.

func (*Graph) Assign

func (g *Graph) Assign(id, owner string) error

Assign gives the subtree of id to owner. It refuses a subtree with a node someone else already owns.

func (*Graph) Brief

func (g *Graph) Brief(owner string) string

Brief is the line a turn carries while the plan has open nodes: where it stands, the node in progress, the next ready ones, what is stuck. At most five lines; "" when nothing is open. owner is the agent it is for.

func (*Graph) CanStart

func (g *Graph) CanStart(id string) (bool, []string)

CanStart is the one answer to "can this node start": it is todo and nothing holds it. The ready set, the tool, the screen and the turn's line all read this and no other.

func (*Graph) Clone

func (g *Graph) Clone() Graph

Clone is a deep copy.

func (*Graph) Dependents

func (g *Graph) Dependents(id string) []*Node

Dependents are the nodes that need id directly, in node order.

func (*Graph) Detail

func (g *Graph) Detail(n *Node) string

Detail is a node in full, for the model: its fields and why it waits.

func (*Graph) Get

func (g *Graph) Get(id string) *Node

Get is the node with id, or nil.

func (*Graph) Line

func (g *Graph) Line(n *Node) string

Line is a node on one line.

func (*Graph) List

func (g *Graph) List() string

List is the whole graph as the tree, one line a row, for the model.

func (*Graph) Open

func (g *Graph) Open() int

Open is how many nodes are not finished: todo, doing or blocked.

func (*Graph) Ready

func (g *Graph) Ready(owner string) []*Node

Ready is the nodes owned by owner that can start now, in node order.

func (*Graph) ReadyLine

func (g *Graph) ReadyLine(owner string, max int) string

ReadyLine is "ready: 4 Title; 5 Title" for owner, at most max of them.

func (*Graph) SetStatus

func (g *Graph) SetStatus(id string, to Status, why string) error

SetStatus moves node id to a status, if the table allows it. why is the account for done and the reason for blocked and dropped; each needs one.

func (*Graph) Subtree

func (g *Graph) Subtree(id string) []*Node

Subtree is id and every node that hangs from it: whatever needs it, directly or not. It is the unit a sub-agent is given.

func (*Graph) Tree

func (g *Graph) Tree() []Row

Tree lays the graph out for a person: a node with no needs is a root, and a node hangs under each node it needs, so one with several parents shows under each, marked as a repeat after the first. Siblings are in node order.

func (*Graph) Update

func (g *Graph) Update(id string, p Patch) error

Update applies p to node id. A change of Needs is the re-pointing of an edge.

func (*Graph) Waiting

func (g *Graph) Waiting(id string) []string

Waiting says what a node waits on, one reason per need that is not done, computed from the edges. Empty: nothing holds it. A dropped need does NOT release its dependents; the reason says to replace the edge.

type Node

type Node struct {
	ID    string `json:"id"`
	Title string `json:"title"`
	Goal  string `json:"goal,omitempty"`
	// Status is never "waiting": see Status.
	Status Status `json:"status"`
	// Needs are the ids this node waits on, until each is done.
	Needs []string `json:"needs,omitempty"`
	// Owner is an opaque string the host gives meaning to, kept as it is; "" is
	// nobody in particular. Ready filters on it.
	Owner string `json:"owner,omitempty"`
	// DoneWhen is what done means, set when the node is made.
	DoneWhen string `json:"done_when"`
	Notes    string `json:"notes,omitempty"`
	// Files are the paths the node expects to change. Nothing enforces it yet;
	// parallel runs will need it, to keep siblings off one file.
	Files []string `json:"files,omitempty"`
	// Replaces is a dropped node this one stands in for. It does not move the
	// dropped node's dependents: an update of their needs does.
	Replaces string `json:"replaces,omitempty"`
	// Why is the reason a node is blocked or dropped.
	Why string `json:"why,omitempty"`
	// Account is how DoneWhen was met.
	Account string `json:"account,omitempty"`
}

Node is one task.

type Option

type Option func(*options)

Option shapes Tool for its host.

func WithMayChange

func WithMayChange(f func(actor string, n *Node) bool) Option

WithMayChange sets who may change a node: no hook, anyone. An actor that may not gets a result saying so. It also gates what a new node may need.

func WithNote

func WithNote(s string) Option

WithNote appends host sentences to the tool's description: what the plan means here (a mode, files a node lists) is the host's to say.

func WithOnChange

func WithOnChange(f func(ctx context.Context, actor string, changes []Change)) Option

WithOnChange gives the host every write the tool made: the changes (see Diff for who each names in Tell) after the store kept them, never for a refused or failed write, and only when there is at least one. It runs on the tool call's goroutine, outside Update, so it may View the store; it should hand the notices off (agent.Session.Inject) rather than run a turn.

type Patch

type Patch struct {
	Title, Goal, DoneWhen, Notes *string
	Needs, Files                 *[]string
	Status                       *Status
}

Patch is the fields of a node an update may change; nil leaves one alone. Status is todo or doing only: done, blocked and dropped carry what they need (an account, a reason) through SetStatus.

type Row

type Row struct {
	Node  *Node
	Depth int
	// Repeat: the node already showed under another parent, so this row
	// is a pointer to it and its dependents are not shown again.
	Repeat bool
}

Row is one line of the graph as a tree.

type Status

type Status string

Status is where a node stands.

blocked is only ever somebody saying "stuck", with a reason. Waiting on other nodes is computed from the edges (Waiting) and never stored, so a node cannot go stale when a prerequisite moves.

const (
	Todo    Status = "todo"
	Doing   Status = "doing"
	Done    Status = "done"
	Blocked Status = "blocked"
	Dropped Status = "dropped"
)

type Store

type Store interface {
	View(ctx context.Context) (Graph, error)
	Update(ctx context.Context, fn func(g *Graph) error) error
}

Store is where a plan lives. The integration provides it; this package ships no implementation, the same as agent.Store. A file beside a session, a row in a database and a map under a mutex are all the host's choice.

The contract, which this package cannot enforce:

  • One Store per plan, shared by every session that works the plan. A main agent and its sub-agents call the same Store; a copy per sub-agent loses updates with no error, since each Update sees only its own copy.
  • Update is atomic against every other Update and View of the same plan: fn reads the plan as it is, and its change is kept whole or not at all.
  • Update keeps the change only if fn returns nil and the store's own write succeeds; either failure is Update's error and the plan is as it was.
  • fn may be called more than once (a store that retries a conflicting transaction), so fn must do nothing but change g. Apply relies on this.
  • View is a copy: a caller may change what it returns.

Jump to

Keyboard shortcuts

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