feat(agent-core-v2): add DI scope engine and domain services#1091
feat(agent-core-v2): add DI scope engine and domain services#1091sailist wants to merge 2 commits into
Conversation
Introduce packages/agent-core-v2, the next-generation agent engine built on a four-level DI Scope architecture (Core/Session/Agent/Turn). - Port VSCode-style DI primitives into _base/di and add a Scope layer (LifecycleScope, registerScopedService, Scope tree, IScopeHandle). - Implement all 59 domain services across L1-L7 and cross-cutting domains (log, telemetry, environment, kaos, kosong, records, config, tool, skill, permission, approval, question, context, message, turn, injection, compaction, plan, goal, swarm, usage, tooldedup, background, cron, mcp, agent-lifecycle, session-context, session-activity, session, hooks, event, gateway, terminal, fs, workspace, filestore, auth). - Add a domain-layer import boundary checker and a service dependency graph generator. - Register the package in flake.nix (workspacePaths / workspaceNames). v1 (packages/agent-core) is left untouched; the server/SDK switchover is a later step. See plan/ for the design, roadmap, skeleton spec, and the generated dependency graph.
- normalize file-header and JSDoc comment style across src domains - refactor tests to shared DI helpers (TestInstantiationService, DisposableStore) and consistent `#/` import paths - add package docs (di-testing, errors, flag) plus AGENTS.md and gap analysis
|
commit: |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7749108e39
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| @ITelemetryService _telemetry: ITelemetryService, | ||
| @ILogService _log: ILogService, | ||
| @IAgentLifecycleService _agentLifecycle: IAgentLifecycleService, | ||
| @ILoopRunner private readonly loopRunner: ILoopRunner, |
There was a problem hiding this comment.
Resolve turn runners from a reachable scope
In a real Agent scope, ITurnService is constructed from the Agent collection and its ancestors, but ILoopRunner is registered only at Turn scope in src/turn/loopRunner.ts, which is a child scope and therefore not visible here. Resolving ITurnService from an agent handle will throw for an unknown loopRunner before prompt() can run; create a Turn scope per turn and resolve the runner there, or give the runner a lifetime reachable from Agent scope.
Useful? React with 👍 / 👎.
| const collection = new ServiceCollection(); | ||
| for (const entry of getScopedServiceDescriptors(LifecycleScope.Session)) { | ||
| collection.set(entry.id, entry.descriptor); | ||
| } | ||
| const child = this.instantiation.createChild(collection); |
There was a problem hiding this comment.
Seed session context when creating sessions
This child collection contains only Session-scoped descriptors and never registers the required ISessionContext seed. Services returned from a session created by this registry that declare @ISessionContext (for example AgentLifecycleService and CronService) will fail to resolve under the strict scoped container, so a gateway-created session cannot create agents or service prompts. Add the session context seed, or otherwise register that token, before creating the child scope.
Useful? React with 👍 / 👎.
| constructor( | ||
| @IKaosFactory _kaosFactory: IKaosFactory, | ||
| @ILogService _log: ILogService, | ||
| private readonly registry: WorkspaceRegistry = new WorkspaceRegistry(undefined as never, undefined as never), |
There was a problem hiding this comment.
Inject the shared workspace registry
When WorkspaceFsService is instantiated by DI, no third static argument is supplied, so this default creates a fresh empty WorkspaceRegistry instead of using the Core-scoped registry. Workspaces registered through IWorkspaceRegistry.register() are therefore invisible to IWorkspaceFsService.resolve(), causing valid workspace ids to throw as unknown; inject @IWorkspaceRegistry here instead of constructing a separate registry.
Useful? React with 👍 / 👎.
|
|
||
| registerMcpTools(serverId: string, tools: readonly ToolDefinition[]): void { | ||
| for (const def of tools) { | ||
| this.mcp.set(`${serverId}:${def.name}`, def); |
There was a problem hiding this comment.
Store MCP tools under executable names
After registerMcpTools('srv', [{ name: 'm', ... }]), list() advertises a tool named m, but execute('m', ...) calls find() which looks up this.mcp.get(name). Because the map key is srv:m, listed MCP tools cannot be executed by their listed name unless callers know an unexposed prefixed key; use the same key that execute() accepts, or expose the prefixed name consistently.
Useful? React with 👍 / 👎.
| constructor( | ||
| @IConfigService _config: IConfigService, | ||
| @ITelemetryService _telemetry: ITelemetryService, | ||
| private readonly oauth?: OAuthService, |
There was a problem hiding this comment.
Inject OAuth into the auth summary service
When this service is created by DI, the undecorated optional oauth parameter is never supplied, so summarize() always returns an empty array even after callers log in through the Core-scoped IOAuthService. Inject the registered @IOAuthService contract instead of relying on an optional concrete constructor argument.
Useful? React with 👍 / 👎.
Summary
This PR introduces
packages/agent-core-v2, the next-generation agent engine built on a four-level DI Scope architecture (Core / Session / Agent / Turn). It ports VSCode-style dependency-injection primitives into a reusable base layer, then re-implements all domain services on top of it. The v1 engine (packages/agent-core) is left untouched; switching the server/SDK over is a follow-up step.Related Issue
No linked issue. This is foundational groundwork for the v4 agent engine: the current
packages/agent-corewires services together ad hoc, which makes lifecycle ownership, per-session/per-turn isolation, and testing harder than they should be.agent-core-v2makes service lifetimes and dependencies explicit through a typed DI container and a scope tree.Problem
Today, agent-core services are constructed and composed manually. There is no unified notion of service lifetime (singleton vs. per-session vs. per-turn), no enforced dependency direction between domains, and no ergonomic way to override services in tests. As the engine grows, this leads to scattered construction logic, hidden coupling, and brittle test setup.
What changed
1. DI Scope engine
Problem: There is no shared infrastructure for declaring services, their lifetimes, and how they depend on each other.
What was done:
_base/di(service identifiers, decorators, instantiation service, disposable store).Scopelayer on top:LifecycleScope,registerScopedService, aCore/Session/Agent/Turnscope tree, andIScopeHandlefor scoped resolution and disposal.2. Domain services
Problem: Domain logic currently lives in hand-wired modules without a consistent lifetime or registration model.
What was done:
3. Tooling, tests, and docs
Problem: Without guardrails, a large DI graph drifts: domains start importing across layers, and test setup diverges.
What was done:
TestInstantiationService,DisposableStore) and consistent#/import paths.di-testing,errors,flag) plusAGENTS.md.Checklist
gen-changesetsskill, or this PR needs no changeset.gen-docsskill, or this PR needs no doc update.