coding

package
v0.21.0 Latest Latest
Warning

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

Go to latest
Published: Aug 10, 2026 License: MIT Imports: 38 Imported by: 0

Documentation

Overview

Package coding wires the runtime pieces into a working coding agent: provider + agent loop + tools + session persistence. It is the seam the CLI modes sit on, and the precursor to P3's full orchestrator.

Index

Constants

View Source
const DefaultModel = "anthropic/claude-sonnet-5"

DefaultModel is the model tau picks when neither the caller nor settings name one.

It is provider-qualified deliberately. A bare id is ambiguous once the compiled catalog spans every provider — a dozen of them resell claude-sonnet-5 — and the resolver's tie-break is a sort over ids, which would silently start the session on whichever reseller happened to sort highest. A user typing a bare id still gets that behaviour, which is Pi's; tau's own default must not depend on it.

Variables

View Source
var ErrNoSession = errors.New("this session is not persisted, so it has no history to work with")

ErrNoSession is returned by operations that need a persisted session.

View Source
var ErrRunning = errors.New("the agent is still working — press Esc to stop it first")

ErrRunning is returned by operations that cannot run while the agent is mid-turn.

Functions

func BuildRegistry added in v0.2.1

func BuildRegistry(store auth.CredentialStore) (*models.Registry, []string, error)

BuildRegistry composes the compiled provider catalog with ~/.tau/models.json.

A missing or malformed models.json is not fatal: the built-ins still work, which matters because the file is hand-edited and losing every provider over one stray comma would be the worse outcome. The problem is returned as a warning rather than printed, so the caller decides where it surfaces.

func FormatUsage added in v0.2.1

func FormatUsage(u ai.Usage) string

FormatUsage renders a usage summary for a human.

The cache figures appear only when there are any, but when there are they matter more than the input count: on a cached turn the input number is the small remainder, and printing it alone makes a working cache look like a broken token counter.

func RefreshRadiusCatalog added in v0.10.0

func RefreshRadiusCatalog(ctx context.Context, store auth.CredentialStore) (*provider.RadiusCatalog, error)

RefreshRadiusCatalog fetches the gateway's model list and caches it.

It runs after a login rather than at startup: it is a network round trip, and making every `tau` invocation wait on a gateway would slow down sessions that have nothing to do with Radius. The moment a user signs in is when they are online, waiting, and expecting setup to happen.

func RenderTree added in v0.12.0

func RenderTree(ctx context.Context, s *session.Session, roots []*session.TreeNode) string

RenderTree draws the session tree as indented text.

Entries that carry no conversation — a leaf move, a label — are collapsed away rather than shown. They are real structure, but the question /tree answers is "where in the conversation can I go back to", and a tree where every navigation left a node would drown the answer in its own history.

func ResolveTrust added in v0.18.0

func ResolveTrust(cwd string, override *bool) trust.Outcome

ResolveTrust answers the same question for callers outside a session — the package subcommands, which install into .tau and must not do so for a checkout the user has not trusted.

There is no prompt on this path, so an undecided project is denied and the user reaches it with --approve. A decision saved by a session is honored here, and one saved here is honored by the next session.

Types

type ExtensionLoader added in v0.13.0

type ExtensionLoader interface {
	// Load returns the extensions to register, plus any non-fatal problems to
	// show the user. A loader that cannot start one extension reports it and
	// returns the rest — a single bad file must not cost the user their
	// session.
	Load(ctx context.Context, req LoadRequest) ([]extension.Extension, []string)
	// Invalidate marks every loaded extension's captured session state stale,
	// without stopping anything.
	Invalidate()
	// Stop shuts the extensions down. reason is "exit", "reload", or "switch".
	Stop(reason string)
	// Reload stops everything and loads again, so an author can edit an
	// extension and see the new code run.
	Reload(ctx context.Context, req LoadRequest) ([]extension.Extension, []string)
}

ExtensionLoader discovers and launches extensions that live outside the binary.

It is an interface rather than a direct call into the subprocess host so that `coding` stays a library: an embedder can supply its own loader, or none, and the package does not drag a process supervisor in with it.

Load runs after the trust decision has been made and settings have been merged, and never before: a project's extensions must not be launched in a directory the user has not trusted, and the settings that name them are themselves project-scoped.

type JSONEvent

type JSONEvent struct {
	Type string `json:"type"`

	// Message carries the subject message for message_* and turn_end.
	Message json.RawMessage `json:"message,omitempty"`
	// Delta is the incremental text or thinking for message_update, so
	// consumers can render progressively without reassembling the message.
	Delta string `json:"delta,omitempty"`
	// DeltaKind is "text" or "thinking" when Delta is set.
	DeltaKind string `json:"deltaKind,omitempty"`

	ToolCallID string          `json:"toolCallId,omitempty"`
	ToolName   string          `json:"toolName,omitempty"`
	Args       map[string]any  `json:"args,omitempty"`
	Result     json.RawMessage `json:"result,omitempty"`
	IsError    bool            `json:"isError,omitempty"`

	// Usage and Cost accompany the terminal event.
	Usage *ai.Usage `json:"usage,omitempty"`
	// Error is set on the terminal event when the run failed.
	Error string `json:"error,omitempty"`
	// SessionPath is emitted once at start so a caller can resume later.
	SessionPath string `json:"sessionPath,omitempty"`
	Model       string `json:"model,omitempty"`
}

JSONEvent is one line of `tau --mode json` output. The stream is JSONL: exactly one event per line, LF-terminated, so consumers can parse incrementally without buffering the whole run.

type JSONWriter

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

JSONWriter serializes agent events as JSONL.

func NewJSONWriter

func NewJSONWriter(w io.Writer) *JSONWriter

NewJSONWriter builds a JSONL event writer.

func (*JSONWriter) Emit

func (j *JSONWriter) Emit(ev JSONEvent)

Emit writes one event.

func (*JSONWriter) Err

func (j *JSONWriter) Err() error

Err returns the first write failure, if any.

func (*JSONWriter) Sink

func (j *JSONWriter) Sink() agent.Sink

Sink returns an agent.Sink that renders the run as JSONL.

type LoadRequest added in v0.13.0

type LoadRequest struct {
	// Cwd is the session's working directory.
	Cwd string
	// Trusted reports the project-trust decision. A loader must not read the
	// project's extension directory when it is false.
	Trusted bool
	// SettingsPaths are the extension paths named in the merged settings.
	SettingsPaths []string
	// Mode is the host's UI mode, for diagnostics.
	Mode extension.Mode
	// Snapshot is the session state an out-of-process extension needs to be
	// able to answer synchronously. Pi's ExtensionAPI getters return values,
	// not promises, and an extension puts them straight into a message; a
	// loader that omits this leaves every one of them empty.
	Snapshot Snapshot
}

LoadRequest is what a loader needs to know to find extensions.

type Options

type Options struct {
	Cwd           string
	ModelID       string
	ThinkingLevel ai.ModelThinkingLevel
	SystemPrompt  string
	// NoTools disables tool use entirely.
	NoTools bool
	// NoSession skips persistence (useful for one-shot runs).
	NoSession bool
	// AppendSystemPrompt is appended after the built system prompt.
	AppendSystemPrompt string
	// NoSkills disables skill discovery.
	NoSkills bool
	// TrustOverride forces the project-trust decision (--approve/--no-approve).
	TrustOverride *bool
	// Extensions are loaded before the session starts, in order.
	Extensions []extension.Extension
	// ExternalExtensions discovers and launches extensions that live outside
	// the binary. Nil loads only the ones compiled in.
	ExternalExtensions ExtensionLoader
	// Mode is reported to extensions so they can degrade gracefully.
	Mode extension.Mode
	// Resume opens the most recent session for Cwd instead of creating one.
	Resume bool
	// SessionPath opens a specific session file.
	SessionPath string
	// UI is the host's interactive surface, handed to extensions and to the
	// built-in commands that need to ask the user something. Nil is headless.
	UI extension.UI
	// Changelog is the release notes /changelog shows, as a Keep-a-Changelog
	// document. Empty means the binary ships none, and the command says so.
	// cmd/tau passes the copy embedded at the repository root.
	Changelog string
	// Interactive supplies the built-in commands that need dialogs. The host
	// may pass a value whose session pointer is filled in after New returns —
	// nothing calls it during construction.
	Interactive slashcmd.Interactive
}

Options configures a coding session.

type PromptPoint added in v0.12.0

type PromptPoint struct {
	EntryID   string
	Timestamp string
	Text      string
}

PromptPoint is a place the conversation can be forked or rewound to.

type RPCServer added in v0.13.0

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

RPCServer drives a coding session from JSONL commands on a stream.

It is the third headless surface, after print and json, and the only one that is bidirectional. That is what it exists for: an editor or a supervisor can steer a running agent, answer an extension's dialog, and navigate the session tree, none of which a one-shot invocation can do.

func NewRPCServer added in v0.13.0

func NewRPCServer(s *Session, out io.Writer) *RPCServer

NewRPCServer builds a server around an existing session.

func (*RPCServer) Attach added in v0.13.0

func (r *RPCServer) Attach(s *Session, out io.Writer)

Attach binds the session and output stream.

It exists separately from construction because of an ordering problem: the extension UI has to be handed to coding.New, and coding.New produces the session this server serves. So the server is created first, its UI is passed in, and the session is attached once it exists. An extension that opens a dialog from its factory then reaches a real client rather than a nil one.

func (*RPCServer) Dispatch added in v0.13.0

func (r *RPCServer) Dispatch(ctx context.Context, cmd rpc.Command)

Dispatch runs one command, as though it had arrived on the input stream. A prompt given on the command line uses it, so the client sees exactly the events it would have seen had it sent the prompt itself.

func (*RPCServer) EmitExtensionLog added in v0.13.0

func (r *RPCServer) EmitExtensionLog(name, level, message string)

EmitExtensionLog surfaces a subprocess extension's diagnostic on the client's stream. Without it those lines land on tau's stderr, where a program driving tau over a pipe never sees them.

func (*RPCServer) Serve added in v0.13.0

func (r *RPCServer) Serve(ctx context.Context, in io.Reader) error

Serve reads commands until the stream ends or ctx is cancelled.

func (*RPCServer) UI added in v0.13.0

func (r *RPCServer) UI() extension.UI

UI returns the extension surface that proxies dialogs to the client.

It is exposed so a caller can attach it before building the session: an extension may open a dialog from its factory, and a UI attached afterwards would miss it.

type Session

type Session struct {
	Agent   *agent.Agent
	Env     *osenv.OSEnv
	Model   *ai.Model
	Session *session.Session
	Path    string
	// Cwd is the directory the session is bound to.
	Cwd string
	// Extensions dispatches hooks; nil when no extensions are loaded.
	Extensions *extension.Runner
	// Trust records whether project-scoped resources were allowed to load.
	Trust trust.Outcome
	// Models is the composed provider catalog: built-ins plus models.json.
	Models *models.Registry
	// Settings is the merged global+project configuration.
	Settings *settings.Resolved

	// Skills are the discovered Agent Skills. They appear in the system prompt
	// and, unless disabled, as /skill:<name> commands.
	Skills []skills.Skill
	// Prompts are the discovered prompt templates, each registered as a slash
	// command that expands to its body.
	Prompts []prompttemplate.Template
	// Commands is the slash-command registry for this session.
	Commands *slashcmd.Registry
	// UI is the host's interactive surface; never nil.
	UI extension.UI
	// Warnings are non-fatal startup problems — a malformed models.json, say.
	// The session runs regardless; the host decides whether to show them.
	Warnings []string
	// contains filtered or unexported fields
}

Session is a running coding agent bound to a persisted session file.

func New

func New(ctx context.Context, opts Options) (*Session, error)

New builds a coding session.

func (*Session) AvailableModels

func (s *Session) AvailableModels() []ai.Model

AvailableModels lists every model the registry knows, provider-qualified.

func (*Session) Close

func (s *Session) Close(ctx context.Context, reason string)

Close emits session shutdown to extensions and stops the ones running in their own processes. Skipping the second half would leak a process per session.

func (*Session) Compact added in v0.12.0

func (s *Session) Compact(ctx context.Context, instructions string) (*compaction.Result, error)

Compact summarizes the older part of the conversation and checkpoints it.

instructions is an optional focus for the summary. Returns nil when there was nothing to compact, which is not an error: /compact on a short session should say so rather than fail.

func (*Session) CycleModel

func (s *Session) CycleModel(ctx context.Context, delta int) *ai.Model

CycleModel steps through the cycle set by delta, wrapping at both ends.

func (*Session) CycleModels

func (s *Session) CycleModels() []ai.Model

CycleModels is the ordered set Ctrl+P cycles through: the models named by the enabledModels setting, or everything when the setting is empty.

Pi treats an unmatched pattern as a warning rather than an error, so a setting that names a model from an unconfigured provider still leaves a usable cycle set.

func (*Session) CycleThinkingLevel

func (s *Session) CycleThinkingLevel(ctx context.Context, delta int) ai.ModelThinkingLevel

CycleThinkingLevel steps to the next level this model supports.

func (*Session) Describe

func (s *Session) Describe() string

Describe renders a one-line summary for status output.

func (*Session) ExportSession added in v0.19.0

func (s *Session) ExportSession(ctx context.Context, path string) (string, error)

ExportSession writes the conversation to a file and returns the path.

The format follows the extension: a .jsonl path writes the current branch as a session file that tau can open again, anything else renders the self-contained HTML page. An empty path takes the default name in the working directory.

func (*Session) ExtensionNames added in v0.13.0

func (s *Session) ExtensionNames() []string

ExtensionNames lists the loaded extensions, for /reload and diagnostics.

func (*Session) ExtensionPaths added in v0.18.0

func (s *Session) ExtensionPaths() []string

ExtensionPaths are the extension entry points to load: the ones named in settings first, then the ones installed packages contribute.

func (*Session) Fork added in v0.12.0

func (s *Session) Fork(ctx context.Context, entryID string) error

Fork copies this session up to entryID into a new session file and switches to it, leaving the original untouched.

entryID names a user message, and the copy stops just before it: the point of forking at a request is to make it differently. An empty entryID copies the whole session, which is /clone.

func (*Session) ImportSession added in v0.20.0

func (s *Session) ImportSession(ctx context.Context, path string) (string, error)

ImportSession adopts a session file and continues the conversation in it.

The file is copied into this directory's session folder before it is opened, which is what makes an imported transcript show up in /resume afterwards. It also means further turns are appended to tau's copy rather than to the file that was handed over — importing something out of a shared directory should not start writing to it.

This is the other half of `/export <path>.jsonl`. `tau import` is a different thing entirely: that adopts a whole Pi installation.

func (*Session) LastAssistantText

func (s *Session) LastAssistantText() string

LastAssistantText returns the text of the most recent assistant message.

func (*Session) ListSessions

func (s *Session) ListSessions(ctx context.Context) ([]session.Metadata, error)

ListSessions returns this directory's sessions, most recent first.

func (*Session) MaybeCompact added in v0.12.0

func (s *Session) MaybeCompact(ctx context.Context) (bool, error)

MaybeCompact compacts if the context has outgrown its reserve.

Reports whether it compacted. A failure is returned rather than swallowed, but the caller is expected to keep going: a turn that cannot be compacted is still worth attempting, and the provider's own error is a clearer diagnosis than tau refusing pre-emptively.

func (*Session) MoveTo added in v0.12.0

func (s *Session) MoveTo(ctx context.Context, entryID string, summarize bool) (*compaction.BranchResult, error)

MoveTo repositions the conversation at another entry in the tree.

summarize asks for a summary of the branch being left. It costs a request, so it is the caller's decision; without one the abandoned work simply drops out of context, which is sometimes exactly what the user wants.

func (*Session) PrepareCompaction added in v0.12.0

func (s *Session) PrepareCompaction(ctx context.Context) (*compaction.Preparation, error)

PrepareCompaction reports what a compaction would replace, without spending a request. Returns nil when there is nothing to compact.

func (*Session) Prompt

func (s *Session) Prompt(ctx context.Context, text string) ([]ai.Message, error)

Prompt runs one agent loop, compacting around it if the context needs it.

The check happens twice, because there are two ways to be over the window and only one of them is visible in advance. Before the turn, the running estimate says whether the conversation has outgrown its reserve. After it, a provider that rejected the request for length says so in a way no estimate could have predicted — a cache-heavy turn, an unusually long tool result — and that rejection is recoverable exactly once.

func (*Session) ReloadExtensions added in v0.13.0

func (s *Session) ReloadExtensions(ctx context.Context) error

ReloadExtensions restarts the out-of-process extensions and rebuilds the dispatch runner around them.

The generation bump is the load-bearing part: every context an extension captured before this point now belongs to a session that is gone, and a handler still holding one gets ErrStale rather than a live view of state the old code was reasoning about.

Bundled extensions are re-registered too. They cannot have changed on disk, but they share the runner with the reloaded ones, and rebuilding half of it would leave the surviving half bound to a runner nothing dispatches through.

func (*Session) ResolveModelSpec

func (s *Session) ResolveModelSpec(spec string) (models.Match, error)

ResolveModelSpec is a lookup that does not switch models, for previewing a selection.

func (*Session) RestoredMessages

func (s *Session) RestoredMessages() []ai.Message

RestoredMessages returns the transcript the session was opened with, so a host can replay it into its display.

func (*Session) RunCommand

func (s *Session) RunCommand(ctx context.Context, line string) (slashcmd.Result, error)

RunCommand parses and executes a slash-command line.

It must not be called from a host's render goroutine: a command may open a dialog and block until the user answers.

func (*Session) SaveTrust

func (s *Session) SaveTrust(ctx context.Context, decision string) (string, error)

SaveTrust records a project-trust decision for future sessions.

func (*Session) ScopedModels added in v0.20.0

func (s *Session) ScopedModels() string

ScopedModels renders the cycle set — the models Ctrl+P moves between — and says where it was configured.

An unset enabledModels puts every model in the cycle, and printing a thousand of them helps nobody, so that case reports the count instead.

func (*Session) SessionSummary

func (s *Session) SessionSummary() string

SessionSummary renders the /session report.

func (*Session) SetModel

func (s *Session) SetModel(ctx context.Context, spec string) (*ai.Model, error)

SetModel switches the model for subsequent turns, records the change in the session, and notifies extensions.

func (*Session) SetScopedModels added in v0.20.0

func (s *Session) SetScopedModels(ctx context.Context, patterns []string) (string, error)

SetScopedModels saves patterns as the cycle set. No patterns clears the setting, which puts every model back in the cycle.

The write goes to global settings. Project scope would be refused outright in an untrusted directory, and a cycle set is a preference about the person using tau rather than about the code they are pointing it at.

func (*Session) SetThinkingLevel

func (s *Session) SetThinkingLevel(ctx context.Context, level ai.ModelThinkingLevel) ai.ModelThinkingLevel

SetThinkingLevel changes the reasoning level, clamped to what the model supports, and records it in the session.

func (*Session) SettingsGet added in v0.20.0

func (s *Session) SettingsGet(key string) (string, error)

SettingsGet renders one key. A dotted key reaches one level into a nested object, which is as far as the settings file nests.

func (*Session) SettingsKeys added in v0.20.0

func (s *Session) SettingsKeys() []string

SettingsKeys lists the keys tau models, for completion.

func (*Session) SettingsList added in v0.20.0

func (s *Session) SettingsList() string

SettingsList renders every configured setting with the scope it came from.

Only what is actually set is listed. The alternative — every key tau knows about, most of them showing a default nobody chose — is the same information as the documentation, and buries the handful of lines that answer "what did I change?".

func (*Session) SettingsSet added in v0.20.0

func (s *Session) SettingsSet(ctx context.Context, key, value string) (string, error)

SettingsSet writes a key to global settings and applies it to this session.

The value is read as JSON when it parses as JSON, and as a plain string when it does not: `theme dark` means "dark", `quietStartup true` means true, and `npmCommand ["pnpm","add"]` means the array. Guessing this way is what makes the command usable without quoting every string.

Writes go to the global scope. Project settings are refused outright in an untrusted directory, and choosing the scope from a one-line command would need a flag that earns its keep only for the rare project-specific value — which can be edited in the file the listing names.

func (*Session) SettingsUnset added in v0.20.0

func (s *Session) SettingsUnset(ctx context.Context, key string) (string, error)

SettingsUnset removes a key from global settings.

func (*Session) ShareSession added in v0.19.0

func (s *Session) ShareSession(ctx context.Context) (string, error)

ShareSession uploads the exported page as a secret GitHub gist and returns the URLs to show the user.

A secret gist is unlisted, not private: anyone holding the link can read the whole transcript, including any file contents and command output in it.

func (*Session) Snapshot added in v0.13.0

func (s *Session) Snapshot() Snapshot

Snapshot describes the session for an out-of-process extension.

It is taken at the moment it is asked for rather than cached: a reload happens mid-session, and handing the new process the state from startup would make its first answers wrong in a way nothing would report.

func (*Session) StartSession

func (s *Session) StartSession(ctx context.Context) error

StartSession replaces the current session with a new empty one.

func (*Session) SwitchSession

func (s *Session) SwitchSession(ctx context.Context, meta session.Metadata) error

SwitchSession opens an existing session file and adopts its transcript.

func (*Session) ThemePaths added in v0.18.0

func (s *Session) ThemePaths() []string

ThemePaths are the theme files to load, from settings and from packages.

func (*Session) ThinkingLevel

func (s *Session) ThinkingLevel() ai.ModelThinkingLevel

ThinkingLevel reports the reasoning level in force.

func (*Session) ToolNames

func (s *Session) ToolNames() []string

ToolNames lists the active tools.

func (*Session) ToolsByName

func (s *Session) ToolsByName() map[string]agent.Tool

ToolsByName exposes the full registered tool set for activation UIs.

func (*Session) TreeNodes added in v0.12.0

func (s *Session) TreeNodes(ctx context.Context) ([]*session.TreeNode, error)

TreeNodes returns the session tree for a navigation UI.

func (*Session) Usage

func (s *Session) Usage() ai.Usage

Usage sums token usage across the transcript.

func (*Session) UserPrompts added in v0.12.0

func (s *Session) UserPrompts(ctx context.Context) ([]PromptPoint, error)

UserPrompts lists the user messages on the current branch, oldest first.

This is what /fork offers to branch from: a fork point is only meaningful at a request the user made, because that is the decision being re-taken.

type Snapshot added in v0.13.0

type Snapshot struct {
	SessionName   string
	ModelID       string
	ModelProvider string
	ContextWindow int
	MaxTokens     int
	ThinkingLevel string
	ActiveTools   []string
	Commands      []SnapshotCommand
}

Snapshot is the session state handed to an out-of-process extension at load.

type SnapshotCommand added in v0.13.0

type SnapshotCommand struct {
	Name        string
	Description string
	Source      string
}

SnapshotCommand is one entry in the command list Pi exposes via getCommands.

Jump to

Keyboard shortcuts

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