Documentation
¶
Overview ¶
Package steward is the library API for building a product-agnostic AI ops & support agent over an open-source project's code, docs, and runbooks.
The engine knows nothing about any specific product: a "domain" (persona, ontology vocabulary, log error patterns, read-only probes, destructive-command red-lines, repos) is supplied as a domain.toml or in code. The same agent then answers and diagnoses questions grounded in a GraphRAG knowledge base, streams its tool use, cites sources, triages logs, and blocks destructive commands.
Minimal use:
a, err := steward.New(steward.Config{DomainFile: "domain.toml"})
if err != nil { log.Fatal(err) }
defer a.Close()
answer, err := a.Ask(ctx, "How do I recover a resource stuck in a degraded state?")
Any Config field left zero falls back to the matching STEWARD_* environment variable and then a built-in default, so a process configured purely through the environment can call steward.New(steward.Config{}).
Index ¶
- Constants
- Variables
- type Agent
- func (a *Agent) AnalyzeLog(path string) (*LogReport, error)
- func (a *Agent) Ask(ctx context.Context, question string) (string, error)
- func (a *Agent) Chat(ctx context.Context, sessionID, message string) (string, error)
- func (a *Agent) CheckCommand(command string) Verdict
- func (a *Agent) Close() error
- func (a *Agent) Decide(id string, approve bool, reason string) error
- func (a *Agent) Diagnose(ctx context.Context, symptom string) (string, error)
- func (a *Agent) Doctor(ctx context.Context) *Diagnosis
- func (a *Agent) Domain() *Domain
- func (a *Agent) IngestDir(ctx context.Context, dir string) (IngestStats, error)
- func (a *Agent) IngestDoc(ctx context.Context, id, title, content string) error
- func (a *Agent) Inventory(ctx context.Context) (*Inventory, error)
- func (a *Agent) LLM() LLMSettings
- func (a *Agent) MCPStatus() []MCPStatus
- func (a *Agent) PurgeSource(ctx context.Context, match string, prefix bool) (chunks, nodes int, err error)
- func (a *Agent) Refresh(ctx context.Context, dir string) (IngestStats, error)
- func (a *Agent) ResolveSpellings(ctx context.Context, dryRun bool) (*SpellingReport, error)
- func (a *Agent) Search(ctx context.Context, query string, topK int) ([]Hit, error)
- func (a *Agent) SetLLM(s LLMSettings, apiKey string) (LLMSettings, error)
- func (a *Agent) Stream(ctx context.Context, sessionID, question string, on func(Event)) (answer string, sources []string, err error)
- func (a *Agent) Walk(ctx context.Context, entity, relation, direction string) (*WalkResult, error)
- type Approval
- type Check
- type Config
- type Diagnosis
- type Domain
- type Event
- type EventKind
- type Hit
- type IngestStats
- type Inventory
- type LLMSettings
- type LogReport
- type MCPServerSpec
- type MCPStatus
- type OntologyDrift
- type Outcome
- type SourceInfo
- type SpellingGroup
- type SpellingReport
- type Suggestion
- type Verdict
- type WalkResult
- type WalkStep
Constants ¶
const ( WalkOut = knowledge.WalkOut WalkIn = knowledge.WalkIn WalkBoth = knowledge.WalkBoth )
Walk directions, re-exported so a caller never imports an internal package.
const DefaultApprovalTimeout = 10 * time.Minute
DefaultApprovalTimeout is how long a held call waits when Config leaves ApprovalTimeout zero.
Variables ¶
var ErrNoSuchApproval = errors.New("no pending approval with that id")
ErrNoSuchApproval is returned by Decide for an id that is not waiting: it was never issued, was already decided, or its turn ended.
Functions ¶
This section is empty.
Types ¶
type Agent ¶
type Agent struct {
// contains filtered or unexported fields
}
Agent is a configured ops/support agent. It is safe for sequential use; run one Ask/Chat/Stream at a time (the underlying ReAct loop is single-flight). Always Close it.
func (*Agent) AnalyzeLog ¶
AnalyzeLog triages a log file, directory, or archive (.tar.gz/.zip/…) into a ranked set of distinct problems using the domain's error patterns.
func (*Agent) Ask ¶
Ask runs one ReAct turn (probes + knowledge_search + red-line wall) and returns the grounded answer.
func (*Agent) Chat ¶
Chat runs a turn within a persistent session: history keyed by sessionID is loaded and saved by the agent, so follow-ups remember earlier turns.
func (*Agent) CheckCommand ¶
CheckCommand runs a command line through the deterministic red-line wall.
func (*Agent) Decide ¶
Decide answers a held tool call: approve runs it with the arguments shown in its EventApproval; reject returns reason to the agent instead. It fails with ErrNoSuchApproval when nothing is waiting under id, and with an error when the agent was built without ApproveWrites.
func (*Agent) Doctor ¶
Doctor reports on the retrieval path: whether the embedder is reachable and unproxied, whether the index caps recall, how the corpus splits between code and prose, how much of the graph is unreachable, and how far the extracted graph has drifted from the vocabulary domain.toml declares.
Inventory answers "what is in the store"; this answers "does retrieval still work", which is the question that goes wrong silently. Every check exists because the corresponding failure produced no error anywhere — the copilot simply answered worse, and nothing said why. A caller embedding this library has no CLI to run `steward doctor` with, which is the whole reason it is here.
func (*Agent) IngestDir ¶
IngestDir walks a local directory into the knowledge base: docs (.md/.adoc/…) as prose, source files mined for their error/log strings. Document ids are prefixed with the directory's base name (the source name used by Refresh/PurgeSource).
func (*Agent) IngestDoc ¶
IngestDoc adds or replaces a single document under the stable key id. When an LLM is configured it also extracts ontology entities/relations into the graph.
func (*Agent) Inventory ¶
Inventory reports what the knowledge base holds: which documents were ingested, how many chunks and graph entities they produced, and the vector width the index was actually built at.
The dimension is read back from the index rather than from configuration because those two disagreeing is a real failure mode with no other symptom: point the agent at a new embedder and every search quietly returns nothing.
func (*Agent) MCPStatus ¶
MCPStatus returns the per-server outcome of mounting the configured MCP servers (whether each connected, how many tools were registered, and any error). It is empty when no MCPServers were configured.
func (*Agent) PurgeSource ¶
func (a *Agent) PurgeSource(ctx context.Context, match string, prefix bool) (chunks, nodes int, err error)
PurgeSource removes a source's chunks (and derived graph nodes/edges). match is a document id, or its prefix when prefix is true. Returns the counts removed.
func (*Agent) Refresh ¶
Refresh purges everything previously ingested from dir (by its source name) and re-ingests it, so both updates and deletions take effect.
func (*Agent) ResolveSpellings ¶
ResolveSpellings merges entities that are one concept spelled several ways.
Entity ids keep separators, so "DRBDResource" and "DRBD resource" are two nodes and each document's edges attach to whichever spelling it used. Walk pools them at query time; this ends the split in the graph itself, restricted to the types domain.toml declares so a code graph in the same store keeps its same-named files apart.
dryRun reports the merges without making them. Deleting a node is not reversible, so a caller should show the list before applying it.
func (*Agent) SetLLM ¶
func (a *Agent) SetLLM(s LLMSettings, apiKey string) (LLMSettings, error)
SetLLM changes the model the agent generates with, without a restart.
This exists because restarting is not a neutral act everywhere it runs: in SDS the agent is a unit in a drbd-reactor promoter's start list, so restarting it to pick up a new model name demotes the resource and fails the whole control plane over to another node. A model change should not cost an outage.
An empty field keeps the current value, which is what makes "switch the model but keep the gateway" a one-field edit and, more importantly, means a caller changing the model never has to resend the API key.
The new provider is built and only then swapped in, so a configuration that cannot be constructed leaves the working one running. What this CANNOT do is prove the endpoint answers — that takes a request, and a settings call is the wrong place to spend one. Callers that want certainty should ask a question afterwards; the swap is reported as done, not as verified.
In-flight requests finish on the provider they started with.
func (*Agent) Stream ¶
func (a *Agent) Stream(ctx context.Context, sessionID, question string, on func(Event)) (answer string, sources []string, err error)
Stream runs a question in the conversation identified by sessionID and delivers Events to on as they happen (tool calls, answer deltas, …). It returns the full answer (with a deterministic Sources footer appended when the model didn't cite) and the sources retrieved this turn.
sessionID is what makes a follow-up a follow-up. Streaming used to drop it, so every turn of a streaming UI started from nothing: ask why a resource degraded, then ask "so how do I fix it", and the agent no longer knew what "it" was. Chat carried a session and Stream did not, which meant the mode a chat sidebar actually uses was the one without memory.
An empty sessionID runs the turn with no history, which is what a one-shot caller wants and what Ask does.
The model sometimes streams a short preamble, then delivers the real answer via its completion tool; when that happens Stream emits an EventReset before the authoritative answer so a UI can clear the preamble.
func (*Agent) Walk ¶
Walk follows the knowledge graph from a named entity along a relation, in a direction — WalkOut ("what does it back"), WalkIn ("what backs it") or WalkBoth.
This is the question retrieval cannot answer. Search finds text that is ABOUT something and returns whatever is one hop from it; "which pools back this resource" has an exact answer sitting in the graph, and routing it through a vector index means hoping the right chunk scores well AND that its neighbourhood contains the answer.
A relation whose ends the domain declared is walked through the ontology, so each result is a node of the far end's declared type — an edge contradicting the declaration is left out rather than returned as though it conformed. One with open ends follows the edges themselves, and says so.
type Approval ¶
type Approval struct {
ID string `json:"id"`
Server string `json:"server"`
Tool string `json:"tool"`
Args map[string]any `json:"args"`
}
Approval is a mutating tool call held for an operator's decision. It is emitted as an EventApproval in the stream whose turn made the call; the call runs only once Agent.Decide approves it, with exactly these arguments.
type Config ¶
type Config struct {
LLMBaseURL string // STEWARD_LLM_BASE_URL (default https://api.openai.com/v1)
LLMAPIKey string // STEWARD_LLM_API_KEY (required for Ask/Diagnose/Chat/Stream)
LLMModel string // STEWARD_LLM_MODEL (default gpt-4o)
EmbBaseURL string // STEWARD_EMB_BASE_URL (defaults to LLMBaseURL)
EmbAPIKey string // STEWARD_EMB_API_KEY (defaults to LLMAPIKey)
EmbModel string // STEWARD_EMB_MODEL (default text-embedding-3-small)
EmbDim int // STEWARD_EMB_DIM (default 1536) — must match the index
KnowledgeDBPath string // STEWARD_KNOWLEDGE_DB_PATH (default ./data/knowledge.db)
// SharedKnowledgeDBPaths are read-only knowledge bases searched alongside
// KnowledgeDBPath — product knowledge built once and installed as a file.
// STEWARD_SHARED_KNOWLEDGE_DBS (comma-separated). Nothing is written to them.
SessionDBPath string // STEWARD_DB_PATH (default ./data/steward.db)
// alchemy, the extraction service. With an address, Ingest reads prose
// through alchemy: every node and edge carries provenance, and a
// document whose sources contradict each other is held rather than
// written. Without one, the built-in per-chunk LLM extractor reads it.
AlchemyAddr string // STEWARD_ALCHEMY_ADDR
AlchemyToken string // STEWARD_ALCHEMY_TOKEN
AlchemyTLS bool // STEWARD_ALCHEMY_TLS
// Domain source. Set Domain for an in-memory config, or DomainFile for a path
// to a domain.toml (STEWARD_DOMAIN_FILE if both are empty). Domain wins if set.
DomainFile string
Domain *Domain
// MCPServers are external MCP servers whose tools are mounted into the ReAct
// loop. This is a code-only knob (no STEWARD_* fallback). A server that fails to
// connect is skipped (the agent degrades to knowledge-only); inspect the
// outcome with Agent.MCPStatus. Mark a spec ReadOnly to mount only its
// observational tools — the safe default for cluster-control servers.
MCPServers []MCPServerSpec
// ApproveWrites holds every mutating MCP tool call for an operator's
// decision. Stream surfaces the call as an EventApproval and waits for
// Decide; a call rejected, left undecided past ApprovalTimeout, or made
// outside Stream is not executed, and the agent is told so.
ApproveWrites bool
// ApprovalTimeout bounds the wait (default DefaultApprovalTimeout).
ApprovalTimeout time.Duration
}
Config configures an Agent. Every zero-valued field falls back to its STEWARD_* environment variable and then a default (see the package docs).
type Domain ¶
Domain is a product configuration: persona, ontology, error patterns, probes, red-lines, repos. Load one from TOML with LoadDomain.
func LoadDomain ¶
LoadDomain reads a domain.toml from disk.
type Event ¶
type Event struct {
Kind EventKind
Text string
Tool string
Args map[string]any
Suggestion *Suggestion // set only for EventSuggestion
Approval *Approval // set only for EventApproval
Outcome *Outcome // set only for EventOutcome
}
Event is one item in a streamed run. For EventSuggestion, Suggestion is set (and Tool is "suggest_action"); for tool events Tool/Args are set; otherwise Text.
type EventKind ¶
type EventKind string
EventKind classifies a streaming Event.
const ( EventText EventKind = "text" // Text is an answer delta EventToolCall EventKind = "tool_call" // Tool + Args: a tool the agent invoked EventToolResult EventKind = "tool_result" // Tool: that tool returned EventReset EventKind = "reset" // discard answer text emitted so far (a preamble) EventError EventKind = "error" // Text is a non-fatal error message EventSuggestion EventKind = "suggestion" // Suggestion: a structured, non-executed action proposal EventApproval EventKind = "approval" // Approval: a write tool call waiting for Decide EventOutcome EventKind = "outcome" // Outcome: how the turn ended — done, or blocked and why )
type IngestStats ¶
IngestStats summarizes an ingest run (doc/code files, mined error strings, skips).
type LLMSettings ¶
LLMSettings is the model the agent generates with, as an operator sees it. The API key is never part of it — see Agent.llmAPIKey.
type LogReport ¶
type LogReport = loganalyze.Report
LogReport is the result of triaging a log file/dir/archive.
type MCPServerSpec ¶
type MCPServerSpec struct {
Name string // logical name, e.g. "sds"
Transport string // "stdio" | "http" | "sse" (default "stdio")
Command string // stdio: executable
Args []string // stdio args
URL string // http/sse base URL
Headers map[string]string // http/sse optional headers
ReadOnly bool // when true, mount only read-only tools
ReadOnlyToolAllow []string // explicit allowlist (optional; overrides the name heuristic)
// WriteToolAllow names mutating tools to mount on a ReadOnly server as
// well: "everything observational, plus these". ReadOnlyToolAllow cannot
// say that — setting it replaces the read-only gate with the list, so
// granting one write tool through it drops every read tool not also
// named. These tools are mounted as mutations: never cached, never run
// concurrently.
WriteToolAllow []string
}
MCPServerSpec configures one external MCP server to mount into the agent.
type MCPStatus ¶
type MCPStatus = agents.MCPMountStatus
MCPStatus reports the outcome of mounting one MCP server in New.
type OntologyDrift ¶
type OntologyDrift = knowledge.OntologyDrift
OntologyDrift is where the extracted graph left the vocabulary domain.toml declares. Carried on Inventory.
type Outcome ¶
type Outcome struct {
Status string `json:"status"` // "complete" | "blocked"
Text string `json:"text,omitempty"`
}
Outcome is how a turn ended: the agent finished the task, or it stopped because something only the operator can resolve stood in the way. Text is the agent's own summary or blocker, empty when the answer already says it.
type SourceInfo ¶
type SourceInfo = knowledge.SourceInfo
SourceInfo is one ingested document's footprint.
type SpellingGroup ¶
type SpellingGroup = knowledge.SpellingGroup
SpellingGroup is one concept and the spellings folded into it.
type SpellingReport ¶
type SpellingReport = knowledge.SpellingReport
SpellingReport is what an entity resolution did. See Agent.ResolveSpellings.
type Suggestion ¶
type Suggestion struct {
Action string `json:"action"`
Params map[string]any `json:"params"`
Reason string `json:"reason"`
Severity string `json:"severity"` // "low" | "medium" | "high"
Verdict Verdict `json:"verdict"`
}
Suggestion is a structured, NON-executed action the agent proposes for an operator to approve. It is emitted via EventSuggestion when the agent calls the built-in suggest_action tool; nothing is run. Verdict is the deterministic red-line wall's ruling on the proposed action (Verdict.Blocked ⇒ the action is destructive and needs explicit, guarded confirmation).
type WalkResult ¶
type WalkResult = knowledge.WalkResult
WalkResult is what a graph walk found. See Agent.Walk.
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
steward
command
Command steward is the CLI entry point for the steward ops & support agent.
|
Command steward is the CLI entry point for the steward ops & support agent. |
|
examples
|
|
|
lib
command
Command lib-example shows how to embed steward as a library.
|
Command lib-example shows how to embed steward as a library. |
|
internal
|
|
|
agents
Package agents builds the ops agent: an agent-go service wired with a frontier LLM, a cortexdb-backed graph memory, the domain's read-only probe tools, a GraphRAG knowledge_search tool, and the deterministic red-line safety wall.
|
Package agents builds the ops agent: an agent-go service wired with a frontier LLM, a cortexdb-backed graph memory, the domain's read-only probe tools, a GraphRAG knowledge_search tool, and the deterministic red-line safety wall. |
|
alchemyclient
Package alchemyclient reads prose through alchemy.
|
Package alchemyclient reads prose through alchemy. |
|
apigraph
Package apigraph turns an OpenAPI 3 or Swagger 2 document into a graph: one node per operation and per schema an operation takes or returns, edges from each operation to those schemas, and — given the code graph and the command line graph — edges to the server function that implements the operation and from the commands that call it.
|
Package apigraph turns an OpenAPI 3 or Swagger 2 document into a graph: one node per operation and per schema an operation takes or returns, edges from each operation to those schemas, and — given the code graph and the command line graph — edges to the server function that implements the operation and from the commands that call it. |
|
cite
Package cite renders source citations for grounded answers.
|
Package cite renders source citations for grounded answers. |
|
cligraph
Package cligraph turns a command line's --help pages into a graph: one node per command, flag and positional argument, with the edges between them, and an edge from each command to the function that implements it when the repository's code graph has one.
|
Package cligraph turns a command line's --help pages into a graph: one node per command, flag and positional argument, with the edges between them, and an edge from each command to the function that implements it when the repository's code graph has one. |
|
config
Package config loads runtime configuration from the environment.
|
Package config loads runtime configuration from the environment. |
|
domain
Package domain is the platform's product-agnostic seam.
|
Package domain is the platform's product-agnostic seam. |
|
extract
Package extract turns a text chunk into an ontology fragment (entities + relations) using an LLM, constrained to the entity/relation vocabulary of a Domain.
|
Package extract turns a text chunk into an ontology fragment (entities + relations) using an LLM, constrained to the entity/relation vocabulary of a Domain. |
|
graphimport
Package graphimport loads an Understand-Anything knowledge-graph.json into the cortexdb knowledge base: node summaries become semantic vectors; nodes/edges become the ontology graph; and the architecture layers + guided tour are imported as their own nodes with membership edges.
|
Package graphimport loads an Understand-Anything knowledge-graph.json into the cortexdb knowledge base: node summaries become semantic vectors; nodes/edges become the ontology graph; and the architecture layers + guided tour are imported as their own nodes with membership edges. |
|
httpapi
Package httpapi exposes the agent over HTTP: a thin JSON layer over the same agent.Service + knowledge.Store the CLI uses.
|
Package httpapi exposes the agent over HTTP: a thin JSON layer over the same agent.Service + knowledge.Store the CLI uses. |
|
ingest
Package ingest walks a project's source/doc repository and feeds it into the knowledge base.
|
Package ingest walks a project's source/doc repository and feeds it into the knowledge base. |
|
knowledge
Package knowledge is the GraphRAG knowledge base over a project's source and docs.
|
Package knowledge is the GraphRAG knowledge base over a project's source and docs. |
|
loganalyze
Package loganalyze is a product-agnostic log triage engine.
|
Package loganalyze is a product-agnostic log triage engine. |
|
objectmodel
Package objectmodel extracts a precise object model from a project's structured source-of-truth — the same idea as schemaimport (SQL DDL), generalized to the other shapes a project ships its data model in:
|
Package objectmodel extracts a precise object model from a project's structured source-of-truth — the same idea as schemaimport (SQL DDL), generalized to the other shapes a project ships its data model in: |
|
probes
Package probes runs READ-ONLY diagnostic commands the agent may call during a ReAct loop.
|
Package probes runs READ-ONLY diagnostic commands the agent may call during a ReAct loop. |
|
safety
Package safety is the platform's deterministic "red-line" interception mechanism.
|
Package safety is the platform's deterministic "red-line" interception mechanism. |
|
salvage
Package salvage rescues an Understand-Anything run that produced intermediate output but never wrote a final knowledge-graph.json (the headless /understand step stalls on large repos).
|
Package salvage rescues an Understand-Anything run that produced intermediate output but never wrote a final knowledge-graph.json (the headless /understand step stalls on large repos). |
|
scaffold
Package scaffold drafts a domain.toml for a new project from its repository, using an LLM.
|
Package scaffold drafts a domain.toml for a new project from its repository, using an LLM. |
|
schemaimport
Package schemaimport extracts a precise object model from a SQL schema (DDL) into the knowledge graph: CREATE TABLE → entity, FOREIGN KEY → relation.
|
Package schemaimport extracts a precise object model from a SQL schema (DDL) into the knowledge graph: CREATE TABLE → entity, FOREIGN KEY → relation. |
|
textcut
Package textcut shortens text for display without cutting a character in half.
|
Package textcut shortens text for display without cutting a character in half. |
|
Package web embeds the built front-end (web/dist) so `steward serve`/`ui` ships the whole app as a single binary.
|
Package web embeds the built front-end (web/dist) so `steward serve`/`ui` ships the whole app as a single binary. |