Skip to content

fix(cli): retire the agent generator — os g agent now names ADR-0063 and points at skills - #11028

Merged
os-elon merged 2 commits into
mainfrom
claude/issue-10359-retire-agent-generator
Aug 22, 2026
Merged

fix(cli): retire the agent generator — os g agent now names ADR-0063 and points at skills#11028
os-elon merged 2 commits into
mainfrom
claude/issue-10359-retire-agent-generator

Conversation

@os-elon

@os-elonos-elon commented Aug 22, 2026

Copy link
Copy Markdown
Collaborator

Part of #10359 — deliberately Part of, not a closing keyword. The maintainer's ruling was Option A (retire the agent generator and add a skill generator) with explicit latitude to split: "The implementer is permitted to split the skill-generator half into its own card if that scopes better; the retirement half proceeds either way." This PR is the retirement half. The skill-generator half is filed as #11025 with the ADR-0063 context, not dropped. Whether #10359 closes on this merge or stays open until #11025 lands is the PM's call, so this PR does not decide it.

Placeholder spelling in this body. GitHub's body sanitizer strips short angle-bracket fragments — including inside code fences and inline code, measured on the first revision of this very body, where four of them vanished. So every placeholder below is spelled in CAPS (NAME, TYPE) where the real artifact writes it in angle brackets.

What was wrong

os g agent NAME scaffolded a typed AI.Agent into src/agents/. ADR-0063 §2 (reversing ADR-0040 §3) withdrew that surface: the kernel ships exactly two agents, ask and build, bound by surface, and the runtime catalog filters out every non-platform agent record. So the generated file parsed, passed os validate, published without complaint, and then never appeared. No error at any step. That is the silent-strip failure mode the #4001 campaign spent itself closing, arriving through the scaffolder instead of through a schema key.

The retirement is loud, and it names the replacement

Deleting the roster entry alone would have moved the silence one step earlier rather than ending it: Unknown type: agent plus the surviving roster tells an author their spelling is not on the list, and the natural next move is to hunt for the right spelling of something that no longer exists. So agent moves into a RETIRED_GENERATORS ledger whose entries owe both halves — the decision that withdrew the surface, and the surface to author instead. Real output, from the CLI as a user runs it:

$ os g agent support ; echo "EXIT=$?"
◆ Generate
────────────────────────────────────────
✗ `os g agent` was retired — agents are platform-internal (ADR-0063 §2).
The kernel ships exactly two agents, `ask` and `build`, bound by surface.
An agent you author still parses and still publishes — and the runtime
catalog then filters it out, so it never appears and nothing tells you.
This command scaffolded exactly that file, so it is retired, not repaired.
Author a SKILL instead. Skills (plus tools / MCP) are the third-party
extension primitive ADR-0063 names — the live surface this one was not.
There is no `os g skill` scaffolder yet. Write the file by hand:
src/skills/NAME.skill.ts
import { defineSkill } from '@objectstack/spec/ai';
Docs: https://objectstack.ai/docs/ai/agents
EXIT=1

Nothing is written — no src/agents/, no barrel index.

Every advertising site, swept — not repaired by line number

The card cited one doc row. There were six live sites, and the sweep found two the card did not name:

#SiteWas
1packages/cli/src/commands/generate.tsthe agent entry in GENERATORS, defaultDir: 'src/agents'
2packages/cli/src/commands/generate.tsthe type argument's own description string, listing agent in the roster
3packages/cli/README.mdAvailable generate types: … , 'agent', …
4content/docs/deployment/cli.mdxos g agent support # Generate an AI agent in the os generate example block
5content/docs/deployment/cli.mdxthe agent / src/agents/ row of the Available types table (the row the card cited)
6content/docs/deployment/cli.mdxos g agent sales-assistant in the Typical Workflow block — not named anywhere on the card

Sites 2 and 3 were found by grepping the roster strings rather than the command spelling; site 6 by sweeping content/** for the command. Retiring the generator while any of them stood would have shipped documentation for a command that no longer exists — the same defect class this change is closing.

content/docs/deployment/cli.mdx now carries a retirement Callout in place of the row, so a reader arriving from an old tutorial gets the same explanation the CLI gives, rather than finding a silently shorter table.

One bounded in-place repair, declared

content/docs/getting-started/quick-start.mdx's project-layout map — the first table a new reader meets — listed the AI area as "Agents, tools, RAG, MCP exposure (src/agents)", naming the withdrawn surface as the directory an app author writes AI metadata into. It now reads "Skills, tools, RAG, MCP exposure (src/skills)".

This is one line outside the card's stated file surface, taken in place because all four conditions held: same defect class (advertising the ADR-0063-withdrawn agent-authoring surface, and specifically the very directory the retired generator wrote into); the correct shape is already pinned by existing repo evidence (content/docs/ai/index.mdx, content/docs/ai/skills-reference.mdx and content/docs/concepts/metadata-lifecycle.mdx all state that agents are platform-owned and skills are the third-party extension primitive); no other PR holds that file (checked against the three open PRs); and it is inside the same docs gate family this PR already runs. Named here rather than folded in silently.

ADR anchor

scripts/adr-anchors/packages__cli__src__commands__generate.ts.json pins ADR-0063 to generate.ts, so the absence of an agent generator reads to the next author as a decision rather than an oversight — and so does the requirement that the refusal keep pointing at skills. Per Prime Directive #13.

Non-vacuity — reverse-verified in both directions

packages/cli/test/generate-agent-retired.e2e.test.ts spawns the real CLI through bin/run-dev.js and asserts on a real child process's exit status and stdout, for the reasons invocation-loudness.e2e.test.ts documents (a process.exitCode inside a vitest worker is not an exit status; utils/format.ts prints through console.log). It pins the content of the refusal, not only the absence from the roster — a bare "unknown generator" passes every "the type is gone" assertion and fails this file.

Predicted direction before running: red. Observed, with packages/cli/src/commands/generate.ts restored to origin/main and everything else left in place:

 × fails instead of scaffolding — a CI script that still calls it stops
× says the command was RETIRED, not that the type is unrecognised
× names the decision that withdrew the surface
× names the two platform agents and the silent strip, so the WHY is in the message
× points the author at SKILLS — the half a bare removal would drop
× writes nothing — no `src/agents/`, no barrel index
× omits `agent` from the available types an unknown spelling prints
Tests 7 failed | 2 passed (9)

The failure diff literally reproduces the defect — ✓ Created src/agents/support.ts / ✓ Created src/agents/index.ts. The two survivors are the deliberate controls (the surviving roster still lists six types; os g object … --dry-run still previews).

Restored: 9 passed (9).

No rebuild was needed on either leg, and that is a property of this pair rather than an omission: bin/run-dev.js runs packages/cli/src/** through tsx directly, so the mutated subject is the source the spawn executes — there is no dist/ between the edit and the measurement. The workspace closure the CLI's dependencies resolve through was built before both legs.

Release discipline

minor on @objectstack/cli. This removes a published capability, so patch would understate it; major is refused by scripts/check-changeset-no-major.mjs, whose header states the convention this follows — during the launch window breaking changes ship as minor under pre-1.0 lockstep semantics. Same level as the closest precedent in the pending stock, http-request-errors-total-retired (a published-capability retirement).

The changeset declares **BREAKING** and leads with the consequence, so it reaches the release notes rather than being buried: a script, Makefile or CI step that runs os g agent now exits 1. The ruling accepted that outcome (「响亮,但会报」).

Retirement protocol / registry — does one apply here? No, and the changeset says so in writing rather than staying silent. The ADR-0087 disposition is not-required (no-migration-prescription), verified green by scripts/check-adr-0087-registration.mjs. The reasoning: a CLI command name is an invocation surface, not authorable metadata — no authorable key, no sys_metadata row, no schema to tombstone — so there is nothing for objectstack migrate meta to rewrite, nothing for spec-changes.json to project, and no FROM→TO spelling for the upgrade guide, because a skill is a different artifact rather than a renamed agent and no replacement command exists to rewrite the call into. That is also the difference from http-request-errors-total-retired, where the ledger was the only channel (a Grafana panel silently drew a flat zero): here the command itself refuses at the point of use. Same reasoning shape as ADR-0087's D7 addendum, one surface over — there the compiler carries the notice, here the CLI does. The spec-property-retirement skill is likewise out of scope: it governs authorable packages/spec properties under ADR-0049 enforce-or-remove, and this diff touches no packages/spec file.

Related, deliberately out of scope here

Verification

Every line below is a verdict the gate printed, at 567a1a036c (this PR's head; the union was derived and run after the final commit). Exit codes were captured before any pipe.

Gate family derived from the real change setnode scripts/pm/dispatch-gates.mjs with no path arguments, so the 7 changed paths come from the merge base rather than from a hand-written list.

CheckVerdict line
pnpm --filter @objectstack/cli exec vitest runTest Files 153 passed (153) · Tests 1691 passed (1691)
pnpm --filter @objectstack/cli typechecktsc --noEmit, exit 0, no diagnostics
eslint (both changed TS files)errors= 0 warnings= 0 on each, confirmed via --format json so the run is not vacuous
pnpm check:type-check-debtcheck-type-check-coverage --re-measure: OK — 33 ledger entr(ies) re-measured in 262.0s, 1908 raw tsc error(s) total, none above its recorded number. — the new test file lands in this package's TEST_DEBT layer, recorded 146 "exactly, no bootstrap margin", and adds nothing to it
pnpm check:type-check-coverageexit 0
node scripts/check-adr-0087-registration.mjs✓ 1 declared-breaking changeset(s), each carrying an ADR-0087 disposition.
node scripts/check-changeset-no-major.mjs✓ This diff introduces no major bump.
node scripts/check-empty-changeset.mjs✓ No empty-frontmatter changeset introduced by this diff (1 declaring changeset(s) added).
pnpm check:changeset-gate-self-testsexit 0
pnpm check:objectui-changesetexit 0
pnpm check:adr-anchorscheck-adr-anchors: OK (52 anchored file(s), every governing ADR still referenced; …)
pnpm check:doc-anchors✅ 279 internal #fragment link(s) across 407 source file(s) all resolve to a real heading
pnpm check:doc-authoring✓ doc authoring guard: 389 files clean — no bare metadata literals.
pnpm check:docs-audit-scope✓ docs-accuracy-audit scope is in sync with content/docs/: 189 hand-written doc(s).
pnpm check:docs-redirectscheck-docs-redirects: OK (apps/docs/redirects.mjs: 92 entries …)
pnpm check:published-readme-links✓ 175 outbound link(s) across 60 published markdown file(s): … 102/102 relative target(s) found in the tree.
node scripts/check-doc-frontmatter.mjs✓ 402 page(s) under content/docs parse with yaml@2.9.0 …
pnpm check:role-wordexit 0
pnpm --filter @objectstack/spec run check:skill-examples✅ 227 prose examples type-check against @objectstack/spec
pnpm --filter @objectstack/lint run check:doc-security-posture✅ 22 ObjectSchema.create example(s) in 191 marked block(s) across 189 docs file(s) carry an os validate-clean security posture
pnpm --filter @objectstack/lint run check:doc-formula-expressionsexit 0
node scripts/docs-audit/check-affected-docs.mjsexit 0 (✓ affected-docs self-test: 339 cases pass.)
pnpm --filter @objectstack/spec run check:liveness / check:empty-state / check:strictness-ledger / check:variant-docsexit 0 each
pnpm check:nul-bytescheck-nul-bytes: OK (scanned 6378 text file(s) … no raw ASCII control bytes).
pnpm check:cross-package-test-inputsOK: 13 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.
pnpm check:test-source-aliasexit 0
pnpm check:engine-double-contract · check:where-matcher · check:query-options-erasureexit 0 each (convention-triggered by the new test file)
pnpm check:entry-guard · check:parse-guard · check:slot-lookup · check:type-source-resolution · check:ci-filter-parity · check:plugin-teardown-shapeexit 0 each

No gate was weakened: no threshold moved, no baseline raised, no test skipped or quarantined. The check:type-check-debt run reports one pre-existing surplus on @objectstack/plugin-auth (records 109, measures 97) that this PR neither created nor touches.


Generated by Claude Code

`os g agent` scaffolded into `src/agents`, a surface ADR-0063 §2 withdrew. The
kernel ships exactly two agents (`ask`, `build`) and the runtime catalog filters
out every other agent record, so the generated file passed `os validate`,
published without complaint, and then never appeared — no error at any step.
That is the silent-strip failure mode, arriving through the scaffolder.
Deleting the roster entry alone would have moved the silence one step earlier
rather than ending it: `Unknown type: agent` plus the surviving roster tells the
author their spelling is not on the list, and the natural next move is to hunt
for the right spelling of something that no longer exists. So `agent` moves to a
`RETIRED_GENERATORS` ledger whose entries owe both halves — the decision that
withdrew the surface, and the surface to author instead. The refusal names
ADR-0063, the two platform agents, the silent strip, and skills
(`src/skills/<name>.skill.ts`, `defineSkill`) as the live extension primitive,
and it exits 1 so a CI script that still calls the command stops.
Advertising sites swept, not fixed by line number: the generator entry and the
`type` argument's roster string in `generate.ts`, `packages/cli/README.md`'s
"Available generate types" line, and three sites in
`content/docs/deployment/cli.mdx` (the `os g agent support` example, the
`agent` / `src/agents/` row of the Available types table, and
`os g agent sales-assistant` in Typical Workflow — the last of which the card
did not name). `content/docs/getting-started/quick-start.mdx`'s project-layout
map advertised `src/agents` as the AI area's authoring directory; it now names
`src/skills`, which is what ADR-0063 says an app author writes.
`scripts/adr-anchors/packages__cli__src__commands__generate.ts.json` pins
ADR-0063 to the file, so the absence of an `agent` generator reads as a decision
rather than an oversight to the next author.
`os g skill` is deliberately NOT in this change — see the follow-up card. The
message says the scaffolder does not exist yet rather than promising it.
Part of #10359
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019bmVFqoQPq63zhKrxdYG1r
…d about it
Declares the removal as breaking (shipped `minor` under the launch-window
convention) and states the consequence a release-notes reader needs: a CI step
that still calls `os g agent` starts exiting 1.
The ADR-0087 disposition is `no-migration-prescription`, and it is a positive
finding rather than a detector miss: a CLI command name is an invocation
surface with no authorable key, no stored row and no schema tombstone, so no
ledger reader has anything to carry — and unlike the metric retirement that
category is usually weighed against, the ledger is not the only notification
channel here, because the command itself now refuses at the point of use.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019bmVFqoQPq63zhKrxdYG1r
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/cli, touching 3 documentable anchor(s).

3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/api/data-flow.mdx(via os generate (command))
  • content/docs/deployment/cli.mdx(via os generate (command))
  • content/docs/protocol/kernel/lifecycle.mdx(via os generate (command))
What this run could not see
  • 1 changed file(s) yielded no anchor (packages/cli/README.md) — pages documenting those are invisible to this run
  • 9 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 45 of 221 client-bound route-ledger rows — the other 176 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run: node scripts/docs-audit/affected-docs.mjs --bridge-coverage

Coarse fallback — 23 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 8d21f7a76912df531c144f17bc3f5bedd07f79c5packageMentionDocs.

Which tree this was computed on

This run read content/docs from d3ade473e42cbd8ba67a5bf09d064f7448d56ea5 — the merge of head 567a1a036c00fe647f58a160e117318de6e23c9a into base 8d21f7a76912df531c144f17bc3f5bedd07f79c5, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin d3ade473e42cbd8ba67a5bf09d064f7448d56ea5 && git checkout d3ade473e42cbd8ba67a5bf09d064f7448d56ea5
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 8d21f7a76912df531c144f17bc3f5bedd07f79c5 567a1a036c00fe647f58a160e117318de6e23c9a && git checkout -B drift-repro 8d21f7a76912df531c144f17bc3f5bedd07f79c5 && git merge --no-ff 567a1a036c00fe647f58a160e117318de6e23c9a
node scripts/docs-audit/affected-docs.mjs --json 8d21f7a76912df531c144f17bc3f5bedd07f79c5

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 8d21f7a76912df531c144f17bc3f5bedd07f79c5 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@github-actions

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 32567403033 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

失败的 job(日志抽取,best effort):

  • Console Pin Gate — 失败步骤: Build the Console SPA at the pinned objectui SHA

    ✗ Build failed in 6.06s
    

↳ 失败原因 是判读的关键:超时Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

跨 PR 相同签名(24h,按失败测试文件聚合):

  • ⚠️本次没有可用的聚合签名(日志里没有能解析出测试文件名的 FAIL 行)—— 这不是「没有同签名的其他 PR」,是这一轮没测到。跨 PR 聚合本次不可用,请手工比对其他 PR 的同类评论。
  • ⚠️ 24h 评论账本没读完(超过 5 页仍未读到窗口尽头),所以上面的「不同 PR 数」是下界,不是全量。

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 94 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

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

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@os-elon@claude