From 72c0996b74153ec48f725bf2e66320f414a4fe53 Mon Sep 17 00:00:00 2001 From: agent-bot Date: Tue, 29 Sep 2026 05:09:54 +0000 Subject: [PATCH 1/2] refactor(squad): inject Options + backend factory, drop config.Config dependency KTD1 first step (in-repo decoupling, behavior preserved): - squad.Options{BackendName, BackendTimeout, DataDir, NewBackend} replaces *config.Config; defaults applied in withDefaults - backend construction injected by caller (bizdev assembly): credentials, environment and concurrency stay product-side; nil factory fails closed instead of silently degrading to Stub - verified: go vet ./... clean, squad/bizdev short tests pass --- internal/bizdev/service.go | 10 ++++++- internal/squad/service.go | 53 +++++++++++++++++++++++++++++--------- 2 files changed, 50 insertions(+), 13 deletions(-) diff --git a/internal/bizdev/service.go b/internal/bizdev/service.go index 0d813bea..baf8a216 100644 --- a/internal/bizdev/service.go +++ b/internal/bizdev/service.go @@ -10,6 +10,7 @@ import ( "strings" "time" + "chat-agent-system/internal/agent" "chat-agent-system/internal/app/uow" "chat-agent-system/internal/authz" "chat-agent-system/internal/config" @@ -192,7 +193,14 @@ func NewService(cfg *config.Config) (*Service, error) { svc := &Service{ cfg: cfg, kb: kb.NewClient(cfg), - squad: squad.NewService(cfg), + squad: squad.NewService(squad.Options{ + BackendName: cfg.AgentBackend, + BackendTimeout: cfg.AgentTimeout, + DataDir: cfg.DataDir, + NewBackend: func(name string) agent.Backend { + return agent.NewBackend(name, cfg) + }, + }), ctxMgr: ctxMgr, cas: casStore, materialIndex: matIndex, diff --git a/internal/squad/service.go b/internal/squad/service.go index 1f9b3662..8b18de70 100644 --- a/internal/squad/service.go +++ b/internal/squad/service.go @@ -10,7 +10,6 @@ import ( "time" "chat-agent-system/internal/agent" - "chat-agent-system/internal/config" "chat-agent-system/internal/quote" "chat-agent-system/internal/stream" "chat-agent-system/internal/workflowdef" @@ -109,18 +108,42 @@ type SquadService interface { ResumeOrModifyWorkspace(ctx context.Context, req ModifyWorkflowRequest) (*SquadRunResult, error) } +// Options 是 Squad 依赖注入的唯一配置面;不反向依赖主仓 config.Config, +// 为独立模块做准备(见多仓方案 KTD1)。 +type Options struct { + // BackendName 为执行后端名(空 = 由调用方默认)。 + BackendName string + // BackendTimeout 为单次执行超时;零值由调用方默认。 + BackendTimeout time.Duration + // DataDir 为工作区根(任务目录挂在其下)。 + DataDir string + // NewBackend 由调用方注入的后端工厂(Core 装配:凭证、环境、并发上限 + // 留在产品侧)。nil 时返回错误,不静默降级 Stub。 + NewBackend func(name string) agent.Backend +} + +func (o Options) withDefaults() Options { + if o.BackendName == "" { + o.BackendName = "pi-agent" + } + if o.BackendTimeout <= 0 { + o.BackendTimeout = 10 * time.Minute + } + return o +} + // Service SquadService 实现 type Service struct { - cfg *config.Config + opts Options engine *WorkspaceEngine driver *WorkflowDriver gate *QualityGate } -func NewService(cfg *config.Config) *Service { +func NewService(opts Options) *Service { engine := NewWorkspaceEngine() return &Service{ - cfg: cfg, + opts: opts.withDefaults(), engine: engine, driver: NewWorkflowDriver(engine), gate: NewQualityGate(), @@ -207,12 +230,12 @@ func (s *Service) ExecuteTask(ctx context.Context, req SquadRunRequest) (*SquadR workDir := req.WorkDir if workDir == "" { ts := time.Now().UnixNano() - workDir = filepath.Join(s.cfg.DataDir, "workspaces", "bizdev", fmt.Sprintf("%s_%d", req.TaskKey, ts)) + workDir = filepath.Join(s.opts.DataDir, "workspaces", "bizdev", fmt.Sprintf("%s_%d", req.TaskKey, ts)) } backendName := req.Backend if backendName == "" { - backendName = s.cfg.AgentBackend + backendName = s.opts.BackendName } // 1. 初始化工作区目录体系 @@ -258,10 +281,13 @@ func (s *Service) ExecuteTask(ctx context.Context, req SquadRunRequest) (*SquadR streamer.EmitPhase("squad_assembly", fmt.Sprintf("正在组建并启动【%s】智能体小队(后端: %s)...", TaskArtifactTitle(req.TaskKey), backendName), "leader", 0, 0, 5) } - backend := agent.NewBackend(backendName, s.cfg) + if s.opts.NewBackend == nil { + return nil, fmt.Errorf("squad: NewBackend factory not injected") + } + backend := s.opts.NewBackend(backendName) timeout := req.Timeout if timeout <= 0 { - timeout = s.cfg.AgentTimeout + timeout = s.opts.BackendTimeout } execOpts := ExecuteOptions{ Timeout: timeout, @@ -397,9 +423,12 @@ func repairBudgetForSquadTask(req SquadRunRequest) int { func (s *Service) ExecuteWorkflow(ctx context.Context, wsDir string, backendName string, opts ExecuteOptions) (*SquadRunResult, error) { start := time.Now() if backendName == "" { - backendName = s.cfg.AgentBackend + backendName = s.opts.BackendName } - backend := agent.NewBackend(backendName, s.cfg) + if s.opts.NewBackend == nil { + return nil, fmt.Errorf("squad: NewBackend factory not injected") + } + backend := s.opts.NewBackend(backendName) execRes, err := s.driver.Execute(ctx, wsDir, backend, opts) if err != nil { @@ -491,12 +520,12 @@ func (s *Service) ResumeOrModifyWorkspace(ctx context.Context, req ModifyWorkflo backendName := req.Backend if backendName == "" { - backendName = s.cfg.AgentBackend + backendName = s.opts.BackendName } timeout := req.Timeout if timeout <= 0 { - timeout = s.cfg.AgentTimeout + timeout = s.opts.BackendTimeout } opts := ExecuteOptions{ -- 2.54.0 From 97879e59568d5ef70fabc1df59acdb9ff764b086 Mon Sep 17 00:00:00 2001 From: agent-bot Date: Tue, 29 Sep 2026 05:13:54 +0000 Subject: [PATCH 2/2] arch(boundaries): declare bizdev->agent assembly edge bizdev is the composition point injecting the backend factory into squad.Options (KTD1: assembly stays in the product app); the new import is intentional and now owned by the machine contract, same PR as the code. --- .doc/system-design/module-boundaries.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.doc/system-design/module-boundaries.yaml b/.doc/system-design/module-boundaries.yaml index 6a9138e1..26828d7b 100644 --- a/.doc/system-design/module-boundaries.yaml +++ b/.doc/system-design/module-boundaries.yaml @@ -26,6 +26,7 @@ imports: authz: - docduty bizdev: + - agent - authz - app/uow - config -- 2.54.0