Skip to content

Add typecheck budget guard to catch type-instantiation regressions#563

Closed
sroussey wants to merge 4 commits into
claude/stoic-bell-RLJWTfrom
main
Closed

Add typecheck budget guard to catch type-instantiation regressions#563
sroussey wants to merge 4 commits into
claude/stoic-bell-RLJWTfrom
main

Conversation

@sroussey

Copy link
Copy Markdown
Collaborator

Summary

Adds a CI guard (scripts/typecheck-budget.ts) that gates per-package TypeScript instantiation counts to catch type-level performance regressions at PR time. Includes investigation documentation of two recent slowdowns and refactors AI task types to inline FromSchema calls, reducing instantiation pressure.

Key Changes

  • New typecheck budget guard (scripts/typecheck-budget.ts):

    • Measures per-package instantiation counts via tsc --extendedDiagnostics
    • Compares against committed budgets in scripts/typecheck-budget.json
    • Supports --update to re-baseline, --json to emit measurements, --tolerance to adjust headroom
    • Runs in CI as a new typecheck-budget job in .github/workflows/test.yml
  • Investigation documentation (docs/technical/typecheck-performance-investigation.md):

    • Timeline of typecheck slowdown from ~11s (Feb 2026) to ~40s (Jun 2026)
    • Bisected two culprits: 16a94b54 (MCP allOf spreading, already fixed) and 888da0e1 (durable ai regression from task-runner refactor)
    • Root-cause analysis and prototyped fixes for the diffuse ai cost
  • AI task type refactoring (multiple files in packages/ai/src/task/):

    • Replaced FromSchema<typeof SomeSchema> with inlined type definitions
    • Affected tasks: AiChatTask, AiChatWithKbTask, PoseLandmarkerTask, FaceLandmarkerTask, GestureRecognizerTask, HandLandmarkerTask, ObjectDetectionTask, FaceDetectorTask, ImageClassificationTask, ImageSegmentationTask, ImageToTextTask, ImageEmbeddingTask, TextEmbeddingTask, ToolCallingTask, HierarchyJoinTask, ChunkRetrievalTask, HierarchicalChunkerTask, ContextBuilderTask, TextChunkerTask, ChunkVectorUpsertTask, and others
    • Also removed unused FromSchema imports and helper types (WithImageValuePorts, TypedArraySchemaOptions) where no longer needed
    • Inlining reduces instantiation cost by avoiding deep schema-to-type inference at multiple sites
  • ModelSchema refactoring (packages/ai/src/model/ModelSchema.ts):

    • Inlined ModelConfig and ModelRecord type definitions instead of deriving from FromSchema
  • Documentation updates:

    • Updated packages/task-graph/README.md and packages/task-graph/src/storage/README.md to show new tabularTaskOutputStorage() wrapper in examples
    • Added tabularTaskOutputStorage import to code samples
  • CI improvements:

    • Added Hugging Face model cache to RAG test job to speed up repeated runs
    • New typecheck:budget npm script in package.json

Implementation Details

The budget guard uses tsc -b to warm-build all packages (emitting .d.ts for dependency graphs), then measures each package in isolation with tsc -p <pkg> --extendedDiagnostics after removing its tsbuildinfo to force a full re-check. Instantiations is the headline metric—it is deterministic and machine-independent, unlike wall-clock time, making the gate stable across CI runners while still catching regressions like the tasks spike (289k → 6.77M) or the ai doubling (0.9s → 3.4s check time).

The AI task type inlining is a targeted mitigation for the 888da0e1 regression. While the root cause is diffuse in core generics and no surgical fix was found, inlining avoids repeated instantiation of the same schema-to-type inference at multiple task sites, reducing overall pressure on the type checker.

https://claude.ai/code/session_01VgNMXhHCKrbY1Eaa4rfRe8

sroussey and others added 4 commits June 9, 2026 05:18
…ons (#555)

* ci: add typecheck instantiation-budget guard + perf investigation writeup

Adds scripts/typecheck-budget.ts, which type-checks each composite package in
isolation and gates its (deterministic, machine-independent) instantiation count
against committed per-package budgets in scripts/typecheck-budget.json. Wired
into CI as the `typecheck-budget` job and exposed as `bun run typecheck:budget`.

Also documents the typecheck slowdown investigation (timeline, bisected culprit
commits, root-cause analysis, and prototyped fixes) in
docs/technical/typecheck-performance-investigation.md.

https://claude.ai/code/session_018SckHLTLmkxKCSWyb74Lke

* perf(ai): replace FromSchema-derived types with concrete interfaces (AI Text slice)

Proof-slice for the typecheck-cost work: the dominant `packages/ai` check cost is
the `FromSchema<typeof Schema>` conditional-type machinery (json-schema-to-ts),
not the Workflow/CreateWorkflow generics. Converting the plain
`type X = FromSchema<typeof Schema>` aliases to concrete types removes that
per-use instantiation cost.

This slice converts the AI Text task family + the foundational model types
(ModelConfig/ModelRecord). Each concrete type is the type checker's exact
resolution of the original FromSchema type (verified by a clean full `tsc -b`),
so it is equivalent to the prior derived type; the schema constants remain the
runtime contract. Budget baseline ratcheted to match.

Measured (pinned tsc, deps prebuilt, isolated `tsc -p`):
- ai: 564,545 -> 520,877 instantiations (this 7-file slice)
- full-ai conversion projects to ~320k instantiations / -39% check time,
  with a ~-14% ripple on packages/test (tracked in #556)

Tradeoff: a hot-path `Equal<X, FromSchema<...>>` drift guard is intentionally
omitted because it would recompute FromSchema and defeat the win; the schemas
stay the runtime source of truth.

Refs #556.

https://claude.ai/code/session_018SckHLTLmkxKCSWyb74Lke

* perf(ai): convert remaining FromSchema-derived task types to concrete types

Completes the FromSchema->concrete migration started in the AI Text slice.
Converts the remaining plain and composite (Omit<...> / WithImageValuePorts<...>)
type aliases across packages/ai to concrete types, removing the json-schema-to-ts
conditional-type instantiation cost from the AI task input/output types.

Each concrete type is the type checker's exact resolution of the original
FromSchema type (verified by a clean full `tsc -b`); the schema constants remain
the runtime contract. ModelConfig/ModelRecord and ChatMessage are referenced by
name rather than inlined for readability. Four vector/embedding outputs that
involve branded typed-array types (TextEmbedding, ImageEmbedding,
VectorSimilarity) are intentionally left on FromSchema since they do not print
to a clean concrete form.

Measured (pinned tsc, deps prebuilt, isolated `tsc -p`):
- ai:   564,545 -> 92,750 instantiations (-84%), 4.29s -> ~2.0s check (-52%)
- test (ripple): 1,427,138 -> 947,215 instantiations (-34%), 12.4s -> ~9.2s

Budget baselines ratcheted to match.

Closes #556.

https://claude.ai/code/session_018SckHLTLmkxKCSWyb74Lke

---------

Co-authored-by: Claude <noreply@anthropic.com>
… new constructor signature

After commit c011234 removed the legacy `{ tabularRepository: ... }` option
shim, copy-paste from these README examples produced a TS error against the
new `{ storage: ITaskOutputStorage }` constructor signature. Replaced the
eight stale `tabularRepository:` constructor calls across two READMEs with
`storage: tabularTaskOutputStorage(...)` and pulled the helper into each
example's import block.
* ci: authenticate and cache Hugging Face model downloads

CI test jobs were downloading ONNX/GGUF models anonymously from the
Hugging Face Hub on every run, which hits anonymous IP rate limits on
shared GitHub runners (downloads denied).

- Pass WORKGLOW_SECRETS_PASSPHRASE to the model-downloading vitest jobs
  (hft, nodellama; rag already had it) so the test preload decrypts the
  on-disk credential store and hydrates HF_TOKEN into process.env.
  transformers.js authenticates Hub downloads when HF_TOKEN is set,
  raising the rate limit against the account.
- Cache the repo-root ./models directory (where the test cache dir is
  configured) per job so models are not re-fetched on every run.

* ci: add encrypted hf-token credential

---------

Co-authored-by: Claude <noreply@anthropic.com>
@sroussey sroussey closed this Jun 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants