diff --git a/cmd/olliesrv/internal/fs/spec.go b/cmd/olliesrv/internal/fs/spec.go index 9ce5fce..ea28852 100644 --- a/cmd/olliesrv/internal/fs/spec.go +++ b/cmd/olliesrv/internal/fs/spec.go @@ -666,7 +666,10 @@ func buildSessionChildren( defer cancel() session.PublishEvent("session."+s.ID+".agent."+ag.ID()+".new", "") - ag.Submit(subCtx, req.Prompt) + subPrompt := "You are a sub-agent. Complete ALL work using tools before producing your final response. " + + "Your response is returned to the caller — it must be a summary of what you accomplished, not a plan of what you intend to do. " + + "Do not narrate your intentions; act, then report results.\n\n" + req.Prompt + ag.Submit(subCtx, subPrompt) ag.EnsureTrailingNewline() reply := ag.Reply() s.RemoveAgent(ag.ID()) diff --git a/cmd/olliesrv/internal/prompts/system_prompt.md b/cmd/olliesrv/internal/prompts/system_prompt.md index 83d9f1b..b02d116 100644 --- a/cmd/olliesrv/internal/prompts/system_prompt.md +++ b/cmd/olliesrv/internal/prompts/system_prompt.md @@ -208,7 +208,7 @@ The sub-agent gets its own context window and tool access and runs independently Optional parameters: `profile=`, `backend=`, `model=`, `name=`, `timeout=`, `max_depth=`, `max_parallel=`, `fork_at=`. **Guardrails** (enforced server-side): -- `timeout=600` — sub-agent is killed after 10 minutes (default). Top-level agents are never timed out. +- `timeout=600` — sub-agent's internal execution limit (default 10 minutes). The tool itself has no execution timeout. - `max_depth=1` — sub-agents cannot spawn their own sub-agents by default. - `max_parallel=5` — up to 5 concurrent sub-agents per parent (set to -1 for unlimited). diff --git a/cmd/toolsrv/internal/server/proc.go b/cmd/toolsrv/internal/server/proc.go index 3e30335..54fa62a 100644 --- a/cmd/toolsrv/internal/server/proc.go +++ b/cmd/toolsrv/internal/server/proc.go @@ -310,6 +310,9 @@ func (st *State) NewProc(ctx context.Context, payload string, background bool) ( var outputWriter io.Writer var startedCh chan exec.StartResult timeout := 30 // default for foreground + if info.Timeout >= 0 { + timeout = info.Timeout // tool declares its own default + } if background { outputWriter = &procWriter{proc: proc} startedCh = make(chan exec.StartResult, 1) diff --git a/data/tools/subagent_spawn.meta b/data/tools/subagent_spawn.meta index 751a721..2ee30b2 100644 --- a/data/tools/subagent_spawn.meta +++ b/data/tools/subagent_spawn.meta @@ -3,6 +3,7 @@ "prompt": "## subagent_spawn\n\nSpawn a sub-agent with a task. Blocks until the sub-agent completes and returns its reply.\nThe sub-agent gets its own context window and tool access. It is destroyed when done.\n\n```\nsubagent_spawn(prompt=\"fix the failing tests in auth/\")\nsubagent_spawn(prompt=\"implement the login page\", cwd=\"$PWD\", model=\"sonnet\")\n```\n\nMultiple subagent_spawn calls in the same turn run in parallel automatically.", "scope": "read", "tier": "hot", + "timeout": 0, "args": { "type": "object", "required": [ @@ -32,6 +33,10 @@ "cwd": { "type": "string", "description": "Working directory (optional, default: current)" + }, + "timeout": { + "type": "integer", + "description": "Timeout in seconds (default: 0, no timeout). Do not set a timeout on sub-agents." } } } diff --git a/toolsrv/metadata/meta.go b/toolsrv/metadata/meta.go index 0d45afc..caf4742 100644 --- a/toolsrv/metadata/meta.go +++ b/toolsrv/metadata/meta.go @@ -18,6 +18,7 @@ type MetaFile struct { Tier string `json:"tier,omitempty"` Scope string `json:"scope,omitempty"` OutputFormat string `json:"outputFormat,omitempty"` + Timeout *int `json:"timeout,omitempty"` Cmd string `json:"cmd,omitempty"` Variants []Variant `json:"variants,omitempty"` } @@ -156,6 +157,14 @@ func checkCondition(key, val string) bool { return false } +// resolveTimeout returns -1 (use server default) if nil, or the value if set. +func resolveTimeout(t *int) int { + if t == nil { + return -1 + } + return *t +} + // ToolInfoFromMeta builds a ToolInfo from a MetaFile. // Applies variant resolution first. Returns zero ToolInfo if no variant matches. func ToolInfoFromMeta(name string, m *MetaFile) protocol.ToolInfo { @@ -179,6 +188,7 @@ func ToolInfoFromMeta(name string, m *MetaFile) protocol.ToolInfo { Tier: tier, Scope: resolved.Scope, OutputFormat: resolved.OutputFormat, + Timeout: resolveTimeout(m.Timeout), } } diff --git a/toolsrv/protocol/types.go b/toolsrv/protocol/types.go index f9d7fec..8ef0c82 100644 --- a/toolsrv/protocol/types.go +++ b/toolsrv/protocol/types.go @@ -13,6 +13,7 @@ type ToolInfo struct { Tier string Scope string OutputFormat string + Timeout int // default execution timeout; 0 = no timeout, -1 = use server default } // ToolResult is the structured result from a tool execution.