summaryrefslogtreecommitdiff
path: root/internal/agentchannel
diff options
context:
space:
mode:
authorClaude Sonnet 5 <noreply@anthropic.com>2026-07-03 08:49:43 +0000
committerClaude Sonnet 5 <noreply@anthropic.com>2026-07-03 08:49:43 +0000
commit67e0081c6d573b701ed931f96e14dbe5b4258a17 (patch)
treebc7f8ce0d57876dd85720924d0275dc39c05e5b4 /internal/agentchannel
parent3d286974cdc28c68c5ee536ce9303899dae9540e (diff)
refactor(executor): extract provider-neutral tool-use loop (Phase 1)
Splits LocalRunner's OpenAI-specific agentic loop into reusable, provider- agnostic pieces so later phases can add native Anthropic/OpenAI/Google/Groq/ OpenRouter adapters without duplicating the control flow: - internal/provider: neutral Provider/ChatRequest/ChatResponse types, plus an openaicompat adapter wrapping the existing internal/llm.Client unchanged - internal/sandbox: Sandbox interface + HostSandbox (git clone/push/cleanup, read_file/write_file/run_bash/glob), lifted verbatim from local.go/localtools.go - internal/agentloop: the extracted tool-use loop (request/response/tool- dispatch/loop, ask_user blocking, stream-json envelope, summary fallback) - internal/agentchannel: AgentChannel/SubtaskSpec/BlockedError/ErrAgentBlocked moved out of internal/executor so agentloop can use them without an import cycle; internal/executor re-exports via type aliases, so no call site changes - internal/executor/nativerunner.go: NativeRunner replaces LocalRunner, wiring agentloop.Loop + openaicompat + HostSandbox together - config.Providers map[string]ProviderConfig added (unused until Phase 2+) Zero intended behavior change: go test -race ./... passes across all packages, and end-to-end stream-json/summary/changestats output was verified byte-compatible against a fake OpenAI-compatible server. Adds test coverage for sandbox tool-dispatch (git clone/push, read/write/bash/glob) that LocalRunner never had. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V1moSNCJRcP6kykA4tyUSs
Diffstat (limited to 'internal/agentchannel')
-rw-r--r--internal/agentchannel/agentchannel.go65
1 files changed, 65 insertions, 0 deletions
diff --git a/internal/agentchannel/agentchannel.go b/internal/agentchannel/agentchannel.go
new file mode 100644
index 0000000..2730dfa
--- /dev/null
+++ b/internal/agentchannel/agentchannel.go
@@ -0,0 +1,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) }