Add parent IDs for sub-agents

This commit is contained in:
Ollie Agent 2026-08-16 11:44:06 +02:00
parent ab9fd60901
commit 72209239b0
3 changed files with 63 additions and 51 deletions

View File

@ -39,6 +39,7 @@ type Agent struct {
reply string // last assistant response
cwd string // working directory for tool execution
id string // agent identity (unique principal)
parentID string // immutable ID of the agent that spawned this agent
fifo Fifo // prompt queue
Feed Feed // streaming input gate (change-detecting)
toolCallCount atomic.Int64
@ -127,6 +128,9 @@ func (ag *Agent) SetName(name string) {
// ID returns the agent's unique identity.
func (ag *Agent) ID() string { return ag.id }
// ParentID returns the immutable ID of the agent that spawned this agent.
func (ag *Agent) ParentID() string { return ag.parentID }
// --- Chat log methods ---
// AppendChat appends data to the chat log and notifies stream readers.
@ -677,6 +681,7 @@ type AgentParams struct {
ID string // unique agent identity (uname)
SessionID string // id of session the agent belongs to
Profile string // config profile name (e.g. "default" → agents/default.json)
ParentID string // immutable ID of the agent that spawned this agent
History *History
Runtime *Runtime
AgentsDir string
@ -702,6 +707,7 @@ func NewAgent(cfg AgentParams) *Agent {
name: cfg.ID, // display name defaults to uname
agentsDir: cfg.AgentsDir,
id: cfg.ID,
parentID: cfg.ParentID,
cwd: cfg.Cwd,
systemPrompt: cfg.SystemPrompt,
envBlock: cfg.EnvBlock,

View File

@ -27,13 +27,14 @@ type PersistedSession struct {
// PersistedAgent is per-agent state within a session.
type PersistedAgent struct {
ID string `json:"id"`
Name string `json:"name"`
Profile string `json:"profile"`
Backend string `json:"backend"`
Model string `json:"model"`
CWD string `json:"cwd"`
Messages []backend.Message `json:"messages"`
ID string `json:"id"`
Name string `json:"name"`
ParentID string `json:"parent,omitempty"`
Profile string `json:"profile"`
Backend string `json:"backend"`
Model string `json:"model"`
CWD string `json:"cwd"`
Messages []backend.Message `json:"messages"`
// Usage tracking
TotalInputTokens int `json:"totalInputTokens,omitempty"`
TotalCachedInputTokens int `json:"totalCachedInputTokens,omitempty"`
@ -70,13 +71,14 @@ func PersistSession(name string) error {
for _, ag := range agents {
pa := PersistedAgent{
ID: ag.ID(),
Name: ag.Name(),
Profile: ag.Profile(),
Backend: ag.BackendName(),
Model: ag.ModelName(),
CWD: ag.Cwd(),
Messages: ag.Messages(),
ID: ag.ID(),
Name: ag.Name(),
ParentID: ag.ParentID(),
Profile: ag.Profile(),
Backend: ag.BackendName(),
Model: ag.ModelName(),
CWD: ag.Cwd(),
Messages: ag.Messages(),
}
if usage := ag.Usage(); usage != nil {
pa.TotalInputTokens = usage.TotalInputTokens
@ -208,6 +210,7 @@ func restoreMultiAgentSession(ps *PersistedSession) (*RestoredSession, error) {
Profile: pa.Profile,
AgentsDir: pkgAgentsDir,
ID: pa.ID,
ParentID: pa.ParentID,
Cwd: cwd,
Log: pkgSink.NewLogger("agent"),
SessionID: ps.ID,
@ -227,14 +230,15 @@ func restoreMultiAgentSession(ps *PersistedSession) (*RestoredSession, error) {
// Active session — use shared buildAgent
history := agent.RestoreHistoryFromMessages(pa.Messages)
ag, err := buildAgent(sess, AgentParams{
Profile: pa.Profile,
ID: pa.ID,
Name: pa.Name,
CWD: cwd,
Backend: pa.Backend,
Model: pa.Model,
Remote: ps.Remote,
History: history,
Profile: pa.Profile,
ID: pa.ID,
Name: pa.Name,
CWD: cwd,
Backend: pa.Backend,
Model: pa.Model,
Remote: ps.Remote,
ParentID: pa.ParentID,
History: history,
})
if err != nil {
pkgLog.Error("restore agent %s: %v", pa.ID, err)

View File

@ -29,13 +29,13 @@ var (
sessions = make(map[string]*Session)
// Package config, set via Init.
serverCtx context.Context
pkgLog *olog.Logger
pkgSink *olog.Sink
pkgAgentsDir string
pkgSessionsDir string
pkgYolo bool
pkgBypassNotify BypassNotifyFunc
serverCtx context.Context
pkgLog *olog.Logger
pkgSink *olog.Sink
pkgAgentsDir string
pkgSessionsDir string
pkgYolo bool
pkgBypassNotify BypassNotifyFunc
)
// InitConfig configures the session package.
@ -345,14 +345,15 @@ func NextUname() string {
// AgentParams describes the parameters for building an agent within a session.
type AgentParams struct {
Profile string
ID string // uname; if empty, generate one
Name string // display name; optional
CWD string // required
Backend string // backend name; empty = config default
Model string // model override; empty = config default
Remote string // remote target override
History *agent.History // nil = fresh agent
Profile string
ID string // uname; if empty, generate one
Name string // display name; optional
CWD string // required
Backend string // backend name; empty = config default
Model string // model override; empty = config default
Remote string // remote target override
ParentID string // immutable ID of the agent that spawned this agent
History *agent.History // nil = fresh agent
}
// buildAgent constructs a fully-wired agent within a session.
@ -432,20 +433,21 @@ func buildAgent(sess *Session, p AgentParams) (*agent.Agent, error) {
}
ag := agent.NewAgent(agent.AgentParams{
History: p.History,
Runtime: rt,
Profile: p.Profile,
AgentsDir: pkgAgentsDir,
ID: p.ID,
Cwd: p.CWD,
SystemPrompt: layers.SystemPrompt,
EnvBlock: layers.EnvBlock,
NewToolServer: infra.NewToolServer,
NewBackend: backend.NewWithName,
Log: pkgSink.NewLogger("agent"),
SessionID: sessID,
Save: sess.saveSession,
Flush: sess.flushSave,
History: p.History,
Runtime: rt,
Profile: p.Profile,
AgentsDir: pkgAgentsDir,
ID: p.ID,
Cwd: p.CWD,
ParentID: p.ParentID,
SystemPrompt: layers.SystemPrompt,
EnvBlock: layers.EnvBlock,
NewToolServer: infra.NewToolServer,
NewBackend: backend.NewWithName,
Log: pkgSink.NewLogger("agent"),
SessionID: sessID,
Save: sess.saveSession,
Flush: sess.flushSave,
})
if p.Name != "" {