// Package agentchannel holds the runner-agnostic types describing how a Runner // reports agent-originated signals (summary, questions, subtasks, progress) // back to the rest of the system, plus the sentinel/blocked-run error types // that flow through that channel. // // These types used to live in internal/executor. They were extracted here in // Phase 1 of the multi-provider refactor so that internal/agentloop (which // needs to construct/inspect them while running the provider-neutral tool-use // loop) does not have to import internal/executor — and internal/executor, in // turn, can import internal/agentloop (via NativeRunner) without creating an // import cycle. internal/executor re-exports everything here via type // aliases/var assignments (see internal/executor/channel.go) so existing code // that refers to executor.AgentChannel, executor.SubtaskSpec, // executor.BlockedError, and executor.ErrAgentBlocked keeps compiling // unchanged. package agentchannel import ( "context" "errors" "fmt" ) // ErrAgentBlocked is returned by AgentChannel.AskUser when the transport cannot // deliver an answer within the current run — e.g. the file transport, which can // only write-and-exit. Runners translate it into a *BlockedError so the task // blocks and resumes later. Transports that can suspend the agent in-session // (MCP, Phase 2) instead block and return the answer. var ErrAgentBlocked = errors.New("agent blocked awaiting user input") // SubtaskSpec describes a child task an agent wants to spawn. type SubtaskSpec struct { Name string Instructions string Model string MaxBudgetUSD float64 // Role, when non-empty, names an internal/role.RoleConfig the child task // should dispatch through (task.AgentConfig.Role — see Phase 5's // role-based dispatch in internal/executor.Pool.execute()). When set, the // storeChannel implementation leaves Agent.Type/Model empty on the child // so tier 0 of the role's escalation ladder resolves them, exactly like // any other role-typed task. Empty (the default) preserves the prior // hardcoded "claude" behavior for backward compatibility. Role string } // AgentChannel is how a Runner reports agent-originated signals to the rest of // the system. Implementations translate these into stored artifacts and events. // The transport by which a Runner detects these signals — post-exit files // today, MCP/tool-use in Phase 2 — is the Runner's private concern. type AgentChannel interface { // AskUser records that the agent needs human input. Transports that can // suspend the agent in-session return the answer; those that cannot return // ErrAgentBlocked. AskUser(ctx context.Context, questionJSON string) (answer string, err error) // ReportSummary records the agent's final summary text. ReportSummary(ctx context.Context, summary string) error // SpawnSubtask creates a child task and returns its ID. SpawnSubtask(ctx context.Context, spec SubtaskSpec) (taskID string, err error) // RecordProgress records a free-form progress note from the agent. RecordProgress(ctx context.Context, message string) error } // BlockedError is returned by Run when the agent asked the user a question and // the run stopped to await an answer. The pool transitions the task to BLOCKED // and stores the question for the user. type BlockedError struct { QuestionJSON string // raw JSON from the question SessionID string // claude session to resume once the user answers SandboxDir string // preserved sandbox path; resume must run here so the agent finds its session files } func (e *BlockedError) Error() string { return fmt.Sprintf("task blocked: %s", e.QuestionJSON) }