subagent: inherit parent context via parent= parameter

Sub-agents now clone the parent agent's conversation history into
their initial context. The tool script passes parent=$OLLIE_UNAME
automatically. Mechanically identical to session restore — uses
RestoreHistoryFromMessages on the parent's Messages().
This commit is contained in:
Levi Neely 2026-08-14 15:12:16 +02:00
parent c25e2e708c
commit 617bb5586a
4 changed files with 25 additions and 6 deletions

View File

@ -290,7 +290,14 @@ func buildSessionChildren(
virtfs.FileNode("new", 0666, virtfs.FileNode("new", 0666,
virtfs.Doc("Create agent. With prompt=, runs as sub-agent: blocks until done, returns reply."), virtfs.Doc("Create agent. With prompt=, runs as sub-agent: blocks until done, returns reply."),
virtfs.Rdwr(func(ctx context.Context, data []byte) ([]byte, error) { virtfs.Rdwr(func(ctx context.Context, data []byte) ([]byte, error) {
params, prompt := parseAgentNewRequest(data) params, prompt, parentID := parseAgentNewRequest(data)
// Sub-agent inherits parent's context.
if prompt != "" && parentID != "" {
if parent := s.FindAgent(parentID); parent != nil {
params.History = agent.RestoreHistoryFromMessages(parent.Messages())
}
}
ag, err := session.CreateAgentWithParams(s.Name(), params) ag, err := session.CreateAgentWithParams(s.Name(), params)
if err != nil { if err != nil {

View File

@ -84,11 +84,11 @@ var (
// - Space-separated key=value (legacy format from GUI) // - Space-separated key=value (legacy format from GUI)
// //
// A bare word (no =) is treated as the profile name (legacy positional arg). // A bare word (no =) is treated as the profile name (legacy positional arg).
// Returns AgentParams and the prompt (empty if not a sub-agent request). // Returns AgentParams, the prompt (empty if not a sub-agent request), and the parent agent ID.
func parseAgentNewRequest(data []byte) (session.AgentParams, string) { func parseAgentNewRequest(data []byte) (session.AgentParams, string, string) {
s := strings.TrimSpace(string(data)) s := strings.TrimSpace(string(data))
if s == "" { if s == "" {
return session.AgentParams{Profile: "default"}, "" return session.AgentParams{Profile: "default"}, "", ""
} }
var tokens []string var tokens []string
@ -99,7 +99,7 @@ func parseAgentNewRequest(data []byte) (session.AgentParams, string) {
} }
p := session.AgentParams{Profile: "default"} p := session.AgentParams{Profile: "default"}
var prompt string var prompt, parent string
for _, tok := range tokens { for _, tok := range tokens {
tok = strings.TrimSpace(tok) tok = strings.TrimSpace(tok)
if tok == "" { if tok == "" {
@ -122,10 +122,12 @@ func parseAgentNewRequest(data []byte) (session.AgentParams, string) {
p.Profile = v p.Profile = v
case "prompt": case "prompt":
prompt = v prompt = v
case "parent":
parent = v
} }
} else if p.Profile == "default" { } else if p.Profile == "default" {
p.Profile = tok p.Profile = tok
} }
} }
return p, prompt return p, prompt, parent
} }

View File

@ -32,6 +32,7 @@ escaped_prompt=$(printf '%s' "$prompt_text" | sed 's/\\/\\\\/g; s/$/\\n/' | tr -
# Build the payload. # Build the payload.
payload="cwd=${cwd} payload="cwd=${cwd}
profile=${profile} profile=${profile}
parent=${OLLIE_UNAME}
prompt=${escaped_prompt}" prompt=${escaped_prompt}"
[ -n "$name" ] && payload="${payload} [ -n "$name" ] && payload="${payload}

View File

@ -1542,6 +1542,15 @@ landed as pure wiring because:
No new abstractions. No new processes. No new protocols. Just connecting No new abstractions. No new processes. No new protocols. Just connecting
existing pieces with 42 lines of glue. existing pieces with 42 lines of glue.
### Context inheritance
Sub-agents inherit the parent's conversation history. When `parent=` is
specified in the payload, the handler clones the parent agent's messages
into the sub-agent's initial context via `RestoreHistoryFromMessages`.
This is mechanically identical to session restore — same function, same
data path. The sub-agent knows everything the parent knows without
rediscovering it.
### Future work ### Future work
- **Shared toolsrv per host** — currently each session spawns its own toolsrv. - **Shared toolsrv per host** — currently each session spawns its own toolsrv.