feat(agent-presets): rename the two-tool preset to minimal

`core-web` said neither of the things that matter about it. The `-web`
suffix is a leftover from the whole-process `core-web.cordis.yml` overlay,
and presets are per-session and not web-specific. `core` reads as "the
foundational one" when it is in fact the one with the fewest capabilities.

`minimal` says what it is and orders the shipped set legibly by capability:
minimal, standard, cordis.

Breaking: a session created under `core-web` records that id in its header
and will fail to resolve it on resume. Nothing outside this repository has
shipped, so no migration path is offered.

The identically named `config/core-web.cordis.yml` — the legacy
whole-process overlay behind the web snapshot test — is a different thing
and keeps its name.
This commit is contained in:
Yichen Jiang
2026-08-05 16:40:03 +08:00
parent 7281615d44
commit 2886c6391b
16 changed files with 71 additions and 71 deletions

View File

@@ -1346,7 +1346,7 @@ export function createFixtureApi(options: FixtureOptions = {}): ApiProxy {
*/
const fixturePresets = new Map<string, { trust: 'system' | 'user'; content: string }>([
['standard', { trust: 'system', content: "- id: tool-bash\n name: '@deepseek-ai/dsh-tool-bash'\n" }],
['core-web', { trust: 'system', content: "- id: tool-web-search\n name: '@deepseek-ai/dsh-tool-web-search'\n" }],
['minimal', { trust: 'system', content: "- id: tool-web-search\n name: '@deepseek-ai/dsh-tool-web-search'\n" }],
['my-agent', { trust: 'user', content: "- id: tool-read\n name: '@deepseek-ai/dsh-tool-read'\n" }],
])
let fixtureDefaultPreset = 'standard'

View File

@@ -75,24 +75,24 @@ describe('the agent-preset settings controller', () => {
const writes: Recorded[] = []
const controller = new AgentPresetSettingsController(fakeApi([
{ id: 'standard', trust: 'system', isDefault: true },
{ id: 'core-web', trust: 'system', isDefault: false },
{ id: 'minimal', trust: 'system', isDefault: false },
], { writes }))
await controller.load()
await controller.select('core-web')
await controller.select('minimal')
expect(writes).toEqual([{ ns: AGENT_PRESET_SETTINGS_NS, patch: { default: 'core-web' } }])
expect(controller.store.getSnapshot().currentValue).toBe('core-web')
expect(writes).toEqual([{ ns: AGENT_PRESET_SETTINGS_NS, patch: { default: 'minimal' } }])
expect(controller.store.getSnapshot().currentValue).toBe('minimal')
})
it('restores the previous value and surfaces the message when the write fails', async () => {
const controller = new AgentPresetSettingsController(fakeApi([
{ id: 'standard', trust: 'system', isDefault: true },
{ id: 'core-web', trust: 'system', isDefault: false },
{ id: 'minimal', trust: 'system', isDefault: false },
], { failWrite: 'read-only settings' }))
await controller.load()
await controller.select('core-web')
await controller.select('minimal')
const state = controller.store.getSnapshot()
expect(state.currentValue).toBe('standard')
@@ -152,17 +152,17 @@ describe('the composer seat controller', () => {
const ROSTER: { id: string; trust: 'system' | 'user'; isDefault: boolean }[] = [
{ id: 'standard', trust: 'system', isDefault: true },
{ id: 'core-web', trust: 'system', isDefault: false },
{ id: 'minimal', trust: 'system', isDefault: false },
]
it('shows what the session runs, not the deployment default', async () => {
const controller = seat(ROSTER, { blank: true, agentPreset: 'core-web' })
const controller = seat(ROSTER, { blank: true, agentPreset: 'minimal' })
await controller.load()
// A resumed session runs what it was created with; showing `standard`
// because it is the current default would be a lie about this session.
expect(controller.store.getSnapshot().current).toBe('core-web')
expect(controller.store.getSnapshot().current).toBe('minimal')
expect(controller.store.getSnapshot().switchable).toBe(true)
})
@@ -187,7 +187,7 @@ describe('the composer seat controller', () => {
const controller = seat(ROSTER, { blank: false, agentPreset: 'standard' }, { writes })
await controller.load()
await controller.select('core-web')
await controller.select('minimal')
// The host enforces the same rule; the seat simply never asks.
expect(writes).toEqual([])
@@ -199,17 +199,17 @@ describe('the composer seat controller', () => {
const controller = seat(ROSTER, { blank: true, agentPreset: 'standard' }, { writes })
await controller.load()
await controller.select('core-web')
await controller.select('minimal')
expect(writes).toEqual([{ ns: 'select', patch: 'core-web' }])
expect(controller.store.getSnapshot().current).toBe('core-web')
expect(writes).toEqual([{ ns: 'select', patch: 'minimal' }])
expect(controller.store.getSnapshot().current).toBe('minimal')
})
it('restores the previous value when the host rejects the switch', async () => {
const controller = seat(ROSTER, { blank: true, agentPreset: 'standard' }, { failSelect: 'already started' })
await controller.load()
await controller.select('core-web')
await controller.select('minimal')
const state = controller.store.getSnapshot()
expect(state.current).toBe('standard')
@@ -292,9 +292,9 @@ describe('the composer seat controller', () => {
const controller = seat(ROSTER, { blank: true, agentPreset: 'standard' }, { throwOn: 'select' })
await controller.load()
await controller.select('core-web')
await controller.select('minimal')
// Showing `core-web` after a failed switch would claim a composition the
// Showing `minimal` after a failed switch would claim a composition the
// session never got.
expect(controller.store.getSnapshot()).toMatchObject({ current: 'standard', busy: false, error: 'socket closed' })
})

View File

@@ -113,16 +113,16 @@ async function harness(presets?: readonly string[]) {
describe('session.create with an agent preset', () => {
it('records the resolved preset on the session header', async () => {
const { api, ctx } = await harness(['standard', 'core-web'])
const { api, ctx } = await harness(['standard', 'minimal'])
const created = await api.sessions.create(request({ sessionId: SessionId('s1'), agentPreset: 'core-web' }))
const created = await api.sessions.create(request({ sessionId: SessionId('s1'), agentPreset: 'minimal' }))
expect(created.result.ok).toBe(true)
expect(ctx.sessions.get(SessionId('s1'))?.header.agentPreset).toBe('core-web')
expect(ctx.sessions.get(SessionId('s1'))?.header.agentPreset).toBe('minimal')
})
it('records the default when the caller names none', async () => {
const { api, ctx } = await harness(['standard', 'core-web'])
const { api, ctx } = await harness(['standard', 'minimal'])
await api.sessions.create(request({ sessionId: SessionId('s2') }))
@@ -140,8 +140,8 @@ describe('session.create with an agent preset', () => {
})
it('refuses to adopt a live session under a different preset', async () => {
const { api } = await harness(['standard', 'core-web'])
await api.sessions.create(request({ sessionId: SessionId('s4'), agentPreset: 'core-web' }))
const { api } = await harness(['standard', 'minimal'])
await api.sessions.create(request({ sessionId: SessionId('s4'), agentPreset: 'minimal' }))
const response = await api.sessions.create(request({ sessionId: SessionId('s4'), agentPreset: 'standard' }))
@@ -151,13 +151,13 @@ describe('session.create with an agent preset', () => {
expect(response.result.error.details).toEqual({
sessionId: 's4',
requestedPreset: 'standard',
existingPreset: 'core-web',
existingPreset: 'minimal',
})
})
it('adopts a live session unchanged when the caller names no preset', async () => {
const { api } = await harness(['standard', 'core-web'])
await api.sessions.create(request({ sessionId: SessionId('s5'), agentPreset: 'core-web' }))
const { api } = await harness(['standard', 'minimal'])
await api.sessions.create(request({ sessionId: SessionId('s5'), agentPreset: 'minimal' }))
// Reconnecting and retrying a create must stay ordinary operations.
const response = await api.sessions.create(request({ sessionId: SessionId('s5') }))
@@ -257,7 +257,7 @@ describe('a capability the session\'s preset mounts', () => {
describe('agentPreset.list', () => {
it('marks the default and carries each preset\'s trust', async () => {
const { api } = await harness(['standard', 'core-web'])
const { api } = await harness(['standard', 'minimal'])
const response = await api.agentPresets.list(request({}))
@@ -265,7 +265,7 @@ describe('agentPreset.list', () => {
if (!response.result.ok) throw new Error('unreachable')
expect(response.result.value.presets).toEqual([
{ id: 'standard', trust: 'system', isDefault: true },
{ id: 'core-web', trust: 'system', isDefault: false },
{ id: 'minimal', trust: 'system', isDefault: false },
])
expect(response.result.value.authorable).toBe(true)
})
@@ -288,26 +288,26 @@ describe('agentPreset.list', () => {
describe('agentPreset.select', () => {
it('recomposes a blank session', async () => {
const { api } = await harness(['standard', 'core-web'])
const { api } = await harness(['standard', 'minimal'])
await api.sessions.create(request({ sessionId: SessionId('sel-1'), agentPreset: 'standard' }))
const response = await api.agentPresets.select(
request({ sessionId: SessionId('sel-1'), agentPreset: 'core-web' }))
request({ sessionId: SessionId('sel-1'), agentPreset: 'minimal' }))
expect(response.result.ok).toBe(true)
if (!response.result.ok) throw new Error('unreachable')
expect(response.result.value.agentPreset).toBe('core-web')
expect(response.result.value.agentPreset).toBe('minimal')
})
it('refuses once the conversation has started', async () => {
const { api, ctx } = await harness(['standard', 'core-web'])
const { api, ctx } = await harness(['standard', 'minimal'])
await api.sessions.create(request({ sessionId: SessionId('sel-2'), agentPreset: 'standard' }))
// One turn is enough: the history from here on was produced under
// `standard`'s tools, and a swap would strand those tool calls.
ctx.sessions.get(SessionId('sel-2'))?.append('turn/start', { turn: 0 })
const response = await api.agentPresets.select(
request({ sessionId: SessionId('sel-2'), agentPreset: 'core-web' }))
request({ sessionId: SessionId('sel-2'), agentPreset: 'minimal' }))
expect(response.result.ok).toBe(false)
if (response.result.ok) throw new Error('unreachable')

View File

@@ -360,14 +360,14 @@ describe('settings domain', () => {
ctx.settings.register(settingsNamespace('agent-presets'), z.object({ default: z.string() }))
const api = createApiProxy(ctx, DEFAULTS)
expectOk(await api.settings.update(request({ ns: 'agent-presets', patch: { default: 'core-web' } })))
expectOk(await api.settings.update(request({ ns: 'agent-presets', patch: { default: 'minimal' } })))
// Both browser surfaces that offer the choice — the General row and the
// management section — write the default through `settings.update`, so a
// namespace outside this boundary makes the picker move and then silently
// forget, which is worse than refusing the control.
expect(ctx.settings.describe().find(view => String(view.ns) === 'agent-presets')?.value)
.toEqual({ default: 'core-web' })
.toEqual({ default: 'minimal' })
})
it('refuses even a model-provider namespace once its directory entry is gone', async () => {

View File

@@ -369,8 +369,8 @@ describe('unary round trip (handler ⇄ client, no network)', () => {
expect((await c.agentPresets.list({})).result).toEqual({
ok: true, value: { presets: [], authorable: false },
})
expect((await c.agentPresets.select({ sessionId: 's' as never, agentPreset: 'core-web' })).result)
.toEqual({ ok: true, value: { agentPreset: 'core-web' } })
expect((await c.agentPresets.select({ sessionId: 's' as never, agentPreset: 'minimal' })).result)
.toEqual({ ok: true, value: { agentPreset: 'minimal' } })
expect((await c.agentPresets.read({ agentPreset: 'mine' })).result).toEqual({
ok: true, value: { agentPreset: 'mine', trust: 'user', content: '', writable: true },
})

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write packages/preset/README.md
README.md: d6d51b4d83a85fed3f690334e9f5704567da83a9
README.zh.md: 6485caa03856e24232648dc36dfbc07d1d6aa6f8
README.md: 997e64c75e36d6a70438a92c223dea93eab5eb32
README.zh.md: 946b98a7b1366facec37360c9f39ca36cc908872

View File

@@ -9,7 +9,7 @@ An **agent preset** is a directory holding one `agent.cordis.yml`. Mounting it u
| `agent-presets/` | Preset vocabulary, filesystem discovery over trusted and user-authored roots, and the guarded per-agent mount | `ctx.agentPresets` |
| `persona/` | The agent persona as a composable row, so a preset can change identity and not only tools | — |
The deployment ships `standard` (the full coding agent), `core-web` (a two-tool benchmark surface), and `cordis` (the standard agent plus the self-referential toolset and a composition-authoring skill, so a person can ask an agent to author another agent).
The deployment ships `standard` (the full coding agent), `minimal` (a two-tool benchmark surface), and `cordis` (the standard agent plus the self-referential toolset and a composition-authoring skill, so a person can ask an agent to author another agent).
The composition split this group assumes: registries and cross-session facilities are process singletons and stay in the host composition, while a preset carries what one agent contributes to them. A preset that names a row publishing a process-global service is rejected at mount rather than allowed to collide with the next session.

View File

@@ -9,7 +9,7 @@
| `agent-presets/` | preset 词汇、在受信任目录与用户自建目录上的文件系统发现,以及带校验的按 agent 挂载 | `ctx.agentPresets` |
| `persona/` | 把 agent 人设做成可组装的行,使 preset 不止能改工具、也能改身份 | — |
部署随附三个 preset`standard`(完整编码 agent`core-web`(两个工具的 benchmark 表层),以及 `cordis`(标准 agent 加上自指工具集与一份组装创作 skill使人可以让 agent 去创作另一个 agent
部署随附三个 preset`standard`(完整编码 agent`minimal`(两个工具的 benchmark 表层),以及 `cordis`(标准 agent 加上自指工具集与一份组装创作 skill使人可以让 agent 去创作另一个 agent
本组假定的组装划分是注册表与跨会话设施是进程单例留在宿主组装中preset 只承载单个 agent 对它们的贡献。若 preset 中某一行发布了进程级全局服务,挂载时即被拒绝,而不是留到与下一个会话相撞。

View File

@@ -82,7 +82,7 @@ When a settings provider is composed, this plugin registers the `agent-presets`
```yaml
agent-presets:
default: core-web
default: minimal
```
The value is read per resolution rather than snapshotted, so a hot-reloaded document takes effect on the next session created and every running session stays on the preset it was composed from. Clearing the user field re-inherits the composition default. A default naming a preset no root supplies is stored without complaint and fails at the next `resolve()` — the roster is a live directory, so a name absent now may exist by the time a session asks for it.

View File

@@ -82,7 +82,7 @@ description: 只向模型呈现 bash 与 str_replace_editor适合 benchmark
```yaml
agent-presets:
default: core-web
default: minimal
```
该值在每次解析时读取而非快照,因此热重载的文档对**此后创建**的会话生效,而每个运行中的会话仍停留在它当初据以组装的 preset 上。清空用户字段即重新继承组装默认值。若默认值指向没有任何根目录提供的 preset写入时不会报错而在下一次 `resolve()` 时失败——名单是一个活动目录,此刻不存在的名字,等到某个会话真正索取时可能已经存在。