feat(agent-presets): compose each session's agent from a preset cordis.yml
A preset is a directory holding one `agent.cordis.yml`. Mounting it under an agent's scope context during `setup(agentCtx)` gives that one session its own tools and prompt sections while every other live session keeps its own. No registry gains a tier. `dsh-tools` and `dsh-system-prompt` already file registrations into the calling context's scope layer, and entry contexts chain to the context a subtree was plugged into, so a composition mounted under `agent.ctx` is that agent's alone and unwinds with it. The mount audits itself because a directly-plugged subtree is absent from `ctx.loader.entries()` and no boot audit covers it. It rejects an unscoped target, a row that never became usable, and a row that published a service into the root service realm — that last one is process-global rather than per-session, and its collision with the next session surfaces as an unhandled rejection `setup` never observes, leaving a half-composed agent that looks healthy. The package invariant re-checks that rule on every service notification, since a row publishing from a timer would escape a one-shot audit. Raises the `packages/README.md` word ceiling from 920 to 980: the group table must enumerate every group, and the new `preset/` row is necessary content. Design: .agents/notes/implemented/architecture/2026-08-03-per-session-agent-presets.md
This commit is contained in:
@@ -80,6 +80,24 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'agentPresets',
|
||||
summary: 'Registry over the deployment\'s agent presets.',
|
||||
methods: [
|
||||
{
|
||||
signature: 'async list(): Promise<AgentPreset[]>',
|
||||
jsDoc: '/**\n * Every profile the configured roots currently supply.\n * @returns the profiles, first-root-wins per id.\n */',
|
||||
},
|
||||
{
|
||||
signature: 'async resolve(id?: string): Promise<AgentPreset>',
|
||||
jsDoc: '/**\n * Resolve one profile by id.\n * @param id - the profile id, or `undefined` for {@link defaultId}.\n * @returns the resolved profile.\n * @throws when no configured root supplies that id.\n */',
|
||||
},
|
||||
{
|
||||
signature: 'async mount(agentCtx: Context, id?: string): Promise<AgentPreset>',
|
||||
jsDoc: '/**\n * Compose one agent from a profile, installing it under that agent alone.\n *\n * Call from the agent factory\'s `setup(agentCtx)`; a rejection there rolls\n * the agent creation back, so a broken profile never yields a half-composed\n * session.\n * @param agentCtx - the agent\'s scope context.\n * @param id - the profile id, or `undefined` for {@link defaultId}.\n * @returns the profile that was mounted, for the caller to record.\n * @throws when the profile is unknown or its composition is unusable.\n */',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'agents',
|
||||
summary: 'Agent service (`ctx.agents`): tracks live agents and carries the initiating Agent through one process-local asynchronous driver chain.',
|
||||
@@ -1601,6 +1619,10 @@ export const TYPE_API: readonly TypeApiEntry[] = [
|
||||
name: 'AgentOptions',
|
||||
declaration: 'export interface AgentOptions {\n provider?: string;\n model?: string;\n maxTokens?: number;\n}',
|
||||
},
|
||||
{
|
||||
name: 'AgentPreset',
|
||||
declaration: 'export interface AgentPreset {\n readonly id: string;\n readonly trust: PresetTrust;\n readonly path: string;\n}',
|
||||
},
|
||||
{
|
||||
name: 'AgentSetup',
|
||||
declaration: 'export type AgentSetup = (agentCtx: Context) => AgentSetupCommit | Promise<AgentSetupCommit | void> | void;',
|
||||
@@ -2193,6 +2215,10 @@ export const TYPE_API: readonly TypeApiEntry[] = [
|
||||
name: 'PresetSpec',
|
||||
declaration: 'export interface PresetSpec {\n sandbox: SandboxMode;\n approval: ApprovalPolicy;\n name?: string;\n description?: string;\n}',
|
||||
},
|
||||
{
|
||||
name: 'PresetTrust',
|
||||
declaration: 'export type PresetTrust = \'system\' | \'user\';',
|
||||
},
|
||||
{
|
||||
name: 'ProjectionChangeListener',
|
||||
declaration: 'export type ProjectionChangeListener = (session: Session, key: Extract<keyof SessionProjectionMap, string>, value: unknown, seq: number) => void;',
|
||||
|
||||
Reference in New Issue
Block a user