Documentation
¶
Overview ¶
Package logos provides a reusable stateless agent loop.
Run() executes one agent loop iteration: prompt → LLM → <cmd> blocks → response. The caller provides conversation history, a system prompt, and streaming callbacks. No persistence — the caller receives StepMessages and handles storage.
Library mode: for callers that already have an assistant message in hand and want to run its <cmd> blocks without driving a full agent loop, use ParseCmdBlocks + ExecuteBlocks + FormatResults. NewExecConfig creates an ExecConfig from Config, selecting the appropriate runner (local or sandbox) based on Config.Sandbox.
Plane: shared
Index ¶
- Constants
- func BuildSystemPrompt(data PromptData) (string, error)
- func ContainsBlockedCommand(cmd string) bool
- func ContainsToolCallHallucination(text string) bool
- func FormatResults(results []Result) string
- func ParseCmdBlocks(text string) []string
- func StepsToMessages(steps []StepMessage) []fantasy.Message
- func StripCmdBlocks(text string) string
- type Callbacks
- type CommandDoc
- type Config
- type ExecConfig
- type PromptData
- type Result
- type RunResult
- type StepMessage
- type StepRole
- type StopReason
Constants ¶
const CmdBlockClose = "</cmd>"
CmdBlockClose is the closing tag for command blocks: <cmd>...</cmd>.
const CmdBlockOpen = "<cmd>"
CmdBlockOpen is the opening tag for command blocks: <cmd>...</cmd>.
const DefaultMaxSteps = 30
DefaultMaxSteps is the fallback max steps when Config.MaxSteps is 0.
const DefaultMaxTokens = 16384
DefaultMaxTokens is the fallback max output tokens when Config.MaxTokens is 0.
const MaxHallucinationRetries = 3
MaxHallucinationRetries is the maximum number of tool call hallucination retries before Run() returns an error.
Variables ¶
This section is empty.
Functions ¶
func BuildSystemPrompt ¶
func BuildSystemPrompt(data PromptData) (string, error)
BuildSystemPrompt renders the default system prompt with runtime context. The result is the base prompt — consumers append their own instructions after this.
func ContainsBlockedCommand ¶
ContainsBlockedCommand returns true if cmd contains a blocked in-place editing command (sed -i or perl -i).
func ContainsToolCallHallucination ¶
ContainsToolCallHallucination returns true if text contains tool call patterns produced by models that hallucinate structured formats — XML tags (e.g. <tool_call>) or bracket delimiters (e.g. [TOOL_CALL]...[/TOOL_CALL]). Standalone utility — internal detection is handled by streamFilter during streaming.
func FormatResults ¶
FormatResults renders a slice of Results as the single outer <result>...</result> wrap that ExecuteBlocks callers feed back to the model as a user message. Format matches what streamOneTurn produces internally:
<result> <stdout-1> STDERR: ← only if stderr non-empty <stderr-1> (exit code: N) ← only if exit != 0 AND exit != -1 <stdout-2> ... </result>
Entries are joined with a single "\n" between them. Empty results slice returns an empty string (no outer wrap).
func ParseCmdBlocks ¶
ParseCmdBlocks extracts the content of each <cmd>...</cmd> block from a complete assistant message. Returns contents in document order with surrounding whitespace trimmed. Nested blocks are not supported — the first </cmd> after a <cmd> closes the block. Unclosed blocks are silently dropped (an unclosed block at the end of the message is ignored with no error — callers who care can detect via strings.Contains themselves).
This is the non-streaming sibling of the internal cmdBlockBuffer used by streamOneTurn. Use this when you have the full text; use cmdBlockBuffer when you have a stream of deltas.
func StepsToMessages ¶
func StepsToMessages(steps []StepMessage) []fantasy.Message
StepsToMessages converts StepMessages back to fantasy.Messages for conversation round-tripping. Assistant steps produce ReasoningPart-first ordering (required by Anthropic). Result steps pass through the pre-formatted <result> envelope as a user message.
Used by fn-agent and lenos to rehydrate a conversation history from persisted StepMessages when resuming an agent session.
func StripCmdBlocks ¶
StripCmdBlocks returns text with all <cmd>...</cmd> blocks (including the tags themselves) removed. Runs of blank lines left behind are collapsed to a single blank line. Unclosed blocks are stripped from their opening tag to the end of the string.
Use this to prepare prose for display when the raw assistant message contains cmd blocks you don't want the human to see (e.g. forwarding to chat without showing the tool calls).
Types ¶
type Callbacks ¶
type Callbacks struct {
// OnStepStart fires before each model call. stepIdx is the zero-based step number.
OnStepStart func(stepIdx int)
// OnStepEnd fires after each step completes. stepIdx is the zero-based step number.
OnStepEnd func(stepIdx int)
// OnDelta is called with each text delta as the LLM streams its response.
OnDelta func(text string)
// OnReasoningDelta streams thinking content live as it arrives.
OnReasoningDelta func(text string)
// OnReasoningSignature fires once per step when the reasoning block is finalised.
OnReasoningSignature func(signature string)
// OnStepUsage fires once per step immediately after the model stream finishes,
// before any command is executed. stepIdx is the zero-based step number.
OnStepUsage func(stepIdx int, usage fantasy.Usage, meta fantasy.ProviderMetadata)
// OnCommandResult is called after a command executes (or is blocked/runner errors).
// command: the command that was run.
// output: formatOneResult output on success; directive text on blocked commands (exitCode=-2);
// formatOneResult output with Err set on runner errors (exitCode=-1).
// exitCode: 0 on success, -1 on runner error, -2 on blocked command.
OnCommandResult func(command string, output string, exitCode int)
// OnTurnEnd fires once when Run() exits. reason is a StopReason constant.
OnTurnEnd func(reason StopReason)
}
Callbacks holds optional streaming callbacks for the agent loop. All fields are nil-safe — unset callbacks are simply not called.
Vocabulary:
Step — one iteration of the loop (one model call + optional cmd execution).
Multiple steps per Turn.
Turn — one full agent response cycle. Exactly one Run() call.
Firing order within a Turn:
- OnStepStart(0)
- OnDelta / OnReasoningDelta — interleaved during streaming
- OnReasoningSignature — once when reasoning block finalises
- OnStepUsage(0, usage, meta) — once when the model stream finishes
- OnCommandResult — if a cmd block executed
- OnStepEnd(0) ... (steps repeat 1-6 with stepIdx incrementing) N. OnTurnEnd(reason) — exactly once at Run() exit
type CommandDoc ¶
type CommandDoc struct {
Name string // command name, e.g. "url", "web", "rg"
Summary string // one-line description shown under the heading
Help string // full help text (flags, examples, caveats)
}
CommandDoc describes a command available to the agent. Callers provide these to control which commands appear in the system prompt.
type Config ¶
type Config struct {
Provider fantasy.Provider
Model string
SystemPrompt string
MaxSteps int // 0 means use default (DefaultMaxSteps)
MaxTokens int // 0 means use default (DefaultMaxTokens)
Sandbox bool // true = require temenos sandbox; false = use local exec
SandboxAddr string // temenos socket/address; empty = env fallback chain
SandboxEnv map[string]string // env vars passed to sandbox per-request
// AllowedPaths lists filesystem paths accessible during command execution.
// Path validation (non-empty, absolute) is enforced by the temenos daemon.
// Note: localRunner (Sandbox=false) uses AllowedPaths[0].Path as the working
// directory; additional entries are ignored (no RO enforcement).
AllowedPaths []client.AllowedPath
// contains filtered or unexported fields
}
Config holds everything needed to run one agent loop iteration.
type ExecConfig ¶
type ExecConfig struct {
Env map[string]string
AllowedPaths []client.AllowedPath
TimeoutSec int // maps to RunRequest.Timeout; 0 = daemon default (seconds)
// contains filtered or unexported fields
}
ExecConfig holds the knobs ExecuteBlocks needs to dispatch cmds to a runner. runner is required and set by NewExecConfig. Env, AllowedPaths, TimeoutSec are optional and map directly to temenos client.RunRequest fields.
func NewExecConfig ¶
func NewExecConfig(cfg Config) (ExecConfig, error)
NewExecConfig creates an ExecConfig by resolving the runner from cfg. Uses localRunner if cfg.Sandbox is false; uses temenos client if true. Returns an error only if Sandbox is true but temenos is unreachable.
type PromptData ¶
type PromptData struct {
WorkingDir string
Platform string
Date string
Commands []CommandDoc // caller-provided command documentation
}
PromptData holds the runtime context used to render the default system prompt.
type Result ¶
Result is one command's execution outcome, returned by ExecuteBlocks. Command is the raw bash content that was run (verbatim from the <cmd> block). Stdout/Stderr/ExitCode come from the sandbox runner. Err is non-nil only on runner-level failures (sandbox refusal, daemon unreachable, timeout) — a non-zero exit code from bash is NOT an Err, it's reported via ExitCode. ExitCode == -1 means the runner did not return a normal exit (timeout, sandbox error); Err will be non-nil in that case.
func ExecuteBlocks ¶
func ExecuteBlocks(ctx context.Context, cfg ExecConfig, cmds []string) []Result
ExecuteBlocks runs each cmd concurrently against cfg.runner and returns results in the original order of cmds. Worker pool is capped at 8. Ctx cancellation stops new task submissions; already-submitted tasks run to completion (their Results will contain ctx.Err() on premature exit). Empty cmds returns nil.
ExecuteBlocks panics if cfg.runner is nil.
type RunResult ¶
type RunResult struct {
Response string // final text response (accumulated assistant text)
Steps []StepMessage // all messages generated (for persistence by caller)
}
RunResult contains the agent's output after a loop completes. Per-step token usage is reported via Callbacks.OnStepUsage.
func Run ¶
func Run( ctx context.Context, cfg Config, history []fantasy.Message, prompt string, cbs Callbacks, ) (*RunResult, error)
Run executes the agent loop: prompt → LLM → <cmd> blocks → repeat. Stateless — the caller handles conversation persistence. Complexity is inherent to switch + stream loop routing.
type StepMessage ¶
type StepMessage struct {
Role StepRole
Content string
Reasoning string // thinking block text (empty if no reasoning)
ReasoningSignature string // provider signature for round-trip
Timestamp time.Time
}
StepMessage represents one message generated during the agent loop.
type StepRole ¶
type StepRole string
StepRole represents the role of a message step in the agent loop.
type StopReason ¶
type StopReason string
StopReason describes why a Turn (single Run() invocation) terminated. Used by the OnTurnEnd callback. Each value corresponds to exactly one exit path in Run().
const ( StopReasonFinal StopReason = "final" // model returned final answer (no <cmd>) StopReasonCanceled StopReason = "canceled" // ctx canceled (in stream OR after) StopReasonError StopReason = "error" // stream error (non-cancellation) StopReasonHallucinationLimit StopReason = "hallucination_limit" // MaxHallucinationRetries exceeded StopReasonMaxSteps StopReason = "max_steps" // MaxSteps exhausted )