Documentation
¶
Overview ¶
Package factory supervises chat-spawned subagents. The public model-facing surface is the agent tool: the coordinator writes a self-contained prompt, the supervisor enforces depth/fanout/agent/time budgets, and the UI receives an event tree for live progress. Legacy project-local step resolution remains internal support code, but default named steps are no longer advertised.
Division of labor:
agents decide — planning, sequencing, triage, judgment code constrains — depth, fanout, agent count, time code observes — events, attribution, rendering code never interprets — no state machines over agent outcomes
Index ¶
- Variables
- func DefaultStepPaths(dir string) []string
- func RenderTree(nodes []NodeView) string
- type AgentEvent
- type AgentRunner
- type AgentTool
- type Budget
- type Console
- type Event
- type Node
- type NodeView
- type Resolver
- type Step
- type StepAgent
- type StepResult
- type Supervisor
- func (s *Supervisor) Run(ctx context.Context, dir, rootStep, goal string) (string, error)
- func (s *Supervisor) RunAgentPrompt(ctx context.Context, caller int, dir, prompt string) StepResult
- func (s *Supervisor) RunStep(ctx context.Context, caller int, dir, name, input string) StepResult
- func (s *Supervisor) Snapshot() []NodeView
Constants ¶
This section is empty.
Variables ¶
Functions ¶
func DefaultStepPaths ¶
DefaultStepPaths returns the standard search path for a session rooted at dir.
func RenderTree ¶
RenderTree is a pure function: Snapshot -> one text frame. Stdlib only, so it drops into any view layer with one call.
● agent work the backlog: owainlewis/app 3m12s ├─ ● agent #12 invite teammate by email 2m07s │ │ bash: just test │ └─ ✓ agent PR #34 vs acceptance criteria 31s └─ ✗ agent #13 rate limiting (timeout after 10m) 10m00s
Types ¶
type AgentEvent ¶
type AgentEvent struct {
Kind string `json:"kind"` // "start" | "tool" | "text" | "error" | "usage" | "done" | "fail"
Body string `json:"body,omitempty"`
}
AgentEvent is one observation from a running agent. Lifecycle kinds frame each node: "start" when it registers, then "done" (completed) or "fail" (errored, timed out, denied) exactly once at the end. Everything in between ("tool", "text", "error", "usage") is status.
type AgentRunner ¶
type AgentRunner struct {
Provider llm.Provider
DefaultModel string
Root string // workspace root; bounds file tools via permission policy
BashTimeout time.Duration
// Mode is the permission mode child agents run under. Steps execute
// autonomously (there is no approver inside a step), so "ask" cannot be
// honored mid-step; but "readonly" must propagate — a readonly session
// delegating a step must not gain write access through the side door.
// Empty defaults to trusted (the standalone step CLI case).
Mode permission.Mode
// Sup is set after NewSupervisor. It is used only when a step explicitly
// opts into the agent tool; dynamic chat subagents do not get nested
// delegation by default.
Sup *Supervisor
}
AgentRunner runs agent steps on neo's core agent loop. Each step gets a fresh agent (amnesiac by design) with a registry filtered to the step's frontmatter tool list — role enforcement by construction, not prose.
func (*AgentRunner) RunAgentStep ¶
func (r *AgentRunner) RunAgentStep(ctx context.Context, step Step, dir, input string, nodeID int, events chan<- AgentEvent) (string, error)
type AgentTool ¶
type AgentTool struct {
Sup *Supervisor
CallerNode int
Dir string
}
AgentTool exposes subagent delegation to the model through neo's tool registry. CallerNode is bound per agent-loop instance so children attribute correctly.
type Budget ¶
type Budget struct {
MaxDepth int // coordinator=0, children=1, grandchildren=2
MaxChildren int // per node
MaxAgents int // tree-wide cap on agent steps
MaxWall time.Duration // per agent step
ScriptTimeout time.Duration // per script step
}
Budget is the cage. Enforced by the runtime regardless of what any agent asks for. Agent-count is tree-wide; the rest are per node.
func DefaultBudget ¶
func DefaultBudget() Budget
DefaultBudget is a short leash suitable for early supervised runs.
type Console ¶
type Console struct {
// contains filtered or unexported fields
}
Console renders the supervisor's tree to a terminal as a live, in-place frame (redrawn on a ticker) and tees every event to events.jsonl. It is a consumer of the event stream only — agents never wait on it (the supervisor's send is non-blocking by construction).
func NewConsole ¶
func NewConsole(sup *Supervisor, w io.Writer) *Console
NewConsole prepares a console over w. Live in-place redraw is enabled only when w is the process's terminal.
func (*Console) Repaint ¶
func (c *Console) Repaint()
Repaint draws the current tree. On a TTY it rewrites the previous frame in place; otherwise it is a no-op (the event log lines are the output).
type Event ¶
type Event struct {
At time.Time `json:"at"`
Node int `json:"node"`
Parent int `json:"parent,omitempty"`
Depth int `json:"depth,omitempty"`
Step string `json:"step"`
Task string `json:"task,omitempty"`
Ev AgentEvent `json:"ev"`
}
Event is the attributed stream: every agent event tagged with its node and the node's place in the tree, so a consumer can reconstruct the whole hierarchy from the stream alone. One channel; the UI is a fold over it; events.jsonl is a tee of it.
type Node ¶
type Node struct {
ID int
Parent int // 0 = the root's virtual parent
Step string
Kind string // "agent" | "script"
Task string // clipped, for the UI
Depth int
Started time.Time
// contains filtered or unexported fields
}
Node is one subagent/script execution in the tree.
type NodeView ¶
type NodeView struct {
ID, Parent, Depth int
Step, Kind, Task string
Done bool
Err, LastLine string
Elapsed time.Duration
}
NodeView is an immutable snapshot of a Node for rendering.
type Resolver ¶
type Resolver struct {
Paths []string
}
Resolver locates steps by bare name. Search order: each path in Paths (project ./steps first, then ~/.neo/steps), then the embedded defaults. In a directory, <name>.md (agent step) wins over an executable <name> (script step).
func (Resolver) Catalog ¶
Catalog enumerates every available step across the search path and embedded defaults, deduplicated by name (earlier paths win, mirroring Resolve precedence), sorted by name. Used to advertise steps in the chat system prompt; scripts carry name only.
type Step ¶
type Step struct {
Name string
Kind string // "agent" | "script"
Path string // executable path for scripts; source path for agent steps ("" if embedded)
// Agent-step fields, parsed from YAML frontmatter.
Description string // one-line summary, surfaced in the chat catalog
Prompt string // system prompt body
Tools []string // allowed tool names; empty = read-only default (bash, read_file, grep, glob)
Model string // pinned model; empty = inherit the session default
MaxTurns int // agent loop turn cap; 0 = factory default
}
Step is a resolved step definition. Agent steps carry a prompt (markdown body) plus frontmatter restrictions; script steps carry the executable path.
type StepAgent ¶
type StepAgent interface {
RunAgentStep(ctx context.Context, step Step, dir, input string, nodeID int, events chan<- AgentEvent) (string, error)
}
StepAgent runs a resolved agent prompt against an input in dir, streaming events, returning the final message. NodeID identifies the execution so child agent calls attribute correctly.
type StepResult ¶
type StepResult struct {
Ok bool `json:"ok"`
Output string `json:"output"`
Kind string `json:"kind"`
Took string `json:"took"`
}
StepResult is the uniform envelope returned to the calling agent. Ok means "the step completed", NOT "the answer is yes": scripts conflate the two via exit code (fine — they hold invariants); for agent steps the caller judges the output's content itself. Collapsing agent judgment into a boolean is the classic mistake this design exists to avoid.
type Supervisor ¶
type Supervisor struct {
Events chan Event
// contains filtered or unexported fields
}
Supervisor enforces budgets, owns the node tree, and tags every agent event with its node into one stream. It never interprets agent decisions.
func NewSupervisor ¶
func NewSupervisor(agent StepAgent, b Budget, resolver Resolver) *Supervisor
func (*Supervisor) Run ¶
Run starts the root step with the user's goal and blocks until the tree finishes. Close(Events) afterwards is the caller's choice.
func (*Supervisor) RunAgentPrompt ¶
func (s *Supervisor) RunAgentPrompt(ctx context.Context, caller int, dir, prompt string) StepResult
RunAgentPrompt starts a fresh subagent with a self-contained prompt. This is the chat-native delegation path: no named/static step file is involved, but the execution still participates in the supervisor tree and budgets.
func (*Supervisor) RunStep ¶
func (s *Supervisor) RunStep(ctx context.Context, caller int, dir, name, input string) StepResult
RunStep resolves and executes a named legacy step on behalf of caller node. Both kinds become nodes in the tree (so the UI shows everything); only agent steps consume the agent budget. Denials and failures return as results with the reason — the calling agent reads why and re-plans.
func (*Supervisor) Snapshot ¶
func (s *Supervisor) Snapshot() []NodeView
Snapshot returns the tree for rendering; the UI calls it per frame.