feat(agents): serve a Fabric agent manifest at GET /manifest - #1174
Draft
marcusds wants to merge 1 commit into
Draft
feat(agents): serve a Fabric agent manifest at GET /manifest#1174marcusds wants to merge 1 commit into
marcusds wants to merge 1 commit into
Conversation
A Fabric agent server exposes only /health and /v1/chat/completions, so a caller has no way to learn the contract it must respect. Add /manifest: an allowlist projection of the AgentConfig already loaded into app.state. The manifest carries what the platform otherwise has to guess. Sessions all share one workspace directory (open_session passes the same base_dir every time), so workspace_scope reports "agent" and callers know invocations are not isolated. max_concurrent_invocations exposes the semaphore the server already enforces. telemetry.emits keeps intake from double-instrumenting an agent that already ships traces. agent.revision fingerprints the projection so a consumer can tell whether the agent changed under a set of results. The route is unauthenticated, so a config field reaches the manifest only by being named: no api_key_env, base_url, MCP url/env, prompt values, system instruction, or host paths. The revision hashes the redacted projection rather than the raw config, so it cannot become a side channel on the values it omits. A test walks AgentConfig.model_fields against projected/redacted sets, so a newly added config field fails the suite until it is classified. SESSION_ID_HEADER moves to serving_models so both the manifest and the server can read it without an import cycle; server re-exports it. ASTD-382 Signed-off-by: mschwab <mschwab@nvidia.com>
Contributor
|
marcusds
marked this pull request as ready for review
August 8, 2026 00:13
Contributor
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Enterprise Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughChangesFabric agent manifest
Sequence Diagram(s)sequenceDiagram
participant FabricServer
participant AgentConfig
participant FabricClient
FabricServer->>AgentConfig: load configuration during startup
FabricServer->>FabricServer: build and store AgentManifest
FabricClient->>FabricServer: GET /manifest
FabricServer-->>FabricClient: AgentManifest response
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
marcusds
marked this pull request as draft
August 10, 2026 17:34
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
A Fabric agent server exposes only
/healthand/v1/chat/completions, so anything driving it — Studio, eval, intake — has to guess the contract it must respect. This addsGET /manifest, an allowlist projection of theAgentConfigthe server already holds inapp.state, describing the agent's operational contract.Groundwork for external agents on Fabric (ASTD-314): once an agent can describe itself, registering one by URL can discover it instead of asking the user to type everything. Nothing consumes the endpoint yet — that is ASTD-383.
Related Issue
ASTD-382 (Linear). Parent: ASTD-314.
Changes
fabric/manifest.py(new) — manifest models andbuild_agent_manifest(). Pure read model overAgentConfig, no I/O.fabric/server.py— builds the manifest once during lifespan startup, serves it atGET /manifest.fabric/serving_models.py—SESSION_ID_HEADERmoved here so the manifest and the server can both read it without an import cycle.server.SESSION_ID_HEADERstill resolves via re-export, so existing imports are unchanged.tests/unit/test_fabric_manifest.py(new) — 13 tests.What the manifest carries, and why
Descriptive fields are the cheap part. These are the ones the platform gets wrong when it has to guess:
environment.workspace_scope—open_sessionpasses the samebase_dirtoensure_local_workspace_dirandfabric.start_runtimeon every session, so one workspace is shared by all of them. Reported asagentscope, which tells a caller invocations are not isolated. An eval that doesn't know this lets row 1 leak into row 200 and reads the result as variance.serving.max_concurrent_invocations— surfaces the semaphoreFabricSessionManageralready enforces, so a caller can size its batch instead of queueing or tripping the 503 path.telemetry.emits— keeps intake from double-instrumenting an agent that already ships ATIF.agent.revision— content hash, so a consumer can tell whether the agent changed underneath a set of results.tunable— knob names fornemo-optimization(prompt keys, harness settings keys). Names only.Two shape notes:
workspace_scopeis an enum rather than astatefulbool because scope is a fact about the server that survives Fabric gaining per-session environments, where a bool would need redefining. Andmodelsincludes harness-level models asharness:<name>— the common agent (including this repo's own fixture) declares no top-levelmodelsat all, so reading onlyconfig.modelswould report zero.Redaction
The route is unauthenticated, so this is an allowlist, never a
model_dump(). Excluded:api_key_env,base_url,McpServerConfig.url/.env,promptsvalues,instructions.system.content, and host filesystem paths (skills are emitted as basenames).revisionhashes the redacted projection rather than the raw config, so the fingerprint cannot become a side channel on the values it omits.Two test layers back this: one sentinel value is planted in every dangerous config slot and asserted absent from the serialized manifest; and
test_every_agent_config_field_is_classifiedwalksAgentConfig.model_fieldsagainst projected/redacted sets, so a newly added config field fails the suite until someone classifies it.Type of Change
Quality Gates
Verification
Signed-off-by:traileruv run pre-commit run -apasses, or any blocked checks are identified belowTargeted validation:
pytest plugins/nemo-agents/tests/unit/test_fabric_manifest.py plugins/nemo-agents/tests/unit/test_fabric_server.py→ 39 passedpytest plugins/nemo-agents/tests/unit→ 988 passed, 3 failed. All three failures (test_optimize_submit_targets_agents_route,test_deployments_list_defaults_to_table,test_jobs_discovered_via_entry_points) are pre-existing and caused bynemo_optimizationnot being installed in this venv — confirmed by re-running them with this branch's changes stashed and getting the identical three.test_service.pyfails collection for the same reason, also pre-existing.ruff checkandruff format --checkon the changed paths → cleanty checkon the changed paths → cleanuv run pre-commit run -awas not run repo-wide. Pre-commit ran at commit time over the changed files: ruff, ruff format, ty, copyright headers, plugin-import check, merge-conflict check — all passed.Summary by CodeRabbit
New Features
GET /manifestendpoint for retrieving the manifest.X-Nemo-Session-Id.Tests