Skip to content

feat: run agents through Fabric's fabric-agent-ctl - #13379

Merged
cv merged 18 commits into
v1from
feat/fabric-agent-ctl
Oct 17, 2026
Merged

cv merged 18 commits into
v1from
feat/fabric-agent-ctl

Conversation

@cv

@cv cv commented Oct 17, 2026

Copy link
Copy Markdown
Collaborator

Part of #13318. Closes #13031. Stacked on #13375; review that first.

Failure

Agent images ran NemoClaw's own Python bridge (fabric.py, backend.py, and the bridge protocol, about 2,100 lines with tests) between OpenShell and Fabric. Fabric v0.5.0 ships its own sandbox bridge, fabric-agent-ctl, so NemoClaw was maintaining a second implementation of Fabric's job. Its health path also made every apply fail at the pinned Fabric, which has no health API (#13031).

Decision

Images. runtime.json's command is /opt/fabric/bin/fabric-agent-ctl; sandboxes launch fabric-agent-ctl serve. NemoClaw's bridge, its tests, and the io.nemoclaw.fabric.bridge label are gone. ADAPTER_PYTHON stays: Fabric reads it to choose the interpreter for Python adapters.

Dummy reference image. It now runs Fabric's real runtime and fabric-agent-ctl, with a NemoClaw test-only adapter (image/fabric/test-adapter) packaged like Fabric's own adapters. Catalogs come from catalog.py for every image; an adapters.json entry may name the descriptor file of an adapter Fabric's catalog package doesn't publish.

fabric_agent.

  • Create: waits for the sandbox, then start --config -, retrying only while serve isn't listening yet (host_unavailable with no effects). It stores the returned runtime_id, replacing the running attribute.
  • Read: checks only the sandbox. Fabric reports neither applied configuration nor runtime status (fabric-agent-ctl: read-only status command for the owned runtime NeMo-Fabric#420).
  • Remove: stop --runtime-id. A runtime already gone counts as stopped.
  • runtime_exists: reported with guidance to replace the sandbox.

One runtime per sandbox. A sandbox runs one runtime in its lifetime. After a stop, or a start that failed or lost its reply, it can't start another. When a plan would create an agent in a sandbox that already exists, the SDK plans again with that sandbox replaced (-replace), and the plan shows it. Hand-written HCL users get the guidance instead.

Readiness and health removed. This covers fabric_agent_readiness, OperationResult.health, nemoclaw-backend's health types, the CLI's health output, and the plan's "agent runtime is not running" note. Apply now completes once agents start.

Docs. design/fabric-management.md is rewritten around fabric-agent-ctl. The provider, usage, SDK, CLI, build, getting-started, Kubernetes and contributor docs are updated.

Validation

  • cargo ci passes locally: 1530 unit tests, 149 lifecycle tests.
  • cargo ci live-docker passes, 8 tests. This includes the dummy agent's full lifecycle through a managed Docker gateway with a real fabric-agent-ctl.
  • cargo ci live-kind passes, 5 tests.
  • docker buildx bake check passes: lint, 15 unit tests, 5 reference tests driving fabric-agent-ctl.
  • cargo images qualify --lifecycle passes for the dummy, Pi, Hermes and OpenClaw images: each starts, invokes and stops a runtime, and refuses a second.
  • fern check reports 0 errors.

Not run: the Brev, Spark and hosted-parity live tests (hardware). They now expect a completed apply, and their health assertions are removed.

Follow-ups

cv added 17 commits October 16, 2026 17:21
Fabric v0.5.0's fabric-agent-ctl starts one agent runtime per sandbox, so
a changed agent needs a new sandbox. Prepare for it on the current bridge:

- Sandboxes are reconstructible: no prevent_destroy in generated graphs,
  and the openshell provider no longer refuses their replacement or
  removal. Durable data belongs on mounted volumes.
- fabric_agent replaces on any configuration change, and planning refuses
  a replacement while its sandbox stays the same, explaining how to
  replace it.
- Generated graphs hold each agent's configuration in a terraform_data
  that replaces its sandbox through replace_triggered_by.
- fabric_agent no longer reports configuration changed inside the
  sandbox as drift; Fabric reports no applied configuration.
…acement

# Conflicts:
#	crates/nemoclaw-openshell/src/lifecycle.rs
Agent images now run Fabric's own sandbox bridge, fabric-agent-ctl,
instead of NemoClaw's Python bridge. fabric_agent starts the sandbox's
one runtime (start), stops it on removal (stop), and exports its
runtime_id for invocation (invoke). A sandbox runs one runtime, so the
SDK replans with the sandbox replaced whenever a plan would create an
agent in an existing sandbox.

Fabric reports no runtime status or health, so fabric_agent_readiness,
the bridge health path, and the stopped-runtime plan note are removed;
apply now completes once agents start. The dummy reference image runs
Fabric's runtime with a packaged NemoClaw test-only adapter.
The contract binary gained the sandbox replacement and agent removal
tests, and measured 41.1 s summed on linux_arm64 against a 40 s budget.
The binary has grown to 62 tests and measured 197 s summed on
linux_amd64 (175 s on linux_arm64) against a 190 s budget; this change
adds the sandbox replacement tests.
The catalog label test imported /opt/nemoclaw/catalog.py through the
PYTHONPATH the NemoClaw bridge set; with the bridge gone the import
failed during image qualification.
FabricCatalog::bundled() parsed and checked the 50 KB generated catalog on
every call, and every deployment compile reached it. That was about 40% of
compiling the objects graph: the test compiling every example's deployment
and teardown graphs dropped from 1.17 s to 0.86 s locally, and ran 8.7 s
under CI load, over its 8 s budget.
Base automatically changed from feat/agent-sandbox-replacement to v1 October 17, 2026 07:43
# Conflicts:
#	crates/fabric-provider/src/agent.rs
#	crates/fabric-provider/src/configuration.rs
#	crates/fabric-provider/tests/contract/main.rs
#	crates/nemoclaw-e2e/tests/deployment.rs
#	crates/nemoclaw-e2e/tests/fabric_deployment.rs
#	crates/nemoclaw-test-fixtures/src/openshell.rs
#	docs/contributing/integration-tests.md
#	docs/provider.md
#	docs/troubleshooting.md
@github-actions

Copy link
Copy Markdown
Contributor

@cv
cv merged commit 0854b55 into v1 Oct 17, 2026
51 checks passed
@cv
cv deleted the feat/fabric-agent-ctl branch October 17, 2026 08:15
gaveezy added a commit that referenced this pull request Oct 18, 2026
v1 removed crates/nemoclaw-e2e/tests/health.rs and its module line (#13379); this branch adds the fixture_keepalive module beside it. Keep fixture_keepalive and drop health.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cv added a commit that referenced this pull request Oct 18, 2026
…fusal (#13421)

Part of #12731. Recreates #13161 by @cuongdangn on a fresh branch,
because that branch doesn't allow maintainer edits. The commit keeps
their authorship.

## Failure

The live Kind tests would pass two regressions:

- **SDK destroy:** the tests that deploy through the public SDK only
checked that no destroy change named `kubernetes_storage`. They never
read the cluster after `Deployment::destroy`, so they'd pass a destroy
that recreated the namespace or key, deleted the gateway's claim, or
left the gateway installed.
- **Expired tokens:** the gateway test only checked that a token signed
by an unknown key is refused. A gateway that stopped checking `exp`
would still pass.

## Decision

The same checks as #13161, adapted to current `v1`:

- **Expired token:** after each install, the gateway test signs an
expired development token with the deployment's own key and requires the
gateway to refuse it. It also requires a current token from the same key
to be accepted, so a refusal can only come from expiry.
- **SDK destroy:** destroy must remove the gateway's Helm release record
and StatefulSet. It must keep the namespace, the encryption key Secret
and the gateway's PersistentVolumeClaim with unchanged UIDs.
- **Rebased onto `v1`:** #13161 added a separate plain-Kubernetes
deployment test for the destroy checks. Since #13379, apply completes
through `fabric-agent-ctl`, and the registry test
(`kubernetes_registry.rs`) already deploys an agent on Kubernetes
through `deploy_image_and_export`. So the destroy checks run inside
`deploy_image_and_export`, covering both the registry test and the
OpenShift test, without adding another slow live test.

Production code is unchanged.

## Validation

- `cargo ci` passes locally: 1,589 unit tests and 153 lifecycle tests.
- `cargo ci live-kind` passes locally: all 7 tests, including the new
checks.

Co-authored-by: Leo Dang <ledang@nvidia.com>
cv added a commit that referenced this pull request Oct 18, 2026
…estroy checks (#13422)

Part of #12731. Follows #13421, which merged before this narrowing
reached it.

## Failure

#13421 added three live Kind checks, two of which test other components:

- **The gateway refusing an expired development token:** token
verification is OpenShell's.
- **The Helm release record and StatefulSet disappearing after SDK
destroy:** that's Helm's and Kubernetes' behavior.

NemoClaw's part, classifying an authentication failure and having its
graph destroy the release, already has unit and plan-level coverage.

## Decision

- **Removed:** both checks.
- **Kept:** NemoClaw's own promise. After SDK destroy, the namespace,
encryption key Secret and gateway PersistentVolumeClaims keep their
UIDs, observed through #13421's more reliable `retained()`.
- **Docs:** the live-tests guide and a stale test comment are updated.
The comment claimed apply stops at the agent's health check, which
hasn't been true since #13379.

## Validation

This commit was tested on #13421's branch, whose code matches the merged
#13421:

- `cargo ci` passes locally: 1,589 unit tests and 153 lifecycle tests.
- `cargo ci live-kind` passes locally: all 7 tests.
cv added a commit that referenced this pull request Oct 18, 2026
… Fabric (#13433)

Closes #12016.

## Failure

Downstream blueprints extend NemoClaw agent images: the VSS blueprint,
for example, derives from one pinned by digest and adds skills, a domain
CLI and workspace files. Nothing stated what such a derived image must
keep. Trying it on current `v1` showed:

- **A conforming derived image works:** labels carry over through
`FROM`.
- **A missing catalog label fails at plan time with a misleading
message:** "Harness unavailable: The image declares no runtime for this
adapter", then advice about `spec.gateway.engine`.
- **A different Fabric revision in the image's catalog passes
silently.** NemoClaw compared revisions, but the result, "unknown", was
reported nowhere.
- **An image from an older base fails at apply** with "sandbox
unavailable: SANDBOX_PHASE_ERROR, reason unknown, exit code 1".
OpenShell's supervisor log shows the cause: the image's launch command
exited. An image from before the `fabric-agent-ctl` move lacks that
command.

## Decision

- **Fabric compatibility stays with Fabric.** NemoClaw no longer
compares the image's Fabric revision with the release's. That comparison
was NemoClaw judging another component, and it produced only a silent
"unknown". The image's own `fabric-agent-ctl` validates the agent
configuration and reports interface or validation errors when the agent
starts (#13379, #13326).
- **NemoClaw keeps checking its own contract,** with clearer messages:
- **Missing catalog label:** "Harness unavailable" now names
`io.nemoclaw.fabric.catalog` and the rule that a derived image keeps its
base image's labels. The local-engine postcondition says the same.
  - **The adapter and the platform:** checked as before.
- **Unexplained sandbox exits** now point to OpenShell's own log:
"`openshell logs SANDBOX --source sandbox` shows why its process
exited". Recognized OpenShell reasons keep their specific guidance.
- **Docs:** a new [derived agent images](docs/derived-images.md) page:
  - deriving by digest;
- what to keep: the catalog label, `/opt/fabric` with
`fabric-agent-ctl`, UID and GID 10001, and the platform;
  - using added skills and tools;
  - rebuilding on upgrade;
  - which failures appear at plan time and which at apply.

  The compatibility, provider and build pages link to it.
- **Base image publication** depends on releases (#12638). Until then,
the page says to build the bases and push them to your own registry.

## Validation

- **New tests**, each written before its change:
  - the missing-label message;
  - an unexplained sandbox exit pointing to `openshell logs`.
- **Updated tests:**
- a different Fabric revision now assesses as supported, with no
`fabric_contract` check;
- the prebuilt-images test allows exactly one build, the derived
reference image.
- **Live Docker:** the lifecycle test now runs an image derived from the
reference image, with one added executable, and runs that executable
inside the deployed sandbox. All 9 live Docker tests pass locally.
- **By hand, with `nemoclaw plan` and `apply`:** a conforming derived
image plans; a stale-revision image plans; a no-label image fails naming
the label; a pre-`fabric-agent-ctl` image fails at apply, and its
sandbox log shows why.
- `cargo ci` passes locally: 1,600 unit tests and 149 lifecycle tests.
The docs check reports 0 errors.
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.

1 participant