feat(encord): add Encord curation SaaS workbench tool (push/pull) - #339
Open
jonlwowski012 wants to merge 20 commits into
Open
feat(encord): add Encord curation SaaS workbench tool (push/pull)#339jonlwowski012 wants to merge 20 commits into
jonlwowski012 wants to merge 20 commits into
Conversation
Register-in-place push of S3 media into Encord through an S3-compatible cloud integration (bytes stay in the bucket), plus an upload mode that copies bytes into Encord-hosted storage. Pull materializes a curated Collection, Dataset, or Project's labels back to S3 as media + per-item JSON + a lineage manifest, with a zero-egress server-side copy fast path for same-bucket items. - npa.workbench.encord: schemas, SaaS client seam, push (batched registration + polling, synchronous uploads, receipt-first fail-closed), pull (per-source enumeration, copy/download transfer, LabelRowV2 export) - CLI `npa workbench encord push|pull` + SDK wrappers; title-or-id resolution with create-if-absent on push only - toolRefs workbench.encord.push/pull, secret-env hints, npa[encord] pip extra for image-less stages, stage setup secret guard - encord credential preflight check (`health preflight --checks encord`); ENCORD_SSH_KEY / ENCORD_SSH_KEY_B64 supported end to end - specs: encord-push, encord-pull (human curation between them), and encord-roundtrip-smoke (live e2e: push -> pull in one run, no human) - MCAP discovery is receipt-visible experimental_error pending a supported cloud-registration format upstream - scripts/build_docs.sh: guard an empty-array expansion so macOS bash 3.2 can regenerate the CLI docs Live-verified against Encord: register-mode roundtrip (3 items, all server-side copies) and upload-mode roundtrip (3 items, SHA-256 of pulled bytes matches the originals). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
jonlwowski012
marked this pull request as draft
August 24, 2026 23:25
jonlwowski012
marked this pull request as ready for review
August 24, 2026 23:27
jonlwowski012
marked this pull request as draft
August 24, 2026 23:27
Apply the reuse/simplification/efficiency/altitude findings: - extract _register_items to mirror _upload_items; plain if/else dispatch replaces the empty-list loop gating; drop dead conditions and the unused StorageClient fallback in the JSON writer - share _parse_bucket_uri from npa.clients.storage instead of a third S3 parser; move the JSON writer to encord/storage.py (per-tool sibling norm) - media filters as one FILTER_CATEGORIES table; unified PushedItem construction (mcap rows now never carry an objectUrl) - pull: bulk-sign collection items via get_storage_items (removes N per-item round-trips), list label rows once (PullSource carries them), linear expired-URL retry, media_bytes as a comprehension - single-source CLI/SDK defaults from schemas constants; trim restated defaults from the smoke spec - hydrate ENCORD_* names in load_credentials (env over file, like HF/TF), making check_encord a pure tokens-only check and collapsing the client's ad-hoc env merge; preflight names now import from the client constants - drop dead test-fake fields; pin hydration with a credentials test 94 encord/preflight/health tests, 113 guardrail/workflow tests, ruff green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…us-physical-ai into encord-workbench-tool
docs/workbench/encord.md walks a human through everything needed to use the Encord tool: API key creation, the S3-compatible integration for register mode (endpoint, read keys, strict client-only access, CORS), credentials.yaml wiring (key-file path recommended over PEM paste), the preflight gate, push/curate/pull usage, the three workflow specs with secret forwarding, and a troubleshooting table built from failures observed live (truncated PEM paste, integration read denials, MCAP boundary, composite items, US domain). Indexed in docs/workbench/README and cross-linked from the skill. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
One mermaid figure showing the full loop: S3 media -> push (register objectUrls by default, upload bytes as the dashed variant) -> Encord folder/dataset via the read-only cloud integration -> human curation -> Collection/Dataset/labels -> pull back to S3 (zero-egress server-side copy for registered media, signed-URL download for Encord-hosted), with the durable receipt/manifest called out. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Guard DataRow.backing_item_uuid: the SDK property RAISES NotImplementedError on legacy rows without Storage-API backing, so getattr's default alone crashed dataset pulls - Receipt/manifest now land even when a step throws after Encord or the output prefix was mutated (link_items failures, mid-loop S3 errors, label-export crashes); the run exception is recorded in a new `error` field and re-raised chained - Re-push now re-links: register mode resolves the folder's items by name so skip_duplicate_urls re-pushes still link the dataset instead of silently reporting done with linked_count 0; docs narrowed to say upload-mode re-push creates new copies - Remove a merge-resurrected duplicate _resolve_auth_env; empty-value guards on resolve_project/resolve_collection; health preflight help strings mention the encord check - New tests: raising backing property, receipt-on-link-crash, manifest-on-label-crash, expired-signed-URL refetch retry, re-push relink, empty-source manifest-then-raise (33 tool tests green) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Index the encord skill in AGENTS.md and CLAUDE.md (required when the skill list changes) - Add the three encord specs to SUBMIT_LIVE_MATRIX: the roundtrip smoke as the executable daily twin, push/pull as rotation-skipped human-in-the-loop halves with machine-checked skip reasons; narrow SECRET_ENV_HINTS to the base64 transport form the matrix declares - Python SDK usage example in the operator guide (human-docs contract) - Workflow tests assert the specs' CPU-only resource blocks - Drop a merge-stale HF_PROBE_REPO from credential_preflight __all__ (upstream removed the constant; fixes the ruff F822 lint gate) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
encord-cosmos3-augment.yaml runs the Encord loop's post-curation half in one submit: pull an Encord source, stage the selected item at a deterministic URI, augment it with a real Cosmos 3 video2video pass, and push the result back into Encord as npa-aug-<run-id>. Runs out of the box on repo-pinned data: a seed stage uploads the packaged PAIDF starter clip (public, CC-BY-4.0, SHA-256-verified, fetched through the existing verified-cache machinery) into a run-scoped npa-demo-src-<run-id> dataset, and the default transfer is upload so no cloud integration is required — only the Encord API key. Overriding encord_source_id with a real curated Collection/Dataset id makes the seed stage a no-op, and encord_transfer=register restores in-place registration through an integration. npa.workflows.encord_loop provides the two glue stages: seed_demo_source (skip-aware seeding via the tool's own SDK) and stage_media_for_augment (pull names media by item uuid, which a spec cannot know; this bridges the manifest to the URI cosmos3 generate conditions on, failing closed on missing/failed/out-of-range items). Registered as an executable gpu case in SUBMIT_LIVE_MATRIX (self-seeding makes it standalone), wired into the skill smokes and operator guide. 14 unit/workflow tests; validate-spec/plan-spec render all five states. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The live default-path run caught the gap: the seed stage was run.argv glue with no toolRef, so TOOL_REF_PIP_EXTRAS never installed npa[encord] on the default image and the stage died with "encord SDK is not installed". Promote seeding to a proper CLI verb (`npa workbench encord seed-demo`) and catalog toolRef (workbench.encord.seed_demo): the workbench.encord prefix now matches the pip-extra and secret-hint wiring, the argv guardrail audits the flags, and the skip-when-curated contract is CLI-testable. Spec, catalog doc row, and tests updated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
jonlwowski012
marked this pull request as ready for review
August 31, 2026 17:37
Collaborator
|
My main workflow comment is that a user should be able to leverage headless encord capabilities from workbench. It makes sense to have the ability to operate in Encord, but we should think from how an agent would drive this and minimize human in the loop |
Close the human-in-the-loop gap and adopt the identity/integrity model from nebius#363 into the live-validated transport: - curate: workbench-declared quality filters (brightness, width, ...) mapped onto a live-verified Encord filter-preset shape and evaluated server-side into a Collection - no human in the app. Fails closed on zero selections, empty folders, and unknown metrics; re-issues the one-shot evaluation to absorb metric-indexing lag; deletes its run-scoped preset after evaluation. - Exact identity replaces filename matching: every item registers namespaced npa.source_uri clientMetadata, lineage resolves through metadata or normalized objectUrl only, and conflicting signals fail the item closed. Same-basename objects resolve to distinct items. - Content integrity: receipts record source size/ETag/checksum, pulls hash downloads in-stream, and a terminal verify verb joins receipt to manifest by uuid and fails closed on missing items or size/checksum mismatches (npa.encord.roundtrip_report.v1). - Write-ahead receipts land before the first Encord mutation (push and curate), so an uncatchable kill still leaves a record of intent. - Caller-side idempotency in both transfer modes: identities resolve before anything is sent, so retried stages re-register nothing and copy no duplicate bytes. - Pull transfers run in a bounded parallel pool. - cleanup verb tears down run-scoped Encord state by title prefix (datasets are reported; the SDK cannot delete them). - Credential transports reduced to ENCORD_SSH_KEY_B64 (pods) and ENCORD_SSH_KEY_FILE (laptops); the truncation-prone raw-PEM paste is no longer accepted. - encord-roundtrip-smoke becomes push -> curate -> pull -> pull-curated -> verify; encord-cosmos3-groot-finetune curates headlessly between push and pull and fine-tunes GR00T on the augmented episodes. Live evidence (Nebius eu-north1, managed jobs 90-94): all five stages green; curate selected 3 of 4 fixtures (width filter excluded the low-res thumbnail); verify passed with 4/4 matched and 4/4 checksums verified; register-mode re-push confirmed idempotent with stable uuids. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
# Conflicts: # npa/src/npa/cli/workbench/__init__.py
…buted items KISS pass over the new transport code: - Drop the dead (uuid, name) pair accumulation in register/upload — lineage has been exact-identity-resolved since the nebius#363 adoption, so the transfer helpers now return plain (done, errors, status). - A registered item that exact identity cannot attribute after the post-registration re-list now fails the push closed instead of surviving as a silently unlinked receipt row; verify's unresolved branch becomes impossible and is removed (relation vocabulary shrinks to matched | missing | unexpected). - Pull's parallel transfers use a plain pool.map: a per-item metadata write failure is recorded on the item (fail-closed via media_failed) instead of the futures/first-error machinery. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The local API daemon cwd probe walks /proc, which does not exist on macOS operator workstations. procfs-absent meant every Darwin submit failed with procfs_unavailable before anything launched. Cannot-assess is not unhealthy: return a healthy procfs_unsupported probe when the proc root does not exist, restoring pre-probe behavior on platforms where the poisoned-cwd failure mode was never detectable. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replace this branch's `procfs_unsupported` short-circuit in _probe_local_api_daemon_cwd with the fix that landed on main in nebius#367 (`procfs_unavailable_darwin`), file and test byte-identical to origin/main. Both changes solved the same macOS submit failure; keeping ours would have merged cleanly but shadowed main's block and failed its test. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Standards (CONTRIBUTING, npa-cli-conventions, add-workbench-tool): - CLI verbs use the shared OutputFormat/emit/fail helpers and carry @json_stdout_contract; the contract now reads enum output_format values - seed-demo moves into the tool (npa.workbench.encord.seed_demo) with an SDK wrapper; new system-info management verb (SDK pin, domain, credential transport names only) - resolvers return ResolvedRef through one title-or-id contract; schemas type every enumerated field as a Literal; artifacts are S3-only - parse_bucket_uri is public; ENCORD credential names come from npa.clients.credentials; design note moves next to the operator doc Fail-closed fixes from review: - curate refuses a Collection that already holds items (add_preset_items only adds, so a stale selection would read as this run's) and records items_total and preset_deleted; presets are deleted in a finally - verify fails on planned/failed receipts, zero attributable items, and matched items with neither a comparable checksum nor a size - pull keeps every item's record when one signed-URL fetch raises and records copy_error when the zero-egress copy falls back to download - push derives units_done/units_error from item statuses (no double count), always sends identity metadata on upload, resolves identities through an indexed inventory (O(N) instead of O(N x inventory)), and rejects an empty --integration in register mode before any I/O (seed-demo too) - preflight reports a missing encord SDK as WARN with a pip remedy; the setup credential guard no longer applies to encord.verify; verify path errors name --receipt-uri/--manifest-uri - groot-finetune spec defaults to register (customer data stays in the bucket); augment is the one documented upload opt-in; groot-finetune is registered in the live submit matrix Verified live: encord-roundtrip-smoke run encord-roundtrip-smoke-20260902T162808351118Z-ab1bfb (jobs 100-104), verify passed 4/4 with 4 checksums verified; curate selected 3 of 4. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This was referenced Sep 3, 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
Adds an
encordworkbench tool: push media from the Nebius object store intothe Encord curation SaaS, curate headlessly with
quality filters (or in the Encord app), pull curated media and labels back
to S3, and verify the roundtrip end-to-end — every artifact carrying exact
identity and content checksums.
flowchart LR S3[("Your S3 bucket")] ENC["Encord dataset<br/>(items carry exact<br/>npa.source_uri identity)"] CUR["encord curate<br/>(headless quality filters,<br/>evaluated server-side)"] HCUR{{"or: curate in the<br/>Encord app"}} COLL["Curated Collection"] OUT[("Curated media + labels<br/>+ lineage manifest, in S3")] VER["encord verify<br/>(identity + size + checksum,<br/>fail-closed)"] S3 -->|"encord push<br/>(bytes stay in your bucket)"| ENC ENC --> CUR --> COLL ENC -.-> HCUR -.-> COLL COLL -->|"encord pull"| OUT OUT --> VERTwo transfer modes:
seed-demo, and everyshipped spec but one): Encord references object URLs through an
S3-compatible integration; source bytes never leave object storage. An
empty
--integrationin this mode is rejected before any S3 or Encord I/O.shipped spec that opts in is
encord-cosmos3-augment.yaml, whose defaultsource is the packaged public CC-BY-4.0 starter clip; its config comment and
the docs say to switch to register (plus an integration title) when pointing
it at your own data.
Docs:
docs/workbench/encord.md(setup through troubleshooting) anddocs/workbench/encord-headless-curation.md(the headless-curation design andthe live evidence behind the pinned Encord filter shape).
Identity and integrity (adopts the #363 model)
This PR adopts the exact-identity model from #363 onto the live-validated
transport — thanks to @ for the repro that motivated it:
npa.source_uriclientMetadata; receipt lineage and dataset linking resolvethrough that metadata or the item's normalized objectUrl, and conflicting
signals fail the item closed. Same-basename objects (
ep_001/frame_0000.pngvs
ep_007/frame_0000.png) resolve to distinct items and percent-encodingaliasing (
a%2Fbvsa/b) is handled in URI canonicalization — both pinnedby regression tests. Resolution indexes the folder inventory once per pass,
so a push over thousands of items stays linear.
source size, verbatim ETag, and content checksum (single-part ETag as md5 in
register mode; streamed sha256 in upload mode); pulls hash downloads
in-stream and record the destination ETag for zero-egress copies. A
same-bucket copy that fails falls back to download and records
copy_errorin the manifest row, so paid egress is never silent.
encord verifyjoins receipt to manifest by Encord uuid(
npa.encord.roundtrip_report.v1) and fails closed on: missing orunexpected items, size or checksum mismatches, a receipt whose status is not
done(a write-aheadplannedcopy, afailedortimeoutpush), zeroattributable items (0/0 is a failure, not a vacuous pass), and matched items
with neither a comparable checksum nor a size on both sides
(
unverifiable). The report'sdefectslist names receipt-level reasons.This is the machine-checkable form of any checksum claim; an overwrite
between push and pull is exactly what it catches.
status: plannedreceiptbefore the first Encord mutation, so an uncatchable kill (OOM, preemption)
still leaves a durable record of intent — pinned by tests that assert the
planned receipt is already durable when the first mutating SDK call happens.
folder before anything is sent: a retried stage makes zero registration
round-trips and copies no duplicate bytes. Register mode never falls
through to upload (pinned by test). Receipt counters (
units_done,units_error) are one function of the per-item outcomes, so no item is evercounted twice or reported done without an attributable uuid.
other per-item exception) becomes an error row; the manifest keeps every
item that landed, so resume and cleanup can see what is in the prefix.
Headless curation
npa workbench encord curate --folder ... --filter brightness:0.2:0.8 --filter width:640:4096 --collection ...maps workbench-declared filtersover Encord's built-in quality metrics onto a run-scoped Encord filter preset
and lets Encord evaluate the selection server-side into a Collection — no
human in the app, no media movement. The undocumented per-metric filter JSON
was pinned by a live spike against the real SaaS (an invalid shape hangs
Encord's evaluation; the verb allowlists live-verified metrics and fails
closed on anything else). Zero selections fail closed with a diagnostic that
names the likely cause. A
--collectionthat already holds items is refused:Encord's evaluation only adds, so a stale selection could not be told apart
from this run's. The receipt records
items_totalanditems_selected; thetransient preset (
npa-curate-<run-id>, or a timestamped random-suffixedtitle for ad-hoc runs) is deleted in a
finally, after a crash as well as asuccess, and
preset_deletedrecords whether that delete went through.What's included
npa.workbench.encordimplementation (one module per verb, a SaaS seam thatreturns
ResolvedRefs from one shared title-or-id resolution contract, andan S3-only artifact writer), the
npa workbench encord push|curate|pull|verify|cleanup|seed-demo|system-infoCLI on the sharedworkbench CLI helpers, and SDK wrappers for each verb.
system-infois themanagement verb CONTRIBUTING asks new tools to expose (SDK pin, API domain,
configured credential names — never values).
@json_stdout_contract:--output jsonprintsexactly one JSON document on stdout, including on failure. Only
EncordToolErrorand path-contract violations are client errors (exit 1);anything else propagates to
app_entryas exit 2 with theNPA_DEBUGtraceback path intact. The contract decorator now reads enum-typed
output_formatvalues, which also activates it for the other enum-typedcommands in the repo.
workbench.encord.{push,curate,pull,verify,seed_demo}, credentialhints (verify needs no Encord secret and its setup guard says so), and
npa[encord]dependency support for image-less stages. Thespec-declared-outputs guardrail pins push, curate, pull, and verify outputs
to the tool's own
*_uri_forhelpers.health preflight --checks encord. Exactlytwo credential transports:
ENCORD_SSH_KEY_B64(pods) andENCORD_SSH_KEY_FILE(laptops) — the truncation-prone raw-PEM paste is notaccepted (pinned by test). A credential with no
encordpackage installedis a WARN with a pip remedy, not a failed credential.
encord cleanup --title-prefix <npa-...->tears down run-scoped Encordstate (folders, collections, presets; datasets are reported — the SDK
exposes no dataset deletion). All created titles embed the run id, which
embeds a UTC timestamp.
encord-push.yaml,encord-pull.yaml,encord-roundtrip-smoke.yaml(push → curate → pull → pull-curated →verify),
encord-cosmos3-augment.yaml, andencord-cosmos3-groot-finetune.yaml(push → headless curate → pull →Cosmos 3 augment → materialize LeRobot episodes → GR00T fine-tune). All
five are registered in the live submit matrix; groot-finetune needs
--var encord_integration=<title>because it handles customer data inregister mode.
operator doc, since it applies to one tool), tool-catalog rows, skill
guidance, and the skill-index lines in
CLAUDE.md/AGENTS.md.Also in this PR (small, cross-cutting)
Two fixes surfaced while running the Encord workflows live and were kept
here rather than split out, because each blocked the runs above:
StorageClientnow bounds every boto network wait (connect_timeout=10,read_timeout=60,max_pool_connections=16): a stalled TCP connectionduring workflow-preflight source staging blocked a submit indefinitely.
Pinned by a unit test; affects every tool that uses the shared client.
scripts/build_docs.shguards an empty-array expansion that bash 3.2 (themacOS default) rejects under
set -u.parse_bucket_uriinnpa.clients.storageis now public (its two privateimporters migrated). The SkyPilot procfs probe on this branch is byte-identical
to main's #367 fix, so merging main is a no-op there.
Validation
Everything below is a checked-in test or a durable S3 artifact from a live
run on Nebius (eu-north1 managed Kubernetes, SkyPilot managed jobs):
basenames, percent-encoding aliasing, conflict fail-closed), integrity,
every verify fail-closed path (receipt status, zero items, unverifiable
items, mismatches), write-ahead receipts landing before the first mutation,
raw-PEM rejection, curate's preset lifecycle and populated-collection
refusal, pull's per-item error rows and copy-fallback reason, push counter
consistency and early integration validation, idempotency, cleanup,
seed-demo, and system-info. Catalog argv, doc-sync, three-tier, spec-output,
live-matrix, and skills-index guardrails are green.
encord-roundtrip-smoke, runencord-roundtrip-smoke-20260902T162808351118Z-ab1bfb(managed jobs100–104, all SUCCEEDED, on the code in this PR):
metadataidentity signal, md5 checksums recorded;
items_total4) — the width filter excludedthe deliberately low-res fixture, proving server-side filtering; the
transient preset was deleted (
preset_deleted: true);server-side copies, no
copy_error;unavailable, 0 unverifiable, no defects (
verify/roundtrip_report.jsonin the run prefix);
encord cleanupthen removed the run's folder (4 items) and collectionand reported the one dataset the SDK cannot delete.
round-trips, stable uuids).
Note on earlier evidence wording: a prior commit message claimed checksum
verification before the mechanism existed in-tree; this PR makes that claim
real and machine-checkable — the roundtrip report is the artifact, and the
claim now lives nowhere except where CI and the smoke can enforce it.
Boundaries
.mp4,.png,.jpg, and.jpegare supported push inputs. MCAP cloudregistration is not supported by the pinned Encord SDK upload format;
.mcapinputs are recorded as receipt-visibleexperimental_errorrows andfail closed rather than sending an inferred schema.
pull records them as per-item errors.
nothing until quality metrics are computed for the folder — a one-time
action in the Encord app with no public API. Intrinsic metrics (width,
height, area, aspect-ratio) work on any folder immediately; the
zero-selection error names the cause.
titles (the shipped specs do) or
cleanupa stale one first.cleanupdeletes everything elseand reports them.