logos

package module
v1.1.0 Latest Latest
Warning

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

Go to latest
Published: Mar 25, 2026 License: MIT Imports: 11 Imported by: 1

README

logos

Stateless agent loop for Go. LLMs think in plain text and act via § prefixed shell commands inside <cmd> blocks — no tool schemas, no JSON.

prompt → LLM → scan <cmd> blocks for "§ command" → execute in sandbox → feed <result> back → repeat

Install

go get github.com/tta-lab/logos

Usage

result, err := logos.Run(ctx, logos.Config{
    Provider:     provider,        // fantasy.Provider (LLM abstraction)
    Model:        "claude-sonnet-4-6",
    SystemPrompt: systemPrompt,
    Temenos:      temenosClient,   // sandboxed command runner
    SandboxEnv:   map[string]string{"HOME": "/app"},
    AllowedPaths: []client.AllowedPath{
        {Path: "/app", Permission: "rw"},
    },
}, history, "read main.go and explain what it does", logos.Callbacks{
    OnDelta: func(text string) {
        fmt.Print(text) // stream to terminal
    },
    OnCommandStart: func(cmd string) {
        fmt.Printf("\n> %s\n", cmd)
    },
})

The LLM responds in plain text. When it wants to act, it wraps commands in a <cmd> block:

Let me check the file structure first.

<cmd>
§ ls -la /app
</cmd>

logos detects the commands, executes them in a temenos sandbox, and feeds the output back wrapped in <result>. The loop continues until the LLM responds without any <cmd> blocks.

How it works

  1. Run() takes config, conversation history, a prompt, and streaming callbacks
  2. Each turn, the LLM streams a response
  3. scanAllCommands() extracts all § lines from <cmd>...</cmd> blocks
  4. Commands run via the CommandRunner interface (temenos sandbox)
  5. Output wrapped in <result> becomes the next user message; loop repeats
  6. When the LLM responds with no <cmd> blocks, the loop ends and returns RunResult

Key types

Type Purpose
Config Provider, model, temenos client, sandbox env, allowed paths
RunResult Final response text + all step messages
StepMessage One message in the loop (assistant text, with optional reasoning, or command output)
Callbacks Optional OnDelta and OnCommandStart streaming hooks
CommandRunner Interface for command execution — temenos satisfies it

System prompt

BuildSystemPrompt() renders an embedded template with runtime context (working dir, platform, date, available commands). Consumers typically append their own instructions after the base prompt:

base, _ := logos.BuildSystemPrompt(logos.PromptData{
    WorkingDir: "/app",
    Platform:   "linux",
    Date:       "2026-03-16",
    Commands:   availableCommands,
})
systemPrompt := base + "\n\n" + customInstructions

Design

  • StatelessRun() takes history in, returns steps out. The caller owns persistence.
  • Multi-command blocks — all § lines inside a <cmd> block run sequentially; bare § outside blocks are prose and ignored.
  • Sandboxed — commands execute in temenos, not on the host.
  • Provider-agnostic — uses fantasy for LLM abstraction.
  • Reasoning round-trip — thinking blocks (Anthropic extended thinking) captured in StepMessage.Reasoning and ReasoningSignature for conversation restoration.

Dependencies

  • fantasy — LLM provider abstraction (streaming, messages)
  • temenos — sandboxed command execution daemon

License

MIT

Documentation

Overview

Package logos provides a reusable stateless agent loop.

Run() executes one agent loop iteration: prompt → LLM → tool calls → response. The caller provides conversation history, a system prompt, tools, and an optional sandbox env. No persistence — the caller receives StepMessages and handles storage.

Plane: shared

Index

Constants

View Source
const DefaultMaxSteps = 30

DefaultMaxSteps is the fallback max steps when Config.MaxSteps is 0.

View Source
const DefaultMaxTokens = 16384

DefaultMaxTokens is the fallback max output tokens when Config.MaxTokens is 0.

View Source
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 ContainsToolCallHallucination added in v0.8.0

func ContainsToolCallHallucination(text string) bool

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.

Types

type AllowedPath added in v0.3.0

type AllowedPath = client.AllowedPath

AllowedPath specifies a filesystem path allowed in the sandbox.

type BlockRunner added in v1.1.0

type BlockRunner interface {
	RunBlock(ctx context.Context, req RunBlockRequest) (*RunBlockResponse, error)
}

BlockRunner executes a block of commands in the sandbox. *client.Client satisfies this interface automatically.

func NewClient added in v0.3.0

func NewClient(addr string) (BlockRunner, error)

NewClient creates a BlockRunner connected to a temenos daemon. addr formats:

  • Empty string: resolve from TEMENOS_LISTEN_ADDR → TEMENOS_SOCKET_PATH → default socket
  • Starts with "/" or ".": unix socket path
  • Starts with "http://": HTTP base URL (TCP)
  • Otherwise (e.g. ":8081", "localhost:8081"): TCP

type Callbacks

type Callbacks struct {
	// OnDelta is called with each text delta as the LLM streams its response.
	OnDelta func(text string)
	// OnCommandResult is called after a command executes with the command string,
	// raw combined stdout+stderr output (no exit code suffix), and the exit code.
	// Fires once per CommandResult from the batch response.
	OnCommandResult func(command string, output string, exitCode int)
	// OnRetry is called when a tool call hallucination (XML or bracket) is detected
	// and an "unprocessed" directive is injected. reason is "tool_call".
	OnRetry func(reason string, step int)
}

Callbacks holds optional streaming callbacks for the agent loop. All fields are nil-safe — unset callbacks are simply not called.

type CommandDoc added in v0.9.0

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 CommandResult added in v1.1.0

type CommandResult = client.CommandResult

CommandResult is one command's execution result within a block.

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)
	Temenos      BlockRunner
	SandboxEnv   map[string]string // env vars passed to temenos per-request
	// AllowedPaths lists filesystem paths accessible during command execution.
	// Path validation (non-empty, absolute) is enforced by the temenos daemon.
	AllowedPaths []AllowedPath
	// Prefix is the command prefix the LLM uses (e.g. "§ ").
	// Passed to RunBlock so temenos can parse commands. Defaults to "§ ".
	Prefix string
}

Config holds everything needed to run one agent loop iteration.

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 RunBlockRequest added in v1.1.0

type RunBlockRequest = client.RunBlockRequest

RunBlockRequest is the request payload for batch block execution.

type RunBlockResponse added in v1.1.0

type RunBlockResponse = client.RunBlockResponse

RunBlockResponse is the response from batch block execution.

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.

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 → § commands → repeat. Stateless — the caller handles conversation persistence.

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.

const (
	StepRoleAssistant StepRole = "assistant" // LLM turn (with or without commands)
	StepRoleUser      StepRole = "user"      // human input
	StepRoleResult    StepRole = "result"    // command output fed back to LLM
)

Jump to

Keyboard shortcuts

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