design/22 §8-2(主人 2026-10-03 拍板):跨角色协作 = 把其他角色以子代理形式派出去,
该子代理以目标角色的 instructions 运行、并按目标角色的记忆作用域读写。
- AgentSpec / SubAgent / SubAgentResult 新增 PersonaID;Options.PersonaExists 作角色解析器
- scheduler:派发前校验角色(ErrUnknownPersona),批量 all-or-nothing;
run ctx 经 withPersonaEnv 只覆盖 SessionEnv.PersonaID —— ActorEnv/MemWritable 原样复制
(身份识别靠 channel 元数据,persona 决定她怎么答、不决定她能碰什么:项目红线)
- 记忆归属键改为 PersonaID 优先(回落 RoleID)→ 替班者写的是目标角色的记忆域
- spawntool:schema 顶层与 tasks[] 新增可选 persona;省略 = 与旧行为逐字节一致
- 分支根节点新增 props.persona(可观测性);SinkMemory 移入 memory.go(守 500 行红线)
- 组合根:personaExists(agent 池);cmd/cyrene/wire.go +5 行
契约:Docs/api-client.md -> v0.47(graph_update 新增可选 props.persona,纯增量)
验证:go build/vet ./... 绿;internal/runtime/orchestration + cmd/cyrene 测试绿;
go test -race ./internal/runtime/orchestration -run Persona 绿;
linux/amd64 + darwin/arm64 交叉编译绿
变异验证:① 目标角色换回父/默认角色(3 处断言 FAIL)② 关调度器侧角色校验 ③ 关工具侧预检
43 lines
1.5 KiB
Go
43 lines
1.5 KiB
Go
package orchestration
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/memory"
|
|
)
|
|
|
|
// This file owns the scheduler's long-term-memory half: handing a sub-agent's task
|
|
// memory to the injected *memory.Store. It sits next to persona.go because the role
|
|
// a sub-agent runs as (design/22 §8-2) is exactly the scope its memory is filed
|
|
// under.
|
|
|
|
// SinkMemory persists a task memory item for a sub-agent through the injected
|
|
// *memory.Store. The role the sub-agent ran as is used as the persona/role key —
|
|
// the dispatched target persona when one was named, otherwise the legacy RoleID
|
|
// binding — so a sub-agent standing in for another role writes that role's memory
|
|
// domain, never the spawning session's (design/22 §8-2/§8-5). Content and the
|
|
// scope/kind classification (user/persona/project) are supplied by the caller,
|
|
// keeping scope policy at the call site. It returns ErrNoMemory when no store was
|
|
// injected.
|
|
func (s *Scheduler) SinkMemory(ctx context.Context, sub *SubAgent, content string, scope memory.Scope, kind memory.Kind) (memory.MemoryItem, error) {
|
|
if s.memory == nil {
|
|
return memory.MemoryItem{}, fmt.Errorf("orchestration: sink memory: %w", ErrNoMemory)
|
|
}
|
|
if sub == nil {
|
|
return memory.MemoryItem{}, fmt.Errorf("orchestration: sink memory: %w", ErrAgentNotFound)
|
|
}
|
|
role := sub.PersonaID
|
|
if role == "" {
|
|
role = sub.RoleID
|
|
}
|
|
item := memory.MemoryItem{
|
|
PersonaID: role,
|
|
RoleID: role,
|
|
Scope: scope,
|
|
Kind: kind,
|
|
Content: content,
|
|
}
|
|
return s.memory.Add(ctx, item)
|
|
}
|