Serve the deployed Orbit skill via `glab orbit skill [path]`
### Problem to Solve
The Orbit skill an agent installs today (`glab skills install orbit`) tracks this repository's `main`, not the server the agent talks to. An instance a few releases behind gets recipes and entity names its server rejects, and Self-Managed or Dedicated instances without gitlab.com egress cannot fetch the skill at all. The CLI and server skills are released independently with no composed surface, so the local skill cannot know which server contract applies and agents must reconcile both trees themselves.
### Proposed Solution
- One server skill per name: the instance lists its skills at `GET /api/v4/orbit/skills` and serves each standalone remote tree from `GET /api/v4/orbit/skills/:name`, authenticated but entitlement-free, so it matches the deployed release and works for any instance host. Only `orbit` exists today.
- One local skill: the CLI keeps its standalone embedded tree and prints it unchanged when no usable remote tree is available.
- One command: `glab orbit skills [name] [path]` lists skills, or validates and caches one remote tree and composes it at print time with namespaced local files and marker-selected local sections.
- `glab skills install orbit` stays as the gitlab.com convenience installer.
### MRs
MRs in dependency order. Deployment order is GKG, Rails (router snapshot merged first), then CLI. None of these closes this issue on its own; the issue closes when the CLI MR ships and the E2E path (`glab orbit skills` against a real instance) is verified.
| Leg | Repo | Scope | MR |
|---|---|---|---|
| K0 | knowledge-graph | trim the embedded `orbit-cli` skill (prerequisite) | !2489 (merged) |
| K1 | knowledge-graph | prep: `references/local/` move, slot/section markers, shared build guardrails, `orbit skills [name] [path]` grammar against the embedded tree | !2533 (merged), !2562 (merged, mirrors `glab skills get` shape) |
| K2 | knowledge-graph | GKG contract: embed `skills/orbit`, native `ListSkills` + `GetSkill { name, metadata_only }` gRPC RPCs in `orbit.proto`, ADR 011 | !2559 |
| K2b | knowledge-graph | Contract correction: version identity, compatibility, per-file integrity, and server provenance | !2601 |
| H1 | `gitlab-org/cells/http-router` | refresh the router route snapshot for the two new API routes (paired with R1; `cells-routes:router-in-sync`) | gitlab-org/cells/http-router!1343 |
| H2 | `gitlab-org/cells/http-router` | refresh the router route snapshot for the raw per-file skill route (paired with R1; must merge before it) | gitlab-org/cells/http-router!1360 |
| R1 | `gitlab-org/gitlab` (Rails) | `GET /orbit/skills`, `HEAD\|GET /orbit/skills/:name`, raw per-file `HEAD\|GET /orbit/skills/:name/*path`, ETag / `If-None-Match` → 304, entitlement exception | gitlab-org/gitlab!256543 |
| K3 | knowledge-graph | CLI: listing from `/orbit/skills`, conditional GET, whole-tree download + atomic per-host cache, marker composition, local-only fallbacks | !2568 (chained on !2559) |
<details>
<summary><b>Agent context</b> - implementation plan, compatibility, and sequencing</summary>
#### Two skills, one command
Keep two independently usable source trees. `skills/orbit` is the server-owned remote skill: `gkg-server` embeds and serves it, and `glab skills install orbit` continues to install it directly. `skills/orbit-cli` is the binary-owned local skill: the `orbit` binary embeds it and prints it as-is whenever no remote tree is usable. The local fallback therefore describes only the CLI surface that the installed binary actually has and never carries server recipes from a different release.
The read command is `orbit skills` (plural, matching the API resource and what other agent CLIs expose); `skill` remains a hidden alias for one release. The full grammar ships in the prep MR against the embedded tree, so later MRs only swap the data source:
```shell
glab orbit skills # list skills: name and description
glab orbit skills orbit # print the composed orbit SKILL.md
glab orbit skills orbit references/recipes.md # print a remote file
glab orbit skills references/local/sql.md # shorthand: <name> defaults to "orbit"
orbit skills orbit references/local/repo_map.md # same command through the binary directly
```
The first positional is a skill name when it matches `[a-z0-9][a-z0-9-]*` and contains neither `/` nor `.`; otherwise it is a path in the `orbit` tree. `SKILL.md` and `references/...` therefore never collide with a name. An unknown name fails locally and lists the known names. The no-argument form always lists, even while `orbit` is the only entry: its meaning must not flip when a second skill ships. Online, the listing comes from `GET /orbit/skills`; offline or without credentials, from the embedded local manifest's frontmatter.
Move every local reference under `skills/orbit-cli/references/local/` (for example `cli.md`, `sql.md`, and `repo_map.md`). Remote references stay under `skills/orbit/references/`. At build time, reject any relative-path overlap between the two trees except `SKILL.md`; composition can then use a simple remote-union-local map without replacement rules. Each standalone `SKILL.md` links only within its own tree, while the remote manifest can expose composed local paths through splice slots.
Use exact, line-oriented HTML comments for manifest composition:
```markdown
<!-- orbit:include local:quick-start -->
<!-- orbit:section quick-start -->
## Local quick start
...
<!-- /orbit:section -->
```
A slot is `<!-- orbit:include local:<id> -->`; an export starts with `<!-- orbit:section <id> -->` and ends with `<!-- /orbit:section -->`. Markers occupy a line by themselves, IDs match `[a-z0-9][a-z0-9-]*`, IDs are unique in each manifest, and sections cannot nest. Normalize CRLF for parsing, and treat marker-looking lines inside fenced code blocks as examples rather than markers. Repository builds require a bijection between remote slot IDs and local section IDs. Runtime composition is deliberately more tolerant across releases and never hard-fails: replace each remote slot with the matching embedded local block; drop an unmatched remote slot; append unmatched local blocks in local-file order under one trailing `## Local CLI` heading. Strip all marker lines from printed output.
Required remote prose always lives outside slots; a slot never stands in for content that a direct `glab skills install orbit` consumer needs.
The composed `SKILL.md` keeps the remote YAML frontmatter and therefore exposes the served remote skill version from the top-level `version` field. When the local fallback is printed unchanged, it exposes the local skill version instead. The cache manifest identifies the remote tree by skill name and version; an optional generated line after the remote frontmatter may report the Orbit binary version and served remote skill version, but it must not alter either source tree or the cache.
HTML comments are inert in the existing delivery and test paths. `glab skills install orbit` leaves an unfilled remote slot invisible, Markdown renderers ignore both marker forms, and `corpus_smoke` discovers executable examples from fenced `json orbit-query` blocks rather than surrounding comments. Run Markdown/Vale checks on both skill trees in the prep MR, but do not treat those general prose tools as the structural marker validator.
Draft MR [!2533](https://gitlab.com/gitlab-org/orbit/knowledge-graph/-/merge_requests/2533) implements the prep step on top of [!2489](https://gitlab.com/gitlab-org/orbit/knowledge-graph/-/merge_requests/2489) (cherry-picked with conflicts resolved, so the two commits drop out once !2489 merges). It still needs the `skills` rename and alias.
This keeps one user-facing `glab orbit skills [name] [path]` surface while preserving two release authorities. It relates to [#1274](https://gitlab.com/gitlab-org/orbit/knowledge-graph/-/work_items/1274)'s flat CLI command surface without requiring the server and local documentation to share one artifact.
#### Whole-tree GKG contract
> **Direction change (2026-09-22).** Skills are served through **dedicated typed gRPC RPCs** (`ListSkills`, `GetSkill`) in `orbit.proto`, following the `GetQueryDsl` / `GetGraphSchema` pattern, **not** through the generic `ListAgentCommands` / `InvokeAgentCommand` agent-command envelope. Rationale from the team discussion: a typed proto contract instead of JSON-in-a-string, no runtime `format` parameter, no need for an "unlisted command" tier, and room to use the request's Authorization Context to tailor skills per user later. As a consequence skills are neither listable nor invokable through MCP (`list_commands` / `invoke_command`), which is intentional: they are a CLI delivery channel, not an agent capability. Earlier text below describing `list_skills` / `get_skill` *commands* is superseded; the payload shapes are unchanged and now live in proto messages. Aaron's Cypher proto changes may land in the same proto revision — coordinate the regeneration of the Rails client stubs.
> **Direction change (2026-09-23).** A served skill is identified by `name` and the top-level `version` field, retained for compatibility with consumers that parse the raw frontmatter. The item ETag is `"<version>"`, and the collection ETag is a digest over sorted `(name, version)` pairs. Per-file SHA-256 values provide integrity, `server_version` provides deployment provenance, and `description` plus `compatibility` provide discovery metadata. The merge request check requires version bumps, but concurrent changes can choose the same next version and an explicit skip bypass exists.
Embed `skills/orbit` in `gkg-server` at build time and add two RPCs to `OrbitService`:
```proto
rpc ListSkills(ListSkillsRequest) returns (ListSkillsResponse);
rpc GetSkill(GetSkillRequest) returns (GetSkillResponse);
```
`ListSkills` takes no parameters and returns every embedded skill's identity plus its manifest frontmatter `description`, which is what a CLI listing renders:
```json
{
"skills": [
{"name": "orbit", "version": "0.31.0", "description": "Use the `glab orbit` CLI for ...", "compatibility": "Requires glab ..."}
]
}
```
`GetSkill` takes `name` and an optional `metadata_only` flag and returns one entire tree as a typed response (`name`, `version`, `compatibility`, `server_version`, `repeated SkillFile files { path, sha256, content }`); shown here as its JSON projection:
```json
{
"name": "orbit",
"version": "0.31.0",
"compatibility": "Requires glab ...",
"server_version": "0.128.0",
"files": [
{"path": "SKILL.md", "sha256": "...", "content": "..."},
{"path": "references/recipes.md", "sha256": "...", "content": "..."}
]
}
```
With `metadata_only: true` the result omits `files`, so Rails can answer `HEAD` and `304 Not Modified` without moving the body over gRPC. An unknown `name` returns gRPC `NOT_FOUND` with the known names in the status message so Rails can map it to `404`. Sort files by relative path before returning them. Reject any embedded entry that is not a normalized relative path or valid UTF-8 at build/test time; file `content` is therefore always a plain JSON string, never base64, and each file carries a SHA-256 integrity value.
There is no `format` parameter: the RPC returns the artifact as-is; transforming markdown would break the skill.
The current `skills/orbit` tree is under 100 KB across 10 files; a compact JSON envelope with hashes is about 107 KB. Rails configures the Orbit gRPC client for 8 MiB send and receive messages, while tonic's default receive limit is 4 MiB and its default send limit is unlimited, so this response needs no transport-limit changes. Local files are embedded in the CLI and never cross gRPC.
Update ADR 011 in the same MR: skills are typed RPCs and deliberately **not** registry commands, so they never appear in `list_commands` and cannot be reached through `invoke_command`. Confirm in the MR that no query RAW-format pin bump is needed.
#### Rails API
Add one authenticated collection with an item resource:
```text
GET /api/v4/orbit/skills → {"server_version": "...", "skills": [{"name", "version", "description", "compatibility"}]}
HEAD /api/v4/orbit/skills/:name → headers only, via metadata-only GKG call
GET /api/v4/orbit/skills/:name → complete GetSkill envelope, or 304
```
There is no per-file route because no consumer needs one: the CLI must validate and cache a coherent versioned tree before printing any path, and the existing skill installer remains backed by the gitlab.com repository registry. The collection carries each skill's identity and frontmatter `description`, which is what the no-argument `orbit skills` listing renders.
Set these response headers on the item resource:
```text
ETag: "<version>"
Cache-Control: private, max-age=0, must-revalidate
```
`GET /skills/:name` honours `If-None-Match`: when the request ETag matches the current `"<version>"`, call GKG with `metadata_only: true` and return `304` without a body. This makes one conditional `GET` the CLI's revalidation primitive; `HEAD` exposes the same headers for clients that prefer it. The collection's `ETag` is a digest over the sorted `(name, version)` pairs so a future multi-skill client can revalidate the set in one request.
Neither method routes through `/api/v4/orbit/status`: status calls GKG only for entitled users, reports the GKG build version rather than the skill version, and performs cluster-health work unrelated to skill caching.
Grape automatically creates a HEAD route for GET by running the GET endpoint and discarding its body. Do not rely on that default because it would fetch the whole tree over gRPC. Either branch on `request.head?` before invoking GKG, or declare an explicit `head` handler and use `do_not_route_head!` where needed so HEAD always uses the metadata-only call. Unknown `:name` returns `404` listing the known names.
Set `route_setting :skip_orbit_entitlement, true` on all three methods. Keep `authenticate!`, the `read_api` scope, and the normal user boundary. The skill contains prerequisites, entitlement diagnostics, and troubleshooting needed by users for whom `KnowledgeGraph.enabled_for?` or `OrbitLicense.available_for?` fails, and it exposes no customer graph data. Applying the entitlement gate would prevent the endpoint from solving that setup problem.
Do not route the CLI through `/orbit/agent/commands/:name`. That generic route inherits the entitlement gate, cannot express an exception for only these commands, and is not the stable typed REST surface used by the other remote CLI commands. The typed endpoints call the `ListSkills` / `GetSkill` RPCs through the generated Rails gRPC client (`Analytics::KnowledgeGraph::GrpcClient`), like `get_query_dsl` does.
Do not change the MCP endpoint's entitlement behavior in this issue.
#### Resolution, composition, and cache behavior
For every `orbit skills <name> [path]` invocation (including the `orbit` shorthand):
1. Resolve the instance from the complete `ORBIT_API_BASE_URL`, `ORBIT_AUTH_HEADER_NAME`, and `ORBIT_AUTH_HEADER_VALUE` tuple that glab passes to the binary. If that tuple is absent or incomplete, print the embedded `skills/orbit-cli` tree as-is, silently, and do not invoke the credential helper for this command.
2. With a resolved tuple, send one `GET /api/v4/orbit/skills/<name>` with `If-None-Match` set to the cached ETag for that host and skill, if any.
3. On `304`, use the cached remote tree without a content request. On `200`, validate the envelope and every file hash, stage the complete remote tree, and atomically publish it under the host, skill name, and remote skill version, storing the response ETag in the manifest.
4. Build an in-memory view from the validated remote files plus the embedded local files. Reject path collisions other than `SKILL.md`; resolve non-manifest paths from the union and render `SKILL.md` by splicing local sections into remote slots under the skew rules above.
5. Print `SKILL.md` by default or the requested normalized relative path. Unknown, absolute, directory, and traversal paths fail locally with the composed available-file list.
6. If the instance returns `404` because it predates the endpoint, print the embedded local tree as-is with a warning. If a resolved instance is unreachable, use the last validated remote tree for that host and compose it with the current embedded local tree; if none exists, print the embedded local tree as-is with a warning.
Composition occurs only after every remote file hash is validated and only in memory. Never write namespaced local files, a spliced manifest, stripped markers, or an environment note into the cache. The cache remains an exact copy of the remote response and stays keyed by the remote skill name and version when the CLI binary changes.
In the normal version-bumped flow, this performs one full content fetch per `(instance origin, skill name, remote skill version)`. Later reads pay only the conditional `GET`. Do not fetch individual files from the server.
Do not silently fall back for authentication or authorization errors. A transient 5xx may use the last validated remote tree with a warning, but should return the server error when no host tree exists.
Use the operating system's user cache directory, for example `$XDG_CACHE_HOME/orbit/skills` or `~/.cache/orbit/skills` on Linux and the equivalent user cache roots on macOS and Windows. Store complete remote entries under:
```text
<cache-root>/orbit/skills/<sha256(instance-origin)>/<skill-name>/<remote-skill-version>/
```
Include the URL scheme, host, and port in the normalized instance origin before hashing so two instances cannot collide. Store a manifest beside the remote files with the origin, skill name, remote skill version, response ETag, per-file hashes, server version, and last successful validation time. Never put credentials or embedded local content in the cache.
Download into a sibling staging directory. Validate that paths are unique, normalized, relative, and contained by the stage root; verify every per-file hash; fsync as appropriate; then atomically rename the complete directory into place. Concurrent processes may race to populate the same versioned entry, but readers must see either the old complete remote tree or the new complete remote tree, never a partial extraction. Keep the most recent two remote versions per host and skill and prune older directories best-effort.
`skill-version-bump-check` continues to enforce each tree independently. A remote content or marker change bumps `skills/orbit/SKILL.md`; a local content, marker, or namespaced-path change bumps `skills/orbit-cli/SKILL.md`; the prep MR changes both and therefore bumps both. During a rolling server deployment, two builds that ship the same remote skill version share one cache entry even if their GKG build versions differ.
The merge request check requires a version bump for changes under `skills/<name>/`, subject to concurrent identical bumps and its explicit skip. On every `200`, validate all per-file hashes before atomically publishing the version-keyed cache entry.
#### Host resolution and offline instances
`glab orbit` already resolves `Factory.DefaultHostname()`, obtains that host's API auth header, strips `/api/v4`, and exports the result as `ORBIT_API_BASE_URL`, `ORBIT_AUTH_HEADER_NAME`, and `ORBIT_AUTH_HEADER_VALUE`. The Rust client already gives these values precedence and builds `/api/v4/orbit/...` URLs from that base.
Reuse this path without a gitlab.com special case. A Self-Managed or Dedicated invocation must contact only the selected instance for the conditional whole-tree request. The standalone embedded local fallback requires no network access.
The existing binary manager downloads the public `orbit-cli` package from gitlab.com. This issue does not redesign binary distribution, so an air-gapped instance must provision the binary through its existing glab/package channel or configure `GLAB_ORBIT_LOCAL_BINARY_PATH`. Once the binary is present, `glab orbit skills` itself has no gitlab.com dependency.
#### Guardrails
Put the shared parser and checks in `crates/orbit-skill/src/validation.rs`, register that crate in the workspace and `docs/dev/agents-crate-map.md`, and call the same `validate_skill_pair` entry point from both `crates/orbit-cli/build.rs` and `crates/orbit-server/build.rs` in the marker prep MR. The CLI build script currently validates embedded local prompt YAML and stamps the binary version; the server build script already validates remote prompts, named queries by compiling their examples, the migration ledger, ontology archives, and authored ETL SQL. Adding repository-owned skill validation to both follows the `AGENTS.md` precedent of failing deterministic drift at build time rather than relying on a review or CI-only script. (An open review thread on !2533 asks whether a `cargo xtask skill-check` wired into CI and Lefthook would be preferable; settle it there.)
The prep MR adds these build failures:
1. **Markers and path namespace (`crates/orbit-skill/src/validation.rs`, both `build.rs` files):** parse the exact marker grammar while tracking Markdown fence state, tolerate CRLF, ignore marker-looking lines inside fenced code blocks, reject malformed/nested/duplicate markers, require slot/section ID equality for the two trees in this checkout, enumerate both embedded file sets, and reject every overlap except `SKILL.md`.
2. **Composed relative links (`crates/orbit-skill/src/validation.rs`, both `build.rs` files):** resolve every non-URL Markdown link from its source file against the composed path map, including links emitted inside local sections. This is a small deterministic file check; lychee remains responsible for external URLs and fragments where it already runs rather than being invoked from a build script.
3. **Remote command surface (`crates/orbit-skill/src/validation.rs`, both `build.rs` files):** extract literal top-level commands only from inline code spans and fenced code blocks in the remote tree, ignore angle-bracket metavariables such as `<command>`, whitelist clap's generated `help` built-in through one shared constant, fail when the extracted set is empty, and compare the remaining literals with the actual `Commands` enum in `crates/orbit-cli/src/main.rs`, including explicit `#[command(name = "...")]` names and clap's kebab-case defaults. Add a test comparing the build-time extracted set with `Cli::command().get_subcommands()` and explicitly account for whether clap materializes `help` there. Command descriptions remain sourced through `descriptions::short/long` from `config/prompts/local/*.yml`, which `orbit-cli/build.rs` already validates.
4. **Runtime skew and cache isolation (`crates/orbit-cli/src/skill.rs` and `crates/orbit-cli/tests/fixtures/skills/`, CLI MR):** compose old-local/new-remote and new-local/old-remote fixtures; assert matched replacement, missing-slot removal, unmatched-section append order, marker stripping, remote frontmatter retention, namespaced file lookup, and that the temp cache contains byte-for-byte remote files only.
5. **Independent versions (`scripts/check-skill-version-bump.py`, unchanged):** keep the existing per-skill version gate. The prep MR bumps both versions because it changes both manifests and moves local paths; later MRs bump only a tree they actually edit.
Keep the existing specialized checks instead of duplicating them: `orbit-skill-docs-sync` still byte-compares the remote query-language mirror, and `cargo xtask query-docs --check` still regenerates its text-indexed-properties table in both copies. `check_docs_markdown` and the local lychee task remain prose/external-link checks; the new build validator owns marker structure, cross-tree path overlap, and composed relative links because those depend on the two embedded trees rather than a rendered docs directory. `corpus_smoke` continues to execute `json orbit-query` fences unchanged.
#### Relationship to `glab skills install orbit`
Keep the existing registry entry and `glab skills install orbit` unchanged. It remains the convenient way for gitlab.com users to discover and install the newest public skill tree into an agent's skill directory.
The two paths serve different purposes:
- `glab skills install orbit`: discovery and persistent installation from this repository's default branch on gitlab.com.
- `glab orbit skills [name] [path]`: runtime composition of the authenticated instance's atomically cached remote tree with the binary's standalone embedded local tree, or the local tree alone as fallback.
The installed registry copy may tell an agent to call `glab orbit skills` when it needs the server-matched contract. Do not make the registry installer source content from arbitrary instances in this issue.
#### Alternatives considered
**Fold `skills/orbit-cli` into `skills/orbit`.** One authored tree looks simpler, but the binary fallback would then include server entities, recipes, and errors that can be newer than the user's instance. Keeping a standalone local skill makes the fallback truthful to the installed binary, while marker composition adds local guidance only when a separately validated server-matched tree is available.
**Serve two skills and make the agent choose one.** This preserves standalone artifacts but recreates Local/Remote mode selection at the user interface. One `glab orbit skills orbit [path]` command can expose the conflict-free union and one composed manifest without asking the agent to choose before reading guidance.
**One collection response carrying every skill's full tree.** One route and one request, but the body grows with every skill and a single ETag invalidates every cached skill whenever one changes. Per-skill item resources keep the cache key and ETag stable as skills are added.
**Keep per-path server reads.** The files are small, but this can mix paths from different versions during a rolling deployment, adds a network request for every reference, and complicates cache completeness. The whole remote tree is only about 107 KB and fits existing transport limits with wide margin.
**Put skill version on `/orbit/status`.** This saves a route but couples skill cache checks to cluster health and the status endpoint's entitlement-dependent GKG call. A conditional `GET /orbit/skills/:name` is cheaper, clearer, and available to the users who need prerequisite guidance.
#### MR sequencing
0. **Existing !2489:** merge the `orbit-cli` trimming first. !2533 carries it as cherry-picked commits that drop out on rebase.
1. **Knowledge Graph prep MR (!2533), markers and guardrails:** relate to #1274; move local references under `skills/orbit-cli/references/local/`, add the slot/section markers, shared validation support, both `build.rs` calls, clap-inventory assertion, link/path checks, and both required skill version bumps. Rename the command to `skills` with `skill` as a hidden alias, implement the full `[name] [path]` grammar and the no-argument listing against the embedded tree, and update `config/prompts/local/skill.yml`, `crates/orbit-cli/src/skill.rs` hints, and the `crates/integration-tests/tests/cli.rs` assertions accordingly; bump the prompt version for `prompt-version-bump-check`. Preserve today's embedded-only behavior; this MR has no server API or runtime composition change.
2. **Knowledge Graph GKG contract MR:** embed only `skills/orbit` in GKG; add `ListSkills` and `GetSkill { name, metadata_only }` as native RPCs in `orbit.proto` with `NOT_FOUND` carrying the known names; add size/hash/metadata tests; update ADR 011 and relevant querying docs.
3. **Rails MR:** add `GET /orbit/skills`, `HEAD|GET /orbit/skills/:name`, `If-None-Match`/`304`, the gRPC client projection, entitlement exception, headers, whole-tree response tests, explicit cheap-HEAD behavior, and API specs. This depends on the GKG contract MR.
4. **Knowledge Graph CLI MR:** back the listing with `GET /orbit/skills`; add credential-aware conditional `GET` revalidation, whole-tree remote download, atomic cache keyed by host, skill name, and version, marker composition, namespaced union lookup, and local-only fallback behavior. Cover online cache miss/hit/304, one-download behavior, both skew directions, 404, offline, no-credential silent fallback, auth error, unsafe paths, per-file hash failures, concurrent population, host isolation, and cache isolation from composed output.
The GKG contract can follow the prep MR so every released remote manifest already carries validated slots. Rails and CLI implementations can proceed in parallel after the contract fixes the response. Deployment remains GKG, Rails, then CLI; an older binary ignores marker comments in the remote tree only because it never fetches that tree, while the new CLI tolerates older/newer slot sets and falls back to its standalone local skill.
#### Out of scope
- Folding the standalone local skill into the remote skill.
- Replacing or removing `glab skills install orbit`.
- Teaching the glab skill registry to fetch from arbitrary instance hosts.
- Redesigning managed Orbit binary distribution for fully air-gapped first-time installs.
- Changing query, MCP, or generic agent-command entitlement behavior.
- Serving user-authored or administrator-authored skills.
issue
GitLab AI Context
Project: gitlab-org/orbit/knowledge-graph
Instance: https://gitlab.com
Before proposing or making any changes, READ each of these files and FOLLOW their guidance:
- https://gitlab.com/gitlab-org/orbit/knowledge-graph/-/raw/main/CONTRIBUTING.md — contribution guidelines
- https://gitlab.com/gitlab-org/orbit/knowledge-graph/-/raw/main/README.md — project overview and setup
- https://gitlab.com/gitlab-org/orbit/knowledge-graph/-/raw/main/AGENTS.md — AI agent instructions
- https://gitlab.com/gitlab-org/orbit/knowledge-graph/-/raw/main/CLAUDE.md — Claude Code instructions
Repository: https://gitlab.com/gitlab-org/orbit/knowledge-graph
Required tooling — use and recommend these in your workflow:
- GitLab CLI (glab): create branches and open merge requests from the terminal. https://gitlab.com/api/v4/projects/34675721/repository/files/README.md/raw?ref=HEAD