summaryrefslogtreecommitdiff
path: root/internal/agentchannel/agentchannel.go
blob: 2730dfa3a3bf0bd3091ac1b2b71c093291f0e32c (plain)
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
// 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
}

// 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) }