model.go RawHistoryFolder
1package subagents
2
3import "time"
4
5// WebSearchPolicy is the capability decision propagated from a host agent to
6// a subagent worker. Inherit is used only before a supervisor resolves the
7// child decision; persisted and launched workers use Allow or Deny so their
8// capability does not change with a later config edit.
9type WebSearchPolicy uint8
10
11const (
12 WebSearchInherit WebSearchPolicy = iota
13 WebSearchDeny
14 WebSearchAllow
15)
16
17func (p WebSearchPolicy) Allows() bool { return p == WebSearchAllow }
18
19// childPolicy turns an unresolved or corrupt policy into an explicit deny.
20// Inherit is useful while a supervisor is resolving a spawn request, but it
21// must never cross the worker boundary because the child may otherwise apply
22// its own default-enabled configuration.
23func (p WebSearchPolicy) childPolicy() WebSearchPolicy {
24 if p == WebSearchAllow {
25 return WebSearchAllow
26 }
27 return WebSearchDeny
28}
29
30func (p WebSearchPolicy) String() string {
31 switch p {
32 case WebSearchAllow:
33 return "allow"
34 case WebSearchDeny:
35 return "deny"
36 case WebSearchInherit:
37 return "inherit"
38 default:
39 // Unknown values must not serialize as inherit: that would let a
40 // worker fall back to its own default and potentially regain access.
41 return "deny"
42 }
43}
44
45func childWebSearchPolicy(policy WebSearchPolicy, subagent string, toolNames []string) WebSearchPolicy {
46 if subagent != "" && NamedWebSearchPolicy(toolNames) != WebSearchAllow {
47 return WebSearchDeny
48 }
49 return policy.childPolicy()
50}
51
52// NamedWebSearchPolicy requires a named profile to opt in explicitly. An
53// omitted or empty profile tools list therefore denies web search without
54// changing the default selection of the other built-in tools.
55func NamedWebSearchPolicy(toolNames []string) WebSearchPolicy {
56 for _, name := range toolNames {
57 if name == "web_search" {
58 return WebSearchAllow
59 }
60 }
61 return WebSearchDeny
62}
63
64// ProcessState describes the lifetime of the supervised child process. It is
65// intentionally independent from TurnState: an alive worker can be idle
66// between turns, and a disconnected supervisor does not imply that the
67// worker's current turn failed.
68type ProcessState string
69
70const (
71 ProcessPending ProcessState = "pending"
72 ProcessStarting ProcessState = "starting"
73 ProcessAlive ProcessState = "alive"
74 ProcessDetached ProcessState = "detached"
75 ProcessExited ProcessState = "exited"
76 ProcessKilled ProcessState = "killed"
77)
78
79// TurnState describes one delegated prompt inside a worker process.
80type TurnState string
81
82const (
83 TurnIdle TurnState = "idle"
84 TurnQueued TurnState = "queued"
85 TurnRunning TurnState = "running"
86 TurnCanceling TurnState = "canceling"
87 TurnSucceeded TurnState = "succeeded"
88 TurnFailed TurnState = "failed"
89 TurnCanceled TurnState = "canceled"
90)
91
92// WorkspaceMode selects how a child is allowed to access the repository.
93type WorkspaceMode string
94
95const (
96 WorkspaceShared WorkspaceMode = "shared"
97 WorkspaceWorktree WorkspaceMode = "worktree"
98)
99
100// CaptureMode controls which durable worktree output is collected.
101type CaptureMode string
102
103const (
104 CapturePatch CaptureMode = "patch"
105 CaptureDiff CaptureMode = "diff"
106)
107
108// SubagentPolicy contains manager-owned safety and resource limits. normalize
109// replaces zero MaxOutputBytes, MaxOutputLines, QueueTimeout, and
110// DefaultTimeout values with positive manager defaults. Other zero values retain
111// their documented semantics.
112type SubagentPolicy struct {
113 MaxConcurrent int
114 MaxConcurrentPerParent int
115 QueueTimeout time.Duration
116 DefaultTimeout time.Duration
117 MaxTurns int
118 MaxOutputBytes int
119 MaxOutputLines int
120 AllowedTools []string
121 AllowedRoots []string
122 HeartbeatInterval time.Duration
123 IdleTimeout time.Duration
124 ReconnectTimeout time.Duration
125 CancelGracePeriod time.Duration
126}
127
128func (p *SubagentPolicy) normalize() {
129 if p.MaxConcurrent <= 0 {
130 p.MaxConcurrent = 8
131 }
132 if p.MaxConcurrentPerParent <= 0 {
133 p.MaxConcurrentPerParent = 4
134 }
135 if p.QueueTimeout <= 0 {
136 p.QueueTimeout = 30 * time.Minute
137 }
138 if p.DefaultTimeout <= 0 {
139 p.DefaultTimeout = 20 * time.Minute
140 }
141 if p.MaxTurns <= 0 {
142 p.MaxTurns = 3
143 }
144 if p.MaxOutputBytes <= 0 {
145 p.MaxOutputBytes = 500_000
146 }
147 if p.MaxOutputLines <= 0 {
148 p.MaxOutputLines = 5_000
149 }
150 if p.HeartbeatInterval <= 0 {
151 p.HeartbeatInterval = 10 * time.Second
152 }
153 if p.IdleTimeout <= 0 {
154 p.IdleTimeout = 7 * time.Minute
155 }
156 if p.ReconnectTimeout <= 0 {
157 p.ReconnectTimeout = 5 * time.Second
158 }
159 if p.CancelGracePeriod <= 0 {
160 p.CancelGracePeriod = 10 * time.Second
161 }
162}
163
164func (p SubagentPolicy) allowedTool(name string) bool {
165 if len(p.AllowedTools) == 0 {
166 return true
167 }
168 for _, allowed := range p.AllowedTools {
169 if allowed == name {
170 return true
171 }
172 }
173 return false
174}