Files
Cyrene_v2/internal/runtime/loop/sessionenv.go
T
Aska adad4e1f62
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
fix(session): 群聊/陌生人记忆隔离——MemWritable 贯穿 loop(AGENTS.md 红线)
群聊/陌生人查记忆只读不写、不泄露主人敏感;owner 默认行为完全不变。

- loop/sessionenv.go:SessionEnv 类型 + WithSessionEnv/sessionEnvFrom/OwnerEnv——每次 Run 携带会话
  隔离上下文,经 context.Context 传递(不改 Run 签名、不存 Loop/Options 字段,避免 16 并发渠道
  消息共享 Loop 的数据竞争;Chatter 接口与测试桩签名不变)。
- loop/memory.go 门控:injectMemory 读——MemWritable=true(owner)走原 Retriever.Inject 全量不变;
  false(群聊/陌生人)改 Retrieve 后只留 ScopeProject(人人可读)、剔除 ScopeUser(owner习惯私有)与
  ScopePersona(角色日常=owner敏感),块首加「群聊隔离」标记,无项目记忆不注入;extractMemory 写
  false 直接 return 跳过(不调用 extractor 不落库)。
- 渠道入站(Channelbridge)传真实会话 MemWritable/Env;Web/REST/WS/voice chat 绑 OwnerEnv(JWT=owner
  行为不变);backthink/automations/orchestration 默认 owner 全量。
- 测试:memory_gating_test 6 用例(owner/群聊只读不写/陌生人受限/无项目记忆不注入/默认owner全量)全绿;
  loop/session/gateway 既有测试无回归。go build/vet/test 47 包全绿。

遗留:ScopeProject 过滤在 loop 内后处理实现(未改 memory 包),若后续允许可在 Retriever 加 scope
过滤选项消除与 Inject 模板的重复(2处)。
2026-09-06 20:34:06 +08:00

63 lines
2.5 KiB
Go

package loop
import (
"context"
"git.yeij.top/AskaEth/Cyrene_v2/internal/capability/restrict"
)
// SessionEnv is the per-conversation context that gates memory injection and
// extraction for one Run call. It travels on the call's context rather than on
// the shared Loop because Loop is reused across concurrent sessions (every
// channel/group/stranger message resolves its own isolation boundary); storing it
// on the Loop struct would race.
type SessionEnv struct {
// ActorEnv is the resolved actor environment: owner, group_member, stranger.
// It is the strongly-typed identity lane decided structurally by the channel
// layer (design/11 §0), never inferred from prompt text.
ActorEnv restrict.ActorEnv
// MemWritable reports whether this conversation may write shared memory. It is
// true only for the owner's private conversation; group chats and strangers
// are read-only (design/02 §3, design/11 §2).
MemWritable bool
}
// Restricted reports whether this is a group-chat or stranger conversation, i.e.
// the read-only memory isolation context. Because MemWritable is true only for
// the owner's private conversation, !MemWritable is exactly the restricted set.
func (e SessionEnv) Restricted() bool { return !e.MemWritable }
// OwnerEnv returns the full-access owner session environment (private
// conversation, memory read+write). It is the Web main-session and internal
// automated-task default, matching the pre-existing owner-full behavior.
func OwnerEnv() SessionEnv {
return SessionEnv{ActorEnv: restrict.ActorOwner, MemWritable: true}
}
// defaultSessionEnv is the owner-full environment used when a caller attaches no
// explicit env: full memory read and write, the pre-existing behavior.
func defaultSessionEnv() SessionEnv {
return OwnerEnv()
}
type sessionEnvKey struct{}
// WithSessionEnv attaches env to ctx so Run's memory gates honor the
// group/stranger read-only isolation per call. It is concurrency-safe: the env
// travels with the call's context and is never stored on the shared Loop.
func WithSessionEnv(ctx context.Context, env SessionEnv) context.Context {
return context.WithValue(ctx, sessionEnvKey{}, env)
}
// sessionEnvFrom returns the env attached to ctx, or the owner-full default when
// none was attached (backward compatible).
func sessionEnvFrom(ctx context.Context) SessionEnv {
if ctx == nil {
return defaultSessionEnv()
}
if env, ok := ctx.Value(sessionEnvKey{}).(SessionEnv); ok {
return env
}
return defaultSessionEnv()
}