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, StartupTimeout,
110// and DefaultTimeout values with positive manager defaults. Other zero values
111// retain their documented semantics.
112type SubagentPolicy struct {
113 MaxConcurrent int
114 MaxConcurrentPerParent int
115 QueueTimeout time.Duration
116 StartupTimeout time.Duration
117 DefaultTimeout time.Duration
118 MaxTurns int
119 MaxOutputBytes int
120 MaxOutputLines int
121 AllowedTools []string
122 AllowedRoots []string
123 HeartbeatInterval time.Duration
124 IdleTimeout time.Duration
125 ReconnectTimeout time.Duration
126 CancelGracePeriod time.Duration
127}
128
129func (p *SubagentPolicy) normalize() {
130 if p.MaxConcurrent <= 0 {
131 p.MaxConcurrent = 8
132 }
133 if p.MaxConcurrentPerParent <= 0 {
134 p.MaxConcurrentPerParent = 4
135 }
136 if p.QueueTimeout <= 0 {
137 p.QueueTimeout = 5 * time.Minute
138 }
139 if p.StartupTimeout <= 0 {
140 p.StartupTimeout = time.Minute
141 }
142 if p.DefaultTimeout <= 0 {
143 p.DefaultTimeout = 20 * time.Minute
144 }
145 if p.MaxTurns <= 0 {
146 p.MaxTurns = 3
147 }
148 if p.MaxOutputBytes <= 0 {
149 p.MaxOutputBytes = 500_000
150 }
151 if p.MaxOutputLines <= 0 {
152 p.MaxOutputLines = 5_000
153 }
154 if p.HeartbeatInterval <= 0 {
155 p.HeartbeatInterval = 10 * time.Second
156 }
157 if p.IdleTimeout <= 0 {
158 p.IdleTimeout = 7 * time.Minute
159 }
160 if p.ReconnectTimeout <= 0 {
161 p.ReconnectTimeout = 5 * time.Second
162 }
163 if p.CancelGracePeriod <= 0 {
164 p.CancelGracePeriod = 10 * time.Second
165 }
166}
167
168func (p SubagentPolicy) allowedTool(name string) bool {
169 if len(p.AllowedTools) == 0 {
170 return true
171 }
172 for _, allowed := range p.AllowedTools {
173 if allowed == name {
174 return true
175 }
176 }
177 return false
178}