deepseek-ai/deepseek-harness · error · Error
tools.presentAs() requires a scoped context (agent.ctx): a…
Error message
tools.presentAs() requires a scoped context (agent.ctx): a context-global presentation is the `mode` config field on the tools row
What it means
API guard on ToolsService.presentAs(): it was called from a context-global (non-scoped) context. Presentation declarations are scoped per agent chain so a preset's declaration covers its agents; a process-global presentation would instead be the tools row's `mode` config field, so the scoped-only entry point refuses global contexts.
Solutions
- Call presentAs() from an agent-scoped context (agent.ctx), e.g. inside an agent plugin or preset
- For a process-wide default, set the `mode` config field on the tools row in cordis.yml instead of calling presentAs()
Defensive patterns
Strategy: validation
When it happens
Trigger: Thrown at packages/core/tools/src/index.ts:949 when the library encounters an invalid state.
Common situations: See trigger scenarios.
AI-assisted analysis of deepseek-ai/deepseek-harness@b150a551b8 (2026-08-24).
Data as JSON: /api/errors/89e192bbfe9c6aa2.
Report an issue: GitHub.
Appendix: source
Thrown at packages/core/tools/src/index.ts:949
})
return this.codeTransport
}
/**
* Present the calling scope's tools in `mode` instead of the deployment
* default. Nearest scope on the chain wins, so a preset's standing
* declaration covers every agent joined under it.
*
* Scoped only, and one declaration per scope: this is how an agent preset
* composes Code Mode agents beside native ones in the same process, and a
* process-global override would be the `mode` config field instead.
* @param mode - the presentation the covered agents' models see.
* @returns the exact disposer that restores the deployment default.
*/
presentAs(mode: ToolPresentationMode): () => void {
const ctx = this.ctx
if (scopeOf(ctx) === undefined) {
throw new Error('tools.presentAs() requires a scoped context (agent.ctx): a context-global presentation is the `mode` config field on the tools row')
}
const dispose = ctx.effect(function* (this: ToolRuntime) {
yield this.layers.effect(
ctx,
(layer) => {
if (layer.mode !== undefined) {
throw new Error(`tools.presentAs("${mode}") conflicts with "${layer.mode}" already declared for this scope; one composition selects one presentation`)
}
layer.mode = mode
return () => { layer.mode = undefined }
},
{ label: 'tools.presentAs()' },
)
// The SDK and collapse sections are per scope for the same reason the
// mode is. Under a deployment that already defaults to a code mode this
// shadows the global registration with an identical body, which costs
// nothing and keeps one rule instead of a case analysis.
if (mode !== 'native') {View on GitHub (pinned to b150a551b8)