Skip to content

feat(spec)!: retire agent.lifecycle — a conversation phase is a skill, orchestration is Flow, record transitions are the state_machine rule; the XState StateMachineSchema family leaves with it (#21320) - #21461

Merged
os-zhuang merged 13 commits into
mainfrom
claude/issue-21320-agent-lifecycle-retired
Oct 2, 2026
Merged

os-zhuang merged 13 commits into
mainfrom
claude/issue-21320-agent-lifecycle-retired

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #21320

Part of #20274

Clause-②: yes (narrowing)

What this does

The maintainer ruled D (retire) on objectstack-ai/cloud#2569 (batch #267 item 5, 「其他同意」). This PR carries out that ruling's spec half through the spec-property-retirement playbook.

agent.lifecycle is retired. It was parsed and never read. No runtime, in this repository or in cloud, moved an agent through a declared state or refused an undeclared transition. What it reached for is already served elsewhere:

  • a conversation phase is a skill with its own instructions and tools, selected by triggerConditions (ADR-0064);
  • a multi-step process is a Flow (ADR-0019);
  • a record's status transitions are the state_machine validation rule (ADR-0020).

The XState StateMachineSchema family leaves the package with it, under the card's scope item 3. The census below shows agent.lifecycle was its last authorable consumer. ADR-0020 implementation note 1 had kept the file only for this door.

This run resumed a lost one. The container restarted mid-flight. The predecessor's eight WIP commits survived, but its census and gate results did not. Everything below was re-measured in this run; nothing from before the restart counts as measured. The last section says what was found done and what this run finished.

The retirement kit

  • Tombstone. AgentSchema.lifecycle is now a retiredKey() (packages/spec/src/ai/agent.zod.ts). Its prescription names the three destinations and ends with the house os migrate meta --from 17 sentence. tsc refuses the key, because its input type is never.
  • Form. The agent form drops its lifecycle composite row (agent.form.ts). The four platform-objects *.metadata-forms.generated.ts catalogs lose the row's label and help text in every locale.
  • D2 conversion agent-lifecycle-removed. It runs at step 18 with retiredFromLoadPath, retiredAfter 17.6.0 and order 57, and it sits in identifier order in MAJOR_18_CONVERSIONS. It deletes lifecycle from every agents[] entry, whatever the key holds, with one notice per agent. The delete is lossless. An object's ADR-0057 lifecycle block shares the name and is not touched; a pin asserts this.
  • Registration. RETIRED_KEYS_BY_MAJOR[18] gains ai/Agent:lifecycle. RETIRED_DEFS_BY_MAJOR[18] gains the five published defs: automation/StateMachine, StateNode, Transition, ActionRef and GuardRef. Each is one entry file, written into the generated regions by gen:migration-registry.
  • D3 entry agent-lifecycle-retired. One entry covers the family. It carries the judgement no conversion can make: which of the three destinations each deleted machine meant. Its STEP18_RATIONALE fragment is order 62.
  • Family deletion. automation/state-machine.zod.ts and its test are deleted. ./automation stops re-exporting the module. StateNodeConfig leaves the root and /ai entries, whose only structural mention of it was the tombstoned key.
  • Ledger. The liveness/agent.json row lifecycle moves from experimental to dead, with verifiedAt 2026-10-02 and the REMOVED note. The tombstone keeps the key in the walked shape (the rls.priority precedent). state-counts/agent.md is regenerated. The README's agent row no longer says "autonomy tier experimental": no agent row is experimental any more (A6).
  • Generated baselines. These are regenerated, not hand-edited: authorable-surface/{ai,automation}.json (one new ai/Agent:lifecycle [RETIRED] row and 18 family rows gone), authorable-defaults, json-schema.manifest (five defs gone), api-surface, export-origins, declaration-map, content/docs/references/** (the state-machine page is gone) and the strictness-ledger counts.
  • Pins. packages/spec/src/ai/agent-lifecycle-retirement.test.ts is a repo-project test with 14 cases:
    • the refusal for every value, with the issue code, the path and the prescription;
    • the defineStack door's ADR-0112 envelope (STACK_SCHEMA_INVALID / 422);
    • the stored-row replay and the boot-door before/after;
    • idempotence and load-path retirement;
    • the registration;
    • the family's runtime absence from ./automation;
    • a tree-scoped absence walk over the five roots declared for @objectstack/spec#test, with an anti-vacuity matcher case.
  • Changeset. .changeset/21320-agent-lifecycle-retired.md bumps @objectstack/spec minor and @objectstack/platform-objects patch. It carries a BREAKING banner, the FROM → TO table, the one-line fix, Clause-②: yes (narrowing) and the ADR-0087 registered marker naming both ids.

The census (measured in this run)

StateMachineSchema family consumers at origin/main c2c21f357c, before this branch's change:

site symbol what it was disposition
src/ai/agent.zod.ts:7, :341 StateMachineSchema the one authorable consumer: AgentSchema.lifecycle tombstoned
src/data/validation.zod.ts:187 StateMachineValidationSchema the ADR-0020 state_machine rule. It is a different export (a flat { from: [to] } table) and never imported the family unchanged; it is the prescription's destination
src/api/protocol.zod.ts:2614 comment claimed "StateMachineSchema stays authorable on the object". That has been false since ADR-0020 retired object.stateMachines corrected
src/automation/state-machine.zod.ts:25 docblock named the agent lifecycle as the surviving door file deleted
src/ui/chart.zod.ts:50 comment named StateMachineSchema as a positive control updated
migrations/entries/semantic/17.ui-interaction-config-family-retired.ts:38, 17.authoring-schemas-strict-unknown-keys.ts:20 text historical witnesses inside released D3 entries left, because they are dated records
src/ai/index.ts:47, src/index.ts:147 StateNodeConfig re-export entry-nameability only; it was mentioned solely through lifecycle removed
recursive-schema-input-assertions.ts, union-author-message-pins.test.ts, type-alias-convention.pin.test.ts (778 → 773 pins), sync-retirement.test.ts, scripts/export-origins.test.ts, scripts/liveness/check-liveness.test.ts tests and type probes their probes and witnesses were the family or agent.lifecycle removed or re-pointed (FlowSchema, tool.outputSchema)
content/docs/automation/workflows.mdx StateMachineConfig taught the XState type for record lifecycles rewritten to the state_machine rule, os:check green

Nothing outside packages/spec imports the family: git grep over packages/**, examples/**, skills/** and content/** finds no import.

agent.lifecycle producers and readers (A3):

  • Readers: 0 outside packages/spec. The pattern .lifecycle / ['lifecycle'] / "lifecycle" has 62 hits in packages/** and examples/**. Every one is an object's ADR-0057 data lifecycle, a service-registration lifecycle, a schema-migration composition or an i18n key path. One hit is agent-related: the form-label pin in object-lifecycle-panel-echo-decisions.test.ts, which is re-pointed (A2).
  • Producers: 0. examples/** contains no agent definition at all, so its control reading is also 0, and that zero is not a census of agent authors. Seventeen files outside packages/spec name defineAgent / AgentSchema, and none of them authors lifecycle.
  • objectui at the pin 89cad75d55: 0 imports of any family export. The control FilterCondition is found in 44 files. AgentPreview.tsx draws no lifecycle.
  • Cloud: NOT MEASURED here, because this session has no cloud checkout. The card's cloud zero-reader census (cloud @3aadd908) is attributed, not re-taken.

Deviations and conflicts (the reviewer should read these)

  1. This PR touches skills/**, so it is Tier H. The claim's file surface says "No skills/** edit"; this breaches it.
  2. These files are outside the claim's declared file surface. Each is a consequence of retiring the family:
    • hand-written docs: workflows.mdx, quick-reference.mdx, the strictness-ledger prose row and its counts;
    • PROTOCOL_MAP.md, llms.txt and docs-import-surface.baseline.json;
    • the spec test, witness and baseline files in the census table;
    • vitest.repo-tests.json;
    • comment corrections in api/protocol.zod.ts, ui/chart.zod.ts, ai/index.ts, index.ts and automation/index.ts.

Verification

All of this ran on head b4e1682e1c, after merging origin/main 53fd35e3e3 through scripts/pm/os-regen-merge.sh. That merge includes #21431's generated registry entry, analytics-row-wildcard-outside-count-refused. No hunk was hand-resolved, and check:migration-registry is green with nothing to regenerate.

Gates. Every derived gate ran on head b4e1682e1c, with each exit code captured before any pipe:

  • The derived union. node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands derived 124 families, and all 124 exited 0. The reconciliation dispatch-gates --ran reads: ✓ dispatch-gates --ran: 124 derived famil(ies) accounted for — 124 run, 0 NOT-MEASURED (a DERIVED zero — all 124 recorded an exit code and none of them is 3).
  • check:generated: ✓ All 15 generated artifacts are up to date. check:migration-registry reads ✓ src/migrations/registry.ts is current (349 semantic, 245 retired-key, 217 retired-def).
  • check:liveness: ✓ … state-counts/ is current. Across the shards: 989 live, 1 experimental, 1 live-elsewhere, 110 dead, 10 planned.
  • check:api-surface: @objectstack/spec public API surface + factory signatures unchanged ✓, read against the committed snapshot, which carries the removal.
  • check-adr-0087-registration --base origin/main: ✓ … 1 declared-breaking changeset(s), each carrying an ADR-0087 disposition. The arm was read as [BREAKING+bang+clause-②-narrowing] registered agent-lifecycle-removed, agent-lifecycle-retired.
  • check-changeset-no-major: ✓ This diff introduces no major bump.
  • check-empty-changeset: ✓ No empty-frontmatter changeset introduced by this diff.
  • check:i18n: check-i18n-bundles: OK (9 package(s) — all bundles in sync, no undeclared authoring keys).
  • check:doc-authoring: ✓ doc authoring guard: 17266 customer-facing string(s) … clean.
  • check:nul-bytes: check-nul-bytes: OK (… no raw ASCII control bytes).

Tests:

command head result
pnpm --filter @objectstack/spec test b4e1682e1c 600 files, 17689 passed, 1 todo
pnpm --filter @objectstack/spec test:repo d472aaffaf 50 files, 877 passed
pnpm --filter @objectstack/spec typecheck d472aaffaf exit 0; check:test-typecheck: OK
@objectstack/platform-objects vitest run and typecheck b4e1682e1c 59 files, 949 passed; exit 0
@objectstack/lint src/lint-liveness-properties.test.ts b4e1682e1c 95 passed
@objectstack/dogfood test/expression-conformance.test.ts b4e1682e1c 7 passed

The d472aaffaf rows are the merge commit. Its packages/spec tree is byte-identical to b4e1682e1c; the only later commit edits one platform-objects test.

One red was found and fixed in this run. The platform-objects echo-decisions positive control failed with expected 659 to be 660 until the count moved with the retired row.

Ablation of the tombstone ran on the committed head through scripts/ablation-replace.mjs, which restores automatically:

  • The mutation. The anchor lifecycle: retiredKey( became lifecycle_ablated: retiredKey(, a bare delete on the strict schema. The anchor count went 1 → 0, and the blob went 80b6593b3953 → 42c1ebe78de2.
  • The prediction: turns red.
  • The observation: agent-lifecycle-retirement.test.ts went to 5 failed / 9 passed (14). The prescription, walked-shape, tsc-channel, defineStack-envelope and boot-door cases went red. The conversion and absence cases stayed green, as they should, because they do not depend on the tombstone.
  • The restore. The blob after the restore equals HEAD (80b6593b3953), and git diff HEAD is empty.
  • The tsc channel. The pin's @ts-expect-error is live: tsc -p tsconfig.test.json --listFilesOnly lists the pin (count 1, and the control agent-memory-store-retirement.test.ts also counts 1), and the pin carries no debt entry in test-typecheck-debt.json.

skills/** readings

  • The changed file. skills/objectstack-ai/references/_index.md goes from 43 to 42 lines. It is generated, and the change is one deleted line.
  • The whole package. The sum of every SKILL.md is 4397 lines before and 4397 after. No SKILL.md is touched.

Acceptance notes

  • skills/** teaching. No text in skills/** teaches agent.lifecycle or the XState family (A7). skills/objectstack-automation/references/state-machines-and-approvals.md teaches the state_machine validation rule, which is the prescription's destination.
  • Historical comments left as written. These comments still describe StateNodeConfig as one of defineStack's structural mentions: the nine scripts/i18n-extract.config.ts headers, scripts/check-entry-nameability.ts:97 and scripts/root-entry-type-nameability.pin.test.ts:16. They are dated records of 8 packages' scripts/i18n-extract.config.ts fail to type-check: TS2883 on an inferred default naming @objectstack/spec's hashed dist chunks #10868 / @objectstack/spec's root entry does not re-export three types its own public API's inferred types mention — every consumer inferring through defineStack hits TS2883 #11350.
  • The authorable-surface anchor. authorable-surface.base.json still lists the family's rows. Only gen:authorable-surface-base writes that anchor, and the anchor is allowed to lag. check:authorable-surface is green.
  • Historical audit files. packages/spec/ZOD_SCHEMA_AUDIT_REPORT.md and DEVELOPMENT_PLAN.md still name the deleted file.
  • Out-of-repo consumers are NOT MEASURED. That covers tenant-authored agents and code outside this repository, cloud included, that imports the family's exports. The changeset says so, and the prescription and the D2 replay cover stored agent rows.
  • os lint. It reads the unparsed stack, so it now grades an authored agent.lifecycle liveness-dead-property. This was measured with lintLivenessProperties on this tree; the control tool.outputSchema still reads liveness-experimental-property. The parsing doors refuse the key first. The changeset was corrected to say exactly this.

Resume record: found done vs finished in this run

  • Found done. The predecessor's WIP commits held the whole kit: b3b1fe8852, 4729a31154, f1fd8ebb5f, fbd4e311ea, d41227ffd4, 9e40a894aa, f3f5301451 and 54018d8e6a, plus a merge.
  • Finished in this run:
    • re-measured the census, and with it the go-ahead to retire the whole family;
    • merged origin/main twice through os-regen-merge.sh (42bce96be8, d472aaffaf);
    • corrected the changeset's os lint sentence (d3240440b9);
    • fixed a red the predecessor missed: the metadata-form catalog's per-locale positive control in object-lifecycle-panel-echo-decisions.test.ts read 660 and is now 659 (b4e1682e1c);
    • ran the whole gate set and the ablation.

维护者速读(草稿)

改了什么:把 agent 元数据上的 lifecycle(对话状态机)退役,改为编写时报错并给出处方(会话阶段用 skill + triggerConditions,多步流程用 Flow,记录状态流转用 state_machine 校验规则);随之删掉它唯一还在用的 XState 风格 StateMachineSchema 一族导出。表单、四语种表单文案、台账、生成物、文档、迁移登记(D2 转换 + D3 说明)同步。

为什么改:裁决 cloud#2569 定 D(退役)。这个键一直是「声明了但没有任何运行时读取」——写了等于没写,对 AI 编写元数据是陷阱;实测本仓与 objectui 零读取零编写。

风险与代价(含回滚):@objectstack/spec minor + BREAKING:写了 lifecycle 的 agent 会在 parse 时被拒(D2 转换会把存量数据里的该键无损删除);外部若有代码 import 这一族导出会编译失败(仓外未测量)。本 PR 因生成的 skills/objectstack-ai/references/_index.md 少一行而成为 Tier H(人合)。回滚即 revert 本 PR。

席位意见:(留空)

你要做的:审阅通过后在本 PR 上 APPROVE(Tier H),席位随后落地。


Generated by Claude Code

claude added 13 commits October 2, 2026 15:26
…ate surfaces (WIP)

Claude-Session: https://claude.ai/code/session_01YDt3PzwfrkuFzUBF89WPmM
Co-authored-by: Claude <noreply@anthropic.com>
…ent; ledger rows follow (WIP)

Claude-Session: https://claude.ai/code/session_01YDt3PzwfrkuFzUBF89WPmM
Co-authored-by: Claude <noreply@anthropic.com>
…ce; docs follow (WIP)

Claude-Session: https://claude.ai/code/session_01YDt3PzwfrkuFzUBF89WPmM
Co-authored-by: Claude <noreply@anthropic.com>
…the lifecycle head-noun twin (WIP)

Claude-Session: https://claude.ai/code/session_01YDt3PzwfrkuFzUBF89WPmM
Co-authored-by: Claude <noreply@anthropic.com>
…nly the parsing doors refuse it first

The changeset said `os lint` stopped warning because the parse refuses the key
first. `os lint` lints the unparsed stack, so it now reports
`liveness-dead-property` for `agent.lifecycle` (measured with
lintLivenessProperties on this tree). `os validate` and the other parsing doors
refuse the key before any advisory runs.

Claude-Session: https://claude.ai/code/session_01YDt3PzwfrkuFzUBF89WPmM
Co-authored-by: Claude <noreply@anthropic.com>
…ve control reads 659 once the agent lifecycle row leaves

The agent form's `lifecycle` row left with the retired key, and with it a row
label authored in all three locales. The whole-catalog positive control in the
echo-decisions pin counted it (660); it now reads 659, with the reason recorded
beside the history of the count.

Claude-Session: https://claude.ai/code/session_01YDt3PzwfrkuFzUBF89WPmM
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/platform-objects, @objectstack/spec, touching 36 documentable anchor(s). ⚠️ 29 changed file(s) yielded no anchor (packages/spec/PROTOCOL_MAP.md, packages/spec/api-surface/ai.json, packages/spec/api-surface/automation.json, …), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

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

  • content/docs/ai/agents.mdx (via AgentSchema (symbol, a top-level const))
  • content/docs/getting-started/quick-reference.mdx (via AgentSchema (symbol, a top-level const))
  • content/docs/kernel/cluster.mdx (via RETIRED_DEFS_BY_MAJOR (symbol, a top-level const object))
  • content/docs/protocol/objectql/state-machine.mdx (via StateMachineConfig (symbol, a top-level type))
  • content/docs/upgrading.mdx (via AgentSchema (symbol, a top-level const))

⛔ 3 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v13.mdx (via AgentSchema (symbol, a top-level const))
  • content/docs/releases/v17/17-5.mdx (via retiredAfter (symbol, a field of const object agentLifecycleRemoved), retiredFromLoadPath (symbol, a field of const object agentLifecycleRemoved))
  • content/docs/releases/v17/17-6.mdx (via RETIRED_DEFS_BY_MAJOR (symbol, a top-level const object), RETIRED_KEYS_BY_MAJOR (symbol, a top-level const object))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 29 changed file(s) yielded no anchor (packages/spec/PROTOCOL_MAP.md, packages/spec/api-surface/ai.json, packages/spec/api-surface/automation.json, …) — pages documenting those are invisible to this run
  • 17 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 138 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 99589f91a7e721dfe846486988024714878196c8 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 0805e6269e3919c38c16ef4133bba8e7ccd3774f — the merge of head b4e1682e1c2849116a46de9ef40d7b981e04bbfe into base 99589f91a7e721dfe846486988024714878196c8, 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 0805e6269e3919c38c16ef4133bba8e7ccd3774f && git checkout 0805e6269e3919c38c16ef4133bba8e7ccd3774f
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 99589f91a7e721dfe846486988024714878196c8 b4e1682e1c2849116a46de9ef40d7b981e04bbfe && git checkout -B drift-repro 99589f91a7e721dfe846486988024714878196c8 && git merge --no-ff b4e1682e1c2849116a46de9ef40d7b981e04bbfe

node scripts/docs-audit/affected-docs.mjs --json 99589f91a7e721dfe846486988024714878196c8

⚠️ 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 99589f91a7e721dfe846486988024714878196c8 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: b4e1682e1c2849116a46de9ef40d7b981e04bbfe
Local-runs: none

Inputs: card #21320 (body + 3 comments: claim 5955419661, dev report 5960850537, seat amendment 5960894332), the #20274 thread where it bears on agent.lifecycle (5941938242, 5945738955, 5954753152), PR #21461 body, file list (63) and the net diff against main at this head (merge base 53fd35e3e3), the head's check-runs, and git show reads of the merge base and the head. objectstack-ai/cloud#2569 is not readable from this session (HTTP 403), so the ruling is judged against the card's quotation: D (retire), batch #267 item 5, maintainer 「其他同意」. Governed: check-governed-merges --pr 21461 hits skills/objectstack-ai/references/_index.md; the #11705 exception does not lift it because the PR co-edits packages/spec/scripts/**. Tier H. This record is the contract-tier review the pm-dispatch skill owes on the skills/** hunk; it does not lift the landing, which still waits for an authorized APPROVED review (Prime Directive #14).

① Derived judgments

Every accept-set and public-surface change the diff implies, each named right or wrong.

  1. Tombstone AgentSchema.lifecycle — right. retiredKey() on the strict AgentSchema (src/ai/agent.zod.ts), so both channels fire: tsc (input type never) and parse (the prescription, not a bare unrecognized-key error). The guidance string follows the five house conventions: backticked fully-qualified key first; was removed in @objectstack/spec 17.7.0 (ADR-0049 enforce-or-remove) — the same version the two sibling tombstones in this file carry (:197, :363; package label at head is 17.6.0, so 17.7.0 is the removing minor); the dash clause states why it was inert; the imperative fix names three destinations; the os migrate meta --from 17 sentence closes it. The destinations exist and run at head: a skill's instructions (src/ai/skill.zod.ts:343), tools (:375) and triggerConditions (z.array(SkillTriggerConditionSchema), ledger live, reader in cloud skill-registry.ts per the card); FlowSchema (src/automation/flow.zod.ts:977); StateMachineValidationSchema (src/data/validation.zod.ts:187, type: 'state_machine', transitions: { from: [to] }). The reference page content/docs/references/ai/agent.mdx is regenerated to never / [REMOVED] with the same text, and the nested-shape table is gone.
  2. D2 conversion agent-lifecycle-removed — right, lossless. toMajor: 18, retiredFromLoadPath: true, retiredAfter: '17.6.0' — the package label at head and the same value the sibling agent-memory-long-term-store-removed (:10513) carries, which is what retired-after.census.test.ts requires for an entry no published tarball retires. apply is mapCollection(stack, 'agents', stripKeys(agent, ['lifecycle'])): it walks agents[] only (an object's ADR-0057 lifecycle block is untouched — pinned), deletes the key whatever it holds (lossless because no runtime ever read the value; the three-destination judgement is carried by the D3 entry, not faked by a rewrite), is idempotent by construction (stripKeys skips an absent key and returns the input by reference), and emits one notice per agent. Fixture: four agents (full machine, minimal machine, a non-object stray, one without the key) → expectedNotices: 3; its keys avoid every other entry's. Inserted in MAJOR_18_CONVERSIONS in identifier order (actionBlockEndpointToTarget ← agentLifecycleRemoved → agentMemoryLongTermStoreRemoved), order: 57, the next free number after 56.
  3. D3 entry agent-lifecycle-retired — right. One SemanticMigration per retired family (entries/semantic/18.agent-lifecycle-retired.ts), conversionIds: ['agent-lifecycle-removed'], non-empty reason and acceptanceCriteria, surface without backticks (rendered inside a code span by the upgrade-guide builder), replacement naming the three destinations and the import fix. The STEP18_RATIONALE fragment carries order: 62 (unique at head) and sits at the id-sorted position ahead of agent-memory-store-retired-and-limits-required (61), as the header note prescribes; toMajor 18 equals the step's major.
  4. Registration — right. RETIRED_KEYS_BY_MAJOR[18] gains the exact string ai/Agent:lifecycle from entries/retired-keys/18.ai__Agent__lifecycle.ts; RETIRED_DEFS_BY_MAJOR[18] gains automation/ActionRef, GuardRef, StateMachine, StateNode, Transition from five entry files; both generated regions of migrations/registry.ts carry them in id order (check:migration-registry reads 349 semantic, 245 retired-key, 217 retired-def, current). The tombstone and the key entry land in the same PR, so gate (b2) cannot read an orphan registration.
  5. Deletion of the XState StateMachineSchema family — right, and the census holds. Re-measured here at the merge base 53fd35e3e3, independently of the dev report: git grep finds exactly eight importers of automation/state-machine.zod — ONE authorable door, src/ai/agent.zod.ts:7 feeding lifecycle (:341); two nameability type re-exports (src/ai/index.ts:47, src/index.ts:147, both existing only because defineAgent/defineStack return types mentioned StateNodeConfig through lifecycle); the barrel src/automation/index.ts:69; the file's own test; and three type-probe/pin tests (recursive-schema-input-assertions.ts, union-author-message-pins.test.ts, type-alias-convention.pin.test.ts). src/api/protocol.zod.ts:2614 was a comment, not an import — ADR-0020 implementation note 1's "discovery protocol" import had already gone, so the dev report's census (5960850537, "the ONLY authorable consumer was agent.zod.ts:7/:341") is correct. Nothing under examples/**, skills/**, content/** or any other package imported the family. StateMachineValidationSchema is a different declaration: a flat { from: [to] } table at data/validation.zod.ts:187 that never imported this file, and the diff does not touch validation.zod.ts (git diff --stat between base and head on that path is empty). An exported value-schema family with zero consumers reads as a capability (the refactor(spec)!: remove the plugin sandboxing / integrity / approval config that never existed (#3896 follow-up) #3950 rule), so card scope 3 says it goes — and this COMPLETES ADR-0020 rather than reversing it: that ADR's Alternatives rejected "keep the full XState StateMachineSchema and build an interpreter", and its implementation note 1 kept the file only "so as not to churn the agent surface", a deferral. Every public-surface projection moves consistently: api-surface (ai −1 StateNodeConfig, automation −11, root −1), export-origins (same three), declaration-map/automation −9, json-schema.manifest/automation −5, authorable-surface (ai: ai/Agent:lifecycle [RETIRED]; automation −18 rows), authorable-defaults/automation −1, docs-import-surface.baseline.json −1 (automation/StateMachine — no type export), the reference docs (state-machine.mdx deleted, meta.json, automation index 14→13 pages, root index 1522→1517), llms.txt (204→203 modules, automation 14→13), PROTOCOL_MAP.md (the row). api-surface and the manifest MUST move on a whole-def deletion (the playbook's ratchet-visibility table), and they do.
  6. Form row and four-locale catalog leaves — right. agent.form.ts drops the lifecycle composite row with an in-place comment; en, es-ES, ja-JP, zh-CN *.metadata-forms.generated.ts each lose exactly agent.fields.lifecycle.{label,helpText} (merge-mode extraction, pure deletion; check:i18n reported in sync). The echo-decisions positive control moves 660→659, and the AGENT_LIFECYCLE twin that two ADR-0057 rows copied from is re-pointed to head-noun fragments of hook.fields.events.helpText (ライフサイクル / ciclo de vida) — the renderings themselves do not move, which is the right repair for a decision whose twin left the catalog.
  7. Liveness row agent.lifecycle experimental → dead — right route. A retiredKey() keeps the key in the walked shape, so the row STAYS (the rls.priority precedent); verifiedAt 2026-10-02; the note follows the house REMOVED template and states that the cloud zero-reader census is attributed (cloud @3aadd908), not re-taken. state-counts/agent.md moves 23/1/0/2 → 23/0/0/3 (classified 26 unchanged); the README agent row stops saying "autonomy tier experimental", which the ai: guardrails, memory, structuredOutput, lifecycle and tool.outputSchema are enforced by the agent runtime (5 keys), starting with the guardrails the built-in agents already declare #20274 landing comment 5954753152 had flagged as true of lifecycle only. undrilled-containers.baseline.json drops agent/lifecycle (see ③1).
  8. Docs rewrites teach only mechanisms that run — right. content/docs/automation/workflows.mdx replaces the StateMachineConfig example with an ObjectSchema.create carrying a state_machine validation rule whose keys (name, field, events: ['update'], message, transitions) are all declared on the rule shape at head (validation.zod.ts:143-197), inside an os:check fence; the prose now says the rule is enforced by the write path and links the protocol page. quick-reference.mdx drops the State Machine row (3 of 13). content/docs/protocol/objectql/state-machine.mdx:136 already says there is no StateMachineConfig type for object lifecycles. No page under content/docs/ai/** and no file under skills/** teaches agent.lifecycle or the XState family; skills/objectstack-automation/references/state-machines-and-approvals.md teaches the destination rule.
  9. The skills/** hunk — judged explicitly: it is the exact gen:skill-refs output, nothing hand-edited, and the dropped line is the right one. skills/objectstack-ai/references/_index.md base→head is ONE deleted line, byte-identical to the base line for automation/state-machine.zod.ts (43 → 42 lines; no SKILL.md touched). Why the generator must produce exactly this: build-skill-references.ts resolves each relative import … from specifier transitively from the skill's core files and keeps only *.zod.ts (resolveAll filters the visited set on the .zod.ts suffix — "Only src/**/*.zod.ts ships in the published package"); at base the ONLY path into state-machine.zod.ts was agent.zod.ts:7, which the head removes; the deleted file's own imports leave no second line to drop (shared/identifiers.zod is still reached by other core files and still listed; shared/lazy-schema and shared/strict-object are not .zod.ts and were never listed). The two edits under the generator tree are comment-only (build-skill-references.ts:146-154 inside the SKILL_MAP note; lib/skill-map-guards.ts:24-32 docblock), so the generator's behaviour is unchanged. No instruction content enters through the hunk. The verifying gate check:skill-refs runs in Lint & Repo Gates, which is in progress on this head at the time of this record (see ③8); the dev report quotes it green on the same head.
  10. Pins — right. src/ai/agent-lifecycle-retirement.test.ts (14 cases, added to vitest.repo-tests.json as a repo-project test): refusal of every value with code, path and the prescription; a parse-success CONTROL; the walked-shape key; the tsc channel (@ts-expect-error, with the dev's --listFilesOnly count 1 reported); the defineStack ADR-0112 envelope (STACK_SCHEMA_INVALID / 422); stored-row replay; boot-door before/after; agents-only; idempotence; load-path retirement; registration; runtime absence from ./automation with a FlowSchema control; a tree-scoped absence walk with an anti-vacuity matcher case. Its radius is declared: scripts/cross-package-test-inputs.mjs @objectstack/spec names packages/** per extension, examples/** per extension plus examples/*/src/**/*.ts, content/**, skills/**, scripts/**, with a heldBy block. The re-pointed witnesses keep their non-vacuity (export-origins.test.ts and sync-retirement.test.ts on FlowSchema; check-liveness.test.ts MAKES tool.outputSchema experimental in the copy instead of borrowing a shipped row; union-author-message-pins 13→10; type-alias-convention 778→773, machine-checked). The ablation (anchor lifecycle: retiredKey( → 5 failed / 9 passed, restored to HEAD) is read from the report, not re-run.
  11. Correctly NOT regenerated: spec-changes.json and docs/protocol-upgrade-guide.md project released majors only (perMajor holds 17 at head); the sibling retirement landing 22c2d6f4d5 (feat(spec)!: an agent's memory contract states exactly what the runtime honours — maxEntries and reflectionInterval are required once long-term memory is enabled, longTerm.store is retired, and the block is live #21413) moved neither, and both are in the check:generated ledger (check-generated.ts:89-90), which the dev quotes green.

② Semver level

  • @objectstack/spec minor with the BREAKING banner — right. major is refused in the launch window (check-changeset-no-major, success on this head), and the breaking semantics ride the banner, the FROM → TO table (six rows: the key, the three destinations, the family's exports from /automation, StateNodeConfig from root and /ai) and the one-line fix, with the os migrate meta --from 17 sentence. PR title carries !.
  • @objectstack/platform-objects patch — right: four generated catalogs and one test, no API.
  • Clause-②: yes (narrowing) — right, and at a line start in both the PR body and the changeset body: the accept set narrows (every lifecycle value is refused; five defs leave the manifest) ⇒ yes takes at least minor ✓, (narrowing) is BREAKING ✓.
  • ADR-0087 disposition — right: exactly one marker in the changeset body (an HTML-comment line reading adr-0087: registered agent-lifecycle-removed, agent-lifecycle-retired), naming the D2 and D3 ids the registry actually carries. Check Changeset is success on this head; the dev quotes the gate's arm as [BREAKING+bang+clause-②-narrowing].
  • The out-of-repo NOT MEASURED paragraph is in the changeset, where an upgrading agent greps it.

③ Boundary flags

  1. File-surface breach beyond claim 5955419661 — each extra file judged. The claim forbade skills/**; the seat's amendment 5960894332 lifts it for exactly the generated index and names the rest. Forced by the retirement: skills/objectstack-ai/references/_index.md (check:skill-refs; ①9); content/docs/automation/workflows.mdx (its os:check fence imported the deleted StateMachineConfig) and quick-reference.mdx (linked the deleted reference page; Check Documentation Links is a gate); docs/audits/2026-07-unknown-key-strictness-ledger.md and .counts/automation.md (scripts/strictness-ledger.test.ts ratchets the ledger against the inventoried schema files; the row becomes a dated closing paragraph, 67→61 sites / 43→37 strict); PROTOCOL_MAP.md, llms.txt, docs-import-surface.baseline.json (a link to a deleted file, module counts, a baseline row for a def that no longer emits); packages/spec/scripts/liveness/check-liveness.test.ts (its witness borrowed the experimental row, now dead), scripts/export-origins.test.ts (its witness was StateMachineSchema), scripts/liveness/undrilled-containers.baseline.json (once lifecycle is never it is no longer a container, the row becomes undrilledStale and check-liveness.mts:1806 fails on it — and the row cannot leave BEFORE the retirement either, because then the live container is recorded nowhere); the spec src/ pins and witnesses named in ①10; vitest.repo-tests.json; the comment corrections in api/protocol.zod.ts, ui/chart.zod.ts, ai/index.ts, index.ts, automation/index.ts (each stated something now false). Not forced, tier-neutral: the comment-only edits in build-skill-references.ts and lib/skill-map-guards.ts — the three forced scripts/** edits already sit under packages/spec/scripts/, so these two neither raise nor could lift the fence; correcting comments that now described a live door is right. Nothing in the 63 files is outside the amended surface; no cloud edit, no docs/adr/** edit, no SKILL.md edit.
  2. Tier H landing options (dev report 5960850537) — A is right. B (split) is infeasible for the reason just stated: the undrilled baseline row can only move with the retirement, that alone co-edits packages/spec/scripts/, and the spec-skill-refs exception row trusts exactly that prefix (trustedGeneratorPrefixes: ['packages/spec/scripts/']), so the fence fires on any split; additionally the exception never lifts inside the merge-group guard job (no toolchain), by that script's own header. C (keep the family) still edits the index and the witnesses (still Tier H) and leaves a zero-consumer exported family against card scope 3. The path is the skill's Tier H set: this record covers the skills/** hunk; the four lifts still wait for an authorized APPROVED review by a GOVERNED_APPROVERS account; the owning seat lands after it; ⛔ no seat flips ready, enqueues or arms auto-merge before that word.
  3. Part of #20274 — right as a line of its own, no closing keyword next to ai: guardrails, memory, structuredOutput, lifecycle and tool.outputSchema are enforced by the agent runtime (5 keys), starting with the guardrails the built-in agents already declare #20274 (Part-of PR must not also close its card is success). ai: guardrails, memory, structuredOutput, lifecycle and tool.outputSchema are enforced by the agent runtime (5 keys), starting with the guardrails the built-in agents already declare #20274 carries Blocked-by: #21320 (set in 5954753152) and lifecycle is its last key. What closes ai: guardrails, memory, structuredOutput, lifecycle and tool.outputSchema are enforced by the agent runtime (5 keys), starting with the guardrails the built-in agents already declare #20274 after landing: Fixes #21320 closes this card on merge; ai: guardrails, memory, structuredOutput, lifecycle and tool.outputSchema are enforced by the agent runtime (5 keys), starting with the guardrails the built-in agents already declare #20274 then closes by the domain:spec seat's act — a landing comment and completed — not by any keyword. That comment should say that the body's Acceptance ("every row leaves … for live") is met for guardrails, memory and structuredOutput as live, and for lifecycle as dead under ruling D — the ruling, not the Acceptance wording, governs that row — and that tool.outputSchema stays steered per 5943331648.
  4. PR body ## 维护者速读(草稿) — true to the diff with four corrections for the seat's final version. (a) 「实测本仓与 objectui 零读取零编写」: what was measured is readers 0 AND producers 0 in this repo, and 0 family imports / no lifecycle drawn in objectui at pin 89cad75d55; objectui authoring was not separately measured and cloud readers 0 is the ruling card's census (cloud @3aadd908), not re-taken — write 「实测本仓零读取零编写,objectui 零 import 零读取;cloud 零读取沿用裁决卡的普查(cloud @3aadd908),本次未重测」. (b) 「随之删掉它唯一还在用的 XState 风格 StateMachineSchema 一族导出」 reads ambiguously; the fact is the family's ONLY remaining authorable consumer was this key — write 「随之删掉只剩它一个可编写消费者的 XState 风格 StateMachineSchema 一族(5 个 def 及其类型)」. (c) The semver line omits the second package — add 「@objectstack/platform-objects patch(四语种表单文案)」. (d) 「回滚即 revert 本 PR」 is true before 17.7.0 publishes; add 「(发布前)」. Everything else — the three destinations, the parse-time refusal with the D2 lossless strip of stored data, the Tier H cause, 「审阅通过后在本 PR 上 APPROVE(Tier H),席位随后落地」 — is true as written.
  5. Dev deviations 3 and 4 — noted, no action: migrations/registry.ts is NOT_DRIVER_MANAGED in .gitattributes (the PM heads-up was wrong about merge=os-regen), the text merge was clean and check:migration-registry is current with feat(spec)!: the analytics row wildcard '*' is admitted only where a count consumes it (#21409) #21431's entry present; the commit trailers follow the repository's model-free rule.
  6. Attribution, stated everywhere it must be: cloud#2569 was unreadable from this session, so the ruling is taken from the card's quotation; the cloud zero-reader census is attributed, not re-taken; tenant-authored agents and out-of-repo importers are NOT MEASURED. The ledger note, the changeset, the PR body and the dev report all say so in the same words. The tombstone and the D2 replay cover the unmeasured stored population.
  7. Out of scope, carrier none, not blocking: packages/spec/scripts/build-docs.ts:766 still blurbs the automation module as "… webhooks, state machines, execution records.", so the generated content/docs/references/index.mdx row keeps the words after the page left — a one-string generator follow-up. ADR-0020 implementation note 1 is now a historical statement (it says the file is kept); an ADR is a dated record and an amendment pointer is a maintainer-side Tier H edit, not owed by this PR.
  8. Check-runs on b4e1682e1c2849116a46de9ef40d7b981e04bbfe, read at 2026-10-02T20:35Z (33 runs): 25 success — among them Governed Surface Queue Guard, Check Changeset, Spec property liveness, Build Core, Build Docs, Check Documentation Links, Dogfood Regression Gate (1-3/3), Dogfood Verify CLI, Temporal Conformance (live PG + MySQL), Type Check · source gates / consumer gates / debt ledger, The card this PR closes must claim this branch, Part-of PR must not also close its card, both single-writer guards, Check PR Size; 2 skipped by design (Console Pin Gate — no .objectui-sha change; Packed-tarball smoke (opt-in)); 6 in progress — Lint & Repo Gates (carries check:skill-refs, check:generated, check:liveness, check:cross-package-test-inputs), Test Core (1/6, 3/6, 5/6, 6/6), Type Check · workspace; 0 failures. An in-progress gate is read as in progress, not as a pass; the dev report quotes every corresponding family exit 0 on this head, and the Tier H landing additionally requires them green on the remote.

Implemented-by: claude/issue-21320-agent-lifecycle-retired
Reviewed-by: session_01YDt3PzwfrkuFzUBF89WPmM

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

维护者速读(终稿)— PR #21461

domain:spec 坐席 2(session_01YDt3PzwfrkuFzUBF89WPmM)· 2026-10-02T20:43Z · 对照坐席自读的 diff 与达档复核 5961114424 校正自 dev 草稿

改了什么:把 agent 元数据上的 lifecycle(对话状态机)退役。写了它的 agent 在编写时被拒,报错里直接给出处方:会话阶段用 skill 加 triggerConditions,多步流程用 Flow,记录状态流转用 state_machine 校验规则。随之删掉只剩它一个可编写消费者的 XState 风格 StateMachineSchema 一族(5 个 def 及其类型)。表单、四语种表单文案、台账(experimental → dead)、生成物、文档与迁移登记(一条无损转换,外加一条退役说明)同步改。

为什么改:裁决 cloud#2569 定为 D(退役)。这个键一直是「声明了但运行时从不读取」,写了等于没写,对 AI 编写元数据是陷阱。实测本仓零读取、零编写,objectui 零 import、零读取。cloud 的零读取沿用裁决卡的普查(cloud @3aadd908),本次未重测。

风险与代价(含回滚):

  • @objectstack/spec minor,带 BREAKING:写了 lifecycle 的 agent 在 parse 时被拒;存量数据里的这个键由迁移转换无损删除。仓外若有代码 import 这一族导出,会编译失败(仓外未测量)。
  • @objectstack/platform-objects patch(四语种表单文案)。
  • 生成的 skills/objectstack-ai/references/_index.md 少一行,这是退役必然带出的,所以本 PR 成为 Tier H(人合);拆分已实测无法绕开。
  • 回滚:发布前 revert 本 PR 即可。
  • 达档复核 PASS,CI 全绿后才可落地。

席位意见:建议批准。四轴同向:零读取、零编写;契约收紧并在编写时响亮拒绝;删掉零消费的导出,少一项永久义务。落地后由本坐席关闭 #20274(这是它的最后一个键)。

你要做的:在 PR #21461 上点 APPROVE。


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review October 2, 2026 22:15
@os-zhuang
os-zhuang enabled auto-merge October 2, 2026 22:15
@os-zhuang
os-zhuang added this pull request to the merge queue Oct 2, 2026
Merged via the queue into main with commit 6e33b67 Oct 2, 2026
44 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-21320-agent-lifecycle-retired branch October 2, 2026 22:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

3 participants