Documentation
¶
Index ¶
- Constants
- Variables
- func ExtractSection[T any](ctx context.Context, md *Markdown, heading string) (*T, error)
- func ExtractSectionMap(ctx context.Context, md *Markdown, heading string) (map[string]any, error)
- func PrintResult(result *Result) error
- type AIParser
- type Agent
- type AgentResultInfo
- type AgentStatus
- type BodySection
- type CreateTaskCommand
- type IncrementFrontmatterCommand
- type Markdown
- type ParseStep
- type Phase
- type Result
- type ResultDeliverer
- type Section
- type Step
- type StepRunner
- type Task
- type TaskAssignee
- type TaskContent
- type TaskFrontmatter
- func (f TaskFrontmatter) Assignee() TaskAssignee
- func (f TaskFrontmatter) CurrentJob() string
- func (f TaskFrontmatter) Int(key string) (int, bool)
- func (f TaskFrontmatter) MaxRetries() int
- func (f TaskFrontmatter) MaxTriggers() int
- func (f TaskFrontmatter) Phase() *domain.TaskPhase
- func (f TaskFrontmatter) RetryCount() int
- func (f TaskFrontmatter) SpawnNotification() bool
- func (f TaskFrontmatter) Stage() string
- func (f TaskFrontmatter) Status() domain.TaskStatus
- func (f TaskFrontmatter) String(key string) (string, bool)
- func (f TaskFrontmatter) TriggerCount() int
- type TaskIdentifier
- type TaskIdentifierGenerator
- type TaskIdentifiers
- type UpdateFrontmatterCommand
Constants ¶
const CreateTaskCommandOperation base.CommandOperation = "create-task"
CreateTaskCommandOperation is the Kafka command operation for creating a new vault task. The controller materializes a task file at the standard vault location for the given task_identifier. If a file already exists for that identifier, the command is a no-op.
const IncrementFrontmatterCommandOperation base.CommandOperation = "increment-frontmatter"
IncrementFrontmatterCommandOperation is the Kafka command operation for atomically incrementing a single frontmatter field by a delta. Published by the executor on agent-task-v1-request; handled by the controller.
const UpdateFrontmatterCommandOperation base.CommandOperation = "update-frontmatter"
UpdateFrontmatterCommandOperation is the Kafka command operation for atomically setting specific frontmatter keys without touching other keys.
Variables ¶
var CDBSchemaIDs = cdb.SchemaIDs{ TaskV1SchemaID, }
var TaskV1SchemaID = cdb.SchemaID{
Group: "agent",
Kind: "task",
Version: "v1",
}
Functions ¶
func ExtractSection ¶ added in v0.54.0
ExtractSection reads `heading` from the markdown, finds the first ```json fence inside its body, unmarshals into T, and returns a typed pointer.
Errors are formatted for use as needs_input messages:
- "<heading> section missing" — heading not found
- "<heading>: json block missing" — no ```json fence in section
- "<heading>: json malformed: <detail>" — unmarshal failed
func ExtractSectionMap ¶ added in v0.54.0
ExtractSectionMap is the untyped variant — useful when the schema is not known statically.
func PrintResult ¶ added in v0.54.0
PrintResult marshals a framework Result to JSON and prints to stdout. nil result is a no-op (returns nil error). Used by agent main.go entry points to surface the terminal step outcome on stderr/stdout for log aggregators and the K8s Job exit observer.
Types ¶
type AIParser ¶ added in v0.54.0
type AIParser interface {
// Parse reads taskContent (markdown) and populates target (a pointer
// to a typed struct).
Parse(ctx context.Context, taskContent string, target any) error
}
AIParser is the boundary translator: fuzzy markdown → typed Go struct.
Concrete implementations wrap Gemini structured output, Claude with JSON mode, or any other LLM that produces structured outputs. They derive a JSON schema from the target type and instruct the LLM to emit conforming output.
Concrete impls live alongside their AI provider (e.g. lib/gemini for Gemini-backed parser). The interface lives here so framework code (ParseStep) can compose any provider without coupling.
type Agent ¶ added in v0.54.0
type Agent struct {
// contains filtered or unexported fields
}
Agent is a composed list of phases. Build via NewAgent.
func NewAgent ¶ added in v0.54.0
NewAgent constructs an Agent from one or more phases.
Phase names must be unique. Duplicates are rejected at Run time.
func (*Agent) Run ¶ added in v0.54.0
func (a *Agent) Run( ctx context.Context, phaseName domain.TaskPhase, taskContent string, deliverer ResultDeliverer, ) (*Result, error)
Run dispatches by phase and walks the matching step list.
phaseName is the requested phase from the K8s Job env (PHASE) or the CLI flag. Unknown or empty phaseName produces a Failed result via the deliverer (fail-loud sentinel — never a silent escalation).
taskContent is parsed once into *Markdown; the parsed Markdown is mutated by successive steps and re-serialized for each save.
type AgentResultInfo ¶ added in v0.54.0
type AgentResultInfo struct {
Status AgentStatus
Output string // body content (typically heading + fenced JSON)
Message string // human-readable status; used by failure/needs_input paths
// NextPhase is the task phase the agent requests the controller to write
// when Status == AgentStatusDone. Ignored on Failed/NeedsInput (failure
// paths always escalate to human_review). Empty means "use default"
// (phase: done on Status: done). Valid values are vault-cli TaskPhase
// enum strings: planning, in_progress, ai_review, human_review, done.
NextPhase string
}
AgentResultInfo holds the minimum fields a deliverer needs to publish a step's result. ResultDeliverer.DeliverResult takes this directly.
type AgentStatus ¶ added in v0.54.0
type AgentStatus string
AgentStatus represents the outcome status of a step (or single-shot agent).
const ( // AgentStatusDone indicates the step completed successfully. // On the last step of a phase, set NextPhase to advance. // On a mid-phase step, leave NextPhase empty (in-place save). AgentStatusDone AgentStatus = "done" // AgentStatusInProgress indicates the step completed and saved partial state, // but the phase is not yet complete. Phase frontmatter is preserved. // Used by multi-step phases for in-place progress saves between steps. // NextPhase is ignored on this status. AgentStatusInProgress AgentStatus = "in_progress" // AgentStatusFailed indicates a transient infrastructure failure. // Controller retries (trigger_count++); after max_triggers, escalates. AgentStatusFailed AgentStatus = "failed" // AgentStatusNeedsInput indicates a semantic problem in the task body. // Routed straight to human_review — retrying won't help. AgentStatusNeedsInput AgentStatus = "needs_input" )
type BodySection ¶ added in v0.53.1
BodySection describes an idempotent body-section write: the controller's UpdateFrontmatterExecutor calls ReplaceOrAppendSection(content, Heading, Section). Heading MUST include the markdown prefix (e.g. "## Failure"). Section MUST include the heading as its first line and a trailing newline.
type CreateTaskCommand ¶ added in v0.55.0
type CreateTaskCommand struct {
TaskIdentifier TaskIdentifier `json:"taskIdentifier"`
Frontmatter TaskFrontmatter `json:"frontmatter"`
Body string `json:"body,omitempty"`
}
CreateTaskCommand is the payload for CreateTaskCommandOperation. The controller creates a new vault task file at the standard path for TaskIdentifier, writing the supplied Frontmatter and optional Body. If a file for TaskIdentifier already exists the command is a strict no-op (idempotent). Frontmatter MUST include at minimum "assignee" and "status" keys; the executor rejects the command with a validation error if either is absent.
type IncrementFrontmatterCommand ¶ added in v0.53.1
type IncrementFrontmatterCommand struct {
TaskIdentifier TaskIdentifier `json:"taskIdentifier"`
Field string `json:"field"`
Delta int `json:"delta"`
}
IncrementFrontmatterCommand is the payload for IncrementFrontmatterCommandOperation. The controller reads the current value of Field from disk, adds Delta, and writes the result atomically — so the write is never idempotent.
type Markdown ¶ added in v0.54.0
type Markdown struct {
Frontmatter TaskFrontmatter
Preamble string
Sections []Section
}
Markdown is a parsed task document: frontmatter + preamble (text before the first section) + ordered list of sections.
Steps mutate Markdown in place via the methods below. The framework re-serializes Markdown via Marshal after each step's Run and publishes the new content via the deliverer.
func ParseMarkdown ¶ added in v0.54.0
ParseMarkdown parses raw markdown into a Markdown document.
Best-effort parsing: invalid YAML returns an empty Frontmatter without error. Sections are split at every "# " or "## " heading; "### " and deeper sub-headings are part of the parent section's Body.
func (*Markdown) AddSection ¶ added in v0.54.0
AddSection appends a section to the end of the section list.
Use ReplaceSection if a section with the same heading might already exist; AddSection does not deduplicate.
func (*Markdown) FindSection ¶ added in v0.54.0
FindSection returns a pointer to the first section matching heading, and a bool indicating presence. Mutating the returned section's fields updates the Markdown in-place.
func (*Markdown) InsertSection ¶ added in v0.54.0
InsertSection inserts a section at the given position. Out-of-range positions clamp to [0, len(Sections)].
func (*Markdown) Marshal ¶ added in v0.54.0
Marshal serializes the Markdown back to a markdown string.
Output: "---\n<yaml>\n---\n<preamble><section><section>..." Each section is rendered as "<heading>\n\n<body>\n" if body is non-empty, or "<heading>\n" if body is empty.
func (*Markdown) ReplaceSection ¶ added in v0.54.0
ReplaceSection replaces the existing section with the same Heading, or appends if no match exists. Idempotent for "save my output" steps.
type ParseStep ¶ added in v0.54.0
type ParseStep[T any] struct { // contains filtered or unexported fields }
ParseStep wraps an AIParser as a Step.
Boundary translator: markdown → typed Go struct → ## Section JSON. Use this for the planning phase of code-driven agents that take fuzzy human-written tasks.
func NewParseStep ¶ added in v0.54.0
func NewParseStep[T any]( name string, parser AIParser, heading string, nextPhase string, ) *ParseStep[T]
NewParseStep constructs a ParseStep[T].
name: step name for logs (e.g. "parse-plan") parser: the AI parser implementation (Gemini structured output, etc.) heading: the body section to write the typed result to (e.g. "## Plan") nextPhase: the phase to advance to on success
type Phase ¶ added in v0.54.0
Phase ties a phase name to an ordered list of steps.
Compose with NewAgent:
NewAgent(
NewPhase("planning", NewParseStep(parser, "## Plan", "in_progress")),
NewPhase("in_progress", NewExecuteStep(runner, fetcher)),
NewPhase("ai_review", NewVerifyStep(checker)),
)
type Result ¶ added in v0.54.0
type Result struct {
// Status: Done | InProgress | Failed | NeedsInput.
Status AgentStatus
// NextPhase advances the phase frontmatter on Status: Done. Empty
// means "stay in current phase" — used for in-place saves between
// steps in a multi-step phase.
NextPhase string
// Message is a human-readable status. Required for Failed/NeedsInput.
Message string
// ContinueToNext signals whether the StepRunner should proceed to
// the next step in the same Job invocation (true) or exit and let
// the controller re-trigger the same phase (false).
//
// Default is exit-after-save. Multi-step phases set this to true on
// intermediate steps and let the last step decide.
ContinueToNext bool
}
Result tells the StepRunner what status to deliver and whether to advance.
Body and frontmatter changes are NOT in Result — they're applied by mutating *Markdown in Run. This keeps the durability model clear: at any point during Run, the Markdown IS the durable view.
type ResultDeliverer ¶ added in v0.54.0
type ResultDeliverer interface {
DeliverResult(ctx context.Context, result AgentResultInfo) error
}
ResultDeliverer publishes an agent step result back to the task controller.
Implementations live in lib/delivery: NoopResultDeliverer (tests), FileResultDeliverer (local CLI), KafkaResultDeliverer (production K8s).
type Section ¶ added in v0.54.0
Section is one heading-bounded block in a parsed markdown document.
Heading is the exact heading line including '#' characters, e.g. "## Plan". Body is the content between this heading and the next at the same or higher level (no trailing newline).
Section is the parsed structural unit. The CQRS BodySection (in agent_task-commands.go) is a different concept: it carries the full serialized section text including the heading line, used as a partial frontmatter+body update payload.
func MarshalSectionTyped ¶ added in v0.54.0
MarshalSectionTyped renders a typed value as a Section ready for markdown.AddSection or markdown.ReplaceSection.
Output Section.Body format:
```json
{
"field": "value"
}
```
Round-trips with ExtractSection for the same heading and type.
type Step ¶ added in v0.54.0
type Step interface {
// Name identifies the step. Convention: lower-kebab-case.
Name() string
// ShouldRun returns true if the step should execute. Inspects markdown
// state (frontmatter, sections) and returns false if the step has
// already completed (idempotency guard).
//
// Guards must be cheap — no expensive I/O. Use existing markdown state.
ShouldRun(ctx context.Context, md *Markdown) (bool, error)
// Run performs the step's work, mutating markdown in-place. Returns a
// Result describing status + phase transition. Body content changes
// flow through markdown.AddSection / ReplaceSection; frontmatter
// changes flow through direct map mutation.
//
// The framework re-serializes markdown via Marshal after Run returns
// and publishes the new content via the deliverer.
Run(ctx context.Context, md *Markdown) (*Result, error)
}
Step is one unit of work within a phase. Always code; may wrap AI calls.
Three responsibilities:
- Name: identifies the step in logs and tests.
- ShouldRun: cheap guard that inspects task state to decide skip vs run.
- Run: performs work, mutating markdown in-place. Returns a Result describing status + phase transition (NOT body content — body changes happen via markdown.AddSection / ReplaceSection / Frontmatter mutation).
type StepRunner ¶ added in v0.54.0
type StepRunner struct {
// contains filtered or unexported fields
}
StepRunner walks an ordered list of steps for a single phase invocation.
On each iteration:
- step.ShouldRun(markdown) — skip if false
- step.Run(markdown) — mutates markdown in-place, returns Result
- markdown.Marshal() → newContent
- deliverer.DeliverResult(newContent, status, nextPhase)
- Decides whether to continue based on Status, NextPhase, ContinueToNext
Resume semantics: the runner does NOT track "current step" state. On re-invocation, all steps are re-walked; their ShouldRun guards skip completed work based on saved markdown state. Markdown state IS the resume cursor.
func NewStepRunner ¶ added in v0.54.0
func NewStepRunner(deliverer ResultDeliverer, steps ...Step) *StepRunner
NewStepRunner constructs a StepRunner with the given step list and deliverer.
type Task ¶
type Task struct {
base.Object[base.Identifier]
TaskIdentifier TaskIdentifier `json:"taskIdentifier"`
Frontmatter TaskFrontmatter `json:"frontmatter"`
Content TaskContent `json:"content"`
}
Task is the payload published by an agent when it finishes a task. task/controller consumes this from agent-task-v1-request and writes it to the vault file. Frontmatter is a generic map — task/controller serializes it to YAML without interpreting individual fields. Content is the markdown body after the frontmatter closing delimiter. The agent owns the content transformation (status, phase, Result section, etc.).
type TaskAssignee ¶
type TaskAssignee string
TaskAssignee identifies which agent type handles this task. Matched against Config CRD spec.assignee.
func (TaskAssignee) String ¶
func (t TaskAssignee) String() string
type TaskContent ¶
type TaskContent string
TaskContent is the markdown body of a task after the frontmatter closing delimiter.
func (TaskContent) String ¶
func (t TaskContent) String() string
type TaskFrontmatter ¶
type TaskFrontmatter map[string]interface{}
TaskFrontmatter is a generic map of frontmatter key-value pairs. Serializable as JSON (Kafka) and YAML (vault file). Typed accessors provide type-safe access to well-known fields.
func (TaskFrontmatter) Assignee ¶
func (f TaskFrontmatter) Assignee() TaskAssignee
func (TaskFrontmatter) CurrentJob ¶ added in v0.37.0
func (f TaskFrontmatter) CurrentJob() string
CurrentJob returns the K8s Job name recorded when the executor spawned a Job for this task. Returns an empty string when not set.
func (TaskFrontmatter) Int ¶ added in v0.54.0
func (f TaskFrontmatter) Int(key string) (int, bool)
Int reads an integer field by key, accepting both int (JSON-decoded) and float64 (YAML-decoded) underlying types. ok is false when the key is absent or holds a non-numeric value. Generic accessor for ad-hoc fields without dedicated typed methods.
func (TaskFrontmatter) MaxRetries ¶ added in v0.37.0
func (f TaskFrontmatter) MaxRetries() int
MaxRetries returns the maximum number of failures allowed before escalation. Returns 3 when the field is absent (spec default).
func (TaskFrontmatter) MaxTriggers ¶ added in v0.53.1
func (f TaskFrontmatter) MaxTriggers() int
MaxTriggers returns the maximum number of spawn-trigger events allowed for this task. Returns 3 if the field is absent, matching the default for max_retries.
func (TaskFrontmatter) Phase ¶
func (f TaskFrontmatter) Phase() *domain.TaskPhase
func (TaskFrontmatter) RetryCount ¶ added in v0.37.0
func (f TaskFrontmatter) RetryCount() int
RetryCount returns the number of failed attempts recorded in frontmatter. Returns 0 when the field is absent.
func (TaskFrontmatter) SpawnNotification ¶ added in v0.37.0
func (f TaskFrontmatter) SpawnNotification() bool
SpawnNotification returns true when this result is a job-spawn tracking update rather than an agent outcome. The controller skips the retry counter for these.
func (TaskFrontmatter) Stage ¶
func (f TaskFrontmatter) Stage() string
Stage returns the execution stage from the "stage" key. Returns "prod" if the key is absent or empty.
func (TaskFrontmatter) Status ¶
func (f TaskFrontmatter) Status() domain.TaskStatus
func (TaskFrontmatter) String ¶ added in v0.54.0
func (f TaskFrontmatter) String(key string) (string, bool)
String reads a string field by key. ok is false when the key is absent or holds a non-string value. Generic accessor for ad-hoc fields without dedicated typed methods.
func (TaskFrontmatter) TriggerCount ¶ added in v0.53.1
func (f TaskFrontmatter) TriggerCount() int
TriggerCount returns the number of spawn-trigger events that have fired for this task. Returns 0 if the field is absent.
type TaskIdentifier ¶
type TaskIdentifier string
TaskIdentifier uniquely identifies an agent task.
func (TaskIdentifier) Bytes ¶
func (t TaskIdentifier) Bytes() []byte
func (TaskIdentifier) Equal ¶
func (t TaskIdentifier) Equal(identifier base.ObjectIdentifier) bool
func (TaskIdentifier) Ptr ¶
func (t TaskIdentifier) Ptr() *TaskIdentifier
func (TaskIdentifier) String ¶
func (t TaskIdentifier) String() string
type TaskIdentifierGenerator ¶
type TaskIdentifierGenerator base.IdentifierGenerator[TaskIdentifier]
TaskIdentifierGenerator generates unique task identifiers.
type TaskIdentifiers ¶
type TaskIdentifiers []TaskIdentifier
TaskIdentifiers is a slice of TaskIdentifier.
func (TaskIdentifiers) Contains ¶
func (t TaskIdentifiers) Contains(value TaskIdentifier) bool
Contains returns true if the slice contains the given identifier.
type UpdateFrontmatterCommand ¶ added in v0.53.1
type UpdateFrontmatterCommand struct {
TaskIdentifier TaskIdentifier `json:"taskIdentifier"`
Updates TaskFrontmatter `json:"updates"`
Body *BodySection `json:"body,omitempty"`
}
UpdateFrontmatterCommand is the payload for UpdateFrontmatterCommandOperation. Merges Updates into the existing frontmatter (partial merge — absent keys preserved). When Body is set, its section is appended to (or replaced in) the task body via lib/delivery.ReplaceOrAppendSection. Unset Body means frontmatter-only update.
Source Files
¶
- agent_agent.go
- agent_cdb-schema.go
- agent_markdown.go
- agent_parser.go
- agent_phase.go
- agent_print-result.go
- agent_result-deliverer.go
- agent_runner.go
- agent_schema.go
- agent_status.go
- agent_step.go
- agent_task-assignee.go
- agent_task-commands.go
- agent_task-content.go
- agent_task-frontmatter.go
- agent_task-identifier.go
- agent_task.go