Skip to content

Commit 3937ad2

Browse files
feat(spec)!: an agent's structuredOutput is JSON-only — retire the regex / grammar / xml formats and the coerce_types step; the block is live, enforced by the cloud AI runtime (#21367)
Fixes #21277 Part of #20274 Clause-②: no (narrowing) ## What this does Ruling of record 5945617233 (letter A, maintainer 「其他同意」): retire the four `agent.structuredOutput` members the cloud AI runtime refuses, flip the ledger row, correct the describes. - **The four members leave the spec, refused by name with a prescription.** `regex`, `grammar` and `xml` leave `StructuredOutputFormat` (so `format` and `fallbackFormat`), and `coerce_types` leaves `TransformPipelineStep` (so `transformPipeline`). Both enums are now declared through `enumWithRetiredValues` (`packages/spec/src/shared/retired-key.ts`), the house value-level retirement mechanism. An authored retired member fails `tsc` (it is gone from the type) and is refused at parse with its prescription; any other unknown value keeps zod's own message. The prescriptions are module-private, so the public export surface does not grow. - The `regex` / `grammar` / `xml` prescription: structured output is JSON-only; use `json_schema` with a JSON Schema in `schema`, or `json_object`, or delete the block; at `fallbackFormat`, name a JSON format or delete the key. - The `coerce_types` prescription: there is no coercion engine; delete the step and declare the exact types in `schema`. - Both close with the house `os migrate meta --from 17` sentence, and neither carries a tracker number. - **D2 conversion `agent-structured-output-refused-members-removed`** (step 18, retired from the load path, `retiredAfter` 17.6.0, order 55): - a block whose `format` was retired is DELETED, because `format` is required and no rewrite can say which JSON contract the author wanted (the `agent.tools` / dataset-measure precedent); - a retired `fallbackFormat` is deleted; - `coerce_types` is dropped from the pipeline, and the other steps keep their order. The key stays even when the list empties (the `crypto.hash` precedent). - The fixture covers all three edits plus two untouched controls, for 4 notices. - **D3 entry `agent-structured-output-refused-members-retired`** (`conversionIds` names the D2 entry). It carries the judgement the conversion cannot make: whether an agent whose block was deleted should now carry a `json_schema` contract. A `STEP18_RATIONALE` fragment is at order 60. - **Ledger:** the `agent.structuredOutput` row moves `experimental` → `live`. - Evidence: `cloud cb62c3ea`, `agent-runtime.ts#compileStructuredOutput` (via `AgentRuntime.resolveTurnGuardrails`) and `ai-service.ts#AIService.settleFinalAnswer`. The pre-run refusal `TurnGovernor.unsupportedOutput` is named in the evidence text without a path, because the reading names the symbol and not its file. - `evidenceScope: cross-repo`, `verifiedAt` 2026-10-02. - The note attributes all of this to the `repo:cloud#1` seat's reading 5943158888. No seat in this session can read cloud, and none did. - Producer: none in cloud at that ref; tenant-authored agents are NOT MEASURED. - `state-counts/agent.md` was regenerated: live 21 → 22, experimental 3 → 2. - **Describes.** `structuredOutput` drops `[EXPERIMENTAL — not enforced]`, worded on the `guardrails` precedent: the cloud AI runtime enforces it on every final answer, and the open framework edition does not run agents. - `fallbackFormat` now states what the runtime does with it: once the primary format's retries are spent, the last answer is checked against it. - The rider 5944103492 item 1: `guardrails.blockedTopics` states the enforced match, "an exact, case-sensitive match on the tool name, on `action_` plus the action type, or on the tool category". Rider item 2 (`structuredOutput.schema`) is not in this PR; it landed separately, and this branch merged it. - **Regenerated:** `content/docs/references/ai/agent.mdx`. - **Changeset:** `.changeset/21277-agent-structured-output-json-only.md`, `@objectstack/spec` minor, BREAKING, FROM → TO table, one-line fix, `Clause-②: no (narrowing)`, and an ADR-0087 marker `registered` naming both entries. The four surface ratchets (`api-surface`, `authorable-surface`, `json-schema.manifest`, `api-surface-signatures`) are byte-identical, as the playbook predicts for an enum-value narrowing: no def, export or authorable key changed, so nothing lands in `RETIRED_KEYS_BY_MAJOR`. ## Verification (at `ff5810c0ed`, after merging `origin/main` at `23365eaedf`) - `pnpm --filter @objectstack/spec build` + `check:generated`: 15 of 15 artifacts current. - `@objectstack/spec` `vitest --project local`: 598 files, 17523 passed, 1 todo. - `@objectstack/spec` `typecheck` (`tsc --noEmit`, scripts, and the test layer): OK. The two `@ts-expect-error` pins in `agent.test.ts` are in the compiled test program, so this green also proves `tsc` rejects `'regex'` and `'coerce_types'` at their typed positions. - `@objectstack/spec` `vitest --project repo`: 42 of 48 files, 691 passed. The other six are NOT MEASURED; see below. - Consumer package `@objectstack/lint` (reads `liveness/agent.json`; built `formula`, `sdui-parser` and `lint` first): 119 files, 5575 passed. - `node scripts/pm/dispatch-gates.mjs --ran`: 114 derived families, 113 run green, 1 NOT MEASURED (below), 0 unrun. This covers `check:adr-0087-registration` ("1 declared-breaking changeset(s), each carrying an ADR-0087 disposition … registered agent-structured-output-refused-members-removed, agent-structured-output-refused-members-retired"), `check-changeset-no-major` ("This diff introduces no `major` bump"), `check:liveness`, `check:doc-authoring`, `check:nul-bytes`, `check:skill-examples` and `check:migration-registry`. - **Ablation** (committed tree; `scripts/ablation-replace.mjs`, mutation proven on disk, restore proven by blob hash equal to HEAD and an empty `git diff HEAD`): - The format prescription map was swapped for a dummy member: 6 failed (the six `format` / `fallbackFormat` refusal pins), 45 passed. - The `coerce_types` map was swapped: 1 failed (its pin), 50 passed. - In both runs the "never-legal value keeps zod's message" control stayed green, which is the expected direction. **NOT MEASURED** - `pnpm check:type-check-debt`, reason: it re-measures `tsc` over all 80 workspace packages and was cap-killed twice (150 s, then 570 s). It is a repo-wide sweep CI owns; `@objectstack/spec` typecheck is green above. - `@objectstack/spec` repo-project files `publish-smoke-boot-failure`, `publish-smoke-port-collision`, `dist-freshness`, `dist-freshness-adoption`, `build-schemas-check-mode` and `schema-tree-freshness`, reason: they drive whole builds, and two runs were cap-killed at the 595 s foreground ceiling. They exercise build tooling this diff does not touch. - `@objectstack/cli` integration tier, reason: declared to CI. No CLI test authors `structuredOutput`. ## Fixture census - Producers outside `packages/spec`, tests excluded (`examples/**`, `packages/**`): 0 files author `structuredOutput`. - Tests and fixtures outside `packages/spec` naming a retired member or the structured-output keys: one hit, a comment in `packages/lint/src/lint-liveness-properties.test.ts`, which asserts nothing about the row. The lit control (`guardrails` / `blockedTopics`) hits 4 files. - Inside `packages/spec`, the three `agent.test.ts` cases that pinned the old acceptance were flipped: the five-format list, a pipeline with `coerce_types`, and `format: 'regex'` beside a fallback. Seven refusal pins (the three formats at `format` and at `fallbackFormat`, plus `coerce_types`), one never-legal-value control and one type-level pin were added: 9 new tests, so `agent.test.ts` goes from 42 to 51. - objectui at the pinned `31971ff1e2`: 0 hits for `StructuredOutput` / `structuredOutput` / `coerce_types`. The control `blockedTopics` / `AgentSchema` hits 3 files. - `content/docs/ai/**`: 0 hits. `skills/**`: 0 hits for a retired member. `skills/objectstack-ai/references/_index.md` lists the two enum export names, which are unchanged. ## Acceptance notes - **The Studio offer for `structuredOutput` is now undecided.** `packages/spec/src/system/metadata-form-zod-reconciliation.test.ts` carries an `omit` row for `agent.structuredOutput` whose reason cites the `[EXPERIMENTAL — not enforced]` describe and the `experimental` ledger status. This PR removes both. - That ledger's own rule says a not-enforced-yet row is stale once the key is enforced: delete it and decide the offer. - Deciding the offer means a control in `agent.form.ts` plus the four translation bundles in `platform-objects`. That is outside this card's claim surface and was not ruled, so the row is left as it is (the test stays green). - Carrier: the `domain:spec` seat, to route. - **The release number in the prescriptions.** They say "removed in @objectstack/spec 17.7.0", assuming the next release is a minor (the launch-window no-major rule; the label is 17.6.0, which is published). If the next release is cut as a major, those runtime strings need the new number. - **No tree-scoped absence pin.** That follows the value-retirement precedent (`RealtimeEventType`, `HookBodyCapability.crypto.hash`, the sub-day intervals): the pins sit on the enum, and `tsc` plus the parse cover authored sources. The docs and skills census above is zero. - **`action_` plus the action type** renders the reading's `action_` + T as the rider and dispatch worded it. No seat here can open cloud to confirm that T is the action type. - **The claim declared `Clause-②: yes`; this PR declares `no (narrowing)`.** The diff widens nothing (no new export, `api-surface` byte-identical, no accept-set growth) and narrows an accept set, the arm `check-adr-0087-registration` reads. The gate outcome is the same either way: minor, breaking, ADR-0087 marker owed. --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent f397608 commit 3937ad2

9 files changed

Lines changed: 528 additions & 42 deletions

File tree

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
---
2+
'@objectstack/spec': minor
3+
---
4+
5+
feat(spec)!: an agent's `structuredOutput` is JSON-only — the `regex` / `grammar` / `xml` formats and the `coerce_types` step are retired, and the block is `live`, enforced by the cloud AI runtime (#21277)
6+
7+
**BREAKING** — four members leave the agent's structured-output vocabulary:
8+
`regex`, `grammar` and `xml` from `StructuredOutputFormat` (so from
9+
`agent.structuredOutput.format` and `agent.structuredOutput.fallbackFormat`), and
10+
`coerce_types` from `TransformPipelineStep` (so from
11+
`agent.structuredOutput.transformPipeline`). ADR-0049 enforce-or-remove, ruled
12+
retire. The cloud AI runtime, the one runtime that executes agents, enforces
13+
`structuredOutput` on every final answer and refused an agent declaring any of the
14+
four before its first turn: the spec never had a key to carry the pattern or
15+
grammar a `regex` / `grammar` answer would be checked against, an answer is checked
16+
only as JSON, and no coercion engine exists. So no authored value of the four ever
17+
did what it named, and authoring now refuses them by name instead of the first
18+
live turn refusing the agent. `json_object`, `json_schema`, `trim`, `parse_json`
19+
and `validate` are unchanged.
20+
21+
### FROM → TO
22+
23+
| removed | what to write instead |
24+
| --- | --- |
25+
| `structuredOutput.format: 'regex'`, `'grammar'` or `'xml'` | `format: 'json_schema'` with a JSON Schema in `schema` when the answer must have a shape, or `format: 'json_object'`; or delete the `structuredOutput` block if the agent needs no output contract. |
26+
| `structuredOutput.fallbackFormat: 'regex'`, `'grammar'` or `'xml'` | `'json_object'` or `'json_schema'`, or delete the key. |
27+
| `'coerce_types'` in `structuredOutput.transformPipeline` | delete the step, and declare the exact types in `schema` so the answer is validated as the model wrote it. |
28+
29+
**The one-line fix: use `json_schema` with a JSON Schema; drop `coerce_types`.**
30+
`os migrate meta --from 17` lists the mechanical edits for existing sources.
31+
32+
Each retired member is refused at parse with a prescription naming the JSON
33+
formats, and in `tsc` (the members are gone from the `StructuredOutputFormat` /
34+
`TransformPipelineStep` types). Any other unknown value keeps zod's own message.
35+
36+
### The retirement kit
37+
38+
- **Value-level retirement.** Both enums are declared through
39+
`enumWithRetiredValues` (`shared/retired-key.ts`), the house mechanism for a
40+
narrowed vocabulary, with the prescriptions module-private. No authorable KEY and
41+
no def changed, so nothing lands in `RETIRED_KEYS_BY_MAJOR` and the four surface
42+
ratchets (`api-surface`, `authorable-surface`, `json-schema.manifest`,
43+
`api-surface-signatures`) are byte-identical.
44+
- **D2 conversion `agent-structured-output-refused-members-removed`** (step 18,
45+
retired from the load path): it deletes a `structuredOutput` block whose `format`
46+
was retired (the format is required, and no rewrite can say which JSON contract
47+
was meant), deletes a retired `fallbackFormat`, and drops `coerce_types` from the
48+
pipeline, keeping the other steps in order. Stored `sys_metadata` agent rows replay
49+
it at rehydration; one notice per edit.
50+
- **D3 entry `agent-structured-output-refused-members-retired`** carries the
51+
judgement the conversion cannot make: whether an agent whose block was deleted
52+
should now carry a `json_schema` contract.
53+
- **No deprecation window**, per the project's startup-stage posture.
54+
55+
### Describes and the liveness ledger
56+
57+
- `agent.structuredOutput` drops `[EXPERIMENTAL — not enforced]`: it states that the
58+
cloud AI runtime enforces it on every final answer and that the open framework
59+
edition does not run agents. Its ledger row moves `experimental` → `live`, citing
60+
the cloud readers (`agent-runtime.ts#compileStructuredOutput`,
61+
`ai-service.ts#AIService.settleFinalAnswer`) as attested by the cloud seat's
62+
reading at cloud `cb62c3ea`, `verifiedAt` 2026-10-02. `os lint` / `os validate` no
63+
longer warn `liveness-experimental-property` on an agent that sets it.
64+
- `fallbackFormat`'s describe states what the runtime does with it: once the primary
65+
format's retries are spent, the last answer is checked against the fallback.
66+
- `guardrails.blockedTopics`'s describe states the enforced match: an exact,
67+
case-sensitive match on the tool name, on `action_` plus the action type, or on
68+
the tool category.
69+
- The generated agent reference page follows.
70+
71+
⚠️ **The out-of-repo consumer population is NOT MEASURED.** `@objectstack/spec` is
72+
published, and tenant-authored agents were not measured. This repo authors no
73+
`structuredOutput` outside `packages/spec`, and the cloud seat's reading found no
74+
producer in cloud.
75+
76+
Clause-②: no (narrowing)
77+
78+
<!-- adr-0087: registered agent-structured-output-refused-members-removed, agent-structured-output-refused-members-retired -->

‎content/docs/references/ai/agent.mdx‎

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ const result = AIModelConfigSchema.parse(data);
6060
| **planning** | `{ maxIterations: integer }` | optional | Autonomous reasoning and planning configuration |
6161
| **memory** | `{ longTerm?: object; reflectionInterval?: integer }` | optional | [EXPERIMENTAL — not enforced] Agent memory management. Parsed but no runtime consumer yet. |
6262
| **guardrails** | `{ maxTokensPerInvocation?: integer; maxExecutionTimeSec?: integer; blockedTopics?: string[] }` | optional | Safety guardrails for the agent (token budget, time limit, blocked topics), enforced per user turn by the cloud AI runtime; the open framework edition does not run agents. |
63-
| **structuredOutput** | `{ format: Enum<'json_object' \| 'json_schema' \| 'regex' \| 'grammar' \| 'xml'>; schema?: Record<string, any>; strict: boolean; retryOnValidationFailure: boolean; … }` | optional | [EXPERIMENTAL — not enforced] Structured output format and validation configuration. Parsed but no runtime consumer yet. |
63+
| **structuredOutput** | `{ format: Enum<'json_object' \| 'json_schema'>; schema?: Record<string, any>; strict: boolean; retryOnValidationFailure: boolean; … }` | optional | Structured output contract for the agent's final answer (JSON format, schema, retries, fallback format, transform steps), enforced on every final answer by the cloud AI runtime; the open framework edition does not run agents. |
6464
| **protection** | `{ lock: Enum<'none' \| 'no-overlay' \| 'no-delete' \| 'full'>; reason: string; docsUrl?: string }` | optional | Package author protection block — lock policy for this agent. |
6565
| **_lock** | `Enum<'none' \| 'no-overlay' \| 'no-delete' \| 'full'>` | optional | Item-level lock — controls overlay & delete (ADR-0010). |
6666
| **_lockReason** | `string` | optional | Human-readable reason shown when a write is refused by _lock. |
@@ -110,19 +110,19 @@ const result = AIModelConfigSchema.parse(data);
110110
| :--- | :--- | :--- | :--- |
111111
| **maxTokensPerInvocation** | `integer` | optional | Token budget per single invocation |
112112
| **maxExecutionTimeSec** | `integer` | optional | Max execution time in seconds |
113-
| **blockedTopics** | `string[]` | optional | Forbidden topics or action names |
113+
| **blockedTopics** | `string[]` | optional | Forbidden topics or action names: each entry is an exact, case-sensitive match on the tool name, on `action_` plus the action type, or on the tool category |
114114

115115
### Nested Shape: `Agent.structuredOutput`
116116

117117
| Property | Type | Required | Description |
118118
| :--- | :--- | :--- | :--- |
119-
| **format** | `Enum<'json_object' \| 'json_schema' \| 'regex' \| 'grammar' \| 'xml'>` | ✅ | Expected output format |
119+
| **format** | `Enum<'json_object' \| 'json_schema'>` | ✅ | Expected output format |
120120
| **schema** | `Record<string, any>` | optional | JSON Schema definition for output. An untyped subschema that carries a type-scoped keyword (properties, items, pattern, minimum, …) is refused at its path, because the AI runtime's schema reader does not check it; declare its "type". |
121121
| **strict** | `boolean` | optional (default: `false`) | Enforce exact schema compliance |
122122
| **retryOnValidationFailure** | `boolean` | optional (default: `true`) | Retry generation when output fails validation |
123123
| **maxRetries** | `integer` | optional (default: `3`) | Maximum retries on validation failure |
124-
| **fallbackFormat** | `Enum<'json_object' \| 'json_schema' \| 'regex' \| 'grammar' \| 'xml'>` | optional | Fallback format if primary format fails |
125-
| **transformPipeline** | `Enum<'trim' \| 'parse_json' \| 'validate' \| 'coerce_types'>[]` | optional | Post-processing steps applied to output |
124+
| **fallbackFormat** | `Enum<'json_object' \| 'json_schema'>` | optional | Fallback format: once the primary format's retries are spent, the last answer is checked against this format instead |
125+
| **transformPipeline** | `Enum<'trim' \| 'parse_json' \| 'validate'>[]` | optional | Post-processing steps applied to output |
126126

127127
### Nested Shape: `Agent.protection`
128128

@@ -143,28 +143,25 @@ Structured output configuration for agent responses
143143

144144
| Property | Type | Required | Description |
145145
| :--- | :--- | :--- | :--- |
146-
| **format** | `Enum<'json_object' \| 'json_schema' \| 'regex' \| 'grammar' \| 'xml'>` | ✅ | Expected output format |
146+
| **format** | `Enum<'json_object' \| 'json_schema'>` | ✅ | Expected output format |
147147
| **schema** | `Record<string, any>` | optional | JSON Schema definition for output. An untyped subschema that carries a type-scoped keyword (properties, items, pattern, minimum, …) is refused at its path, because the AI runtime's schema reader does not check it; declare its "type". |
148148
| **strict** | `boolean` | optional (default: `false`) | Enforce exact schema compliance |
149149
| **retryOnValidationFailure** | `boolean` | optional (default: `true`) | Retry generation when output fails validation |
150150
| **maxRetries** | `integer` | optional (default: `3`) | Maximum retries on validation failure |
151-
| **fallbackFormat** | `Enum<'json_object' \| 'json_schema' \| 'regex' \| 'grammar' \| 'xml'>` | optional | Fallback format if primary format fails |
152-
| **transformPipeline** | `Enum<'trim' \| 'parse_json' \| 'validate' \| 'coerce_types'>[]` | optional | Post-processing steps applied to output |
151+
| **fallbackFormat** | `Enum<'json_object' \| 'json_schema'>` | optional | Fallback format: once the primary format's retries are spent, the last answer is checked against this format instead |
152+
| **transformPipeline** | `Enum<'trim' \| 'parse_json' \| 'validate'>[]` | optional | Post-processing steps applied to output |
153153

154154

155155
---
156156

157157
## StructuredOutputFormat
158158

159-
Output format for structured agent responses
159+
Output format for structured agent responses (JSON only)
160160

161161
### Allowed Values
162162

163163
* `json_object`
164164
* `json_schema`
165-
* `regex`
166-
* `grammar`
167-
* `xml`
168165

169166

170167
---
@@ -178,7 +175,6 @@ Post-processing step for structured output
178175
* `trim`
179176
* `parse_json`
180177
* `validate`
181-
* `coerce_types`
182178

183179

184180
---

‎packages/spec/liveness/agent.json‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,11 @@
103103
"note": "Flipped from experimental on the cloud seat's reading, not on a reading taken from this repo — no seat in the flipping session could read cloud. The reading: the repo:cloud seat repo:cloud#1 (session_01Wxo1xhh2bU66T73q23jzE4), comment 5940785002 on objectstack-ai/objectstack#20274, of cloud main @235c5b29 (the merge of objectstack-ai/cloud#2559), read 2026-10-01; verifiedAt is that reading's date, and every evidence and producer pointer above is quoted from it. The blanket verdict covers all three children: maxTokensPerInvocation, maxExecutionTimeSec and blockedTopics. These keys are live because the cloud runtime consumes them; the OPEN framework edition does not run agents (this file's _note). ⛔ Re-attest by re-reading cloud, never by re-stamping the date."
104104
},
105105
"structuredOutput": {
106-
"status": "experimental",
107-
"evidence": "no runtime reader",
108-
"note": "aspirational."
106+
"status": "live",
107+
"verifiedAt": "2026-10-02",
108+
"evidenceScope": "cross-repo",
109+
"evidence": "cloud @cb62c3ea: packages/service-ai/src/agent-runtime.ts#compileStructuredOutput — the reader: it compiles the agent's structuredOutput into the turn's output contract, reached through AgentRuntime.resolveTurnGuardrails; cloud @cb62c3ea: packages/service-ai/src/ai-service.ts#AIService.settleFinalAnswer — the enforcement, on every final answer in chatWithToolsImpl and streamChatWithTools, the forced final call included: format json_object / json_schema parsed and then checked against schema, strict, retryOnValidationFailure and maxRetries (each re-ask a guarded model round whose tokens are metered), fallbackFormat checked against the last answer once the primary's retries are spent, and transformPipeline trim / parse_json / validate. The refusal before the first round, TurnGovernor.unsupportedOutput, answered the members this spec has since retired (regex / grammar / xml and coerce_types) with AI_AGENT_STRUCTURED_OUTPUT_UNSUPPORTED.",
110+
"note": "Flipped from experimental on the cloud seat's reading, not on a reading taken from this repo — no seat in the flipping session could read cloud. The reading: the repo:cloud seat repo:cloud#1 (session_01Wxo1xhh2bU66T73q23jzE4), comment 5943158888 on objectstack-ai/objectstack#20274, of cloud main @cb62c3ea (the merge of objectstack-ai/cloud#2570), read 2026-10-02; verifiedAt is that reading's date, and every evidence pointer above is quoted from it. The blanket verdict covers every child: format, schema, strict, retryOnValidationFailure, maxRetries, fallbackFormat and transformPipeline. The same change retired the four members that reading found refused rather than enforced — the regex, grammar and xml formats and the coerce_types step (ruling record 5945617233, letter A): each is refused at parse with a prescription (enumWithRetiredValues in packages/spec/src/ai/agent.zod.ts), and the ADR-0087 conversion agent-structured-output-refused-members-removed repairs stored and existing sources — so every value an author can still write is one the runtime enforces. Producer: none in cloud at that ref; tenant-authored agents are NOT MEASURED. These keys are live because the cloud runtime consumes them; the OPEN framework edition does not run agents (this file's _note). ⛔ Re-attest by re-reading cloud, never by re-stamping the date."
109111
},
110112
"knowledge": {
111113
"status": "dead",

‎packages/spec/liveness/state-counts/agent.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@ committed anywhere: `check:liveness` sums the shards when it reads them.
1212

1313
| Type | live | exp | elsewhere | dead | planned | classified |
1414
|---|---|---|---|---|---|---|
15-
| `agent` | 21 | 3 | 0 | 2 | 0 | 26 |
15+
| `agent` | 22 | 2 | 0 | 2 | 0 | 26 |

0 commit comments

Comments
 (0)