agent

package
v0.22.0 Latest Latest
Warning

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

Go to latest
Published: Jul 18, 2026 License: Apache-2.0 Imports: 29 Imported by: 0

Documentation

Overview

Package agent implements the local Agent-loop: the canonical "while the model requests tools, execute them and feed results back" cycle (Phase 0 — read-only). It is deliberately model-backend-agnostic (any OpenAI-compatible tool-calling Client) and side-effect-agnostic (tools are injected), so the loop logic is unit-testable without a live model or real tools. Stop conditions and the step budget are owned HERE, in code — never by the model — and an erroring or unknown tool is fed back as data (defer-not- crash), never a panic or an aborted run.

Index

Constants

View Source
const ArchitectPrompt = `` /* 1003-byte string literal not displayed */

ArchitectPrompt is the planning-model system prompt for two-tier mode. It has read/search tools only (NO write) and its whole job is to emit ONE complete, unambiguous plan a separate edit model will execute WITHOUT ever seeing the original request — so the plan must stand alone. Phrasing follows aider's architect prompt (provide direction to your editor engineer; make it unambiguous and complete; do NOT reproduce whole files).

View Source
const EditorPrompt = `` /* 428-byte string literal not displayed */

EditorPrompt is the edit-model system prompt for two-tier mode. Its sole user message is the architect's plan (no history, not the original request), and it has the write tools the user's --allow-* flags granted.

Variables

This section is empty.

Functions

func Mem0KeyFromEnvOrFile

func Mem0KeyFromEnvOrFile() string

Mem0KeyFromEnvOrFile resolves the mem0 API key: $MEM0_API_KEY first, then the on-disk key file (~/.mem0/api-key, mode 600 — the mem0 server's own key source). Returns "" if neither is present (memory then stays disabled rather than crashing).

func SystemPrompt

func SystemPrompt(allowWrite, allowOverwrite, allowFetch, allowShell, runGranted, allowSearch, allowGitHub bool) string

SystemPrompt is the loop's capability-aware system prompt: it advertises only the tools actually granted (read-only by default, +write/+web_fetch/+run_shell when enabled) and labels fetched web content as untrusted data. It is shared by every drive mode (headless CLI, MCP front door, standalone) so the agent's behavior is identical regardless of who supplies the goal.

Types

type Action

type Action struct {
	Kind   ActionKind
	Path   string
	Exists bool
}

Action is a proposed mutating operation. Path is worktree-relative (the worktree FS boundary itself is enforced separately by os.Root); Exists tells the broker whether the target already exists (new vs overwrite).

type ActionKind

type ActionKind string

ActionKind is the class of mutating action the broker gates. P2 covers file mutations; later phases extend this (shell, network) behind the same broker.

const (
	ActWrite  ActionKind = "write"
	ActDelete ActionKind = "delete"
	ActFetch  ActionKind = "fetch" // P3: outbound HTTP(S) GET to a host (egress-allowlist gated)
	ActShell  ActionKind = "shell" // P4.6: run a command inside the OS sandbox (opt-in, audited)
)

type Allowlist

type Allowlist struct {
	// contains filtered or unexported fields
}

Allowlist is the P3 egress host allowlist: a set of canonical hosts plus opt-in, boundary-anchored wildcard suffixes. It is built once and never mutated, so the policy broker that reads it stays deterministic. The zero value permits NOTHING (default-deny) — exactly how P0/P1 grant no network tool at all. The host-matching rule is the security boundary; see canonHost.

func NewAllowlist

func NewAllowlist(entries []string) (Allowlist, error)

NewAllowlist parses bare-hostname entries and "*.host" wildcards into an Allowlist, rejecting any entry that carries a scheme, port, path, userinfo, or backslash (bare hostnames only — the Managed-Agents rule). Empty input yields the deny-all zero value. A "*." prefix becomes a boundary-anchored suffix; any other "*" (mid-label, bare) is rejected by canonHost.

type AuditLog

type AuditLog struct {
	// contains filtered or unexported fields
}

AuditLog is an append-only JSONL record of every brokered decision — kept separate from the savings ledger, never co-mingled.

func NewAuditLog

func NewAuditLog(path string) *AuditLog

NewAuditLog returns a logger writing to path (created on first record).

func (*AuditLog) Record

func (l *AuditLog) Record(a Action, d Decision, reason string) error

Record appends one decision as a JSON line and RETURNS any error so the broker can refuse to perform an unauditable allow. A nil receiver is a no-op (nil err).

type BuildConfig

type BuildConfig struct {
	PlannerBase string        // local model base URL (no /v1); required
	Model       string        // planner model id (must support tool-calling); required
	Timeout     time.Duration // per planner-call timeout; default 180s
	MaxSteps    int           // hard step budget; default 12
	MaxTokens   int           // planner max tokens per call; default 1024
	MaxSameTool int           // per-run cap on calls to any one tool name; 0 => Loop's default (3); <0 => disabled

	ReadRoot string      // directory the agent may read (P0 scope); required
	Offload  OffloadFunc // in-process offload (record=false); nil => no offload tools

	// SystemPromptOverride, when set, replaces the capability-aware system prompt
	// (P6 flywheel replay evaluates a CANDIDATE planner prompt). Empty => the normal
	// SystemPrompt. The tool set is still capability-gated as usual, so a read-only
	// build stays side-effect-free regardless of the prompt.
	SystemPromptOverride string

	Unattended   bool   // true => every broker "ask" deny-and-queues (no human in the loop)
	AuditPath    string // append-only broker audit JSONL; must live OUTSIDE the worktree
	AskQueuePath string // P5b: reviewable queue of asks deferred on an unattended run (optional)

	AllowWrite bool // P2: write_file/delete_file in the worktree
	AllowFetch bool // P3: web_fetch behind the egress allowlist
	AllowShell bool // P4.6: run_shell in the LINUX OS cage (granted only on Linux + sandbox.Available)
	AllowRun   bool // C7b: `run` — allowlisted direct-exec runner in the OS sandbox (Linux AND Windows)

	AllowOverwrite bool     // open-write: allow overwrite of existing files in the worktree
	AllowDelete    bool     // open-write: allow delete of files in the worktree
	AllowSearch    bool     // web_search (DuckDuckGo); auto-allowlists the search host
	AllowGitHub    bool     // github_api/create_repo/upload_file; auto-allowlists api.github.com
	GitHubToken    string   // token for the GitHub tools (secret; Authorization header only)
	GitHubRepo     string   // default OWNER/NAME for github_upload_file
	Worktree       string   // RW worktree for write/shell; default = ReadRoot
	EgressHosts    []string // web_fetch allowlist (AllowFetch)

	Memory Memory // optional mem0 layer; nil => no memory
}

BuildConfig declares everything a drive mode needs to construct the agent loop identically. The caller supplies the local planner endpoint, the read scope, an in-process offload closure (record=false — e.g. pipeline.NewRecordlessOffload), and which capabilities are opt-in enabled. Build wires the tools + the single deny→ask→allow broker + the loop the SAME way for every mode (CLI, MCP front door, standalone), so the three modes cannot drift.

type BuildResult

type BuildResult struct {
	Loop         *Loop
	Tools        []Tool
	Policy       *Policy
	Worktree     string // resolved RW worktree (empty if no write/shell/run)
	ShellGranted bool
	RunGranted   bool
	Notes        []string
}

BuildResult is the assembled loop plus what was actually granted. ShellGranted is false when --allow-shell was requested but the OS cage is unavailable here (fail-closed); Notes are human-readable capability lines the caller can log.

func Build

func Build(cfg BuildConfig) (*BuildResult, error)

Build assembles the agent loop for any drive mode. It is the SINGLE place the tool set + broker + loop are constructed, so the CLI, the MCP front door, and the standalone runner stay at parity by construction. Operational failures (bad paths, audit-inside-worktree, worktree creation) are returned as errors, never os.Exit — the caller decides how to surface them.

type Client

type Client interface {
	Chat(ctx context.Context, msgs []Msg, tools []ToolSpec, maxTokens int) (Completion, error)
}

Client is the minimal OpenAI-compatible tool-calling chat interface the loop needs. A concrete implementation targets llama-swap / NIM; tests use a fake.

type Completion

type Completion struct {
	Msg          Msg
	FinishReason string
}

Completion is one model turn: the assistant message plus the finish reason ("tool_calls" => the loop must execute tools and continue; anything else => the loop stops).

type Decision

type Decision string

Decision is the broker's verdict on a mutating action.

const (
	Allow Decision = "allow" // perform it
	Ask   Decision = "ask"   // needs human approval (unattended => deny-and-queue)
	Deny  Decision = "deny"  // never perform
)

type FallbackReason added in v0.21.1

type FallbackReason string

FallbackReason records which path RunTwoTier actually took, so the caller can log it. FallbackNone means the two-tier plan-then-execute path ran; the others mean the architect produced nothing usable and RunTwoTier fell back to a single-model run of the ORIGINAL objective on the editor loop.

const (
	// FallbackNone: the architect produced a usable plan and the editor executed it.
	FallbackNone FallbackReason = ""
	// FallbackDegeneratePlan: the architect returned an empty/degenerate plan
	// (nothing to hand off), so the editor ran the original objective directly.
	FallbackDegeneratePlan FallbackReason = "degenerate-plan"
	// FallbackArchitectError: the architect run errored, so the editor ran the
	// original objective directly.
	FallbackArchitectError FallbackReason = "architect-error"
)

type LLMClient

type LLMClient struct {
	// contains filtered or unexported fields
}

LLMClient is the concrete OpenAI-compatible tool-calling Client. It targets any /v1/chat/completions endpoint — llama-swap (:11436, keyless) by default, or an NIM endpoint (apiKey set) for the hybrid "ask" escalation. One type, base_url swap. It implements the agent.Client interface.

func NewLLMClient

func NewLLMClient(base, model, apiKey string, timeout time.Duration) *LLMClient

NewLLMClient builds a client. base is the server root (no /v1); apiKey "" => no Authorization header (local).

func (*LLMClient) Chat

func (c *LLMClient) Chat(ctx context.Context, msgs []Msg, tools []ToolSpec, maxTokens int) (Completion, error)

Chat sends the running transcript + tool specs and returns the next completion.

type Loop

type Loop struct {
	// contains filtered or unexported fields
}

Loop runs the canonical agent loop over a fixed tool set.

func NewLoop

func NewLoop(c Client, tools []Tool, maxSteps int) *Loop

NewLoop builds a loop. maxSteps is the hard budget guard (owned in code, not the prompt). A non-positive maxSteps defaults to 1.

func (*Loop) Run

func (l *Loop) Run(ctx context.Context, objective string) (Result, error)

Run executes the loop for objective until the model stops, the step budget is exhausted, or the context is cancelled.

func (*Loop) WithContextTokens added in v0.21.1

func (l *Loop) WithContextTokens(n int) *Loop

WithContextTokens sets the model context window (in tokens) that transcript compaction budgets against. Default is defaultCtxTokens (8192, matching the shipped serving templates). A non-positive value is ignored. The derived INPUT budget is ctxTokens - maxTokens - compactionMargin (see inputBudget).

func (*Loop) WithMaxSameTool added in v0.21.1

func (l *Loop) WithMaxSameTool(n int) *Loop

WithMaxSameTool overrides the per-run same-tool call cap (see defaultMaxSameTool). n<=0 disables the cap (unlimited) — use only for tests that specifically need to observe unthrottled repeats.

func (*Loop) WithMaxTokens

func (l *Loop) WithMaxTokens(n int) *Loop

func (*Loop) WithMemory

func (l *Loop) WithMemory(m Memory) *Loop

WithMemory attaches a memory layer: the loop recalls relevant context before planning and persists the run outcome when it finishes. nil = no memory.

func (*Loop) WithProfile added in v0.21.1

func (l *Loop) WithProfile(p Profile) *Loop

WithProfile applies a per-task Profile (Task C6): it NARROWS the advertised tools to the INTERSECTION of p.Tools with what is already enabled, optionally overrides the system prompt, and stores the profile's few-shot exemplars for injection in Run. SAFETY INVARIANT: a profile can only narrow — it can NEVER grant a tool the capability flags didn't enable (a name in p.Tools that is not already present is silently ignored), so selecting a profile can never widen the agent's power beyond what --allow-* granted. An empty p.Tools (the "general" default) leaves the tool set untouched. Safe to call after NewLoop and WithWorktree (call it AFTER so a worktree-registered tool like update_plan is present to be kept).

func (*Loop) WithSystem

func (l *Loop) WithSystem(s string) *Loop

WithSystem sets an optional system prompt. WithMaxTokens overrides the per-call completion cap.

func (*Loop) WithToolResultCap added in v0.21.1

func (l *Loop) WithToolResultCap(n int) *Loop

WithToolResultCap overrides the per-result character cap applied when a tool's output becomes a transcript Msg (see toolResultCapChars). A non-positive value resets to the window-derived default. Use a large value only in tests that need to observe an uncapped result.

func (*Loop) WithWorktree added in v0.21.1

func (l *Loop) WithWorktree(root string) *Loop

WithWorktree gives the loop durable per-workspace working memory rooted at root (Task C5): it loads <root>/AGENT.md once at Run start (fenced as untrusted data), registers the update_plan tool (writes <root>/.agent/plan.md, os.Root-confined), and re-injects the plan on a cadence. An empty root leaves all of that off. Registration of update_plan happens here so the tool appears in the advertised spec set. Safe to call after NewLoop.

type Memory

type Memory interface {
	Recall(ctx context.Context, query string, limit int) ([]Recalled, error)
	Persist(ctx context.Context, text string, meta map[string]string) (string, error)
}

Memory is the loop's optional memory layer: recall relevant context before planning, persist a run outcome after. nil => the loop runs without memory.

type MemoryClient

type MemoryClient struct {
	// contains filtered or unexported fields
}

MemoryClient talks to the mem0 REST server (the agentic-memory system). It is WRITE-ISOLATED by construction: it recalls across readUsers (e.g. the agent's own namespace for run-to-run continuity + the canonical "dmmdea" namespace for the operator's knowledge) but only ever WRITES under writeUser, and only at the "evidence" tier — the mem0 server independently rejects any attempt to write the canonical tier (403), so the agent cannot pollute the canonical anchor.

func NewMemoryClient

func NewMemoryClient(base, apiKey string, readUsers []string, writeUser, agentID string, timeout time.Duration) *MemoryClient

NewMemoryClient builds a client. readUsers are the namespaces to recall from; writeUser is the (isolated) namespace to persist to. A trailing slash on base is trimmed.

func (*MemoryClient) Persist

func (c *MemoryClient) Persist(ctx context.Context, text string, meta map[string]string) (string, error)

Persist writes text as an EVIDENCE-tier memory under the isolated writeUser namespace, tagged source=local-agent. It NEVER sends tier=canonical (the server would 403 anyway) — the agent cannot reach the canonical anchor.

func (*MemoryClient) Recall

func (c *MemoryClient) Recall(ctx context.Context, query string, limit int) ([]Recalled, error)

Recall searches each readUser namespace CONCURRENTLY under one short deadline and returns the merged, score-sorted, de-duplicated top-`limit` memories. A per-namespace error is tolerated (that namespace contributes nothing); a total failure returns the last error with whatever was gathered — the caller treats an empty recall as "no memory". Concurrency + the deadline mean a single slow/ wedged namespace can't stall planning.

type Msg

type Msg struct {
	Role       string
	Content    string
	ToolCalls  []ToolCall
	ToolCallID string
	IsError    bool
}

Msg is one chat message in the loop's running transcript. Role is "system" | "user" | "assistant" | "tool". A tool result carries ToolCallID (matching the originating ToolCall.ID) and IsError when the tool failed.

type OffloadFunc

type OffloadFunc func(ctx context.Context, task, input string, params map[string]any) (result string, err error)

OffloadFunc is the in-process offload capability injected by cmd/local-agent (wired to pipeline.RunTier, which is record=false → no ledger/cache/shadow/ exemplar writes). Keeping it an injected func keeps this package free of any pipeline import and unit-testable with a fake. nil => no offload tools.

type Policy

type Policy struct {
	// contains filtered or unexported fields
}

Policy is the deterministic deny→ask→allow broker — the cage that must exist BEFORE any mutating tool is granted. It is the single chokepoint for every effectful action. For an UNATTENDED run, "ask" resolves to deny-and-queue (never proceed without a human), making approval-before-destructive a mechanism rather than a hope.

func NewPolicy

func NewPolicy(unattended bool, audit *AuditLog) *Policy

NewPolicy builds a broker. unattended=true makes every "ask" deny-and-queue. audit may be nil (decisions then aren't logged). Egress is deny-all (no allowlist) — use NewPolicyWithEgress to grant outbound hosts.

func NewPolicyWithEgress

func NewPolicyWithEgress(unattended bool, audit *AuditLog, allow Allowlist) *Policy

NewPolicyWithEgress is NewPolicy plus a P3 egress allowlist (the set of hosts web_fetch may reach). An empty allowlist is default-deny. The allowlist is set once and never mutated afterward, so classify stays pure and deterministic.

func (*Policy) Decide

func (p *Policy) Decide(a Action) (Decision, string)

Decide returns the EFFECTIVE decision (resolving ask→deny for unattended runs) plus a reason, and records it to the audit log. This is what tools call. If an ALLOW cannot be audited, it is downgraded to DENY — an unauditable mutation is not performed (a mutating agent must leave a trail).

func (*Policy) WithAskQueue

func (p *Policy) WithAskQueue(q *AuditLog) *Policy

WithAskQueue attaches a reviewable queue that records every ask DEFERRED on an unattended run (so a human can review/approve them later). Off by default; the standalone runner sets it. The action is still denied in the moment (unattended ask → deny-and-queue) — this just parks the pending request for review.

func (*Policy) WithShell

func (p *Policy) WithShell(allowed bool) *Policy

WithShell enables (or disables) the ActShell capability. Off by default; the CLI turns it on only when --allow-shell is set AND the OS sandbox is available. Set once at startup before any Decide, so classify stays deterministic.

func (*Policy) WithWritePosture added in v0.21.1

func (p *Policy) WithWritePosture(overwrite, del bool) *Policy

WithWritePosture sets the "open-write" posture. When overwrite is true the broker ALLOWS overwriting an existing file within the worktree instead of asking; when del is true it ALLOWS delete. This is the "fully open in the worktree" mode — the worktree boundary (os.Root) and the unconditional .git deny remain the safety rail. Off by default; set once at startup before any Decide so classify stays deterministic.

type Profile added in v0.21.1

type Profile struct {
	// Name is the CLI selector (e.g. "edit"). "general" is the default no-op.
	Name string
	// Tools is the advertised subset by name. EMPTY means "all currently enabled
	// tools" (the general profile) — no narrowing. A name not among the enabled
	// tools is simply ignored (narrow-only invariant).
	Tools []string
	// System, when non-empty, overrides the loop's system prompt with a prompt
	// tuned for this task shape.
	System string
	// Exemplars are TRUSTED, author-provided few-shot messages showing ONE correct
	// COMPLETE tool cycle for this job: user → assistant(tool_calls) → tool(result).
	// Every assistant message that carries ToolCalls MUST be immediately followed by
	// tool-role messages covering all its ToolCall.IDs — a dangling tool_calls with
	// no matching result violates strict --jinja templates (which this stack pins)
	// and can 400. They are injected right after the system message and before any
	// untrusted recall/AGENT.md/objective.
	Exemplars []Msg
}

Profile is a per-task tuning of the agent loop: a curated tool SUBSET, an optional tuned system prompt, and 2-3 worked tool-call exemplars injected as messages. It exists because small local models pick tools better with FEWER advertised ("Less is More": 63→71% on an 8B) and with worked examples shown in-context as messages (few-shot: Haiku 11→75%). A profile can only NARROW the already-enabled tool set — never grant a tool the capability flags didn't turn on (enforced in Loop.WithProfile) — so it is safe to select at the CLI.

func LookupProfile added in v0.21.1

func LookupProfile(name string) (Profile, error)

LookupProfile returns the named profile. An unknown name is an error whose message lists every valid profile name (sorted), so a CLI user can recover.

type Recalled

type Recalled struct {
	ID     string
	Text   string
	Score  float64
	Source string // which namespace (user_id) it came from
}

Recalled is one retrieved memory.

type Result

type Result struct {
	Output     string // the final assistant content
	Steps      int    // model turns taken
	StopReason string // "done" (model finished) | "budget" (hit maxSteps) | "error"
	Transcript []Msg

	// Fallback is set only by RunTwoTier (two-tier drive): it records whether the
	// architect/editor plan-then-execute path ran (FallbackNone) or a fallback to a
	// single-model editor run occurred, and why. Empty on ordinary single-loop runs.
	Fallback FallbackReason
}

Result is the outcome of a loop run.

func RunTwoTier added in v0.21.1

func RunTwoTier(ctx context.Context, objective string, architect, editor *Loop) (Result, error)

RunTwoTier runs aider's verified architect/editor one-shot handoff (the best-evidenced small-model decomposition: architect/editor 26.2% vs 20.9% solo, edit-format compliance 67.6%->100%).

Flow (exactly aider's semantics):

  1. The architect loop runs the ORIGINAL objective and produces a full prose plan (Result.Output).
  2. If the plan is non-degenerate, the editor loop runs with that plan as its SOLE user message — Loop.Run always starts a fresh transcript (system + optional recall/AGENT.md + the objective it is given), so passing the plan as the editor's objective naturally gives the editor a fresh context with NO conversation history and NOT the original request. The editor's Result is returned.

It is one-shot: the architect is NOT re-invoked after the editor runs. On a single GPU this is exactly ONE cold model swap (architect model -> editor model), cheap because it is plan-once-then-execute, not per-step alternation.

Fallback (never leave the user with nothing): if the architect errored OR produced no usable plan, RunTwoTier runs the ORIGINAL objective directly on the editor loop as a single-model run and records the reason in Result.Fallback.

type Tool

type Tool struct {
	ToolSpec
	Exec func(ctx context.Context, args string) (string, error)
}

Tool is a ToolSpec plus its executor. Exec receives the raw JSON args and returns a result string; an error is fed back to the model as an is_error tool result (the loop does not abort).

func FetchTools

func FetchTools(pol *Policy) []Tool

FetchTools builds the P3 web_fetch tool, registered ONLY when egress is opted in. With no allowlist the broker denies every host, so the network capability is inert by default — mirroring how P0/P1 advertise no network tool at all.

func GitHubTools added in v0.21.1

func GitHubTools(pol *Policy, token, defaultRepo, worktreeRoot string) []Tool

GitHubTools builds the GitHub toolset. token is required (empty => the tools refuse, defer-not-crash). defaultRepo (owner/name) is used when a call omits the repo. worktreeRoot is where github_upload_file reads local files from.

func ReadOnlyTools

func ReadOnlyTools(root string, offload OffloadFunc) ([]Tool, error)

ReadOnlyTools builds the Phase-0 tool set, scoped to root (the worktree): list_dir + read_file (both confined to root), plus the offload_* tools when an offloader is wired. It registers NO write/shell/network tool — P0 is read-only by construction; broader capability is gated to later phases behind the cage.

Containment is OS-ENFORCED via os.Root: every file access goes through a root handle that refuses to escape — `..`, absolute paths, AND reparse points (symlinks / Windows directory junctions) are all rejected by the kernel-level openat-style traversal, and it FAILS CLOSED (any resolution error => the tool errors, never a silent fall-through). This replaces the earlier hand-rolled EvalSymlinks check, which a fresh-context review proved bypassable by a non-privileged Windows junction.

func RunTools added in v0.21.1

func RunTools(pol *Policy, worktree, scratch string) []Tool

RunTools builds the C7b `run` tool: it runs an ALLOWLISTED program DIRECTLY (no shell) inside the OS sandbox — Argv = [command, args...], never /bin/sh -c, so the executable allowlist is a meaningful control. It is registered only when the caller opts in (--allow-run) AND sandbox.Available() is true; it is gated AGAIN at runtime by the deny→ask→allow broker (audited). worktree is the RW working directory; scratch is a RW temp dir.

func SearchTool added in v0.21.1

func SearchTool(root string) (Tool, error)

SearchTool builds the search_files tool, scoped to root (the worktree). It is read-only and registered alongside list_dir / read_file.

func SearchTools added in v0.21.1

func SearchTools(pol *Policy) []Tool

SearchTools builds the web_search tool. Registered only when the search capability is enabled (which also allowlists the DuckDuckGo host).

func ShellTools

func ShellTools(pol *Policy, worktree, scratch string) []Tool

ShellTools builds the run_shell tool: it executes an arbitrary /bin/sh command line inside the LINUX OS cage (no network, filesystem confined to the worktree (RW) + scratch, dangerous syscalls blocked, i386 ABI closed). run_shell is LINUX-ONLY — the builder grants it only on Linux (an arbitrary command line makes an executable allowlist meaningless, so on Windows the restricted `run` tool is used instead). It is registered only when the caller opts in AND the Linux cage is available; it is gated AGAIN at runtime by the deny→ask→allow broker, which audits every command. worktree is the RW working directory; scratch is a RW temp dir.

func WriteTools

func WriteTools(worktreeRoot string, pol *Policy) ([]Tool, error)

WriteTools builds the Phase-2 MUTATING tools (write_file, delete_file), confined to worktreeRoot via os.Root (the same kernel-enforced, fail-closed, reparse-safe containment used for reads) AND gated by the deny→ask→allow policy broker (the cage). Every mutation is brokered + audited; a non-Allow decision returns a "NOT performed" tool result (defer-not-crash) — the file is untouched and the model can react. These tools are registered ONLY when the caller opts into write capability; P0/P1 stay read-only.

type ToolCall

type ToolCall struct {
	ID   string
	Name string
	Args string // raw JSON arguments
}

ToolCall is one tool invocation the model requested.

type ToolSpec

type ToolSpec struct {
	Name        string
	Description string
	Schema      json.RawMessage
}

ToolSpec is the declarative surface advertised to the model.

Jump to

Keyboard shortcuts

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