Skip to content

feat(inference): type llama.cpp serving recipes - #8183

Merged
ericksoa merged 7 commits into
mainfrom
feat/llamacpp-catalog-contract-8181
Aug 4, 2026
Merged

feat(inference): type llama.cpp serving recipes#8183
ericksoa merged 7 commits into
mainfrom
feat/llamacpp-catalog-contract-8181

Conversation

@ericksoa

@ericksoa ericksoa commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Summary

Extend the managed inference catalog with a typed, declarative llama.cpp recipe contract and recipe-linked readiness validation. This stacked change targets the catalog compiler in #8152 and remains inert until #8173 supplies approved production artifacts and adapter identities.

Related Issue

Fixes #8181

Part of #8144. Follow-up production configuration remains in #8173.

Changes

  • Add a discriminated install-llama-cpp recipe schema for the catalog compiler, covering immutable server and GGUF identity, Docker/CUDA/GPU boundaries, bounded resources and serving limits, offline policy, disabled surfaces, readiness probes, and explicit capabilities. A direct production recipe is not appropriate in this slice because its artifact tuple and runtime adapters are not yet approved; synthetic fixtures protect the contract without activating serving.
  • Add typed readiness comparisons and injected observation roles for the accepted Linux, architecture, Docker, GPU-count, and driver matrix. Compiler tests prove that a selecting preset cannot contradict its linked recipe or bypass agent qualification readiness.
  • Add receipt and readiness-contract registries alongside existing materializer and lifecycle registries, and reject unknown references for both generic and llama.cpp recipes.
  • Reserve the llama.cpp backend/provider/server discriminators, require explicit-only selection, and reject mutable artifacts, executable syntax, host paths, unsupported capabilities, incomplete limits, and contradictory declarations.
  • Keep production registries and catalog definitions empty. Deterministic compiler, package-contract, and negative tests cover the new contract.

Type of Change

  • Code change (feature, bug fix, or refactor)
  • Code change with doc updates
  • Doc only (prose changes, no code sample modifications)
  • Doc only (includes code sample changes)

Quality Gates

  • Tests added or updated for changed behavior
  • Existing tests cover changed behavior — justification:
  • Tests not applicable — justification:
  • Docs updated for user-facing behavior changes
  • Docs not applicable — justification: This is an inert schema/compiler contract with synthetic fixtures only; it adds no production recipe, provider selection, lifecycle action, default, or support claim.
  • Sensitive paths changed (security, policy, credentials, preflight, onboarding, inference, runner, sandbox, or messaging)
  • Sensitive-path review completed or maintainer-approved waiver recorded — reviewer/approval link/justification: Codex Desktop completed the repository's nine-category security review at exact head cc298cdfbcbe973ff7dc36bdd6e024a99a12e3b9; all categories passed with no blocking findings.
  • Non-success, skipped, or missing CI check accepted by maintainer — check name, approval link, and follow-up issue:

Documentation Writer Review

  • Documentation writer subagent reviewed the completed changes
  • Result: no-docs-needed
  • Evidence: The exact committed diff adds an inert schema/compiler contract with synthetic fixtures and no production recipe, provider selection, lifecycle action, default, or support claim.
  • Agent: Codex Desktop

DGX Station Hardware Evidence

  • Tested on DGX Station
  • Tested commit:
  • Station profile/scenario:
  • Result:
  • Supporting evidence:

Verification

  • PR description includes a Signed-off-by: line and every commit appears as Verified in GitHub
  • Normal pre-commit, commit-msg, and pre-push hooks passed, or npm run validate:pr passed after refreshing origin/main when hooks were skipped or unavailable
  • Targeted behavior tests pass for the current change set, or tests are marked not applicable above — command/result or justification: npx vitest run --project cli src/lib/inference/serving/catalog.test.ts (54 passed); package-contract catalog tests (2 passed); npm run test:changed (54 passed); the changed-test conditionals guard passes.
  • Applicable broad gate passed — npm test for broad runtime/test-harness changes; npm run check for repo-wide validation/coverage changes — command/result: Not applicable; the focused compiler tests, package contract, npm run build:cli, and repository hooks cover this bounded catalog-contract change.
  • Quality Gates section completed with required justifications or waivers
  • No secrets, API keys, or credentials committed
  • npm run docs builds without warnings (doc changes only)
  • Doc pages follow the style guide (doc changes only)
  • New doc pages include SPDX header and frontmatter (new pages only)

Signed-off-by: Aaron Erickson aerickson@nvidia.com

Summary by CodeRabbit

  • New Features

    • Added richer serving recipe definitions, including provider metadata, runtime requirements, model artifacts, serving limits, readiness probes, policies, surfaces, and capabilities.
    • Added specialized llama.cpp recipe validation with platform, GPU, runtime, model, and agent qualification checks.
    • Added observation-based readiness requirements, including equality, membership, numeric minimum, and version minimum comparisons.
    • Added validation for receipts, readiness contracts, artifacts, revisions, versions, and image digests.
  • Improvements

    • Updated the catalog compiler version to 1.1.0.
    • Strengthened validation for readiness requirements, duplicate declarations, and runtime compatibility.

@ericksoa ericksoa self-assigned this Aug 4, 2026
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 37ef0ddc-3d66-42cb-b230-011fadfb4da1

📥 Commits

Reviewing files that changed from the base of the PR and between cd998d6 and e358a67.

📒 Files selected for processing (1)
  • src/lib/inference/serving/catalog.test.ts

📝 Walkthrough

Walkthrough

The catalog now supports declarative llama.cpp recipes, typed readiness comparisons, structured registries, and compiler version 1.1.0. Compiler semantics validate artifacts, runtime constraints, readiness requirements, agent qualifications, and preset selection rules.

Changes

Declarative llama.cpp catalog

Layer / File(s) Summary
Recipe and readiness contracts
managed-inference/schemas/*.schema.json, src/lib/inference/serving/types.ts
Schemas and TypeScript types define llama.cpp recipe metadata, runtime and serving constraints, readiness comparisons, structured registries, and compiler version 1.1.0.
Recipe validation and registry wiring
src/lib/inference/serving/catalog.ts, src/lib/inference/serving/generate-catalog.ts, src/lib/inference/serving/catalog.test.ts
Recipe validation checks registry references, immutable artifacts, runtime constraints, serving limits, model identity, token limits, agent uniqueness, and qualifications.
Readiness and preset semantics
src/lib/inference/serving/catalog.ts, src/lib/inference/serving/catalog.test.ts
Preset validation checks comparison types, duplicates, contradictions, required host observations, agent qualifications, and explicit-only llama.cpp selection.
Version and regression coverage
managed-inference/schemas/catalog.schema.json, src/lib/inference/serving/catalog.ts, src/lib/inference/serving/catalog.test.ts
Compiler-version expectations and deterministic catalog assertions now use 1.1.0, with coverage for valid and invalid catalog definitions.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CatalogCompiler
  participant ServingRecipe
  participant ServingCatalogRegistries
  participant ServingPreset
  CatalogCompiler->>ServingRecipe: validate recipe contracts and llama.cpp constraints
  ServingRecipe->>ServingCatalogRegistries: resolve receipt and readiness references
  CatalogCompiler->>ServingPreset: validate selection and readiness requirements
  ServingPreset->>ServingCatalogRegistries: resolve observations and qualifications
  CatalogCompiler-->>CatalogCompiler: compile deterministic catalog version 1.1.0
Loading

Possibly related issues

  • 8173 — The production llama.cpp recipe and preset consume the catalog schemas and compiler support added here.

Possibly related PRs

  • NVIDIA/NemoClaw#8129 — Modifies related managed-inference catalog schemas, compiler versioning, and serving validation.
  • NVIDIA/NemoClaw#8152 — Provides the serving catalog schemas, types, compiler, and tests extended by this change.

Suggested labels: area: inference, area: architecture, feature

Suggested reviewers: apurvvkumaria

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the primary change: adding typed llama.cpp serving recipes.
Linked Issues check ✅ Passed The schema, compiler, registries, fixtures, and tests implement the typed llama.cpp contract required by issue #8181.
Out of Scope Changes check ✅ Passed The changes stay within issue #8181 and do not activate production recipes, runtime selection, or serving behavior.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/llamacpp-catalog-contract-8181

Comment @coderabbitai help to get the list of available commands.

@github-code-quality

github-code-quality Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Code Coverage Overview

Languages: TypeScript

TypeScript / code-coverage/plugin

The overall coverage in commit e358a67 in the feat/llamacpp-catalo... branch remains at 96%, unchanged from commit a556201 in the main branch.

TypeScript / code-coverage/cli

The overall coverage in commit e358a67 in the feat/llamacpp-catalo... branch remains at 81%, unchanged from commit a556201 in the main branch.

Show a code coverage summary of the most impacted files.
File main a556201 feat/llamacpp-catalo... e358a67 +/-
src/lib/domain/.../connect-env.ts 97% 89% -8%
src/lib/onboard...shboard-port.ts 96% 90% -6%
src/lib/platform.ts 89% 84% -5%
src/lib/shields/index.ts 70% 70% 0%
src/lib/messagi...nnels/policy.ts 100% 100% 0%
src/lib/sandbox...rce-identity.ts 88% 88% 0%
src/lib/state/config-io.ts 93% 93% 0%
src/lib/state/g...way-registry.ts 94% 94% 0%
src/lib/inferen...ving/catalog.ts 91% 93% +2%

Updated August 04, 2026 05:37 UTC

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

PR Review Advisor — No blocking findings reported

Advisor assessment: No blocking advisor findings reported
Next action: No advisor follow-up needed.
Findings: 0 blockers · 0 warnings · 0 suggestions

Model lanes

  • GPT-5.6 Terra (primary): Completed · high confidence · 0 blockers · 0 warnings · 0 suggestions
  • Nemotron 3 Ultra (second opinion): Completed · high confidence · 0 blockers · 0 warnings · 0 suggestions
  • Model comparison: normalized findings match; normalized terminology decisions differ; normalized E2E selections match; severity counts match.
10 terminology differences from the second opinion

Advisory only. These are normalized differences from the primary terminology receipt.

  • qualification at managed-inference/schemas/recipe.schema.json:275: selected only by the second-opinion lane as conflict.
  • explicit-only at src/lib/inference/serving/catalog.ts:404: selected only by the second-opinion lane as justified.
  • repositoryId at managed-inference/schemas/recipe.schema.json:27: selected only by the second-opinion lane as conflict.
  • comparison at managed-inference/schemas/preset.schema.json:81: selected only by the second-opinion lane as justified.
  • surfaces at managed-inference/schemas/recipe.schema.json:232: selected only by the second-opinion lane as conflict.
  • providerId at managed-inference/schemas/recipe.schema.json:17: selected only by the second-opinion lane as conflict.
  • artifactLabel at managed-inference/schemas/recipe.schema.json:66: selected only by the second-opinion lane as justified.
  • nonPathStableId at managed-inference/schemas/recipe.schema.json:277: selected only by the second-opinion lane as established.
  • readinessContracts at src/lib/inference/serving/catalog.test.ts:37: selected only by the second-opinion lane as established.
  • receiptRef at managed-inference/schemas/recipe.schema.json:151: selected only by the second-opinion lane as established.

Second-opinion terminology and E2E selections are advisory. They do not change the primary assessment or E2E / PR Gate.

3 semantic terminology decisions

Terminology decisions are advisory. They affect the assessment only when a separate finding identifies concrete semantic impact.

  • define — readiness comparison at src/lib/inference/serving/catalog.test.ts:504: Define the catalog-specific meaning before using the term in broader explanatory text.
  • define — receipt contract at src/lib/inference/serving/catalog.ts:163: Define the catalog-specific meaning before using the term in broader explanatory text.
  • established — readiness contract at src/lib/inference/serving/catalog.ts:179: Use the existing term consistently for the recipe readiness contract reference.

E2E guidance

Advisory only. E2E / PR Gate selects and runs jobs independently.

Recommended E2E: inference-routing, network-policy

Workflow run details

This automated review informs maintainers. Warnings and suggestions do not require a response. A maintainer decides whether to merge.

Signed-off-by: Aaron Erickson <aerickson@nvidia.com>

@apurvvkumaria apurvvkumaria left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed exact head cc298cd against stacked base 89d9399. Approve: the new llama.cpp contract remains inert with empty production registries, validates immutable recipe identity, bounded runtime and serving limits, readiness roles and value types, explicit-only selection, and agent qualifications fail closed. The focused catalog suite passed 54 of 54 tests locally; exact-head CLI shards, static analysis, CodeQL, and the primary review advisor also passed with no findings. Current image-build and E2E coordination failures arise from shared dependency and coordination infrastructure outside this seven-file compiler/schema delta and remain for the normal merge gate.

Base automatically changed from feat/declarative-serving-catalog-8144 to main August 4, 2026 03:49
@copy-pr-bot

copy-pr-bot Bot commented Aug 4, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
managed-inference/schemas/recipe.schema.json (1)

349-352: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Reuse imageDigest for spec.runtime.image.

The pattern at Line 349-352 duplicates the inline pattern at Line 80. Two copies can drift. Point spec.runtime.image at the new definition.

♻️ Proposed refactor
-            "image": {
-              "type": "string",
-              "pattern": "^(?:[a-z0-9]+(?:[._-][a-z0-9]+)*(?::[0-9]+)?/)?(?:[a-z0-9]+(?:[._-][a-z0-9]+)*/)*[a-z0-9]+(?:[._-][a-z0-9]+)*`@sha256`:[0-9a-f]{64}$"
-            },
+            "image": { "$ref": "`#/`$defs/imageDigest" },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@managed-inference/schemas/recipe.schema.json` around lines 349 - 352, Update
spec.runtime.image to reference the existing imageDigest schema definition
instead of duplicating the inline digest pattern. Remove the duplicated pattern
from that property while preserving the imageDigest definition as the single
source of truth.
src/lib/inference/serving/catalog.ts (1)

150-150: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Export LlamaCppServingRecipe from types.ts instead of re-deriving it.

src/lib/inference/serving/types.ts already declares the LlamaCppServingRecipe interface at Line 58. The interface is not exported, so this file rebuilds it with Extract. The Extract form works, but it couples the compiler to the internal shape of the discriminator. Export the interface and import it directly.

♻️ Proposed refactor

In src/lib/inference/serving/types.ts:

-interface LlamaCppServingRecipe extends ServingRecipeEnvelope {
+export interface LlamaCppServingRecipe extends ServingRecipeEnvelope {

In src/lib/inference/serving/catalog.ts:

+  LlamaCppServingRecipe,
   ServingReadinessComparison,
   ServingReadinessObservationRole,
   ServingReadinessRequirement,
   ServingRecipe,
 } from "./types";
-type LlamaCppServingRecipe = Extract<ServingRecipe, { spec: { providerId: "llama-cpp-local" } }>;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/inference/serving/catalog.ts` at line 150, Export the existing
LlamaCppServingRecipe interface from types.ts, then update catalog.ts to import
and use that type directly. Remove the local Extract-based LlamaCppServingRecipe
alias while leaving the surrounding serving recipe definitions unchanged.
src/lib/inference/serving/catalog.test.ts (1)

340-389: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

The 21 negative cases assert one generic message.

Every case expects "does not satisfy the ServingRecipe schema". That message proves rejection, not the reason for rejection. A malformed replacement, a YAML indentation mistake, or an unrelated constraint produces the same pass. The replaceSource guard at Line 279 prevents a no-op edit, but it does not prove which constraint fired.

The risk is concentrated in the cases that target a specific new constraint, for example "an arbitrary launch argument" (the serve.not clause), "a path-based served model name" (nonPathStableId), and "shell syntax" (artifactLabel). Add the expected schema pointer or keyword to those cases so a schema change that removes the constraint fails the test.

As per path instructions: "Flag copied production algorithms, broad mocks that bypass the behavior under test, and conditionals that make a test pass without exercising its claim."

💚 Sketch of a targeted assertion

Extend the table with the expected detail fragment and assert on it:

-  ])("rejects %s in a llama.cpp recipe (`#8181`)", (_case, expected, replacement) => {
+  ])("rejects %s in a llama.cpp recipe (`#8181`)", (_case, expected, replacement, detail) => {
     const recipe = replaceSource(llamaCppRecipeSource(), expected, replacement);
 
-    expect(() => compile([recipe])).toThrow("does not satisfy the ServingRecipe schema");
+    expect(() => compile([recipe])).toThrow("does not satisfy the ServingRecipe schema");
+    if (detail) expect(() => compile([recipe])).toThrow(detail);
   });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/inference/serving/catalog.test.ts` around lines 340 - 389, Strengthen
the parameterized rejection test around the table in the “rejects %s in a
llama.cpp recipe” case so each fixture records the expected schema pointer or
keyword, especially for “an arbitrary launch argument,” “a path-based served
model name,” and “shell syntax.” Keep the replaceSource guard, then assert the
thrown validation error includes that case-specific detail rather than only the
generic “does not satisfy the ServingRecipe schema” message.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@managed-inference/schemas/recipe.schema.json`:
- Around line 526-544: Extend the generic-recipe exclusion in the schema’s else
branch so it rejects every llama.cpp-only field added to the shared spec
surface, including runtime, serve, readiness, and model metadata fields listed
in the review. Update the not.anyOf constraints near the else branch without
changing llama.cpp recipe validation or generic recipe fields.
- Around line 335-338: Update the sourceRevision schema property to accept only
valid SHA-1 or SHA-256 digest lengths: exactly 40 or 64 lowercase hexadecimal
characters. Preserve the existing hexadecimal and full-string constraints while
rejecting all intermediate lengths.

In `@src/lib/inference/serving/catalog.ts`:
- Around line 253-256: Update readinessRequirementKey to include the
requirement’s form (state versus comparison) in the generated key, preventing
distinct requirements for the same observation from colliding during
validatePresetReadiness. Add a test covering both forms together and verify they
are accepted without a contradictory-requirement error.

---

Nitpick comments:
In `@managed-inference/schemas/recipe.schema.json`:
- Around line 349-352: Update spec.runtime.image to reference the existing
imageDigest schema definition instead of duplicating the inline digest pattern.
Remove the duplicated pattern from that property while preserving the
imageDigest definition as the single source of truth.

In `@src/lib/inference/serving/catalog.test.ts`:
- Around line 340-389: Strengthen the parameterized rejection test around the
table in the “rejects %s in a llama.cpp recipe” case so each fixture records the
expected schema pointer or keyword, especially for “an arbitrary launch
argument,” “a path-based served model name,” and “shell syntax.” Keep the
replaceSource guard, then assert the thrown validation error includes that
case-specific detail rather than only the generic “does not satisfy the
ServingRecipe schema” message.

In `@src/lib/inference/serving/catalog.ts`:
- Line 150: Export the existing LlamaCppServingRecipe interface from types.ts,
then update catalog.ts to import and use that type directly. Remove the local
Extract-based LlamaCppServingRecipe alias while leaving the surrounding serving
recipe definitions unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: d700028d-a0ff-4a41-b267-5941e7fa3d4d

📥 Commits

Reviewing files that changed from the base of the PR and between 3fb4ac1 and cd998d6.

📒 Files selected for processing (7)
  • managed-inference/schemas/catalog.schema.json
  • managed-inference/schemas/preset.schema.json
  • managed-inference/schemas/recipe.schema.json
  • src/lib/inference/serving/catalog.test.ts
  • src/lib/inference/serving/catalog.ts
  • src/lib/inference/serving/generate-catalog.ts
  • src/lib/inference/serving/types.ts

Comment on lines +335 to +338
"sourceRevision": {
"type": "string",
"pattern": "^[0-9a-f]{40,64}$"
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Tighten sourceRevision to real commit digest lengths.

The pattern ^[0-9a-f]{40,64}$ accepts 41 to 63 hex characters. Those lengths are neither SHA-1 nor SHA-256. spec.model.revision is also constrained by the top-level alternation at Line 42, so it stays at 40 or 64. spec.server.source.revision has no such second constraint, so it accepts the intermediate lengths. This weakens the immutable server identity goal stated in the PR objectives.

🔒 Proposed fix
     "sourceRevision": {
       "type": "string",
-      "pattern": "^[0-9a-f]{40,64}$"
+      "pattern": "^(?:[0-9a-f]{40}|[0-9a-f]{64})$"
     },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"sourceRevision": {
"type": "string",
"pattern": "^[0-9a-f]{40,64}$"
},
"sourceRevision": {
"type": "string",
"pattern": "^(?:[0-9a-f]{40}|[0-9a-f]{64})$"
},
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@managed-inference/schemas/recipe.schema.json` around lines 335 - 338, Update
the sourceRevision schema property to accept only valid SHA-1 or SHA-256 digest
lengths: exactly 40 or 64 lowercase hexadecimal characters. Preserve the
existing hexadecimal and full-string constraints while rejecting all
intermediate lengths.

Comment on lines +526 to +544
"else": {
"type": "object",
"not": {
"anyOf": [
{
"type": "object",
"properties": { "providerId": {} },
"required": ["providerId"]
},
{ "type": "object", "properties": { "policy": {} }, "required": ["policy"] },
{ "type": "object", "properties": { "surfaces": {} }, "required": ["surfaces"] },
{
"type": "object",
"properties": { "capabilities": {} },
"required": ["capabilities"]
}
]
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

The else branch leaves llama.cpp-shaped fields open on generic recipes.

The else branch forbids providerId, policy, surfaces, and capabilities. It does not forbid the other llama.cpp-shaped fields that Line 83-218 added to the shared spec surface: runtime.platforms, runtime.containerRuntime, runtime.hosts, runtime.cuda, runtime.gpu, runtime.resources, serve.protocol, serve.port, serve.chatTemplate, serve.contextSize, serve.slots, serve.idleSleepSeconds, serve.limits, readiness.probes, and the model file sizeBytes, format, quantization, and license fields.

A generic recipe can therefore declare runtime.gpu.cpuFallback: reject or serve.limits and pass schema validation. isLlamaCppServingRecipe in src/lib/inference/serving/catalog.ts returns false for such a recipe, so no llama.cpp semantic check runs. GenericServingRecipe in src/lib/inference/serving/types.ts does not model those fields either, so the compiled catalog carries data that no consumer validates.

Extend the else not.anyOf list, or move the llama.cpp-only subschemas out of the shared spec.properties surface.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@managed-inference/schemas/recipe.schema.json` around lines 526 - 544, Extend
the generic-recipe exclusion in the schema’s else branch so it rejects every
llama.cpp-only field added to the shared spec surface, including runtime, serve,
readiness, and model metadata fields listed in the review. Update the not.anyOf
constraints near the else branch without changing llama.cpp recipe validation or
generic recipe fields.

Comment on lines +253 to 256
function readinessRequirementKey(requirement: ServingReadinessRequirement): string {
const readiness = requirement.readiness;
return `${readiness.scope}:${readiness.kind}:${readiness.id}`;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Find presets or tests that declare both a state form and a comparison form for one observation id.
set -euo pipefail

fd -e yaml -e yml . managed-inference 2>/dev/null | while IFS= read -r file; do
  rg -n -C 6 'kind:\s*observation' "$file" || true
done

rg -n -C 6 'readinessRequirementKey|contradictory readiness requirements' --type=ts

Repository: NVIDIA/NemoClaw

Length of output: 153


🏁 Script executed:

set -euo pipefail
printf '%s\n' '--- candidate files ---'
fd -i -t f 'catalog|readiness|preset|inference' . | head -200
printf '%s\n' '--- relevant symbols ---'
rg -n -C 12 'readinessRequirementKey|validatePresetReadiness|contradictory readiness requirements|ServingReadinessRequirement' src --glob '*.ts' --glob '*.tsx'
printf '%s\n' '--- managed-inference files ---'
if [ -d managed-inference ]; then
  fd -e yaml -e yml . managed-inference
else
  printf '%s\n' 'managed-inference directory not present'
fi

Repository: NVIDIA/NemoClaw

Length of output: 26887


🏁 Script executed:

set -euo pipefail
printf '%s\n' '--- requirement types and validator ---'
sed -n '140,185p' src/lib/inference/serving/types.ts
sed -n '253,307p' src/lib/inference/serving/catalog.ts
printf '%s\n' '--- schemas ---'
rg -n -C 12 '"requirements"|"state"|"comparison"|readiness' managed-inference/schemas schemas src/lib/inference --glob '*.json' --glob '*.ts'
printf '%s\n' '--- readiness evaluation and consumers ---'
rg -n -C 8 'requirements\?\.all|requirements\.all|readiness.*state|readiness.*comparison|contradictory readiness' src managed-inference nemoclaw-blueprint --glob '*.ts' --glob '*.tsx' --glob '*.json' --glob '*.yaml' --glob '*.yml'
printf '%s\n' '--- relevant tests ---'
sed -n '540,630p' src/lib/inference/serving/catalog.test.ts

Repository: NVIDIA/NemoClaw

Length of output: 50372


🏁 Script executed:

set -euo pipefail
printf '%s\n' '--- exact requirement consumers ---'
rg -n 'spec\.requirements|requirements\?\.all|requirements\.all|\.requirements' src/lib/inference src/lib/onboard scripts test --glob '*.ts' --glob '*.tsx' --glob '*.mts' --glob '*.js' --glob '*.json' | head -250
printf '%s\n' '--- preset fixtures and declarations ---'
rg -n -C 8 'requirements:|state: (present|absent)|comparison:|kind: observation' . --glob '*.yaml' --glob '*.yml' --glob '*.json' --glob '*.ts' --glob '*.tsx' | head -300
printf '%s\n' '--- schema requirement definition ---'
sed -n '62,175p' managed-inference/schemas/preset.schema.json
printf '%s\n' '--- all catalog tests around readiness ---'
rg -n -C 10 'readiness|requirement' src/lib/inference/serving/catalog.test.ts | head -300

Repository: NVIDIA/NemoClaw

Length of output: 37765


🏁 Script executed:

set -euo pipefail
sed -n '320,435p' src/lib/inference/serving/catalog.ts
sed -n '474,535p' src/lib/inference/serving/catalog.test.ts
printf '%s\n' '--- documentation for preset requirements ---'
rg -n -C 8 'requirements|readiness requirement|comparison|state: present' docs managed-inference README.md src/lib/README.md 2>/dev/null | head -250

Repository: NVIDIA/NemoClaw

Length of output: 37355


🏁 Script executed:

set -euo pipefail
python3 - <<'PY'
import json
import re
from pathlib import Path

catalog = Path("src/lib/inference/serving/catalog.ts").read_text()
types = Path("src/lib/inference/serving/types.ts").read_text()
schema = json.loads(Path("managed-inference/schemas/preset.schema.json").read_text())

state = {
    "readiness": {
        "scope": "everyNode",
        "kind": "observation",
        "id": "test.os",
        "state": "present",
    }
}
comparison = {
    "readiness": {
        "scope": "everyNode",
        "kind": "observation",
        "id": "test.os",
        "comparison": {"operator": "equals", "value": "linux"},
    }
}

key_match = re.search(
    r"return `\$\{readiness\.scope\}:\$\{readiness\.kind\}:\$\{readiness\.id\}`;",
    catalog,
)
assert key_match, "readinessRequirementKey does not use the claimed key shape"

def key(requirement):
    readiness = requirement["readiness"]
    return f'{readiness["scope"]}:{readiness["kind"]}:{readiness["id"]}'

assert key(state) == key(comparison)
assert '"state": "present"' in types
assert "comparison: ServingReadinessComparison" in types

branches = schema["$defs"]["readinessRequirement"]["properties"]["readiness"]["oneOf"]
required_forms = {tuple(branch["required"]) for branch in branches}
assert ("scope", "kind", "id", "state") in required_forms
assert ("scope", "kind", "id", "comparison") in required_forms

assert "const key = readinessRequirementKey(requirement);" in catalog
assert "if (previous !== undefined)" in catalog
assert "has contradictory readiness requirements" in catalog

print("state and comparison forms are distinct schema/type variants")
print("both forms produce the same readinessRequirementKey")
print("the validator rejects the second form as contradictory")
PY

Repository: NVIDIA/NemoClaw

Length of output: 242


🏁 Script executed:

set -euo pipefail
python3 - <<'PY'
import json
import re
from pathlib import Path

catalog = Path("src/lib/inference/serving/catalog.ts").read_text()
types = Path("src/lib/inference/serving/types.ts").read_text()
schema = json.loads(Path("managed-inference/schemas/preset.schema.json").read_text())

state = {"readiness": {
    "scope": "everyNode", "kind": "observation", "id": "test.os", "state": "present"
}}
comparison = {"readiness": {
    "scope": "everyNode", "kind": "observation", "id": "test.os",
    "comparison": {"operator": "equals", "value": "linux"}
}}

def check(name, condition):
    print(f"{name}: {'PASS' if condition else 'FAIL'}")
    return condition

def key(requirement):
    readiness = requirement["readiness"]
    return f'{readiness["scope"]}:{readiness["kind"]}:{readiness["id"]}'

checks = [
    ("key expression", bool(re.search(
        r"return `\$\{readiness\.scope\}:\$\{readiness\.kind\}:\$\{readiness\.id\}`;",
        catalog,
    ))),
    ("same key", key(state) == key(comparison)),
    ("state union", '"state": "present"' in types),
    ("comparison union", "comparison: ServingReadinessComparison" in types),
    ("validator uses key", "const key = readinessRequirementKey(requirement);" in catalog),
    ("validator rejects prior key", "if (previous !== undefined)" in catalog),
    ("contradiction message", "has contradictory readiness requirements" in catalog),
]

branches = schema["$defs"]["readinessRequirement"]["properties"]["readiness"]["oneOf"]
required_forms = {tuple(branch["required"]) for branch in branches}
checks.extend([
    ("schema state form", ("scope", "kind", "id", "state") in required_forms),
    ("schema comparison form", ("scope", "kind", "id", "comparison") in required_forms),
])

if not all(check(name, condition) for name, condition in checks):
    raise SystemExit(1)
PY

Repository: NVIDIA/NemoClaw

Length of output: 207


🏁 Script executed:

set -euo pipefail
python3 - <<'PY'
import json
import re
from pathlib import Path

catalog = Path("src/lib/inference/serving/catalog.ts").read_text()
types = Path("src/lib/inference/serving/types.ts").read_text()
schema = json.loads(Path("managed-inference/schemas/preset.schema.json").read_text())

state = {"scope": "everyNode", "kind": "observation", "id": "test.os", "state": "present"}
comparison = {
    "scope": "everyNode",
    "kind": "observation",
    "id": "test.os",
    "comparison": {"operator": "equals", "value": "linux"},
}

def key(readiness):
    return f'{readiness["scope"]}:{readiness["kind"]}:{readiness["id"]}'

assert key(state) == key(comparison)
assert re.search(r'\bid: string;\s+state: "present" \| "absent";', types)
assert re.search(r'\bid: string;\s+comparison: ServingReadinessComparison;', types)

branches = schema["$defs"]["readinessRequirement"]["properties"]["readiness"]["oneOf"]
required_forms = {tuple(branch["required"]) for branch in branches}
assert ("scope", "kind", "id", "state") in required_forms
assert ("scope", "kind", "id", "comparison") in required_forms

assert "const key = readinessRequirementKey(requirement);" in catalog
assert "if (previous !== undefined)" in catalog
assert "has contradictory readiness requirements" in catalog

print("state and comparison forms are allowed by the type and schema")
print("both forms produce the same key")
print("the validator rejects the second requirement for that key")
PY

Repository: NVIDIA/NemoClaw

Length of output: 305


Include the requirement form in readinessRequirementKey. The schema and type allow both state and comparison requirements for one observation, but the current key makes them collide and validatePresetReadiness rejects the second as contradictory. Add a test for this combination.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/inference/serving/catalog.ts` around lines 253 - 256, Update
readinessRequirementKey to include the requirement’s form (state versus
comparison) in the generated key, preventing distinct requirements for the same
observation from colliding during validatePresetReadiness. Add a test covering
both forms together and verify they are accepted without a
contradictory-requirement error.

@ericksoa
ericksoa merged commit 04ff661 into main Aug 4, 2026
67 of 68 checks passed
@ericksoa
ericksoa deleted the feat/llamacpp-catalog-contract-8181 branch August 4, 2026 05:51
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.

[Managed inference] Extend catalog contracts for declarative llama.cpp recipes

2 participants