{"record":{"id":"89e192bbfe9c6aa2","repo":"deepseek-ai/deepseek-harness","slug":"tools-presentas-requires-a-scoped-context-agent","errorCode":null,"errorMessage":"tools.presentAs() requires a scoped context (agent.ctx): a context-global presentation is the `mode` config field on the tools row","messagePattern":"tools\\.presentAs\\(\\) requires a scoped context \\(agent\\.ctx\\): a context-global presentation is the `mode` config field on the tools row","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/core/tools/src/index.ts","lineNumber":949,"sourceCode":"    })\n    return this.codeTransport\n  }\n\n  /**\n   * Present the calling scope's tools in `mode` instead of the deployment\n   * default. Nearest scope on the chain wins, so a preset's standing\n   * declaration covers every agent joined under it.\n   *\n   * Scoped only, and one declaration per scope: this is how an agent preset\n   * composes Code Mode agents beside native ones in the same process, and a\n   * process-global override would be the `mode` config field instead.\n   * @param mode - the presentation the covered agents' models see.\n   * @returns the exact disposer that restores the deployment default.\n   */\n  presentAs(mode: ToolPresentationMode): () => void {\n    const ctx = this.ctx\n    if (scopeOf(ctx) === undefined) {\n      throw new Error('tools.presentAs() requires a scoped context (agent.ctx): a context-global presentation is the `mode` config field on the tools row')\n    }\n    const dispose = ctx.effect(function* (this: ToolRuntime) {\n      yield this.layers.effect(\n        ctx,\n        (layer) => {\n          if (layer.mode !== undefined) {\n            throw new Error(`tools.presentAs(\"${mode}\") conflicts with \"${layer.mode}\" already declared for this scope; one composition selects one presentation`)\n          }\n          layer.mode = mode\n          return () => { layer.mode = undefined }\n        },\n        { label: 'tools.presentAs()' },\n      )\n      // The SDK and collapse sections are per scope for the same reason the\n      // mode is. Under a deployment that already defaults to a code mode this\n      // shadows the global registration with an identical body, which costs\n      // nothing and keeps one rule instead of a case analysis.\n      if (mode !== 'native') {","sourceCodeStart":931,"sourceCodeEnd":967,"githubUrl":"https://github.com/deepseek-ai/deepseek-harness/blob/b150a551b8d465e31e418e1b2eaf5e79bbb7d28e/packages/core/tools/src/index.ts#L931-L967","documentation":"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.","triggerScenarios":"Thrown at packages/core/tools/src/index.ts:949 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","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()"],"exampleFix":null,"handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"b150a551b8d465e31e418e1b2eaf5e79bbb7d28e","analyzedAt":"2026-08-24T18:12:29.105Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}