1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
|
// 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) }
|