agent

package
v0.1.33 Latest Latest
Warning

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

Go to latest
Published: Dec 30, 2025 License: Apache-2.0 Imports: 30 Imported by: 0

README

Agent Package

The agent orchestrates an AI-powered setup flow for Tusk Drift. It uses Claude to guide users through SDK installation, configuration, and testing via an interactive TUI.

Architecture

flowchart TB
    subgraph Agent["agent.go"]
        A[Agent.Run] --> PM[PhaseManager]
        A --> CC[ClaudeClient]
        A --> TUI[TUIModel]
    end

    subgraph Phases["phases.go"]
        PM --> P1[Detect Language]
        PM --> P2[Check Compatibility]
        PM --> P3[Gather Info]
        PM --> P4[...]
        PM --> PN[Summary]
    end

    subgraph Tools["executor.go + tools/"]
        CC -->|tool_use| TE[Tool Executors]
        TE --> FS[filesystem.go]
        TE --> PR[process.go]
        TE --> HT[http.go]
        TE --> US[user.go]
        TE --> TK[tusk.go]
    end

    subgraph Prompts["prompts/"]
        P1 -.->|instructions| SYS[system.md]
        P1 -.->|instructions| PH1[phase_*.md]
    end

    CC <-->|API| Claude[(Claude API)]
    TUI -->|renders| Terminal

Key Components

agent/
├── agent.go      # Main loop: iterates phases, sends messages to Claude, handles tool calls
├── phases.go     # Defines phases, their tools, and state transitions
├── executor.go   # Tool registry, schema definitions, and executor dispatch
├── types.go      # Message types, API types, State, Config
├── api.go        # Claude API client
├── tui.go        # Terminal UI (bubbletea) for progress, logs, and user interaction
├── prompts.go    # Embeds prompt markdown files
├── prompts/      # System prompt and per-phase instructions (*.md)
└── tools/        # Tool implementations (filesystem, process, http, etc.)

Data Flow

  1. Phase Loop (agent.go): Runs each phase until transition_phase is called
  2. Prompt Assembly: System prompt + phase instructions + current state → Claude
  3. Tool Execution: Claude returns tool_use → executor runs it → result returned
  4. State Updates: transition_phase updates State and advances to next phase

Adding a New Phase

1. Create the prompt file

Create prompts/phase_<name>.md with instructions for the phase:

## Phase: My New Phase

Your goal is to accomplish X.

### Instructions
1. Do A
2. Do B

When complete, call `transition_phase` with:
{
  "results": {
    "my_field": "value"
  }
}
2. Embed the prompt

In prompts.go, add:

//go:embed prompts/phase_my_new.md
var PhaseMyNewPrompt string
3. Define the phase

In phases.go, add the phase function:

func myNewPhase() *Phase {
    return &Phase{
        ID:           "my_new",
        Name:         "My New Phase",       // Displayed in TUI
        Description:  "Brief description",
        Instructions: PhaseMyNewPrompt,
        Tools: Tools(
            ToolReadFile,
            ToolWriteFile,
            // ... tools this phase needs
            ToolTransitionPhase,
        ),
        Required:      true,  // false = can be skipped
        MaxIterations: 20,    // 0 = use default (50)
        // Optional: inject dynamic content when entering this phase
        OnEnter: func(state *State) string {
            return "Additional context based on state"
        },
    }
}
4. Register in defaultPhases()

Add your phase to the ordered list:

func defaultPhases() []*Phase {
    return []*Phase{
        detectLanguagePhase(),
        checkCompatibilityPhase(),
        myNewPhase(),  // ← insert in correct order
        // ...
    }
}
5. Update State (if needed)

If your phase produces results, add fields to State in types.go:

type State struct {
    // ...
    MyField string `json:"my_field"`
}

Then handle them in UpdateState() in phases.go:

if v, ok := results["my_field"].(string); ok {
    pm.state.MyField = v
}

Adding a New Tool

1. Implement the executor

Create or edit a file in tools/ (e.g., tools/mytool.go):

package tools

import "encoding/json"

func MyTool(input json.RawMessage) (string, error) {
    var params struct {
        Param1 string `json:"param1"`
    }
    if err := json.Unmarshal(input, &params); err != nil {
        return "", err
    }

    // Do work...

    return "Result message", nil
}
2. Register the tool

In executor.go:

a) Add the constant:

const (
    // ...
    ToolMyTool ToolName = "my_tool"
)

b) Add the definition in toolDefinitions():

ToolMyTool: {
    Name:        ToolMyTool,
    Description: "What this tool does and when to use it",
    InputSchema: json.RawMessage(`{
        "type": "object",
        "properties": {
            "param1": {
                "type": "string",
                "description": "Description of param1"
            }
        },
        "required": ["param1"]
    }`),
    RequiresConfirmation: false,  // true = ask user before executing
},

c) Wire up the executor in RegisterTools():

executorMap := map[ToolName]ToolExecutor{
    // ...
    ToolMyTool: tools.MyTool,
}
3. Add to phases

In phases.go, include the tool in relevant phases:

Tools: Tools(
    ToolReadFile,
    ToolMyTool,  // ← add here
    ToolTransitionPhase,
),

Notes

  • Model: Agent is currently configured to use Claude Sonnet 4.5. We may allow model customization in the future, if necessary.
  • Sandboxing: Commands run via run_command are sandboxed using fence. See tools/process.go.

Documentation

Index

Constants

View Source
const (
	PhaseTimeout         = 15 * time.Minute // Max time per phase
	APITimeout           = 5 * time.Minute  // Max time for a single API call
	ToolTimeout          = 3 * time.Minute  // Max time for a single tool execution
	DefaultMaxIterations = 50               // Default max iterations if phase doesn't specify
	MaxTotalTokens       = 500000           // Max total tokens before warning
	MaxAPIRetries        = 3                // Max retries for API errors
)

Timeouts

View Source
const (
	ErrMaxIterations = "exceeded maximum iterations without completing phase"
	ErrMaxTokens     = "exceeded maximum token usage"
)

Error messages

Variables

View Source
var PhaseCheckCompatibilityPrompt string
View Source
var PhaseComplexTestPrompt string
View Source
var PhaseConfirmAppStartsPrompt string
View Source
var PhaseCreateConfigPrompt string
View Source
var PhaseDetectLanguagePrompt string
View Source
var PhaseGatherInfoNodejsPrompt string
View Source
var PhaseInstrumentSDKPrompt string
View Source
var PhaseSimpleTestPrompt string
View Source
var PhaseSummaryPrompt string
View Source
var SystemPrompt string

Functions

func PhasesSummary

func PhasesSummary() string

PhasesSummary returns a summary of all phases

func RecoveryGuidance

func RecoveryGuidance() string

RecoveryGuidance returns a message explaining how to resume after a failure

Types

type APIError

type APIError struct {
	Type    string `json:"type"`
	Message string `json:"message"`
}

APIError represents an error from the Claude API

type APIResponse

type APIResponse struct {
	ID           string    `json:"id"`
	Type         string    `json:"type"`
	Role         string    `json:"role"`
	Content      []Content `json:"content"`
	Model        string    `json:"model"`
	StopReason   string    `json:"stop_reason"` // "end_turn", "tool_use", "max_tokens"
	StopSequence *string   `json:"stop_sequence"`
	Usage        Usage     `json:"usage"`
}

APIResponse from Claude API

type Agent

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

Agent orchestrates the AI-powered setup process

func New

func New(cfg Config) (*Agent, error)

New creates a new Agent

func (*Agent) Run

func (a *Agent) Run(parentCtx context.Context) error

Run executes the agent with TUI

func (*Agent) SetTracker

func (a *Agent) SetTracker(tracker *analytics.Tracker)

SetTracker sets the analytics tracker for the agent

type ClaudeClient

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

ClaudeClient handles communication with the Claude API

func NewClaudeClient

func NewClaudeClient(apiKey, model string) (*ClaudeClient, error)

NewClaudeClient creates a new Claude API client

func (*ClaudeClient) CreateMessage

func (c *ClaudeClient) CreateMessage(
	ctx context.Context,
	system string,
	messages []Message,
	tools []Tool,
) (*APIResponse, error)

CreateMessage sends a message to Claude and returns the response (non-streaming)

func (*ClaudeClient) CreateMessageStreaming

func (c *ClaudeClient) CreateMessageStreaming(
	ctx context.Context,
	system string,
	messages []Message,
	tools []Tool,
	callback StreamCallback,
) (*APIResponse, error)

CreateMessageStreaming sends a message to Claude and streams the response

type Config

type Config struct {
	APIKey          string
	Model           string
	SystemPrompt    string
	MaxTokens       int
	WorkDir         string
	SkipPermissions bool // Skip permission prompts for consequential actions
	DisableSandbox  bool // Disable fence sandboxing for commands
	DisableProgress bool // Don't save or resume from PROGRESS.md
	Debug           bool // Enable debug mode for fence sandbox
}

Config holds agent configuration

type Content

type Content struct {
	Type      string          `json:"type"` // "text", "tool_use", "tool_result"
	Text      string          `json:"text,omitempty"`
	ID        string          `json:"id,omitempty"`          // For tool_use
	Name      string          `json:"name,omitempty"`        // For tool_use
	Input     json.RawMessage `json:"input,omitempty"`       // For tool_use
	ToolUseID string          `json:"tool_use_id,omitempty"` // For tool_result
	Content   string          `json:"content,omitempty"`     // For tool_result (as string)
	IsError   bool            `json:"is_error,omitempty"`    // For tool_result
}

Content represents a content block in a message

type Message

type Message struct {
	Role    string    `json:"role"` // "user", "assistant"
	Content []Content `json:"content"`
}

Message represents a conversation message

type Phase

type Phase struct {
	ID            string
	Name          string
	Description   string
	Instructions  string
	Tools         []PhaseTool // Which tools are available in this phase
	Required      bool        // Must complete, or can skip?
	MaxIterations int         // Max iterations for this phase (0 = use default)
	// OnEnter is called when entering this phase, returns additional context to append to instructions
	OnEnter func(state *State) string
}

Phase represents a distinct phase in the agent's workflow

type PhaseError

type PhaseError struct {
	Phase   string `json:"phase"`
	Message string `json:"message"`
	Fatal   bool   `json:"fatal"`
}

PhaseError represents an error that occurred during a phase

type PhaseManager

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

PhaseManager manages the agent's progress through phases

func NewPhaseManager

func NewPhaseManager() *PhaseManager

NewPhaseManager creates a new PhaseManager with default phases

func (*PhaseManager) AdvancePhase

func (pm *PhaseManager) AdvancePhase() (*Phase, error)

AdvancePhase moves to the next phase

func (*PhaseManager) CurrentPhase

func (pm *PhaseManager) CurrentPhase() *Phase

CurrentPhase returns the current phase

func (*PhaseManager) GetPhaseNames

func (pm *PhaseManager) GetPhaseNames() []string

GetPhaseNames returns all phase names in order

func (*PhaseManager) GetState

func (pm *PhaseManager) GetState() *State

GetState returns the current state

func (*PhaseManager) HasTransitioned

func (pm *PhaseManager) HasTransitioned() bool

HasTransitioned returns true if a transition occurred this iteration

func (*PhaseManager) IsComplete

func (pm *PhaseManager) IsComplete() bool

IsComplete returns true if all phases are done

func (*PhaseManager) PhaseTransitionTool

func (pm *PhaseManager) PhaseTransitionTool() ToolExecutor

PhaseTransitionTool creates the transition_phase tool executor

func (*PhaseManager) ResetTransitionFlag

func (pm *PhaseManager) ResetTransitionFlag()

ResetTransitionFlag resets the transition flag

func (*PhaseManager) RestoreDiscoveredInfo

func (pm *PhaseManager) RestoreDiscoveredInfo(info map[string]string)

RestoreDiscoveredInfo restores discovered information from parsed progress file

func (*PhaseManager) RestoreSetupProgress

func (pm *PhaseManager) RestoreSetupProgress(progress map[string]bool)

RestoreSetupProgress restores setup progress flags from parsed progress file

func (*PhaseManager) SetPreviousProgress

func (pm *PhaseManager) SetPreviousProgress(progress string)

SetPreviousProgress sets the progress from a previous interrupted run

func (*PhaseManager) SkipToPhase

func (pm *PhaseManager) SkipToPhase(phaseName string) bool

SkipToPhase skips to a specific phase by name, returning true if found

func (*PhaseManager) StateAsContext

func (pm *PhaseManager) StateAsContext() string

StateAsContext returns the current state as a string for the prompt

func (*PhaseManager) UpdateState

func (pm *PhaseManager) UpdateState(results map[string]interface{})

UpdateState updates the state with results from a phase

type PhaseTool

type PhaseTool struct {
	Name                 ToolName
	RequiresConfirmation bool          // Require user confirmation before executing
	CustomTimeout        time.Duration // Override default timeout (0 = use default)
}

PhaseTool represents a tool available in a phase with optional configuration

func GetPhaseToolConfig

func GetPhaseToolConfig(phase *Phase, name ToolName) *PhaseTool

GetPhaseToolConfig returns the PhaseTool config for a given tool in a phase (for checking confirmation, timeout, etc.)

func T

func T(name ToolName) PhaseTool

T creates a PhaseTool with default configuration (short name for use with builder pattern)

func Tools

func Tools(names ...ToolName) []PhaseTool

Tools is a helper to create []PhaseTool from ToolName constants (simple case, no config)

func (PhaseTool) WithConfirmation

func (pt PhaseTool) WithConfirmation() PhaseTool

WithConfirmation returns a copy of the PhaseTool that requires user confirmation

func (PhaseTool) WithTimeout

func (pt PhaseTool) WithTimeout(d time.Duration) PhaseTool

WithTimeout returns a copy of the PhaseTool with a custom timeout

type ProcessManager

type ProcessManager = tools.ProcessManager

ProcessManager wraps the tools.ProcessManager for external access

func NewProcessManager

func NewProcessManager(workDir string) *ProcessManager

NewProcessManager creates a new ProcessManager

func NewProcessManagerWithOptions

func NewProcessManagerWithOptions(workDir string, disableSandbox bool, debug bool) *ProcessManager

NewProcessManagerWithOptions creates a new ProcessManager with options

type State

type State struct {
	// Discovery results
	ProjectType      string `json:"project_type"`       // "nodejs", "python", "go", etc.
	PackageManager   string `json:"package_manager"`    // "npm", "yarn", "pnpm", "pip", etc.
	ModuleSystem     string `json:"module_system"`      // "esm", "cjs" (Node.js specific)
	EntryPoint       string `json:"entry_point"`        // e.g., "src/server.ts", "main.py"
	StartCommand     string `json:"start_command"`      // e.g., "npm run start"
	Port             string `json:"port"`               // e.g., "3000"
	HealthEndpoint   string `json:"health_endpoint"`    // e.g., "/health"
	DockerType       string `json:"docker_type"`        // "none", "dockerfile", "compose"
	ServiceName      string `json:"service_name"`       // e.g., "my-service"
	HasExternalCalls bool   `json:"has_external_calls"` // Does it make outbound HTTP/DB calls?

	// Compatibility check results
	CompatibilityWarnings []string `json:"compatibility_warnings"` // e.g., ["mongodb@6.3.0 not instrumented"]

	// Progress tracking
	AppStartsWithoutSDK bool `json:"app_starts_without_sdk"`
	SDKInstalled        bool `json:"sdk_installed"`
	SDKInstrumented     bool `json:"sdk_instrumented"`
	ConfigCreated       bool `json:"config_created"`
	SimpleTestPassed    bool `json:"simple_test_passed"`
	ComplexTestPassed   bool `json:"complex_test_passed"`

	// Error tracking
	Errors   []PhaseError `json:"errors"`
	Warnings []string     `json:"warnings"`
}

State tracks what the agent has learned and done

type StreamCallback

type StreamCallback func(event StreamEvent)

StreamCallback is called with streaming updates

type StreamEvent

type StreamEvent struct {
	Type       string // "text", "tool_use_start", "tool_use_input", "done"
	Text       string
	ToolName   string
	ToolID     string
	ToolInput  string
	StopReason string
}

StreamEvent represents a streaming event

type TUIModel

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

TUIModel is the bubbletea model for the agent TUI

func NewTUIModel

func NewTUIModel(ctx context.Context, cancel context.CancelFunc) *TUIModel

NewTUIModel creates a new TUI model

func (*TUIModel) GetFinalOutput

func (m *TUIModel) GetFinalOutput() string

GetFinalOutput returns the log content for printing after exit

func (*TUIModel) Init

func (m *TUIModel) Init() tea.Cmd

func (*TUIModel) RequestPermission

func (m *TUIModel) RequestPermission(program *tea.Program, toolName string, preview string) string

RequestPermission asks the user for permission to execute a tool. Returns "approve", "approve_all", or "deny".

func (*TUIModel) RequestPortConflict

func (m *TUIModel) RequestPortConflict(program *tea.Program, port int) bool

func (*TUIModel) RequestUserInput

func (m *TUIModel) RequestUserInput(program *tea.Program, question string) string

func (*TUIModel) SendAborted

func (m *TUIModel) SendAborted(program *tea.Program, reason string)

func (*TUIModel) SendAgentText

func (m *TUIModel) SendAgentText(program *tea.Program, text string, streaming bool)

func (*TUIModel) SendCompleted

func (m *TUIModel) SendCompleted(program *tea.Program)

func (*TUIModel) SendError

func (m *TUIModel) SendError(program *tea.Program, err error)

func (*TUIModel) SendFatalError

func (m *TUIModel) SendFatalError(program *tea.Program, err error)

func (*TUIModel) SendPhaseChange

func (m *TUIModel) SendPhaseChange(program *tea.Program, name, desc string, num, total int)

func (*TUIModel) SendRerunConfirm

func (m *TUIModel) SendRerunConfirm(program *tea.Program, responseCh chan bool)

func (*TUIModel) SendSidebarUpdate

func (m *TUIModel) SendSidebarUpdate(program *tea.Program, key, value string)

func (*TUIModel) SendThinking

func (m *TUIModel) SendThinking(program *tea.Program, thinking bool)

func (*TUIModel) SendToolComplete

func (m *TUIModel) SendToolComplete(program *tea.Program, name string, success bool, output string)

func (*TUIModel) SendToolStart

func (m *TUIModel) SendToolStart(program *tea.Program, name, input string)

func (*TUIModel) Update

func (m *TUIModel) Update(msg tea.Msg) (tea.Model, tea.Cmd)

func (*TUIModel) View

func (m *TUIModel) View() string

type Tool

type Tool struct {
	Name        string          `json:"name"`
	Description string          `json:"description"`
	InputSchema json.RawMessage `json:"input_schema"`
}

Tool defines a tool the agent can use

func FilterToolsForPhase

func FilterToolsForPhase(allTools []Tool, phase *Phase) []Tool

FilterToolsForPhase filters tool definitions to only those available in the current phase

func RegisterTools

func RegisterTools(workDir string, pm *ProcessManager, phaseMgr *PhaseManager) ([]Tool, map[string]ToolExecutor)

RegisterTools initializes the tool registry with executors and returns API-compatible formats

type ToolDefinition

type ToolDefinition struct {
	Name                 ToolName
	Description          string
	InputSchema          json.RawMessage
	Executor             ToolExecutor // Set at runtime via RegisterTools
	RequiresConfirmation bool         // Whether this tool requires user confirmation by default
}

ToolDefinition is the single source of truth for a tool's metadata and implementation

type ToolExecutor

type ToolExecutor func(input json.RawMessage) (string, error)

ToolExecutor executes a tool and returns the result

type ToolName

type ToolName string

ToolName is a type-safe identifier for tools

const (
	ToolReadFile               ToolName = "read_file"
	ToolWriteFile              ToolName = "write_file"
	ToolListDirectory          ToolName = "list_directory"
	ToolGrep                   ToolName = "grep"
	ToolPatchFile              ToolName = "patch_file"
	ToolRunCommand             ToolName = "run_command"
	ToolStartBackgroundProcess ToolName = "start_background_process"
	ToolStopBackgroundProcess  ToolName = "stop_background_process"
	ToolGetProcessLogs         ToolName = "get_process_logs"
	ToolWaitForReady           ToolName = "wait_for_ready"
	ToolHTTPRequest            ToolName = "http_request"
	ToolAskUser                ToolName = "ask_user"
	ToolTuskList               ToolName = "tusk_list"
	ToolTuskRun                ToolName = "tusk_run"
	ToolTransitionPhase        ToolName = "transition_phase"
	ToolAbortSetup             ToolName = "abort_setup"
)

Tool name constants - use these instead of raw strings for type safety

func AllToolNames

func AllToolNames() []ToolName

AllToolNames returns all possible tool names

func GetToolsForPhase

func GetToolsForPhase(phase *Phase) []ToolName

GetToolsForPhase returns the tool names available for a phase

type ToolRegistry

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

ToolRegistry holds all tool definitions, keyed by name

func GetRegistry

func GetRegistry() *ToolRegistry

GetRegistry returns the global tool registry (available after RegisterTools is called)

func (*ToolRegistry) All

func (r *ToolRegistry) All() []*ToolDefinition

All returns all tool definitions

func (*ToolRegistry) Get

func (r *ToolRegistry) Get(name ToolName) *ToolDefinition

Get returns a tool definition by name

type Usage

type Usage struct {
	InputTokens  int `json:"input_tokens"`
	OutputTokens int `json:"output_tokens"`
}

Usage tracks token usage

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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