feat(0.30.0): FindingSubject — typed grammar + parser + Zod boundary#61
Merged
Conversation
Closes the substrate gap that turned every per-vertical ImprovementAdapter into dead code: the analyst kinds' actor prompts documented a subject grammar (`agent-knowledge:wiki:<slug>`, `system-prompt:<section>`, ...) but `subject` was an unvalidated `z.string().optional()` and the LLM could emit prose like `subject: "fix the prompt"` which downstream `startsWith(...)` routing silently dropped.
This PR makes the grammar load-bearing:
1. **`src/analyst/finding-subject.ts`** — discriminated-union `FindingSubject`, `parseFindingSubject(raw)` parser, `renderFindingSubject(s)` inverse, and a `FINDING_SUBJECT_GRAMMAR_PROMPT` constant kinds can embed as the single source of truth.
Variants cover every locus the substrate routes on:
- `agent-knowledge:{wiki,claim,raw,stale}:<locus>` → `KnowledgeAdapter`
- `system-prompt`, `tool-doc`, `new-tool`, `rag`, `memory`, `scaffolding`, `output-schema` → `ImprovementAdapter`
- `websearch.outdated`, `prior-run-summary` → stale signals
- `cluster` → failure-mode-only free-form labels
Slugs / tool ids are constrained to `[a-z0-9-]+`; topics / sections / keys allow free-form text trimmed.
2. **`KIND_EXPECTED_SUBJECTS`** — per-kind allow-list. failure-mode emits ONLY `cluster`; knowledge-gap can't sneak in a `system-prompt:*` (the improvement-analyst's job); improvement can't emit stale signals. Enforced at the kind factory boundary.
3. **`RawAnalystFindingSchema.subject`** — Zod `.refine` that runs the parser. Malformed subjects fail the row at Zod parse time with a clear log message instead of being silently lifted with a free-form string.
4. **`kind-factory.ts`** — after `parseRawFinding`, the factory checks the parsed subject against the kind's allow-list. Wrong-kind subjects (e.g. an improvement finding pointing at `cluster:foo`) are logged + counted in `rejected_wrong_subject` and excluded from `out`. Visible to operators in the `analyst.kind <id> done` log line.
5. **Tests**: 38 new cases on `parseFindingSubject` cover every variant (positive + malformed), boundary inputs (null / empty / whitespace / prose), round-trip via `renderFindingSubject`, and the `KIND_EXPECTED_SUBJECTS` truth table (failure-mode is the ONLY kind that emits cluster; improvement excludes stale signals; etc.). Updated the legacy `'tool:foo'` fixtures in `kinds.test.ts` to canonical `'tool-doc:foo'`.
Result: every downstream consumer (agent-runtime's `KnowledgeAdapter` / `ImprovementAdapter`, per-vertical wiring) can now narrow on `FindingSubject['kind']` instead of `startsWith('agent-knowledge:wiki:')` — no more silent skips, no more fabricated paths, no more theater.
Tests: 1196/1196 pass (38 new). Typecheck clean. Bumps to 0.30.0 (npm + pypi + python `__version__`).
drewstone
added a commit
that referenced
this pull request
May 20, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes the substrate gap that made every per-vertical `ImprovementAdapter` dead code: the analyst kinds' actor prompts documented a subject grammar (`agent-knowledge:wiki:`, `system-prompt:
This PR makes the grammar load-bearing — every emitted subject is parsed at the schema boundary; non-conforming rows fail loud (logged + skipped) rather than being lifted with free-form text.
What changes
Why this matters
Downstream substrate work in agent-runtime (`defineAgent` + manifest-driven `ImprovementAdapter`) and per-vertical wiring (tax / legal / gtm / creative / N future verticals) all narrow on `FindingSubject['kind']` instead of fragile prefix matching. No silent skips. No fabricated paths. No theater.
Test plan