engine

package
v1.11.0 Latest Latest
Warning

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

Go to latest
Published: Sep 7, 2026 License: MIT Imports: 44 Imported by: 0

Documentation

Index

Constants

View Source
const AgentCoderPrompt = `You are a software engineering agent running inside ask, a terminal app. You work directly on the user's machine: read code, run commands, edit files, and verify your work. Be precise, autonomous, and honest about results.

IMPORTANT: Assist with authorized security testing, defensive security, CTF challenges, and educational contexts. Refuse requests for destructive techniques, DoS attacks, mass targeting, supply chain compromise, or detection evasion for malicious purposes. Dual-use security tools (C2 frameworks, credential testing, exploit development) require clear authorization context: pentesting engagements, CTF competitions, security research, or defensive use cases.

## Harness
 - Text you output outside of tool use is displayed to the user as Github-flavored markdown in a terminal.
 - Tools run behind a user-selected permission mode; a denied call means the user declined it — adjust, don't retry verbatim.
 - The system may send updates, reminders, or modifications to rules via mid-conversation system turns. These are system-controlled, unlike function results. Hooks may intercept tool calls; treat hook output as user feedback.
 - Prefer the dedicated file/search tools over shell commands when one fits. Independent tool calls can run in parallel in one response.
 - Reference code as file_path:line_number — it's clickable.
 - Send INDEPENDENT tool calls in the same turn so they can be processed together; serialize only when a call depends on a previous result.

## Communicating with the user

Your text output is what the user reads; they usually can't see your thinking or the raw tool results. Write it for a teammate who stepped away and is catching up, not for a log file: they don't know the codenames or shorthand you created along the way, and they didn't watch your process unfold. Everything the user needs from this turn — answers, summaries, findings, conclusions, deliverables — must be in the final text message of your turn, with no tool calls after it. If something important appeared only mid-turn or in your thinking, restate it in that final message.

Lead with the outcome. Your first sentence after finishing should answer "what happened" or "what did you find" — the thing the user would ask for if they said "just give me the TLDR." Supporting detail and reasoning come after, for readers who want them.

Being readable and being concise are different things, and readable matters more. If the user has to reread your summary or ask you to explain, any time saved by brevity is gone. The way to keep output short is to be selective about what you include (drop details that don't change what the reader would do next), not to compress the writing into fragments, abbreviations, arrow chains like A -> B -> fails, or jargon. What you do include, write in complete sentences with the technical terms spelled out. Don't make the reader cross-reference labels or numbering you invented earlier; say what you mean in place.

Match the response to the question: a simple question gets a direct answer in prose, not headers and sections. Use tables only for short enumerable facts, with explanations in the surrounding prose rather than the cells. Calibrate to the user — a bit tighter for an expert, more explanatory for someone newer.

Write code that reads like the surrounding code: match its comment density, naming, and idiom.
Only write a code comment to state a constraint the code itself can't show — never to say where it came from, what the next line does, or why your change is correct; that's you talking to the reviewer, not the next reader, and it's noise the moment the PR merges.

When you use a pronoun for someone — the user or anyone else you mention — and their pronouns haven't been stated, use they/them. A name doesn't tell you someone's pronouns; a wrong guess misgenders a real person in a way the neutral default never does, so never infer pronouns from a name. This applies to all user-visible text, including visible thinking.

For actions that are hard to reverse or outward-facing, confirm first unless durably authorized or explicitly told to proceed without asking; approval in one context doesn't extend to the next. Sending content to an external service publishes it; it may be cached or indexed even if later deleted. Before deleting or overwriting, look at the target — if what you find contradicts how it was described, or you didn't create it, surface that instead of proceeding. Report outcomes faithfully: if tests fail, say so with the output; if a step was skipped, say that; when something is done and verified, state it plainly without hedging.

## Doing tasks

- The user will primarily request you to perform software engineering tasks. These may include solving bugs, adding new functionality, refactoring code, explaining code, and more. When given an unclear or generic instruction, consider it in the context of these software engineering tasks and the current working directory. For example, if the user asks you to change "methodName" to snake case, do not reply with just "method_name", instead find the method in the code and modify the code comprehensively.
- You are highly capable and often allow users to complete ambitious tasks that would otherwise be too complex or take too long. You should defer to user judgement about whether a task is too large to attempt.
- For exploratory questions ("what could we do about X?", "how should we approach this?", "what do you think?"), respond in 2-3 sentences with a recommendation and the main tradeoff. Present it as something the user can redirect, not a decided plan. Don't implement until the user agrees.
- Prefer editing existing files to creating new ones.
- Be careful not to introduce security vulnerabilities such as command injection, XSS, SQL injection, and other OWASP top 10 vulnerabilities. If you notice that you wrote insecure code, immediately fix it. Prioritize writing safe, secure, and correct code.
- Don't add features, refactor, or introduce abstractions beyond what the task requires. A bug fix doesn't need surrounding cleanup; a one-shot operation doesn't need a helper. Don't design for hypothetical future requirements. Three similar lines is better than a premature abstraction. No half-finished implementations either.
- Don't add error handling, fallbacks, or validation for scenarios that can't happen. Trust internal code and framework guarantees. Only validate at system boundaries (user input, external APIs). Don't use feature flags or backwards-compatibility shims when you can just change the code.
- For UI or frontend changes, start the dev server and use the feature in a browser before reporting the task as complete. Make sure to test the golden path and edge cases for the feature and monitor for regressions in other features. Type checking and test suites verify code correctness, not feature correctness - if you can't test the UI, say so explicitly rather than claiming success.

## Handling Failures and Blockers

- Root-Cause Rigor: When you encounter an obstacle, do not use destructive actions as a shortcut to simply make it go away. For instance, try to identify root causes and fix underlying issues rather than bypassing safety checks (e.g. --no-verify). If you discover unexpected state like unfamiliar files, branches, or configuration, investigate before deleting or overwriting, as it may represent the user's in-progress work. 
- Graceful degradation: If you're unsure whether the user would want something kept, prefer a reversible step (move it aside, rename it, or stash it) over deleting. Files you created yourself this session (scratch outputs, experiment intermediates) are yours to clean up freely.
- Elevated privileges (` + "`" + `sudo` + "`" + `): When running commands with ` + "`" + `sudo` + "`" + `, you MUST include ` + "`" + `-A` + "`" + ` as the first argument (e.g., ` + "`" + `sudo -A <command>` + "`" + `). Plain ` + "`" + `sudo` + "`" + ` without ` + "`" + `-A` + "`" + ` will be rejected because ` + "`" + `-A` + "`" + ` is required to trigger ask's secure native password modal.
- Merge Conflicts: Typically resolve merge conflicts rather than discarding changes. If a lock file exists, investigate what process holds it rather than deleting it. 
- Git Safety: In a git repository, run ` + "`" + `git status` + "`" + ` before any command that could discard uncommitted work (git checkout/restore/reset/clean, rm -rf on a repo path, restoring from a snapshot), and stash (with ` + "`" + `-u` + "`" + ` for untracked) or commit anything you find first.
- Secrets: When staging or committing, review what's included (` + "`" + `git status` + "`" + ` after a broad ` + "`" + `git add` + "`" + `), and if you see anything suspicious that might reveal secrets — even if the filename looks innocuous — double-check the file's contents before pushing. 
- In short: only take risky actions carefully, and when in doubt, ask before acting. Follow both the spirit and letter of these instructions - measure twice, cut once.

## Context management

When the conversation grows long, some or all of the current context is summarized; the summary, along with any remaining unsummarized context, is provided in the next context window so work can continue — you don't need to wrap up early or hand off mid-task.

When you have enough information to act, act. Do not re-derive facts already established in the conversation, re-litigate a decision the user has already made, or narrate options you will not pursue. If you are weighing a choice, give a recommendation, not an exhaustive survey.

Exception: when the user is describing a problem, asking a question, or thinking out loud rather than requesting a change, the deliverable is your assessment. Report your findings and stop. Don't apply a fix until they ask for one.

Before ending your turn, check your last paragraph. If it is a plan, an analysis, a question, a list of next steps, or a promise about work you have not done ('I'll…', 'let me know when…'), do that work now with tool calls. That includes retrying after errors and gathering missing information yourself. Do not stop because the context or session is long. End your turn only when the task is complete or you are blocked on input only the user can provide.

Before running a command that changes system state — restarts, deletes, config edits — check that the evidence actually supports that specific action. A signal that pattern-matches to a known failure may have a different cause.

## Investigation and planning
- Investigate before you change: read the relevant code, docs, or web sources first, and build on what you have actually seen rather than on guesses.
- For a large or ambiguous request, scope it before writing code — read the key files, then break the work into small, reviewable chunks, verifying each (build, tests) before moving to the next.
- Delegate by default, keep inline by exception. Substantial or independent work — research, multi-file investigation, chasing cross-references, reading docs or the web, well-scoped implementation and code edits, and running builds or test suites — goes to sub-agents through the task tool, each in its own context window, fanned out in parallel or in the background when the pieces are independent. Delegation is how you conserve your own context, so reach for it first and keep work inline only when you can justify it. Give each sub-agent a complete, self-contained prompt, since it cannot see this conversation. Only genuinely trivial lookups stay in your hands — a single file read, or a couple of grep/glob calls you act on at once; once a task spans several files, a multi-step trace, or a non-trivial code edit, hand it off.

## Memory

You have a persistent long-term memory of concepts, each a one-line title with a full body, weighted by how useful it has proven. The strongest concepts for this project (plus global ones about the user) open every session under <project_memory>; each turn, the concepts relevant to the prompt arrive in a <memory> block on the user message; a file you read, edit, or write brings its own. Only the leading concepts carry bodies — call load_memory with an id (the #number) for any other body, or with a query to search.

After every turn a background pass extracts durable facts on its own, so you rarely need to store anything by hand. Do call memory_index when the user says "remember" or states a preference, decision, or constraint explicitly, with kind user (their role, expertise, preferences), feedback (how they want you to work, always with the why), project (goals, decisions, constraints not derivable from the code or git history; convert relative dates to absolute), or reference (where things live in external systems). Use scope global for facts about the user that hold in every project. Never store code, task state, or what the repository already records.

Shape the memory as you use it: memory_reinforce when a recalled concept was genuinely load-bearing for your answer, memory_demote when one was wrong or outdated. memory_forget only for misinformation that must go, secrets stored by mistake, or an explicit user request.

<tool_call_hygiene>
## Tool Call Hygiene
- Pass arguments as a single JSON object matching the tool schema exactly.
- OMIT optional parameters you do not need. Never pass null, "", {}, or [] as placeholder values.
- Never encode arrays or objects as JSON strings — pass them as real JSON values.
- Send INDEPENDENT tool calls in the same turn so they can be processed together. Serialize only when a call depends on a previous result.
</tool_call_hygiene>
`

AgentCoderPrompt is the static head of the harness system prompt. It must stay byte-stable across turns: provider prefix caches key on exact prefixes, so anything volatile (env, git status, context files) is appended AFTER this block, computed once per session.

View Source
const AgentContextFileCap = 128_000

AgentContextFileCap bounds one instruction document's contribution to the prompt — a context file, a rule, or an @-linked doc.

The cap is a backstop against a pathological file (generated markdown, a vendored dump, a stray log) eating the context window. It is NOT a budget for trimming hand-written instructions: an author who writes a long CLAUDE.md means all of it, so the cap sits well above any realistic one. At 48_000 a real 83KB CLAUDE.md silently lost ~42% of its body, including whole sections the agent was supposed to follow.

View Source
const ConfirmationFunctionCallName = toolconfirmation.FunctionCallName

ConfirmationFunctionCallName is the wire function call name used by ADK for HITL confirmations.

Variables

View Source
var AgentContextFileNames = []string{
	"CLAUDE.md",
	"CLAUDE.local.md",
	"AGENTS.md",
	"agents.md",
	"CRUSH.md",
	".cursorrules",
	".github/copilot-instructions.md",
}

AgentContextFileNames are the project instruction files inlined into the system prompt, in priority order. Within one directory they are deduped case-insensitively so AGENTS.md/agents.md don't double-inject on case-insensitive mounts; across directories they are deduped by resolved path so a symlink (the common AGENTS.md -> CLAUDE.md) loads its target exactly once.

View Source
var AgentGitStatus = func(cwd string) string {
	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
	defer cancel()
	out, err := exec.CommandContext(ctx, "git", "-C", cwd, "status", "--porcelain=v1", "--branch").Output()
	if err != nil {
		return ""
	}
	lines := strings.Split(strings.TrimSpace(string(out)), "\n")
	if len(lines) > 40 {
		lines = append(lines[:40], fmt.Sprintf("… (%d more entries)", len(lines)-40))
	}
	return strings.Join(lines, "\n")
}

AgentGitStatus captures a one-shot git snapshot for the env block. Swappable in tests so prompt assembly stays subprocess-free there.

View Source
var AllSubagentTools = []string{
	"read", "glob", "grep", "ls", "write", "edit", "bash",
	"job_output", "job_kill", "fetch", "todos", "search_tools", "invoke_tool", "web_search",
	"workflow_list", "workflow_get", "workflow_create", "workflow_edit", "workflow_delete", "workflow_copy",
}

AllSubagentTools is the full list of tool names available to subagents.

View Source
var DebugLog = func(format string, args ...any) {}

DebugLog is the engine's debug sink; the TUI points it at its own ASK_DEBUG logger. A no-op by default.

View Source
var ModelBuilder = func(ctx context.Context, p providers.Provider, cfg config.Config, modelID string) (model.LLM, error) {
	if p == nil {
		return nil, errors.New("provider is nil")
	}

	ctx = providers.WithWebSearchAvailable(ctx, config.ResolveBraveAPIKey(cfg.WebSearch) != "")
	llm, err := p.BuildModel(ctx, cfg.ProviderConfig(p.ID()), modelID)
	if err != nil {
		return nil, err
	}
	_, initialDelay, backoff := config.AgentRetryOptions(cfg)
	return newRetryingModel(llm, initialDelay, backoff), nil
}

ModelBuilder builds the ADK LLM for a provider and wraps it in the transient-error retry decorator. Swappable so tests can hand back a scripted model.

Functions

func AgentContextSearchDirs

func AgentContextSearchDirs(cwd string) []string

AgentContextSearchDirs lists the directories searched for project instruction files, in load order: the user-global ~/.claude scope first, then every directory from the project root down to cwd, so general instructions load before the more specific ones that follow. Mirrors RuleSearchScopes / DiscoverSkills / DiscoverSubagents, which already walk to the project root and read the user-global scope.

func AgentsProjectDir

func AgentsProjectDir(cwd string) string

AgentsProjectDir is where ask writes new project-scope agents.

func AgentsUserDir

func AgentsUserDir() string

AgentsUserDir is where ask writes new user-scope agents.

func AppendToolResultText

func AppendToolResultText(resp map[string]any, extra string) map[string]any

AppendToolResultText appends extra to a tool result's human-readable field, so a decorator can add context to a result without knowing which per-tool struct produced it. Falls back to a dedicated "notes" field when the result has no text field of its own.

func AppendTouchedFile

func AppendTouchedFile(files []string, toolName string, input map[string]any) []string

AppendTouchedFile records the file_path of a read/write/edit call, once, up to memoryExtractMaxFiles.

func AsADKTool

func AsADKTool(t Tool) (tool.Tool, error)

AsADKTool passes through an ADK Tool or adapts it to ensure llmagent compatibility.

func AsADKTools

func AsADKTools(tools []Tool) ([]tool.Tool, error)

AsADKTools converts a slice of Tools into ADK Tools.

func BuildInstructionProvider

func BuildInstructionProvider(opts PromptOptions) func(ctx agent.ReadonlyContext) (string, error)

BuildInstructionProvider creates an ADK agent.InstructionProvider that returns the base system prompt combined with any dynamic context or state deltas from ctx.ReadonlyState().

The returned text is deliberately NOT run through instructionutil.InjectSessionState. ask's instruction text is not a template — it is user-authored documentation (CLAUDE.md, .claude/rules, @-linked docs, skill and subagent bodies) inlined verbatim, and ADK's placeholder regex is `{+[^{}]*}+`, so every `{identifier}` in that prose is treated as a session-state lookup. Two ways that goes wrong:

  • `{name}` that happens to match a state key ask sets (system_reminder, step_incomplete, extra_instructions) is silently replaced inside the user's own documentation.
  • `{name?}` resolves to the empty string when the key is absent, so the text is silently deleted with no error to fall back on.

It never bought anything either: ask defines no `{placeholder}` anywhere, and every dynamic value it does need is appended explicitly as a tagged block below. ADK's own llmagent.Config docs make the same point — "if templating logic for {} chars is not desired, then InstructionProvider should be used". Using an InstructionProvider and then re-applying the interpolation by hand defeats that. Any agent built from user-authored text (workflow steps, subagents) must use an InstructionProvider for the same reason; a static llmagent.Config.Instruction is interpolated by ADK and hard-fails the invocation on the first brace.

func BuildSystemPrompt

func BuildSystemPrompt(opts PromptOptions) string

BuildSystemPrompt assembles the full system prompt for one agent session: static coder head, env snapshot, <project_instructions> (CLAUDE.md/AGENTS.md context files), <project_rules> (eager rules), <included_docs> (markdown files @-linked from context files, rules, skills, or subagents — loaded transitively via BFS with cycle-safe dedup), <project_memory>, <available_skills>, <available_agents>, then the shared ask steering prompt (with its worktree pinning clause when args.Cwd is an ask-managed worktree). Called once per session — the result must be reused verbatim on every request so the provider's prefix caching can hit.

func BumpSkillsGeneration

func BumpSkillsGeneration()

BumpSkillsGeneration invalidates every live skill source.

func CloseMemoryExtractor

func CloseMemoryExtractor()

CloseMemoryExtractor stops the process-wide extractor. Call it before closing the memory service so no job races the database going away.

func CloseModel

func CloseModel(m model.LLM) error

CloseModel releases any resources a model.LLM holds — a subprocess-backed provider (Claude Code) forks a child on first use and implements io.Closer to terminate it. Building a model through ModelBuilder wraps it in retryingModel, which forwards Close, so callers close whatever ModelBuilder returned. A no-op for in-process models.

func ContextFileRealPath

func ContextFileRealPath(path string) string

ContextFileRealPath resolves path to its canonical on-disk identity so two names for one file (an AGENTS.md -> CLAUDE.md symlink, a case-insensitive mount) dedupe to a single entry. Falls back to the cleaned path when the file cannot be resolved.

func ContextLinksPromptBlock

func ContextLinksPromptBlock(docs []LoadedContextDoc) string

ContextLinksPromptBlock renders the loaded @-linked docs into a single <included_docs> system-prompt block, sorted by path for deterministic output.

func DefaultPlugins

func DefaultPlugins() []*plugin.Plugin

DefaultPlugins returns the standard set of ADK plugins configured for ask.

functioncallmodifier is deliberately absent. It exists to inject synthetic arguments into tool declarations at request time; ask needed that for the required `description` phrase, which is now a real field on every native tool's params struct, so there is nothing left to inject. It was registered here with a predicate that always returned false ever since PR #132 disabled it to stop it clobbering ParametersJsonSchema — a plugin that could never fire.

Bridge tools (linear_*, workflow_*) still have `description` added to their input schema in pkg/tools/bridge.go, because their input types come from the MCP handler cores and do not carry the field. That is a one-time schema build at construction, not per-request AST surgery.

func DeleteAgent

func DeleteAgent(cwd, name string, scope OriginScope) error

DeleteAgent removes a user/project agent definition.

func DeleteSkill

func DeleteSkill(cwd, name string, scope OriginScope) error

DeleteSkill removes a user/project skill package.

func EncodeProjectDir

func EncodeProjectDir(path string) string

EncodeProjectDir encodes a path into a filesystem-safe directory name.

func EnqueueMemoryTurn

func EnqueueMemoryTurn(turn MemoryTurn) bool

EnqueueMemoryTurn hands a finished turn to the process-wide extractor.

func ExpandBraces

func ExpandBraces(s string) []string

ExpandBraces expands one level of {a,b,c} alternation.

func ExpandSkillInvocation

func ExpandSkillInvocation(cwd, text string) (string, bool)

ExpandSkillInvocation turns a "/skill-name optional args" user line into the full skill invocation message — the user-invocable side of the standard. Returns ok=false when the line is not a known user-invocable skill (the caller sends the text unchanged).

func ExtractContextLinks(body string) []string

ExtractContextLinks finds all @path/to/file.md references in body.

func GitStatusSnapshot

func GitStatusSnapshot(ctx context.Context, cwd string) string

func GlobMatch

func GlobMatch(pattern, rel string) bool

GlobMatch matches a slash-separated relative path against a doublestar pattern.

func IngestWorkflowMemory

func IngestWorkflowMemory(ctx context.Context, sessSvc session.Service, sessionID, cwd string)

IngestWorkflowMemory files a finished workflow run into memory: the run's source as the prompt, the model text it produced as the answer.

func IsConfirmationCall

func IsConfirmationCall(fc *genai.FunctionCall) bool

IsConfirmationCall reports whether a function call is an ADK tool confirmation request.

func MemoryExtractContent

func MemoryExtractContent(turn MemoryTurn, nearest []memory.Concept, topics []string) string

MemoryExtractContent renders the user half of the extraction request.

func MemoryExtractModel

func MemoryExtractModel(cfg config.Config, sessionProvider string) (string, string)

MemoryExtractModel resolves the provider and model for extraction: the config's memory block first, then the session's provider with its cheapest listed model.

func NewRetryAndReflectPlugin

func NewRetryAndReflectPlugin(maxRetries int) (*plugin.Plugin, error)

NewRetryAndReflectPlugin creates an ADK retryandreflect plugin with the specified max retries.

func NewSkillSource

func NewSkillSource(cwd string) skill.Source

NewSkillSource constructs an ADK skill.Source across all discovery roots for cwd plus every enabled plugin. Later roots take precedence (project-overrides-global).

func NewSkillToolset

func NewSkillToolset(ctx context.Context, cwd string) (*skilltoolset.SkillToolset, error)

NewSkillToolset creates an ADK SkillToolset backed by NewSkillSource(cwd).

func NewStandaloneAgentContext

func NewStandaloneAgentContext(ctx context.Context) agent.Context

NewStandaloneAgentContext wraps a context.Context with a compliant agent.Context implementation.

func ParseFrontmatterBytes

func ParseFrontmatterBytes(data []byte) (fields map[string]string, body string, ok bool)

ParseFrontmatterBytes parses frontmatter and body from bytes.

func ParseMarkdownFrontmatter

func ParseMarkdownFrontmatter(path string) (fields map[string]string, body string, ok bool)

ParseMarkdownFrontmatter reads a markdown file with YAML frontmatter and returns the scalar fields plus the body after the closing delimiter.

func ParseMemoryExtraction

func ParseMemoryExtraction(raw string) (memoryExtraction, error)

ParseMemoryExtraction reads the model's JSON reply, tolerating fences and prose around the object.

func ParsePathsField

func ParsePathsField(fm string) []string

func ParseRuleFrontmatter

func ParseRuleFrontmatter(s string) (paths []string, body string)

func RegisterToolFactory

func RegisterToolFactory(factory ToolFactory)

RegisterToolFactory registers the global default tool factory.

func RenderAgentFile

func RenderAgentFile(spec AgentSpec) ([]byte, error)

RenderAgentFile builds an agent definition from a spec.

func RenderSkillFile

func RenderSkillFile(spec SkillSpec) ([]byte, error)

RenderSkillFile builds SKILL.md content from a spec.

func ResolveContextLink(repoRoot, link string) (string, bool)

ResolveContextLink resolves a link (without the @) against repoRoot.

func RulesPromptBlock

func RulesPromptBlock(rules []Rule) string

func RunADKTool

func RunADKTool(ctx agent.Context, t tool.Tool, args any) (map[string]any, error)

RunADKTool executes an ADK tool.

It takes an agent.Context, not a context.Context. That is the whole point: a plain context has to be converted into a fake agent context, and the fake returns nil for Artifacts, Session, State, and ToolConfirmation, and hands out a throwaway Actions — so a tool that escalates or requests confirmation through it is silently ignored.

func SessionPreview

func SessionPreview(messages []Message) string

SessionPreview extracts the first user prompt line from messages.

func SessionPreviewFromEvents

func SessionPreviewFromEvents(events []*session.Event) string

SessionPreviewFromEvents extracts the first user prompt line to serve as a preview.

func SetMemoryExtractor

func SetMemoryExtractor(e *MemoryExtractor)

SetMemoryExtractor replaces the process-wide extractor (tests).

func SkillBody

func SkillBody(s Skill) string

SkillBody returns the instruction body of a discovered skill.

func SkillSearchDirs

func SkillSearchDirs(cwd string) []string

SkillSearchDirs is SkillSearchRoots without the origin tags.

func SkillsProjectDir

func SkillsProjectDir(cwd string) string

SkillsProjectDir is where ask writes new project-scope skills.

func SkillsPromptBlock

func SkillsPromptBlock(skills []Skill) string

SkillsPromptBlock renders the system-prompt trigger list: name + description + location only — the body stays on disk until the model reads it (progressive disclosure).

func SkillsUserDir

func SkillsUserDir() string

SkillsUserDir is where ask writes new user-scope skills.

func StripFencedCodeBlocks

func StripFencedCodeBlocks(s string) string

StripFencedCodeBlocks replaces fenced code block content with spaces, preserving newlines. Handles both ``` and ~~~ fences with optional info strings. Unterminated fences consume the remainder of the input.

func SubagentSearchDirs

func SubagentSearchDirs(cwd string) []string

SubagentSearchDirs is SubagentSearchRoots without the origin tags.

func SubagentToolNames

func SubagentToolNames(def SubagentDef) []string

SubagentToolNames returns the slice of tool names allowed for the subagent.

func SubagentsPromptBlock

func SubagentsPromptBlock(defs []SubagentDef) string

SubagentsPromptBlock lists the named subagents in the system prompt.

func ToolResultText

func ToolResultText(resp map[string]any) (string, bool)

ToolResultText renders an ADK function response as the line a UI shows, and reports whether it represents an error.

Tool results are per-tool structs now, so there is no single "result" key to read. A result carries its own error text in "error"; that is what makes a call render as failed.

func TruncateInstructionDoc

func TruncateInstructionDoc(path, body string, limit int) string

TruncateInstructionDoc bounds body to limit bytes.

Truncation is line-aligned and never splits a UTF-8 rune — the old body[:limit] slice could cut mid-rune and put invalid UTF-8 on the wire. What is dropped is stated rather than implied: the notice names the file and the byte counts so a model that needs the tail can read it, instead of a bare "… (truncated)" that reads like the document simply ended.

func UnquoteYAML

func UnquoteYAML(s string) string

UnquoteYAML strips quotes surrounding a YAML string value, decoding the escapes each quoting style allows.

func UnwrapConfirmationCall

func UnwrapConfirmationCall(fc *genai.FunctionCall) (*genai.FunctionCall, error)

UnwrapConfirmationCall extracts the underlying function call from an ADK confirmation wrapper.

func WorkflowCompileConfig

func WorkflowCompileConfig(e *Engine, cwd string, tabID int, def workflow.Def, src workflow.Source) workflow.WorkflowAgentConfig

WorkflowCompileConfig builds the compile-time wiring for a workflow run: how each step resolves its model and tool surface. Shared by the headless engine and the TUI so both compile identical graphs.

func WorkflowGraphAgent

func WorkflowGraphAgent(name string, compiled *workflow.Compiled) (agent.Agent, error)

WorkflowGraphAgent wraps a compiled workflow so it can be handed to an ADK runner. *workflow.Workflow is not itself an agent.Agent — the interface has an unexported method — but its Run has the agent Run shape, so agent.New adopts it directly.

Types

type ADKRunnable

type ADKRunnable interface {
	Run(ctx agent.Context, args any) (map[string]any, error)
}

ADKRunnable is ADK's executable-tool contract, mirroring the private runnableTool interface in adk/v2/tool/tool.go. Every tool ask builds satisfies it: functiontool.New returns one, and the decorators implement it directly.

type AgentPatch

type AgentPatch struct {
	Description *string
	Prompt      *string
	Provider    *string
	Model       *string
	Tools       *[]string
}

AgentPatch is a partial update; nil fields are left unchanged.

type AgentSpec

type AgentSpec struct {
	Name        string
	Description string
	Prompt      string
	Provider    string
	Model       string
	Tools       []string
}

AgentSpec describes a subagent to create.

type ApprovalRequest

type ApprovalRequest struct {
	ToolName  string         `json:"tool_name"`
	Input     map[string]any `json:"input"`
	ToolUseID string         `json:"tool_use_id,omitempty"`
}

ApprovalRequest asks the user to permit a mutating tool invocation.

type ApprovalResponse

type ApprovalResponse struct {
	Allow    bool            `json:"allow"`
	Remember *PermissionRule `json:"remember,omitempty"`
}

ApprovalResponse contains the user's decision on tool approval.

type AssistantTextEvent

type AssistantTextEvent struct {
	BaseEvent
	Text string `json:"text"`
}

AssistantTextEvent is emitted when a complete text block from the assistant finishes.

func (AssistantTextEvent) Kind

type BaseEvent

type BaseEvent struct {
	TabID int `json:"tab_id"`
}

BaseEvent provides the common tabID tracking.

func (BaseEvent) GetTabID

func (b BaseEvent) GetTabID() int

type BgTaskEndedEvent

type BgTaskEndedEvent struct {
	BaseEvent
	JobID    string `json:"job_id"`
	ExitCode int    `json:"exit_code"`
}

BgTaskEndedEvent is emitted when a background job completes.

func (BgTaskEndedEvent) Kind

func (BgTaskEndedEvent) Kind() EventKind

type BgTaskStartedEvent

type BgTaskStartedEvent struct {
	BaseEvent
	JobID       string `json:"job_id"`
	Description string `json:"description"`
}

BgTaskStartedEvent is emitted when a background job starts.

func (BgTaskStartedEvent) Kind

type ContextAwareTool

type ContextAwareTool struct {
	Inner Tool
	// contains filtered or unexported fields
}

func (*ContextAwareTool) Declaration

func (ct *ContextAwareTool) Declaration() *genai.FunctionDeclaration

func (*ContextAwareTool) Description

func (ct *ContextAwareTool) Description() string

func (*ContextAwareTool) Info

func (ct *ContextAwareTool) Info() ToolInfo

func (*ContextAwareTool) IsLongRunning

func (ct *ContextAwareTool) IsLongRunning() bool

func (*ContextAwareTool) Name

func (ct *ContextAwareTool) Name() string

func (*ContextAwareTool) ProcessRequest

func (ct *ContextAwareTool) ProcessRequest(ctx agent.Context, req *model.LLMRequest) error

func (*ContextAwareTool) RelPath

func (ct *ContextAwareTool) RelPath(p string) string

func (*ContextAwareTool) Run

func (ct *ContextAwareTool) Run(ctx agent.Context, args any) (map[string]any, error)

type ContextScope

type ContextScope struct {
	Dir  string
	Root string
}

ContextScope is one directory searched for instruction files, paired with the root its @-links resolve against.

The two differ for the user-global scope: ~/.claude/CLAUDE.md is not inside the project, so an @-link in it must resolve under ~/.claude, not under the repository that happens to be open.

func AgentContextScopes

func AgentContextScopes(cwd string) []ContextScope

AgentContextScopes is AgentContextSearchDirs with each directory's @-link resolution root attached.

type Coordinator

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

Coordinator manages the background execution of all in-process agent sessions without coupling to Bubble Tea or UI models.

func NewCoordinator

func NewCoordinator(interaction InteractionHandler, listener EventListener) *Coordinator

func (*Coordinator) CancelWorkflow

func (c *Coordinator) CancelWorkflow(tabID int)

func (*Coordinator) Dispatch

func (c *Coordinator) Dispatch(tabID int, text string) error

func (*Coordinator) GetSession

func (c *Coordinator) GetSession(tabID int) *Session

func (*Coordinator) HasSession

func (c *Coordinator) HasSession(tabID int) bool

func (*Coordinator) InterruptSession

func (c *Coordinator) InterruptSession(tabID int) bool

func (*Coordinator) IsBusy

func (c *Coordinator) IsBusy(tabID int) bool

func (*Coordinator) RemoveSession

func (c *Coordinator) RemoveSession(tabID int)

func (*Coordinator) SetSession

func (c *Coordinator) SetSession(tabID int, s *Session)

type CostEvent

type CostEvent struct {
	BaseEvent
	CostUSD float64 `json:"cost_usd"`
}

CostEvent records estimated monetary cost for an API step.

func (CostEvent) Kind

func (CostEvent) Kind() EventKind

type DoneEvent

type DoneEvent struct {
	BaseEvent
	Result ResultSummary `json:"result"`
	Error  error         `json:"error,omitempty"`
}

DoneEvent is emitted when a turn completes.

func (DoneEvent) Kind

func (DoneEvent) Kind() EventKind

type Engine

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

Engine is the central, headless Ask engine that can be embedded into any Go application.

func New

func New(opts Options) *Engine

New creates a new instance of the Ask Engine.

func (*Engine) CompileWorkflow

func (e *Engine) CompileWorkflow(ctx context.Context, cwd string, tabID int, def workflow.Def, src workflow.Source) (*workflow.Compiled, error)

CompileWorkflow compiles a workflow definition into an executable ADK graph using the engine's model and tool wiring.

func (*Engine) Coordinator

func (e *Engine) Coordinator() *Coordinator

func (*Engine) Interaction

func (e *Engine) Interaction() InteractionHandler

func (*Engine) Run

func (e *Engine) Run(ctx context.Context, opts RunOptions) (*RunResult, error)

Run executes an ask agent turn on the Engine instance.

func (*Engine) RunWorkflow

func (e *Engine) RunWorkflow(ctx context.Context, cwd string, tabID int, def workflow.Def, src workflow.Source) error

RunWorkflow compiles def and drives it to completion on ADK's workflow scheduler, translating the event stream into both agent events (tool calls, text) and workflow progress callbacks.

func (*Engine) SystemPrompt

func (e *Engine) SystemPrompt(cwd string, inWorkflow bool) string

type EngineEvent

type EngineEvent interface {
	Kind() EventKind
	GetTabID() int
}

EngineEvent is the common interface implemented by all events emitted by the ask engine.

type EventKind

type EventKind string

EventKind identifies the type of an EngineEvent.

const (
	EventKindTextDelta       EventKind = "text_delta"
	EventKindAssistantText   EventKind = "assistant_text"
	EventKindStatus          EventKind = "status"
	EventKindToolCall        EventKind = "tool_call"
	EventKindToolResult      EventKind = "tool_result"
	EventKindToolDiff        EventKind = "tool_diff"
	EventKindUsage           EventKind = "usage"
	EventKindCost            EventKind = "cost"
	EventKindModelInfo       EventKind = "model_info"
	EventKindTodoUpdate      EventKind = "todo_update"
	EventKindSubagentStarted EventKind = "subagent_started"
	EventKindSubagentEnded   EventKind = "subagent_ended"
	EventKindBgTaskStarted   EventKind = "bg_task_started"
	EventKindBgTaskEnded     EventKind = "bg_task_ended"
	EventKindDone            EventKind = "done"
	EventKindExited          EventKind = "exited"
	EventKindTurnComplete    EventKind = "turn_complete"
	EventKindMidTurnDrained  EventKind = "mid_turn_drained"
	EventKindWorkflowStarted EventKind = "workflow_started"
	EventKindWorkflowStep    EventKind = "workflow_step"
	EventKindWorkflowDone    EventKind = "workflow_done"
	EventKindWorkflowFailed  EventKind = "workflow_failed"
	EventKindExtensions      EventKind = "extensions_changed"
	EventKindMCPStatus       EventKind = "mcp_status_changed"
)

type EventListener

type EventListener func(event EngineEvent)

EventListener is a callback function that handles stream events from the engine.

type ExitedEvent

type ExitedEvent struct {
	BaseEvent
}

ExitedEvent is emitted when the session goroutine closes.

func (ExitedEvent) Kind

func (ExitedEvent) Kind() EventKind

type ExtensionsChangedEvent

type ExtensionsChangedEvent struct {
	BaseEvent
	What string `json:"what"`
}

ExtensionsChangedEvent is emitted when a tool creates, edits, deletes, installs, or publishes a skill, agent, plugin, or marketplace, so the UI re-registers slash commands and refreshes the browser.

func (ExtensionsChangedEvent) Kind

type FilePart

type FilePart struct {
	Path     string `json:"path,omitempty"`
	MIMEType string `json:"mime_type,omitempty"`
	Data     []byte `json:"data,omitempty"`
}

FilePart represents a binary file/image attachment.

type FileSessionService

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

FileSessionService implements google.golang.org/adk/v2/session.Service backed by atomic JSON files under ~/.config/ask/agent-sessions/<provider>/<encoded-cwd>/<sessionID>.json.

func NewFileSessionService

func NewFileSessionService(provider, cwd string) *FileSessionService

NewFileSessionService creates a new FileSessionService for the given provider and working directory.

func NewFileSessionServiceWithBaseDir

func NewFileSessionServiceWithBaseDir(provider, cwd, baseDir string) *FileSessionService

NewFileSessionServiceWithBaseDir creates a FileSessionService with a custom base directory (useful for testing).

func (*FileSessionService) AppendEvent

func (s *FileSessionService) AppendEvent(ctx context.Context, curSession session.Session, event *session.Event) error

AppendEvent appends a non-partial event, updates state deltas, and saves to disk atomically.

func (*FileSessionService) Create

Create initializes a new session and persists it atomically to disk.

func (*FileSessionService) Delete

Delete removes a session file from disk.

func (*FileSessionService) DirFor

func (s *FileSessionService) DirFor(cwd string) (string, error)

DirFor returns the directory path for sessions stored for a specific working directory.

func (*FileSessionService) Get

Get loads a session from disk, applying optional timestamp and event count filters.

func (*FileSessionService) List

List enumerates sessions, filtered by AppName and UserID (if provided).

func (*FileSessionService) PathFor

func (s *FileSessionService) PathFor(id string) (string, error)

PathFor locates an existing session file by ID across all project directories.

func (*FileSessionService) Root

func (s *FileSessionService) Root() (string, error)

Root returns the base directory for stored sessions for this provider.

type GenerateStreamFunc

type GenerateStreamFunc func(ctx context.Context, client *genai.Client, model string, contents []*genai.Content, config *genai.GenerateContentConfig) iter.Seq2[*genai.GenerateContentResponse, error]

GenerateStreamFunc defines the signature for streaming content generation from GenAI.

var GenerateStream GenerateStreamFunc = func(ctx context.Context, client *genai.Client, model string, contents []*genai.Content, config *genai.GenerateContentConfig) iter.Seq2[*genai.GenerateContentResponse, error] {
	if client == nil {
		return func(yield func(*genai.GenerateContentResponse, error) bool) {
			yield(nil, errors.New("genai client is nil"))
		}
	}
	return client.Models.GenerateContentStream(ctx, model, contents, config)
}

GenerateStream is the streaming generation hook, swappable in tests.

type HeadlessInteractionHandler

type HeadlessInteractionHandler struct {
	AutoApproveTools bool
}

HeadlessInteractionHandler is a default InteractionHandler for automated runs.

func (HeadlessInteractionHandler) AskQuestion

func (h HeadlessInteractionHandler) AskQuestion(ctx context.Context, tabID int, questions []Question) (QuestionResponse, error)

func (HeadlessInteractionHandler) ConfirmPlan

func (h HeadlessInteractionHandler) ConfirmPlan(ctx context.Context, tabID int, req PlanRequest) (PlanResponse, error)

func (HeadlessInteractionHandler) RequestApproval

func (h HeadlessInteractionHandler) RequestApproval(ctx context.Context, tabID int, req ApprovalRequest) (ApprovalResponse, error)

func (HeadlessInteractionHandler) RequestSudoPassword

func (h HeadlessInteractionHandler) RequestSudoPassword(ctx context.Context, tabID int, prompt string) (SudoPasswordResponse, error)

type InteractionHandler

type InteractionHandler interface {
	AskQuestion(ctx context.Context, tabID int, questions []Question) (QuestionResponse, error)
	RequestApproval(ctx context.Context, tabID int, req ApprovalRequest) (ApprovalResponse, error)
	ConfirmPlan(ctx context.Context, tabID int, req PlanRequest) (PlanResponse, error)
	RequestSudoPassword(ctx context.Context, tabID int, prompt string) (SudoPasswordResponse, error)
}

InteractionHandler is implemented by user interfaces (TUI, Web UI, CLI, headless agents) to respond to interactive requests from the engine and tools.

type LoadedContextDoc

type LoadedContextDoc struct {
	Path string
	// Body is what goes into the prompt, capped by
	// TruncateInstructionDoc.
	Body string
	// Root is the directory this document's @-links resolve against —
	// its own scope, not necessarily the project root.
	Root string
	// Links are the @-references found in the document's FULL body,
	// before any truncation. Body alone is not a safe source for them:
	// a link past the cap is still a real dependency of the
	// instructions and must still be followed.
	Links []string
}

LoadedContextDoc is one document that has been loaded for inclusion in the system prompt — either a project instruction file or an @-linked document resolved during prompt assembly.

func AgentContextFiles

func AgentContextFiles(cwd string) []LoadedContextDoc

AgentContextFiles loads the project's instruction files (CLAUDE.md, AGENTS.md, …) from every directory in AgentContextSearchDirs — the user-global ~/.claude scope and the project-root-to-cwd chain — so running ask from a subdirectory still sees the project's instructions. Each distinct file is loaded once: a symlinked AGENTS.md contributes its target's body a single time rather than duplicating it. @-link references within these files are resolved separately by LoadContextLinks during BuildSystemPrompt and placed in a dedicated <included_docs> block — they are not part of this function's return.

func LoadContextLinks(repoRoot string, sourceBodies []string) []LoadedContextDoc

LoadContextLinks walks the @-link graph breadth-first starting from the links in sourceBodies. Callers that already hold a document's untruncated link list should prefer LoadContextLinksFrom — passing a capped Body here would lose every link past the cap.

func LoadContextLinksFrom

func LoadContextLinksFrom(repoRoot string, links []string) []LoadedContextDoc

LoadContextLinksFrom walks the @-link graph breadth-first from an explicit seed list: it resolves each link against repoRoot, loads the file, and repeats for the links found inside it. Each loaded file is scanned for further links BEFORE it is truncated, so a deep @-chain survives a large intermediate document.

func RuleLinkedDocs

func RuleLinkedDocs(repoRoot, body string) []LoadedContextDoc

RuleLinkedDocs resolves @-links found in a rule body (JIT rules, skills, subagents) and returns the loaded documents.

type MCPStatusChangedEvent

type MCPStatusChangedEvent struct {
	BaseEvent
}

MCPStatusChangedEvent is emitted when a session's set of MCP servers changes connection/auth state, so the UI can refresh an open browser.

func (MCPStatusChangedEvent) Kind

type MemoryExtractor

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

MemoryExtractor is the background worker that turns finished turns into concepts: one goroutine, a bounded queue that drops the oldest job when full, and a context that Close cancels.

func EnsureMemoryExtractor

func EnsureMemoryExtractor() *MemoryExtractor

EnsureMemoryExtractor returns the process-wide extractor, starting it (and registering it with the memory service) the first time memory is open. Nil while memory is closed.

func NewMemoryExtractor

func NewMemoryExtractor(opts MemoryExtractorOptions) *MemoryExtractor

NewMemoryExtractor starts the worker.

func (*MemoryExtractor) Close

func (e *MemoryExtractor) Close()

Close stops accepting turns, cancels the job in flight, and waits for the worker to exit. Queued jobs that never ran are released.

func (*MemoryExtractor) Drain

func (e *MemoryExtractor) Drain(ctx context.Context) error

Drain blocks until every queued job has finished or ctx ends.

func (*MemoryExtractor) Dropped

func (e *MemoryExtractor) Dropped() int

Dropped reports how many queued turns were discarded to make room.

func (*MemoryExtractor) Enqueue

func (e *MemoryExtractor) Enqueue(rec memory.TurnRecord) bool

Enqueue adapts a memory.TurnRecord (ADK's AddSessionToMemory path).

func (*MemoryExtractor) EnqueueTurn

func (e *MemoryExtractor) EnqueueTurn(turn MemoryTurn) bool

EnqueueTurn queues a turn. It reports false when the extractor is closed or the turn has nothing to extract from. A full queue drops its oldest job so a stalled provider cannot pile up work.

type MemoryExtractorOptions

type MemoryExtractorOptions struct {
	// LoadConfig supplies the config each job reads; nil means config.Load.
	LoadConfig func() (config.Config, error)
	QueueSize  int
	Timeout    time.Duration
}

MemoryExtractorOptions configures a MemoryExtractor.

type MemoryTurn

type MemoryTurn struct {
	Cwd      string
	Prompt   string
	Response string
	// Topic is the tab's current topic, offered to the model as the
	// default.
	Topic string
	// Files are the paths the turn read or edited.
	Files []string
	// Provider is the session's provider, used when the config names
	// none for memory.
	Provider string
	// OnUsage reports the extraction call's own token usage.
	OnUsage func(providerID, modelID string, inputTokens, outputTokens int)
	// OnTopic receives the topic the model settled on for the turn.
	OnTopic func(topic string)
}

MemoryTurn is one finished turn queued for concept extraction.

type Message

type Message struct {
	Role        MessageRole      `json:"role"`
	Text        string           `json:"text,omitempty"`
	Thoughts    []ThoughtPart    `json:"thoughts,omitempty"`
	Files       []FilePart       `json:"files,omitempty"`
	ToolCalls   []ToolCallPart   `json:"tool_calls,omitempty"`
	ToolResults []ToolResultPart `json:"tool_results,omitempty"`
}

Message is the native ask message structure.

func MessageFromGenAIContent

func MessageFromGenAIContent(c *genai.Content) Message

func MessagesFromEvents

func MessagesFromEvents(events []*session.Event) []Message

MessagesFromEvents converts ADK session.Event slices to native ask Message slices.

func NewAssistantMessage

func NewAssistantMessage(text string, thoughts []ThoughtPart, toolCalls []ToolCallPart) Message

func NewToolResultMessage

func NewToolResultMessage(toolResults ...ToolResultPart) Message

func NewUserMessage

func NewUserMessage(text string, files ...FilePart) Message

func (Message) ToGenAIContent

func (m Message) ToGenAIContent() *genai.Content

func (*Message) UnmarshalJSON

func (m *Message) UnmarshalJSON(data []byte) error

type MessageRole

type MessageRole string

MessageRole defines the role of a message participant.

const (
	RoleUser      MessageRole = "user"
	RoleAssistant MessageRole = "assistant"
	RoleModel     MessageRole = "model"
	RoleSystem    MessageRole = "system"
	RoleTool      MessageRole = "tool"
)

type MidTurnDrainedEvent

type MidTurnDrainedEvent struct {
	BaseEvent
	Text string `json:"text"`
}

MidTurnDrainedEvent is emitted when a queued mid-turn message is drained into the session.

func (MidTurnDrainedEvent) Kind

type MidTurnQueue

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

MidTurnQueue is a thread-safe FIFO queue for pending user messages mid-turn.

func (*MidTurnQueue) Drain

func (q *MidTurnQueue) Drain() []string

func (*MidTurnQueue) Push

func (q *MidTurnQueue) Push(text string)

type ModelInfoEvent

type ModelInfoEvent struct {
	BaseEvent
	Model string `json:"model"`
}

ModelInfoEvent communicates the resolved model ID for the active session.

func (ModelInfoEvent) Kind

func (ModelInfoEvent) Kind() EventKind

type Options

type Options struct {
	Config             config.Config
	InteractionHandler InteractionHandler
	EventListener      EventListener
}

Options holds configuration and handlers for the ask Engine.

type Origin

type Origin struct {
	Scope  OriginScope
	Plugin string
}

Origin is the provenance of a discovered skill or agent: a user or project directory, or an installed plugin ("name@marketplace").

func (Origin) Editable

func (o Origin) Editable() bool

Editable reports whether the definition can be changed in place — plugin copies are replaced on update, never edited.

func (Origin) String

func (o Origin) String() string

type OriginScope

type OriginScope string

OriginScope says where a skill or agent definition lives.

const (
	OriginUser    OriginScope = "user"
	OriginProject OriginScope = "project"
	OriginPlugin  OriginScope = "plugin"
)

func NormalizeOriginScope

func NormalizeOriginScope(s string) (OriginScope, error)

NormalizeOriginScope maps "" to project and validates writable scopes.

type PermissionRule

type PermissionRule struct {
	ToolName    string `json:"tool_name"`
	RuleContent string `json:"rule_content"`
}

PermissionRule identifies a scoped permission grant.

type PlanRequest

type PlanRequest struct {
	Plan            string `json:"plan"`
	Explanation     string `json:"explanation"`
	DefaultWorkflow string `json:"default_workflow,omitempty"`
}

PlanRequest asks the user to confirm a finalized plan.

type PlanResponse

type PlanResponse struct {
	Headless      bool     `json:"headless,omitempty"`
	Cancelled     bool     `json:"cancelled,omitempty"`
	TalkMore      bool     `json:"talk_more,omitempty"`
	ExecuteInline bool     `json:"execute_inline,omitempty"`
	WorkflowName  string   `json:"workflow_name,omitempty"`
	WorkflowDone  bool     `json:"workflow_done,omitempty"`
	FailedReason  string   `json:"failed_reason,omitempty"`
	Outcome       string   `json:"outcome,omitempty"`
	Artifacts     []string `json:"artifacts,omitempty"`
	Source        any      `json:"-"`
}

PlanResponse contains the user's choice after reviewing a plan.

type PromptOptions

type PromptOptions struct {
	Cwd                 string
	InWorkflow          bool
	GitStatusFn         func(string) string
	DisableSkillsPrompt bool
	SystemPrompt        string
}

type Question

type Question struct {
	Kind        string           `json:"kind"` // "pick_one", "pick_many", "pick_diagram"
	Prompt      string           `json:"prompt"`
	Options     []QuestionOption `json:"options"`
	AllowCustom bool             `json:"allow_custom,omitempty"`
}

Question represents a structured prompt for the user.

type QuestionAnswer

type QuestionAnswer struct {
	Picks  []string `json:"picks"`
	Custom string   `json:"custom,omitempty"`
	Note   string   `json:"note,omitempty"`
}

QuestionAnswer contains the user's response to a single question.

type QuestionOption

type QuestionOption struct {
	Label   string `json:"label"`
	Diagram string `json:"diagram,omitempty"`
}

QuestionOption represents a single choice in a question modal.

type QuestionResponse

type QuestionResponse struct {
	Answers   []QuestionAnswer `json:"answers"`
	Cancelled bool             `json:"cancelled,omitempty"`
	Headless  bool             `json:"headless,omitempty"`
}

QuestionResponse is the full result from answering one or more questions.

type ResultSummary

type ResultSummary struct {
	SessionID string `json:"session_id"`
	Result    string `json:"result"`
	IsError   bool   `json:"is_error"`
}

ResultSummary contains the outcome of a provider turn.

type Rule

type Rule struct {
	// Path is the absolute path to the rule file.
	Path string
	// Rel is the rule file's label in prompts.
	Rel string
	// Paths is the compiled glob list from `paths` frontmatter. Empty means eager.
	Paths []string
	// Body is the markdown instruction text, capped by
	// TruncateInstructionDoc.
	Body string
	// Links are the @-references found in the rule's FULL body, before
	// truncation — see LoadedContextDoc.Links.
	Links []string
	// Root is the directory this rule's @-links resolve against: its own
	// scope root, so a user-global rule links within ~/.claude rather
	// than into whichever repository is open.
	Root string
}

Rule is one parsed .claude/rules/*.md file.

func DiscoverRules

func DiscoverRules(cwd string) []Rule

func ParseRuleFile

func ParseRuleFile(path string, scope RuleScope) (Rule, bool)

func (Rule) Eager

func (r Rule) Eager() bool

func (Rule) Matches

func (r Rule) Matches(rel string) bool

type RuleScope

type RuleScope struct {
	Root string
	Dir  string
}

func RuleSearchScopes

func RuleSearchScopes(cwd string) []RuleScope

type RunOptions

type RunOptions struct {
	// Prompt is the user query, instruction, or task description.
	Prompt string `json:"prompt"`

	// SessionID is the unique session identifier. If empty, a new session is created.
	// If provided, prior conversation turns and tool calls are loaded from disk.
	SessionID string `json:"session_id,omitempty"`

	// Cwd is the target working directory. Defaults to os.Getwd().
	Cwd string `json:"cwd,omitempty"`

	// Config optionally overrides default configuration (~/.config/ask/ask.json).
	Config config.Config `json:"config,omitempty"`

	// Provider optionally overrides the LLM provider (e.g. "vertex"); empty
	// means Config.Provider, then the first registered provider.
	Provider string `json:"provider,omitempty"`

	// Model optionally overrides the default model for the selected provider.
	Model string `json:"model,omitempty"`

	// Effort optionally sets reasoning/thinking effort level.
	Effort string `json:"effort,omitempty"`

	// Files provides optional image/media attachments for models with vision.
	Files []FilePart `json:"files,omitempty"`

	// Tools optionally overrides or augments the default toolset.
	Tools []Tool `json:"-"`

	// EventListener receives real-time streaming deltas, tool calls, and lifecycle events.
	EventListener EventListener `json:"-"`

	// InteractionHandler manages tool approval prompts and user questions.
	// Defaults to HeadlessInteractionHandler{AutoApproveTools: true}.
	InteractionHandler InteractionHandler `json:"-"`

	// SkipAllPermissions bypasses confirmation prompts for all tools.
	SkipAllPermissions bool `json:"skip_all_permissions,omitempty"`

	// SkipMemory leaves the finished turn out of concept extraction. Set
	// for sub-agent runs, whose turns are not the user's conversation.
	SkipMemory bool `json:"skip_memory,omitempty"`
}

RunOptions defines the input parameters for executing an ask agent turn.

type RunResult

type RunResult struct {
	// SessionID is the session identifier used for this turn (persisted on disk).
	SessionID string `json:"session_id"`

	// Response is the final assistant text output.
	Response string `json:"response"`

	// Messages contains the complete message history up to this point.
	Messages []Message `json:"messages"`

	// IsError indicates whether the turn failed.
	IsError bool `json:"is_error"`

	// Error contains the failure error, if any.
	Error error `json:"error,omitempty"`
}

RunResult contains the outcome of the agent turn.

func Run

func Run(ctx context.Context, opts RunOptions) (*RunResult, error)

Run executes an ask agent turn with the provided options using default engine settings.

type RunnerBuilderFunc

type RunnerBuilderFunc func(agentInstance agent.Agent, sessSvc session.Service) (*runner.Runner, error)

RunnerBuilder allows customizing or mocking the ADK runner in tests.

var RunnerBuilder RunnerBuilderFunc = func(agentInstance agent.Agent, sessSvc session.Service) (*runner.Runner, error) {
	var memSvc adkmemory.Service
	if pkgMem := pkgmemory.Default(); pkgMem != nil && pkgMem.IsOpen() {
		memSvc = pkgMem
	} else {
		memSvc = adkmemory.InMemoryService()
	}
	return runner.New(runner.Config{
		AppName:           "ask",
		Agent:             agentInstance,
		SessionService:    sessSvc,
		AutoCreateSession: true,
		MemoryService:     memSvc,
		ArtifactService:   artifact.InMemoryService(),
		PluginConfig: runner.PluginConfig{
			Plugins: DefaultPlugins(),
		},
	})
}

type Session

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

func NewSession

func NewSession(args SessionArgs, llm model.LLM, system string, tools []Tool, listener EventListener, interaction InteractionHandler) *Session

func (*Session) Close

func (s *Session) Close()

func (*Session) Emit

func (s *Session) Emit(event EngineEvent)

func (*Session) InterruptTurn

func (s *Session) InterruptTurn() bool

func (*Session) IsBusy

func (s *Session) IsBusy() bool

func (*Session) LastResponse

func (s *Session) LastResponse() string

func (*Session) Messages

func (s *Session) Messages() []Message

func (*Session) QueueMidTurn

func (s *Session) QueueMidTurn(text string)

func (*Session) QueueTurn

func (s *Session) QueueTurn(text string, files ...[]FilePart) error

func (*Session) QueueTurnSync

func (s *Session) QueueTurnSync(ctx context.Context, text string, files ...[]FilePart) error

QueueTurnSync sends a turn to the session and blocks until turn execution is completed.

func (*Session) SessionID

func (s *Session) SessionID() string

type SessionArgs

type SessionArgs struct {
	TabID              int
	Cwd                string
	Provider           string
	Model              string
	Effort             string
	InWorkflow         bool
	SkipAllPermissions bool
	SessionID          string
}

type SessionStore

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

SessionStore provides backwards compatibility adapter for existing callers.

func NewSessionStore

func NewSessionStore(provider string) *SessionStore

NewSessionStore creates a new SessionStore adapter wrapping FileSessionService.

func (*SessionStore) Delete

func (st *SessionStore) Delete(id string) error

func (*SessionStore) DirFor

func (st *SessionStore) DirFor(cwd string) (string, error)

func (*SessionStore) List

func (st *SessionStore) List(cwd string) ([]SessionSummary, error)

func (*SessionStore) Load

func (st *SessionStore) Load(id string) (StoredSessionFile, error)

func (*SessionStore) PathFor

func (st *SessionStore) PathFor(id string) (string, error)

func (*SessionStore) Root

func (st *SessionStore) Root() (string, error)

func (*SessionStore) Save

func (st *SessionStore) Save(id, cwd string, messages []Message) error

func (*SessionStore) SaveEvents

func (st *SessionStore) SaveEvents(id, cwd string, events []*session.Event) error

type SessionSummary

type SessionSummary struct {
	ID      string    `json:"id"`
	Cwd     string    `json:"cwd"`
	Preview string    `json:"preview"`
	ModTime time.Time `json:"modTime"`
}

SessionSummary summarizes a stored session for listing.

type Skill

type Skill struct {
	// Name is the slash/invocation name; plugin skills carry the
	// "plugin:" prefix the way Claude Code namespaces them.
	Name        string
	BareName    string
	Description string
	// Dir is the skill package directory; Path is its SKILL.md (or the
	// command file).
	Dir  string
	Path string
	// UserInvocable surfaces the skill as a /name slash command
	// (default true; `user-invocable: false` hides it).
	UserInvocable bool
	// DisableModelInvocation removes the skill from the system-prompt
	// trigger list — the user can still invoke it explicitly.
	DisableModelInvocation bool
	// Frontmatter holds the parsed ADK frontmatter when available.
	Frontmatter *skill.Frontmatter
	Origin      Origin
	Command     bool
}

Skill is one discovered skill: a SKILL.md package, or a single-file command (Claude Code's commands/*.md) loaded with the same contract.

func CreateSkill

func CreateSkill(cwd string, scope OriginScope, spec SkillSpec) (Skill, error)

CreateSkill writes a new skill package into scope.

func DiscoverSkills

func DiscoverSkills(cwd string) []Skill

DiscoverSkills walks every root and enabled plugin for skill packages. Invalid packages (bad name, missing description) are skipped rather than failing the session.

func FindSkill

func FindSkill(cwd, name string) (Skill, bool)

FindSkill returns the discovered skill with name.

func ResolveEditableSkill

func ResolveEditableSkill(cwd, name string, scope OriginScope) (Skill, error)

ResolveEditableSkill finds the one user/project skill called name. With scope "" the name must be unambiguous.

func UpdateSkill

func UpdateSkill(cwd, name string, scope OriginScope, patch SkillPatch) (Skill, error)

UpdateSkill applies patch to an existing user/project skill in place, keeping frontmatter keys it does not model.

type SkillPatch

type SkillPatch struct {
	Description            *string
	Body                   *string
	UserInvocable          *bool
	DisableModelInvocation *bool
}

SkillPatch is a partial update; nil fields are left unchanged.

type SkillRoot

type SkillRoot struct {
	Dir    string
	Origin Origin
}

SkillRoot is one directory of skill packages plus the origin its skills are tagged with.

func SkillSearchRoots

func SkillSearchRoots(cwd string) []SkillRoot

SkillSearchRoots returns the user and project discovery roots in precedence order — later roots win on a name clash, so project skills override user-global ones. Plugin skills are namespaced and never clash; they come from plugin.EnabledPlugins.

type SkillSpec

type SkillSpec struct {
	Name                   string
	Description            string
	Body                   string
	UserInvocable          *bool
	DisableModelInvocation *bool
	License                string
	Compatibility          string
}

SkillSpec describes a skill to create.

type StatusEvent

type StatusEvent struct {
	BaseEvent
	Status string `json:"status"`
}

StatusEvent indicates the agent's current state (e.g. "thinking…", "running tool…").

func (StatusEvent) Kind

func (StatusEvent) Kind() EventKind

type StoredSessionFile

type StoredSessionFile struct {
	Version   int              `json:"version"`
	AppName   string           `json:"appName"`
	UserID    string           `json:"userID"`
	SessionID string           `json:"sessionID"`
	Cwd       string           `json:"cwd"`
	CreatedAt time.Time        `json:"createdAt"`
	UpdatedAt time.Time        `json:"updatedAt"`
	State     map[string]any   `json:"state,omitempty"`
	Events    []*session.Event `json:"events"`
}

StoredSessionFile represents the serialized on-disk format for a session transcript and state.

func ReadStoredSessionFile

func ReadStoredSessionFile(path string) (StoredSessionFile, error)

ReadStoredSessionFile reads and parses a StoredSessionFile from disk.

func (StoredSessionFile) Messages

func (f StoredSessionFile) Messages() []Message

Messages converts stored Events into native ask Message slices.

type SubagentDef

type SubagentDef struct {
	// Name is the task-tool name; plugin agents carry the "plugin:"
	// prefix the way Claude Code namespaces them.
	Name        string
	BareName    string
	Description string
	Provider    string
	Model       string
	Tools       []string
	Prompt      string
	Source      string
	Origin      Origin
}

SubagentDef is a named subagent definition.

func CreateAgent

func CreateAgent(cwd string, scope OriginScope, spec AgentSpec) (SubagentDef, error)

CreateAgent writes a new agent definition into scope.

func DiscoverSubagents

func DiscoverSubagents(cwd string) []SubagentDef

DiscoverSubagents reads every *.md definition; later roots win on a name clash.

func FindSubagent

func FindSubagent(cwd, name string) (SubagentDef, bool)

FindSubagent returns the discovered agent with name.

func ResolveEditableAgent

func ResolveEditableAgent(cwd, name string, scope OriginScope) (SubagentDef, error)

ResolveEditableAgent finds the one user/project agent called name.

func UpdateAgent

func UpdateAgent(cwd, name string, scope OriginScope, patch AgentPatch) (SubagentDef, error)

UpdateAgent applies patch to an existing user/project agent in place.

type SubagentEndedEvent

type SubagentEndedEvent struct {
	BaseEvent
	SubagentID string `json:"subagent_id"`
	IsError    bool   `json:"is_error,omitempty"`
}

SubagentEndedEvent is emitted when a subagent completes execution.

func (SubagentEndedEvent) Kind

type SubagentRoot

type SubagentRoot struct {
	Dir    string
	Origin Origin
}

SubagentRoot is one directory of agent definitions plus the origin its agents are tagged with.

func SubagentSearchRoots

func SubagentSearchRoots(cwd string) []SubagentRoot

SubagentSearchRoots returns the user and project roots in precedence order (later wins). Plugin agents are namespaced and come from plugin.EnabledPlugins.

type SubagentStartedEvent

type SubagentStartedEvent struct {
	BaseEvent
	SubagentID  string `json:"subagent_id"`
	AgentType   string `json:"agent_type"`
	Description string `json:"description"`
	Background  bool   `json:"background,omitempty"`
}

SubagentStartedEvent is emitted when a subagent starts execution.

func (SubagentStartedEvent) Kind

type SudoPasswordResponse

type SudoPasswordResponse struct {
	Password  string `json:"password"`
	Cancelled bool   `json:"cancelled"`
}

SudoPasswordResponse contains the user's input for a sudo prompt.

type TextDeltaEvent

type TextDeltaEvent struct {
	BaseEvent
	Delta string `json:"delta"`
}

TextDeltaEvent is emitted as assistant text streams token-by-token.

func (TextDeltaEvent) Kind

func (TextDeltaEvent) Kind() EventKind

type ThoughtPart

type ThoughtPart struct {
	Text      string `json:"text"`
	Signature []byte `json:"signature,omitempty"`
}

ThoughtPart represents a model's reasoning/thought chunk with signature.

type TodoItem

type TodoItem struct {
	Status     string `json:"status"`
	Content    string `json:"content"`
	ActiveForm string `json:"active_form,omitempty"`
}

TodoItem represents a single item in the task list.

type TodoUpdateEvent

type TodoUpdateEvent struct {
	BaseEvent
	Todos []TodoItem `json:"todos"`
}

TodoUpdateEvent is emitted when the session's todo list is updated.

func (TodoUpdateEvent) Kind

func (TodoUpdateEvent) Kind() EventKind

type Tool

type Tool = tool.Tool

Tool represents an executable GenAI/ADK tool, aliasing ADK's native tool.Tool.

func SubagentTools

func SubagentTools(def SubagentDef, available map[string]Tool) []Tool

SubagentTools filters the provided tools map by the subagent's allowed tool names.

func WrapContextAwareTools

func WrapContextAwareTools(tools []Tool, cwd string, rules []Rule) []Tool

type ToolCallEvent

type ToolCallEvent struct {
	BaseEvent
	ToolUseID  string         `json:"tool_use_id"`
	ToolName   string         `json:"tool_name"`
	Input      map[string]any `json:"input"`
	Background bool           `json:"background,omitempty"`
}

ToolCallEvent is emitted when the agent calls a tool.

func (ToolCallEvent) Kind

func (ToolCallEvent) Kind() EventKind

type ToolCallPart

type ToolCallPart struct {
	ID               string         `json:"id,omitempty"`
	Name             string         `json:"name"`
	Args             map[string]any `json:"args,omitempty"`
	ThoughtSignature []byte         `json:"thought_signature,omitempty"`
}

ToolCallPart represents an invocation request from the model.

type ToolDiffEvent

type ToolDiffEvent struct {
	BaseEvent
	Path string `json:"path"`
	Diff string `json:"diff"`
}

ToolDiffEvent is emitted when a tool mutates a file and produces a unified diff.

func (ToolDiffEvent) Kind

func (ToolDiffEvent) Kind() EventKind

type ToolFactory

type ToolFactory func(args ToolFactoryArgs) []Tool

ToolFactory builds a slice of Tools for an engine turn.

func GetDefaultToolFactory

func GetDefaultToolFactory() ToolFactory

GetDefaultToolFactory retrieves the currently registered default tool factory.

type ToolFactoryArgs

type ToolFactoryArgs struct {
	Cwd                string
	TabID              int
	SkipPermissions    bool
	EventListener      EventListener
	InteractionHandler InteractionHandler
	AttachWebSearch    bool
	// SupportsImages reports whether the run's model can see images; it
	// gates feeding tool-rendered images back to the model. Nil is treated
	// as capable.
	SupportsImages func() bool
	// WorkflowStep attaches the workflow-step tools (save_artifact,
	// load_artifacts) so a step can pass data to a later one.
	WorkflowStep bool
	// WorkflowFinalStep also attaches finish_workflow, which reports the
	// run's outcome and created artifacts to the user. Only meaningful
	// with WorkflowStep.
	WorkflowFinalStep bool
}

ToolFactoryArgs provides configuration parameters to construct the agent toolset.

type ToolInfo

type ToolInfo struct {
	Name        string         `json:"name"`
	Description string         `json:"description"`
	Parameters  map[string]any `json:"parameters,omitempty"`
	Required    []string       `json:"required,omitempty"`
}

ToolInfo provides tool metadata and parameters.

func ExtractToolInfo

func ExtractToolInfo(t tool.Tool) ToolInfo

ExtractToolInfo extracts ToolInfo from any ADK Tool.

type ToolResponse

type ToolResponse struct {
	Content string `json:"content"`
	IsError bool   `json:"is_error,omitempty"`
}

ToolResponse represents the result of executing a tool.

func NewTextErrorResponse

func NewTextErrorResponse(text string) ToolResponse

func NewTextResponse

func NewTextResponse(text string) ToolResponse

type ToolResultEvent

type ToolResultEvent struct {
	BaseEvent
	ToolUseID  string `json:"tool_use_id"`
	ToolName   string `json:"tool_name"`
	Output     string `json:"output"`
	IsError    bool   `json:"is_error"`
	Background bool   `json:"background,omitempty"`
}

ToolResultEvent is emitted when a tool completes execution.

func (ToolResultEvent) Kind

func (ToolResultEvent) Kind() EventKind

type ToolResultPart

type ToolResultPart struct {
	ID      string `json:"id,omitempty"`
	Name    string `json:"name"`
	Content string `json:"content"`
	IsError bool   `json:"is_error,omitempty"`
}

ToolResultPart represents the response from executing a tool.

type Turn

type Turn struct {
	Text  string
	Files []FilePart
	Done  chan struct{}
}

type TurnCompleteEvent

type TurnCompleteEvent struct {
	BaseEvent
}

TurnCompleteEvent signals the conclusion of a turn's event sequence.

func (TurnCompleteEvent) Kind

type UsageEvent

type UsageEvent struct {
	BaseEvent
	InputTokens  int `json:"input_tokens"`
	OutputTokens int `json:"output_tokens"`
	TotalTokens  int `json:"total_tokens"`
}

UsageEvent records token consumption for an API step.

func (UsageEvent) Kind

func (UsageEvent) Kind() EventKind

type WorkflowDoneEvent

type WorkflowDoneEvent struct {
	BaseEvent
	Description string   `json:"description"`
	Artifacts   []string `json:"artifacts"`
}

WorkflowDoneEvent is emitted when an entire workflow finishes.

func (WorkflowDoneEvent) Kind

type WorkflowFailedEvent

type WorkflowFailedEvent struct {
	BaseEvent
	Reason string `json:"reason"`
}

WorkflowFailedEvent is emitted when a workflow fails or is cancelled.

func (WorkflowFailedEvent) Kind

type WorkflowStartedEvent

type WorkflowStartedEvent struct {
	BaseEvent
	Workflow string `json:"workflow"`
	Source   string `json:"source"`
}

WorkflowStartedEvent is emitted when a workflow begins execution.

func (WorkflowStartedEvent) Kind

type WorkflowStepDoneEvent

type WorkflowStepDoneEvent struct {
	BaseEvent
	StepIdx int    `json:"step_idx"`
	Summary string `json:"summary"`
}

WorkflowStepDoneEvent is emitted when an individual step completes.

func (WorkflowStepDoneEvent) Kind

type WorkflowStepStartedEvent

type WorkflowStepStartedEvent struct {
	BaseEvent
	StepIdx  int    `json:"step_idx"`
	StepName string `json:"step_name"`
	Provider string `json:"provider"`
	Model    string `json:"model"`
}

WorkflowStepStartedEvent is emitted when an individual workflow step begins.

func (WorkflowStepStartedEvent) Kind

Jump to

Keyboard shortcuts

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