Skip to content

feat(files): serve a fileset straight from a GitHub repository [ASTD-519] - #1663

Draft
marcusds wants to merge 3 commits into
mainfrom
astd-519-import-a-fabric-agent-from-a-github-repo-url/mschwab
Draft

feat(files): serve a fileset straight from a GitHub repository [ASTD-519]#1663
marcusds wants to merge 3 commits into
mainfrom
astd-519-import-a-fabric-agent-from-a-github-repo-url/mschwab

Conversation

@marcusds

@marcusds marcusds commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Summary

Studio can create a Fabric agent from a GitHub repository URL, including a private one. The agent's spec fileset is backed directly by the repository and read with a personal access token held in the Secrets service — the token never reaches the browser.

Deployment staging already downloads the agent spec fileset through the files API (stage_fabric_spec_dirsdk.download(fileset=...)), so a GitHub-backed fileset serves a deployment with no copy step and the token never leaves the cluster.

Related Issue

ASTD-519

Changes

  • GithubStorageConfig — owner, repo, revision (+ original_revision), an optional directory within the repo, token_secret, and api_base_url for GitHub Enterprise. Declares its token via get_secret_references(), the same mechanism the Huggingface and NGC configs use.
  • GithubStorageImpllist_files from the recursive trees API, download streaming the contents API with Accept: application/vnd.github.raw (which serves private repos), resolve_config pinning a branch or tag to a commit SHA, and a cache key scoped to that SHA.
  • storage_impl_factory dispatches the new config type.
  • https://api.github.com added to the files service allowed_external_hosts default; config reference doc regenerated.
  • Studio: the upload modal takes a repository URL and an optional secret alongside the directory picker, and creates the spec fileset with a GitHub storage config instead of uploading files. claimFileset/rollback move to agentSpecFileset.ts so both create paths share one implementation.
  • The datasets table keeps its own copy of the storage union, so it and the storage-backend label map were widened too.

Notes on behaviour worth reviewing:

  • resolve_config pins the revision at fileset creation. A push to the branch cannot shift the files under a running deployment. original_revision keeps what the user asked for.
  • 404 maps to GithubConfigError, not an access error. Without a token that can see it, a private repository and a nonexistent one produce the same response, so the message says "has no X, or the token cannot see it" rather than asserting which.
  • 403 with x-ratelimit-remaining: 0 maps to GithubUnavailableError. It is transient; calling it a permissions problem sends people after the wrong fix.
  • upload/delete are refused and owns_storage_data stays false, so deleting a fileset never reaches back into the repository.

Type of Change

  • Code change (feature, bug fix, or refactor)
  • Code change with documentation updates
  • Documentation only
  • Contributor tooling or automation
  • CI, build, or test infrastructure

Quality Gates

  • Tests added or updated for changed behavior
  • Existing tests cover changed behavior — justification:
  • Tests not applicable — justification:
  • Documentation updated for user-visible behavior
  • Documentation not applicable — justification: docs/set-up/config-reference.mdx is regenerated by the pre-commit hook. The new UI is self-describing; no docs page covers the upload modal today.

Verification

  • Pull request title follows the repository's Conventional Commit format
  • Every commit includes an appropriate Signed-off-by: trailer
  • uv run pre-commit run -a passes, or any blocked checks are identified below
  • Targeted tests pass, or tests are marked not applicable above
  • No secrets, API keys, or credentials are included

Targeted validation:

  • uv run pytest services/core/files/tests — 610 passed, 51 skipped. The new test_github_backend.py contributes 33.
  • Studio src/routes/agents/AgentsListRoute/UploadAgentModal + src/api/agents — 83 passed.
  • Studio src/components/DatasetsTable + src/util (the widened-union call sites) — 467 passed.
  • Studio typecheck and lint — clean.
  • uv run ruff check / uv run ruff format --check on the changed files — clean.
  • uv run --frozen ty check on backends/github.py and files/storage_config.py — clean.
  • Pre-commit ran on commit (ruff, ruff format, ty, config-reference doc, copyright headers, merge conflicts) — all passed. A full uv run pre-commit run -a was not run.
  • The pre-push hooks were skipped with --no-verify: helm-docs is not installed locally, and the uv-lock hook requires uv 0.9.14 against a local uv 0.9.30. This change touches no Helm chart and no pyproject.toml, so neither applies; CI runs both.

Notes for review

  • The agent name is prefilled from the repository, not from agent.yaml. The fileset is named after the agent, so the name has to be settled before the fileset exists — and agent.yaml only becomes readable once it does. The upload path still reads the name out of the file. The user can edit the prefilled value either way.
  • A branch containing a slash is ambiguous in a /tree/ URL — the first segment after tree is taken as the ref. @<ref> is the explicit form, matching the Experimentalist plugin's <git-url>[@<ref>][#<agent_path>] spec.
  • The non-UTF-8 check does not run on the GitHub path. The upload path reads every file in the browser to reject binaries before deploy staging chokes on them; the repository path never holds the bytes locally. Worth deciding whether the files service should enforce it.

Not in this PR

  • make update-sdk (Stainless / Python SDK). The web SDK is generated locally by orval from openapi.yaml and is gitignored; the Python SDK will drift until that is run separately.
  • Verification against a real private repository. The token path is covered by unit tests only.

Importing an agent from GitHub needs the repository's files in a spec
fileset, and private repositories need a token to read them. Deployment
staging already downloads the spec fileset through the files API, so a
fileset backed by GitHub serves a deployment with no copy step and the
token never leaves the cluster.

GithubStorageConfig names the repository, an optional directory within
it, and the secret holding a personal access token; the files service
resolves that secret and hands the backend its value, the same path the
Huggingface and NGC backends already take.

resolve_config pins the requested branch or tag to a commit SHA when the
fileset is created, so a push to the branch cannot shift the files under
a running deployment, and the cache key is scoped to that SHA.

A 404 is reported as a config error rather than an access error: without
a token that can see it, a private repository and a missing one are the
same response. A 403 carrying an exhausted rate limit is reported as
unavailable, since it is transient and retrying is the fix.

Uploads and deletes are refused and owns_storage_data stays false, so
deleting a fileset never reaches back into the repository.

Signed-off-by: mschwab <mschwab@nvidia.com>
@github-actions github-actions Bot added the feat label Aug 31, 2026
@github-actions

Copy link
Copy Markdown
Contributor

@github-actions

github-actions Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor
Suite Lines Covered Line Rate Branch Rate
Unit Tests 36856/46762 78.8% 63.2%
Integration Tests 21695/44000 49.3% 22.4%

Signed-off-by: mschwab <mschwab@nvidia.com>
The upload modal now takes a repository URL and an optional workspace
secret alongside the directory picker. The spec fileset is created with
a GitHub storage config rather than by uploading files, so the token
stays in the files service and the browser never holds it.

agent.yaml is read back through the files API once the fileset exists,
which is also the only way to see a private repository's contents from
here. A failure at that point rolls the fileset back, matching the
upload path.

The agent name has to be settled before the fileset can be named, so it
is prefilled from the repository or the chosen subdirectory instead of
being read out of agent.yaml the way the upload path does.

claimFileset and the rollback move to agentSpecFileset.ts so both create
paths share one implementation of who owns a spec fileset.

Widening the storage union reaches the datasets table, which keeps its
own copy of it, and the storage-backend label map.

Signed-off-by: mschwab <mschwab@nvidia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant