Skip to content

[docs] ai/agents.mdx documents the retired agent.knowledge key as live RAG access #10730

Description

@claude

Found while implementing #10355 (the sibling agent.tools row on the same table). Out of that card's scope — it names tools only — so filed rather than fixed there, following that card's own rider to sanity-check the remaining field-table rows against AgentSchema and report drift rather than widen.

The defect

content/docs/ai/agents.mdx — the "The shape of an agent" field table — carries this row:

| `knowledge` | RAG access: `{ sources: string[], indexes: string[] }`. `sources` is the only key; the `topics` alias was removed in protocol 17 (#3855) — `os migrate meta --from 16` rewrites it |

It documents knowledge as a live authoring field and notes only that a nested alias was removed. But the entire key is retired. packages/spec/src/ai/agent.zod.ts:

knowledge: retiredKey(
  '`agent.knowledge` was removed in @objectstack/spec 17.0.0 (#3896 audit close-out) — '
  + 'declaring knowledge sources/indexes on an agent never scoped retrieval: the '
  + "`search_knowledge` tool takes `sourceIds` from the LLM's tool-call arguments, not from "
  + 'the agent record. Delete the block. ...',
),

retiredKey is z.never(): the key types as never (so it fails tsc at the authoring site) and any value that reaches the runtime is rejected at parse with the prescription. The row teaches a key that cannot be written.

The page already contradicts itself

The Sales Assistant example further down the same file states the correct rule in a code comment:

// (There is no agent-level `knowledge` block — it was removed in protocol
// 17 (#3896 close-out): declaring sources never scoped retrieval. Restrict
// access at the knowledge-service/source level; describe intended
// grounding in `instructions`.)

Why it matters beyond a stale row

Same shape as the tools case: the key read as a security control and was not one. Declaring sources / indexes on an agent never scoped retrieval — search_knowledge takes sourceIds from the LLM's own tool-call arguments, not from the agent record. An author who believed they had scoped retrieval here scoped nothing, which is the dangerous direction.

Suggested repair

Replace the row with the retirement and its prescription, taken from the tombstone's own words: delete the block; restrict retrieval at the knowledge-service / source level (per-source permissions); describe intended grounding in instructions. The topics sub-alias is moot once the parent key is gone, so the os migrate meta clause currently attached to it should not survive as-is.

⚠️ Note for whoever picks this up: this row is currently cited as the page's house-style precedent for documenting a retirement. It is the form that is worth copying, not its content.


Generated by Claude Code

Activity

  1. added theissue type on Aug 21, 2026
  2. os-zhuang commented on Aug 21, 2026

    @os-zhuang
    Contributor

    Triage: lands in content/docs/ai/agents.mdx (field-table row); pm:queue · domain:devx · type Task. Rationale: the row documents a retiredKey (z.never()) as live RAG access, and the dangerous direction is real — an author who believes they scoped retrieval scoped nothing; the page already carries the correct rule in its Sales Assistant example, so this is convergence, not a ruling. Serial constraint (fold-or-serial answered): SERIAL — #10355 is in-flight on the same file (its tools row; this card exists precisely because that dispatch kept knowledge out of scope). Dispatch only after #10355's PR lands, and require a same-day re-read of the file's current main state before editing. Folding into #10355 is not available — that dispatch's file surface is closed and its dev already declined the widening.

    Size/model suggestion: S, mode:subagent, sonnet; Clause-②: no.


    Generated by Claude Code

  3. claude commented on Aug 21, 2026

    @claude
    ContributorAuthor

    Claim — PM dispatch (devx seat)

    • Session: c970724d-303c-5614-9d20-a3f92205cfad
    • Branch: claude/issue-10730-agent-knowledge-retired

    Serial constraint cleared: PR #10732 (#10355), which held content/docs/ai/agents.mdx,
    merged at 2026-08-21T10:29:41Z.

    Premise re-verified on origin/main = 78ac958552, both sides:

    ⭐ And the page already contradicts itself, 64 lines further down. :275-278, inside the
    worked example:

    "(There is no agent-level knowledge block — it was removed in protocol 17 (#3896
    close-out): declaring sources never scoped retrieval. Restrict access at the
    knowledge-service/source level; describe intended grounding in instructions.)"

    So the correct prescription is already written on this page — the field table simply never
    got it. That makes this a smaller and better-evidenced edit than it looked: the wording does not
    have to be invented, only moved into the row.

    ⚠️ This row is worse than the tools row #10732 just fixed, because it hands the reader a
    migration command for a key that no longer exists
    . agent.tools' tombstone at least says
    "NOT a rename"; this one points at os migrate meta --from 16, which cannot help.

    For the record: this card exists because the #10355 dispatch mirrored this row's form as
    house style on my instruction, noticed its content was itself defective, and reported it
    rather than copying it. My brief held up a broken example as the standard.


    Generated by Claude Code

  4. claude commented on Aug 21, 2026

    @claude
    ContributorAuthor
    {
      "issue": 10730,
      "status": "done",
      "branch": "claude/issue-10730-agent-knowledge-retired",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/10768",
      "premise_still_valid": true,
      "summary": "Premise verified on both sides at origin/main = 78ac958552 (unchanged across two fetches): packages/spec/src/ai/agent.zod.ts:248 is `knowledge: retiredKey(...)`, and content/docs/ai/agents.mdx:211 still taught the key as live RAG access while pointing at `os migrate meta --from 16` to 'rewrite it'. Rewrote that single table row from the tombstone's own words, following the shape of the sibling `tools` row (PR #10732): removal + protocol/issue, `never` typing, explicitly not a rename, the REASON the key is gone (declaring sources never scoped retrieval - `search_knowledge` takes `sourceIds` from the LLM's tool-call arguments, not the agent record), and the prescription (delete the block; restrict at the knowledge-service/source level via per-source permissions; describe intended grounding in `instructions`). One file, one row, 1 insertion / 1 deletion. TWO CORRECTIONS TO THE BRIEF, neither blocking: (1) the brief warned of a new gate `check-doc-frontmatter.mjs` landed on main 'an hour ago' - it does not exist anywhere in the tree; `git ls-tree -r --name-only origin/main | grep -i frontmatter` returns nothing and origin/main was still 78ac958552 on re-fetch. (2) The brief said to offer no `os migrate meta` command; the tombstone DOES name one verbatim, but as a LISTER ('to list the mechanical edits for existing sources; apply them by hand'), not a rewriter - see open_questions, I omitted it and flagged it in the PR body.",
      "tests": "Gate set derived with `node scripts/pm/dispatch-gates.mjs` (no paths, per instruction - it derived the changeset itself: '1 path(s) vs merge base 78ac95855', committed 1 / working tree 0 / untracked 0). It returned 13 families, not the 4 the brief predicted, so 9 were added beyond the brief. Full union re-run AFTER the final commit, on `git rev-parse --short HEAD` = 934022ee4a, all 14 green (exit codes captured with redirect-then-capture, never through a pipe): check:cross-package-test-inputs 0 | check:doc-anchors 0 | check:doc-authoring 0 | check:doc-formula-expressions 0 | check:docs-audit-scope 0 | check:docs-redirects 0 | check:published-readme-links 0 | check:role-word 0 | check:empty-state 0 | check:liveness 0 | check:strictness-ledger 0 | check:variant-docs 0 | check-cross-package-test-inputs.mjs 0 | check:nul-bytes 0. Gates' own verdict lines: 'check-doc-anchors: 272 internal #fragment link(s) across 408 source file(s) all resolve to a real heading'; 'doc authoring guard: 389 files clean - no bare metadata literals'; 'docs-accuracy-audit scope is in sync with content/docs/: 189 hand-written doc(s)'; 'check-role-word: OK, no new occurrences of the reserved word'; 'check-nul-bytes: OK (scanned 6234 text file(s) ... no raw ASCII control bytes)'. check:doc-formula-expressions was RED on first run purely from an unbuilt dependency closure in the fresh worktree (ERR_MODULE_NOT_FOUND for @objectstack/formula/dist) - fixed by `pnpm --workspace-concurrency=2 --filter '@objectstack/lint^...' build`, then green: '22 record-scoped formula example(s) across 416 files / 1443 TS blocks judged clean'. All heavy commands ran through `bash scripts/pm/os-verify-lock.sh -c` (VERDICT command-exit 0 each; longest hold 128s for the closure build, longest wait 169s). MUTATION CONFIRMED ON DISK, both directions, before trusting anything: old-row anchor count 1 -> 0 and new-row anchor count 0 -> 1, asserted in the edit script itself (it aborts without writing unless the old anchor matches exactly once); file line count unchanged at 391, `git diff --stat` = 1 file / 1 insertion / 1 deletion. check:skill-examples deliberately NOT run - verified for my own hunk rather than inherited: the only `{/* os:check */}` fence on the page is at line 247, below my hunk at 211 and outside it. No ablation applies (docs-only, no guard under test).",
      "open_questions": [
        {
          "question": "The brief said to offer no `os migrate meta` command for `knowledge`, with the escape hatch 'if the tombstone names a real conversion, quote it exactly'. The tombstone DOES name one, verbatim: 'Run `os migrate meta --from 16` to list the mechanical edits for existing sources; apply them by hand.' That is a lister, not a rewriter - and it is the identical boilerplate sentence carried by the `tools` tombstone. The old row's false half was the claim that it 'rewrites it'. Include it framed as a lister, or omit it?",
          "options": [
            "A - Omit the command entirely (what I shipped). Matches the sibling `tools` row, which omits it despite its tombstone carrying the same sentence; avoids implying an asymmetry where `knowledge` has a migration path `tools` lacks. The row instead says plainly there is no key the value moves to and the block is deleted.",
            "B - Include it quoted exactly, framed as a lister that emits by-hand edits. Maximally faithful to the tombstone and directly corrects the 'rewrites it' claim rather than leaving a silent gap - but breaks symmetry with the adjacent `tools` row unless that row is amended too, which is out of this card's scope."
          ],
          "recommendation": "A, because the two rows sit adjacent in one field table and their tombstones carry the same sentence, so an asymmetry between them would itself teach something false. B is defensible and a one-line follow-up if the reviewer prefers it - but it should then be applied to BOTH rows, which is a separate card. Flagged in the PR body so the reviewer can rule without reading the tombstone."
        },
        {
          "question": "Should the `topics` / #3855 fact survive in the row now that the parent key is gone? (The brief asked me to decide and say what I decided.)",
          "options": [
            "A - Drop it from the row (what I shipped).",
            "B - Keep it as a historical note alongside the retirement."
          ],
          "recommendation": "A. It remains historically true, and the protocol-17 `agent-knowledge-topics-to-sources` conversion does remain in the migration chain - the header comment in agent.zod.ts says it rewrites historical SOURCES and imports nothing from the removed schema. But for an author, a nested alias of a key that cannot be written at all is unreachable information, and leading with the alias is exactly what made the old row imply the parent was live. The conversion-chain fact stays recorded where it belongs (agent.zod.ts + the ADR-0087 registries), not in an authoring field table. Note #3855 (topics alias removal) and #3896 (the key's own removal) are distinct and both real; the old row cited only the first, the new row cites #3896."
        }
      ],
      "out_of_scope_findings": []
    }

    No third retired key found on this page — checked rather than assumed. AgentSchema
    carries exactly two retiredKey( entries: tools (line 231, fixed by #10732) and
    knowledge (line 248, this PR). The other two removed agent fields, tenantId and
    visibility, were deleted outright rather than tombstoned, and neither appears anywhere in
    content/docs/ai/agents.mdx (grep: no match). Every remaining row in the field table —
    surface, role, instructions, model, skills — is live in the schema. Nothing filed.

    Row before:

    | `knowledge` | RAG access: `{ sources: string[], indexes: string[] }`. `sources` is the only key; the `topics` alias was removed in protocol 17 (#3855) — `os migrate meta --from 16` rewrites it |
    

    Row after:

    | `knowledge` | **Removed in protocol 17 (#3896)** — typed `never`, so writing it fails `tsc`, and a value that reaches the runtime is rejected at parse. Declaring `sources` / `indexes` here never scoped retrieval: `search_knowledge` takes `sourceIds` from the LLM's own tool-call arguments, not from the agent record, so an author who "scoped" access here scoped nothing. This is **not** a rename: there is no key the value moves to — delete the block. Restrict retrieval at the knowledge-service / source level (per-source permissions), and describe intended grounding in `instructions` so the model asks for the right sources |
    

    Re-read after the edit: the row and the :275-278 example comment now agree on all four
    points — removed in protocol 17 (#3896), declaring sources never scoped retrieval, restrict
    at the knowledge-service/source level, describe grounding in instructions.

    skip-changeset applied additively (POST /issues/10768/labels, never a whole-set write)
    and read back after the bots ran: ['documentation', 'size/xs', 'skip-changeset'] — the
    bots added the first two, mine survived.

    Worktree removed without --force (git status --porcelain clean first); sibling agents'
    worktrees untouched.


    Generated by Claude Code


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions