Files
OpenShell/CONTRIBUTING.md
T
Max DubrinskyandDrew Newberry 35fb27ef14 feat(sdk): add TypeScript SDK (@nvidia/openshell-sdk) (#2122)
* feat(sdk): add TypeScript SDK (@nvidia/openshell-sdk)

First native, per-language SDK for the OpenShell gateway: a thin, idiomatic
TypeScript client over proto-generated gRPC stubs (connect-es), no FFI. Covers
the v0.1 surface — sandbox lifecycle (create/get/list/delete + waitReady/
waitDeleted), health, and streamed exec.

- sdk/typescript/: package, client/transport/errors, protoc + protoc-gen-es
  codegen (gen/ gitignored, absorbed into dist/ at build), committed lockfile.
- tasks/typescript.toml: sdk:ts install/proto/typecheck/build/ci/publish;
  sdk:ts:typecheck wired into `check`; sdk-typescript job in branch-checks
  (typecheck, build, and a --dry-run publish that validates the release path).
- Enforce SPDX headers on .ts/.tsx/.mts/.cts (skip node_modules and gen/);
  back-fill docs/_components/jsx.d.ts and fern/components/CustomFooter.tsx.
- release.py gains an npm version format; release-tag.yml publishes to
  GitHub Packages on tag, stamping the version (0.0.0 placeholder in git);
  prerelease builds publish under the `next` dist-tag, not `latest`.

Ships as @nvidia/openshell-sdk on GitHub Packages pre-GA; public npm
(@openshell/sdk) follows at GA with an unchanged public API.

Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com>

* chore(sdk): adopt TypeScript 6, tidy @types/node range

- typescript ^5.7.2 -> ^6.0.3 (6.0 is now `latest`; the old caret capped at 5.x)
- @types/node ^24.0.0 -> ^24 (same range, tidier)

No source changes; codegen, typecheck, and build pass on 6.0.3. Verified the
emitted d.ts still type-check for downstream consumers on TypeScript 5.0.4
through 5.9.3, so this does not raise the SDK's consumer TS floor.

Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com>

* refactor(sdk): group operations under a composable SandboxClient

Reshape the client from flat methods (createSandbox, listSandboxes, exec) to a
scoped SandboxClient reached as `client.sandbox.create/get/list/delete/exec`
(+ waitReady/waitDeleted), mirroring the CLI's noun-verb model and the Python
SDK's SandboxClient.

SandboxClient is also usable standalone via SandboxClient.connect();
OpenShellClient composes it over a single shared transport, so future
service/provider clients reuse one connection. health() stays top-level as a
gateway call. No behavior change; types are unchanged.

Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com>

* chore(sdk): generate TypeScript SDK stubs with buf

Replace the protoc gen.sh with `buf generate` + buf.gen.yaml. `buf`
(@bufbuild/buf) is a package devDependency and self-compiles the protos, so
the TS SDK no longer depends on the mise-pinned protoc; it drives the same
connect-es plugin. Generation stays limited to the client-surface closure
(openshell/sandbox/datamodel) via the input paths.

Output is byte-identical to the previous protoc + protoc-gen-es pipeline. Lays
the groundwork for a shared buf.yaml (lint/breaking/LSP) as a follow-up.

Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com>

* build(proto): add repo-level buf module with lint

Declare proto/ as a single buf v2 module in a root buf.yaml so buf
generate, lint, breaking, and the editor LSP resolve imports the same
way. Lint uses STANDARD with six documented exceptions for deviations
the current protos intentionally make: the flat proto/ layout with
nested packages (DIRECTORY_SAME_PACKAGE, PACKAGE_DIRECTORY_MATCH) and
the established API shape with unsuffixed services and reused
request/response messages (RPC_REQUEST_RESPONSE_UNIQUE,
RPC_REQUEST_STANDARD_NAME, RPC_RESPONSE_STANDARD_NAME, SERVICE_SUFFIX).
Every other STANDARD rule now enforces on future protos. Breaking uses
FILE.

Code generation stays package-scoped in sdk/typescript/buf.gen.yaml
since it binds to that package's connect-es plugin and output dir;
its inputs are unchanged and regeneration is byte-identical.

Wire the check in via a proto:lint mise task that runs buf from the
SDK devDependencies. It is a dependency of both sdk:ts:ci (so the
TypeScript SDK CI job enforces it) and the top-level lint aggregate
(so local pre-commit covers it).

Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com>

* chore(sdk): publish as unscoped openshell-sdk on public npm

Rename the package from @nvidia/openshell-sdk to the unscoped
openshell-sdk and target public npm (registry.npmjs.org) instead of
GitHub Packages. GitHub Packages requires a scope matching the owning
org, and the @openshell scope is blocked by an unrelated existing
package, so an unscoped name on public npm is the lowest-friction
distribution path and needs no org approval.

Rework the release-tag publish job to auth against registry.npmjs.org
with NPM_TOKEN (the job now only needs packages: read to pull the CI
image). Update the README install instructions and usage imports.

Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com>

* chore(sdk): publish @nvidia/openshell-sdk to GitHub Packages

Revert the unscoped-name switch. GitHub Packages only accepts scoped
names matching the owning org, so shipping there first (which needs no
external npm org or NPM_TOKEN, just the repo's GITHUB_TOKEN) requires
the @nvidia scope. Keeping the @nvidia/openshell-sdk name also lets a
later public-npm release use the same install specifier, so adding
public npm becomes a second publish step rather than a rename.

Restore the GitHub Packages publish auth in the release-tag job and the
scoped install instructions in the README (keeping the buf codegen
note).

Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com>

* feat(sdk-ts): add streaming exec, forward, ssh, provider, and config methods

Grow SandboxClient to the surface the first two consumers need. execStream
yields stdout/stderr chunks as they arrive and exec now drains it, keeping its
buffered ExecResult and signature unchanged. execInteractive is the TTY + stdin
transport primitive (start-first framing, output/write/resize/close/done, no
terminal glue). forward binds a local TCP listener that tunnels each accepted
connection into the sandbox for the process lifetime, minting and revoking a
per-socket SSH session token around a forwardTcp bidi. Adds createSshSession /
revokeSshSession, attach/detach/listProviders, and getConfig / setPolicy /
setSetting (sandbox-scoped, network-policy-only, with an optional wait poll).

Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com>

* build(sdk-ts): add Biome and Vitest tooling

The TypeScript SDK had no formatter or linter and no test runner. Add Biome
(format + lint, generated src/gen excluded) enforcing 2-space indent, single
quotes, semicolons, and a 120-column width, and reformat the existing
hand-written sources accordingly. Add Vitest for unit tests. Wire sdk:ts:format,
sdk:ts:lint, and sdk:ts:test mise tasks into the fmt/lint aggregates, the root
test suite, and sdk:ts:ci so they run in CI.

Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com>

* test(sdk-ts): cover the sandbox surface with in-memory transport tests

Exercise SandboxClient against an in-memory OpenShell service built with
createRouterTransport: request assembly and id resolution, u64/int64 rendered as
strings, enum lowercasing, fromConnect code mapping, the exec/execStream drain
plus a backward-compat check on exec, execInteractive start-first ordering and
done resolution, and a forward() byte relay against a loopback echo with close()
teardown.

Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com>

* docs(sdk-ts): document the new surface and connect/upload/download boundaries

Document execStream, execInteractive, forward, ssh sessions, providers, and
config/policy in the SDK README, and record the intentional boundaries:
interactive connect / PTY ownership, upload/download (no file-transfer RPC), and
detached forwards stay out of scope. Note the Biome/Vitest dev commands.

Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com>

* feat(sdk-ts): support mTLS client authentication

Add clientCert and clientKey to ConnectOptions so the SDK can
authenticate to the default local gateway, which uses mTLS user
authentication. Without a client certificate and key the SDK could
verify the server but never authenticate the caller, so it could not
connect to the standard Docker, VM, Homebrew, or Linux-package gateway.

Validate the pair as both-or-neither and pass cert and key through to
the Node TLS options for https gateways. The h2c path is unchanged.

Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com>

* chore(sdk-ts): drop the demo script and its tsx dependency

Remove src/demo.ts, the demo npm script, the tsx devDependency, and the
tsconfig build exclude for the demo. The demo was never part of the
published package, and dropping it also removes the only place that
logged part of an SSH session token.

Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com>

* fix(sdk-ts)!: harden exec streaming, waits, SSH, and forwarding

Address review feedback on the sandbox surface.

- Make the streamed command exit code observable from idiomatic
  for-await: the terminal exit is now an in-band ExecStreamEvent
  ({ type: 'exit', exitCode }) rather than the async generator return
  value, which for-await discards. A stream that ends without an exit
  event now throws instead of reporting success.
- Bound waitReady, waitDeleted, and the setPolicy wait by their timeout:
  each poll RPC carries a per-iteration deadline and the waits accept an
  AbortSignal, so a stalled call can no longer leave a wait pending
  forever. Add waitTimeoutSecs to SetPolicyOptions.
- Validate the CreateSshSession response against the proto charset and
  range contract before returning it or using its token, since the
  values feed an OpenSSH ProxyCommand.
- Respect socket backpressure when relaying forwarded responses: pause
  reading the gRPC stream when the local socket buffer is full and
  resume on drain so memory stays bounded.
- Expose create-time sandbox policy: add policy and an advanced rawSpec
  passthrough to SandboxSpec so the safety boundary is expressible at
  creation and new spec fields do not require an SDK change.

BREAKING CHANGE: execStream and the interactive exec output now yield a
terminal { type: 'exit', exitCode } event; consumers iterating the
stream must handle that arm. The exit code is no longer the async
generator return value.

Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com>

* feat(sdk-ts): export error contract, enum unions, and caller cancellation

Address Tier-1 review feedback on the TypeScript SDK public surface (PR #2122).

- Errors: export SdkError and SdkErrorCode so callers can use instanceof and
  exhaustively switch on .code. fromConnect preserves the originating
  ConnectError as .cause and its status as .connectCode, maps Aborted to a new
  'aborted' code for optimistic-concurrency conflicts, and maps Canceled and
  DeadlineExceeded to 'canceled'. errorCode() behavior is unchanged.
- Enums: replace the string-typed phase, status, scope, and policySource fields
  with lowercase literal unions (SandboxPhaseName, HealthStatus,
  SettingScopeName, PolicySourceName) backed by exhaustive Record maps. The
  unions are a hand-maintained mirror of the generated proto enums; a new drift
  test pins each literal to its generated member name.
- Cancellation: accept an optional AbortSignal on exec, execInteractive, and
  forward, threaded into both sandbox resolution and the streaming RPC. forward
  tears down its local listener on abort.

Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com>

* feat(sdk-ts): add raw escape hatch for uncurated gateway RPCs

The curated sub-clients reduce proto messages to ergonomic subsets (for
example get() drops created_at_ms, the full spec, conditions, runtime
endpoints, and current_policy_version), and not every gateway RPC has a
typed helper yet. Rather than ship methods that exist but throw, expose a
generated client for the full surface.

OpenShellClient.raw and SandboxClient.raw are generated clients covering
every gateway RPC, returning the verbatim wire messages so proto
distinctions the curated types smooth over are preserved. .transport
exposes the shared connection for building extra clients over one socket.
Generated request/response types are published at the new
@nvidia/openshell-sdk/raw subpath. Curated methods stay the default;
raw is the always-available floor.

Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com>

* fix(sdk-ts): address review feedback on exec, forward, and auth transport

Settle exec's `done` promise before yielding the exit event so a consumer
that breaks on exit no longer leaves it pending forever, and give it a lone
rejection handler plus a finally-settle so a stream error or early abandon
can never surface as an unhandled rejection or a hang.

Attach an 'error' listener to each accepted forward socket synchronously,
before forwardConnection awaits CreateSshSession; a peer reset in that
window previously emitted an unhandled 'error' and crashed the process.

Reject ambiguous or unsafe transport configs at buildTransport: oidcToken
and edgeToken together (silently OIDC-only), and any auth token sent over
plaintext http:// to a non-loopback host unless allowInsecureAuth is set.

Wrap versionPin so a non-u64 expectedResourceVersion raises
SdkError('invalid_config') instead of a raw BigInt SyntaxError, and raise
the Node engine floor to >=20.3 for AbortSignal.any().

Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com>

* fix(sdk-ts): address review feedback

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* docs(sdk-ts): defer published sdk guide

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

---------

Signed-off-by: Max Dubrinsky <mdubrinsky@nvidia.com>
Signed-off-by: Drew Newberry <anewberry@nvidia.com>
Co-authored-by: Drew Newberry <anewberry@nvidia.com>
2026-08-13 20:50:38 +00:00

30 KiB

Contributing to OpenShell

OpenShell is built agent-first. We design systems and use agents to implement them. Your agent is your first collaborator — point it at this repo before opening issues, asking questions, or submitting code.

The Critical Rule

You must understand your code. Using AI agents to write code is not just acceptable, it's how this project works. But you must be able to explain what your changes do and how they interact with the rest of the system. If you can't, don't submit it.

Submitting agent-generated code without understanding it — regardless of how clean it looks — wastes maintainer time and will result in your PR being closed. Repeat offenders will be blocked from the project.

AI Usage

OpenShell is agent-first, not agent-only. The distinction matters:

  • Do use agents to explore the codebase, run diagnostics, generate code, and iterate on implementations.
  • Do use the skills in .agents/skills/ — they exist to make your agent effective.
  • Do interrogate your agent until you understand every edge case and interaction in your changes.
  • Don't submit code you can't explain without your agent open.
  • Don't use agents as a substitute for understanding the system. Read the architecture docs.

First-Time Contributors

We use a vouch system. This exists because AI makes it trivial to generate plausible-looking but low-quality contributions, and we can no longer trust by default.

  1. Open a Vouch Request discussion.
  2. Describe what you want to change and why.
  3. Write in your own words. AI-generated vouch requests will be denied.
  4. A maintainer will comment /vouch if approved.
  5. Once vouched, you can submit pull requests.

If you are not vouched, any pull request you open will be automatically closed. Org members and collaborators with push access bypass this check.

Finding Work

Issues labeled good first issue are scoped, well-documented, and friendly to new contributors. Start there. If you need guidance, comment on the issue.

An open issue is not necessarily accepted or ready to be worked on. Human contributors should look for state:accepted, good first issue, or help wanted, or ask a maintainer before starting. Unattended agents additionally require the appropriate human-applied agent:* request label; an agent directly asked to work on a specific issue does not. Roadmap placement describes sequencing and does not authorize work.

Before You Open an Issue

This project ships with agent skills that can diagnose problems, explore the codebase, generate policies, and walk you through common workflows. Before filing an issue:

  1. Clone the repo and point your coding agent at it.
  2. Load the relevant skill - debug-openshell-cluster for gateway or deployment problems, debug-inference for inference setup problems, openshell-cli for usage questions, generate-sandbox-policy for policy help.
  3. Have your agent investigate. Let it run diagnostics, read the architecture docs, and attempt a fix.
  4. If the agent cannot resolve it, open an issue with the agent's diagnostic output attached. The issue template requires this.

When to Open an Issue

  • A real bug that your agent confirmed and could not fix.
  • A feature proposal with a design — not a "please build this" request.
  • An infrastructure problem that the gateway deployment troubleshooting skill could not resolve.
  • An inference setup problem that the debug-inference skill could not resolve.
  • Security vulnerabilities must follow SECURITY.md — not GitHub issues.

When NOT to Open an Issue

  • Questions about how things work — your agent can answer these from the codebase and architecture docs.
  • Configuration problems - your agent can diagnose these with openshell-cli, debug-openshell-cluster, and debug-inference.
  • "How do I..." requests — the skills cover CLI usage, policy generation, TUI development, and more.

Agent Skills for Contributors

Skills live in .agents/skills/. Your agent's harness can discover and load them natively. Here is the full inventory:

Category Skill Purpose
Getting Started openshell-cli CLI usage, sandbox lifecycle, provider management, BYOC workflows
Getting Started debug-openshell-cluster Diagnose gateway deployment and health issues
Getting Started debug-inference Diagnose inference.local, host-backed local inference, and direct external inference setup issues
Contributing create-spike Investigate a problem, produce a structured GitHub issue
Contributing create-rfc Create RFC proposals from the repository template
Contributing build-from-issue Plan and implement work from a GitHub issue (maintainer workflow)
Contributing create-github-issue Create well-structured GitHub issues
Contributing create-github-pr Create pull requests with proper conventions
Reviewing review-github-pr Summarize PR diffs and key design decisions
Reviewing review-security-changes Review code changes for security vulnerabilities and boundary regressions
Reviewing review-security-issue Assess security issues for severity and remediation
Reviewing fix-security-issue Implement an approved security remediation plan
Reviewing watch-github-actions Monitor CI pipeline status and logs
Reviewing launch-openshell-gator Launch and supervise OpenShell gator agents for issue and PR monitoring
Reviewing test-release-canary Dispatch and iterate on the Release Canary workflow that smoke-tests published artifacts
Triage triage-issue Assess, classify, and route community-filed issues
Platform generate-sandbox-policy Generate YAML sandbox policies from requirements or API docs
Platform helm-dev-environment Start and manage the local Kubernetes development environment
Platform tui-development Development guide for the ratatui-based terminal UI
Platform build-openshell-mxc-windows Maintain and validate the build-only x64 and ARM64 Windows MSVC lane
Documentation update-docs Scan recent commits and draft doc updates for user-facing changes
Maintenance sync-agent-infra Detect and fix drift across agent-first infrastructure files
Reference sbom Generate SBOMs and resolve dependency licenses

Workflow Chains

Skills connect into pipelines. Individual skill files don't describe these relationships.

  • Community inflow: triage-issue → human disposition and roadmap placement → create-spike when needed → build-from-issue
  • Internal development: create-spike → human disposition and roadmap placement → build-from-issue
  • Security: review-security-issue → fix-security-issue
  • Policy iteration: openshell-cli → generate-sandbox-policy

Issue Lifecycle, Roadmap, and Agent Work

OpenShell separates technical assessment, roadmap decisions, sequencing, and agent delegation.

An open issue is not automatically accepted or ready for implementation. Check its state:* label before starting work, and ask a maintainer when its status is unclear.

The Four Decisions

Each issue can require four independent decisions:

Decision Question Recorded by
Assessment Is the report technically valid, and is there enough evidence to act on it? state:*
Disposition Should OpenShell pursue the work? state:accepted or closure as not planned
Sequencing Where does accepted work sit relative to everything else? Placement on the OpenShell Roadmap
Ownership Will a human implement the issue, will a user directly instruct an agent, or will a maintainer queue it for an unattended agent? Direct instruction or optional agent:* workflow

Completing one decision does not imply the others. state:validated confirms that the factual assessment is complete, but it does not mean the project has accepted the work. Roadmap placement communicates sequencing, but it does not authorize an agent to begin.

Who Controls Each Decision

Agents investigate issues, collect evidence, and report technical findings. Humans retain the product and investment decisions.

Action Who performs it
Assess technical validity and impact Triage agent or human triager
Request missing evidence Triage agent or human triager
Mark the assessment complete with state:validated Triage agent or human triager
Accept or decline the work Maintainer
Place the issue on the roadmap or move it Maintainer
Directly request an agent plan User
Queue an agent plan with agent:plan-requested Maintainer
Produce a plan, implement it, and open a pull request Agent
Directly request agent implementation User
Queue approved implementation with agent:implementation-requested Maintainer

Agents do not apply state:accepted, place issues on the roadmap, or apply agent:plan-requested or agent:implementation-requested.

Issue State

The state:* namespace records the issue's disposition for all contributors, regardless of who might implement it.

State Meaning Normal next action
state:triage-needed The issue has not been assessed. New issues from users without repository write access receive this automatically. Investigate the report and record the result.
state:needs-info The assessment needs specific evidence or reproduction details. The reporter or another contributor supplies the requested information.
state:validated The factual assessment is complete. A maintainer accepts the issue, declines it, or asks for more evidence.
state:accepted A maintainer decided that OpenShell should pursue the issue. A human may implement it, or a maintainer may delegate work to an agent.

Keep one of these states on an open issue. When new evidence resolves a state:needs-info request, reassess the issue and move it to state:validated if the evidence is sufficient.

state:stale is an inactivity marker, not a lifecycle decision. Accepted issues and issues awaiting human disposition are exempt from stale handling. An issue in state:needs-info can become stale if no new evidence arrives.

Assessing an Incoming Issue

Triage checks the report, its diagnostic evidence, related issues, current releases, and the relevant code paths. The assessment ends in one of these outcomes:

Outcome State or resolution
A bug is confirmed. Replace the intake state with state:validated.
A feature proposal is technically coherent and feasible. Replace the intake state with state:validated.
The report is credible but needs a deeper investigation or spike. Add the spike label when available and use state:validated so a human can decide whether to invest in the investigation.
Critical evidence is missing, or a faithful attempt cannot reproduce the problem. Use state:needs-info and request the exact evidence needed.
A released change already fixes the behavior. Explain the fix and version. Close the issue only when the causal link is clear; otherwise request a retest.
Another issue is the canonical report. Link the canonical issue and close the duplicate.
The behavior is expected or caused by unsupported configuration. Explain the finding and close the issue with the appropriate GitHub reason.
The report describes a security vulnerability. Stop public triage and follow the private process in SECURITY.md.

Triage establishes facts and impact. It does not decide whether the project should spend time on the work.

Human Disposition

When an issue reaches state:validated, a maintainer chooses one of three paths:

  • Accept: replace state:validated with state:accepted and place it on the roadmap.
  • Decline: close it as not planned and record the rationale.
  • Await more evidence: replace state:validated with state:needs-info and leave it off the roadmap.

Do not use state:accepted as shorthand for technical validity, roadmap sequencing, or agent authorization. It records only the human decision that OpenShell should pursue the work.

Roadmap

OpenShell does not use priority labels. Sequencing comes from the OpenShell Roadmap: a maintainer associates an accepted issue with a roadmap item, and the roadmap item's own timing carries the urgency. Issues tracked on the roadmap carry the roadmap label.

An accepted issue with no roadmap association is real work the project intends to do, but it is not scheduled. Ask a maintainer before starting on one.

Roadmap placement does not assign an owner. A roadmap issue still needs a human contributor, a direct user instruction to an agent, or an unattended-agent queue label.

good first issue and help wanted describe contributor suitability, not sequencing.

Human or Agent Ownership

A human contributor may implement an accepted issue without any agent:* label. Before starting, check for an assignee, linked pull request, active branch, or comment that shows someone else is already working on it.

Maintainers use the agent:* workflow to queue work for always-on or unattended agents that scan issues. Keep exactly one agent-workflow label on the issue at a time. When a user directly asks an agent to plan or implement a specific issue, that instruction authorizes the requested phase and the corresponding request label is not required.

Agent workflow Applied by Meaning
agent:plan-requested Maintainer Ask an agent to produce an implementation plan.
agent:plan-ready Agent The plan is ready for human review.
agent:implementation-requested Maintainer The plan is approved and an agent may implement it.
agent:in-progress Agent Authorized implementation is underway.
agent:pr-opened Agent The implementation produced a pull request.

The normal delegated workflow is:

state:accepted
  |
  +-- agent:plan-requested
        |
        +-- agent:plan-ready
              |
              +-- agent:implementation-requested
                    |
                    +-- agent:in-progress
                          |
                          +-- agent:pr-opened

agent:plan-requested authorizes an unattended agent to pick up planning, not implementation. agent:implementation-requested confirms that a human reviewed the plan and authorizes an unattended agent to pick up implementation. Agents never apply either request label. Planning authority does not imply implementation authority.

Spikes

Use a spike when the report is credible but technical uncertainty prevents a buildable plan. The triage assessment should identify the unknowns and the evidence the spike needs to produce.

A maintainer first decides whether OpenShell should invest in the investigation. If accepted, the maintainer places it on the roadmap and may request agent work. The spike records its findings in an issue and uses:

  • state:validated when the evidence supports a human accept or decline decision.
  • state:needs-info when material evidence or an external decision is still missing.

A completed spike does not automatically authorize implementation. The resulting issue follows the same human disposition process.

Security Issues

Do not file or discuss suspected vulnerabilities in a public GitHub issue. Follow the disclosure instructions in SECURITY.md.

Maintainers use the specialized security review and remediation workflow for an authorized security issue. For unattended processing, it uses the same queue controls:

  1. A maintainer applies agent:plan-requested to request a security review and remediation plan.
  2. The review agent replaces it with agent:plan-ready.
  3. A maintainer reviews the plan and applies agent:implementation-requested.
  4. The remediation agent implements the approved plan.

A user may instead directly request review or remediation from the specialized skill. The direct request replaces the corresponding queue label, but a request for review still does not authorize remediation. General implementation agents do not process issues labeled topic:security.

When an Issue Is Ready for Work

You are Ready when
A human contributor The issue has state:accepted, invites contribution or has maintainer confirmation, and has no conflicting owner or implementation.
An unattended agent scanning for planning work The issue has state:accepted and the human-applied agent:plan-requested label.
An unattended agent scanning for implementation work The issue has state:accepted, an approved plan, and the human-applied agent:implementation-requested label.
An agent directly instructed by a user The issue has state:accepted, no conflicting owner or implementation, and the instruction explicitly requests the phase the agent will perform.

Issues with state:triage-needed, state:needs-info, or state:validated are not ready for implementation. Roadmap placement alone never makes an issue ready.

Stale Issues

Inactive issues and pull requests are automatically labeled state:stale after 14 days without activity. Automated closing is currently disabled. Comment on the item or remove state:stale to keep it active. Issues awaiting triage or human disposition, accepted issues, active agent workflows, and roadmap issues are exempt. state:needs-info may become stale when no new evidence arrives.

Prerequisites

Install mise. This is used to set up the development environment.

# Install mise (macOS/Linux)
curl https://mise.run | sh

After installing mise, activate it with mise activate or add it to your shell.

Shell setup examples:

# Bash
echo 'eval "$(~/.local/bin/mise activate bash)"' >> ~/.bashrc

# Fish
echo '~/.local/bin/mise activate fish | source' >> ~/.config/fish/config.fish

# Zsh
echo 'eval "$(~/.local/bin/mise activate zsh)"' >> ~/.zshrc

Project requirements:

  • Rust 1.90+
  • Python 3.11+
  • Docker (running)

Optional: Bazel (experimental)

Install Bazelisk, which auto-downloads the Bazel version pinned in .bazelversion:

# macOS
brew install bazelisk

# npm (any platform)
npm install -g @bazel/bazelisk

Bazel builds Z3 from source, so no system Z3 installation is needed when using Bazel. If you have previously built with Cargo, add Cargo's output directory to .bazelignore to prevent conflicts:

echo "target" >> .bazelignore

Z3 installation

The openshell-prover crate links against Z3. On macOS and Linux, install the system Z3 development package; z3-sys discovers it through pkg-config.

# macOS
brew install z3

# Ubuntu / Debian
sudo apt install libz3-dev

# Fedora
sudo dnf install z3-devel

If you prefer not to install Z3 system-wide, use the bundled Z3 feature. This compiles Z3 from source during the Rust build:

cargo build -p openshell-prover --features bundled-z3

For x86-64 Windows MSVC builds, use one of these Z3 paths:

  • System Z3: point Z3_LIBRARY_PATH_OVERRIDE at the directory containing the 64-bit MSVC Z3 library and Z3_SYS_Z3_HEADER at the full path to z3.h. The windows:* tasks use this path automatically when Z3_LIBRARY_PATH_OVERRIDE is set.
  • Bundled Z3: pass --features bundled-z3 so z3-sys builds Z3 from source.

Both Windows paths still require libclang.dll for bindgen. If LLVM is not on the default search path, set LIBCLANG_PATH to the directory containing libclang.dll.

$env:LIBCLANG_PATH='C:\Program Files\Microsoft Visual Studio\2022\<Edition>\VC\Tools\Llvm\x64\bin'
cargo build -p openshell-cli --target x86_64-pc-windows-msvc --features bundled-z3

To use a local x64 Z3 release with the Windows task wrapper:

$env:Z3_LIBRARY_PATH_OVERRIDE='C:\path\to\z3-4.16.0-x64-win\bin'
$env:Z3_SYS_Z3_HEADER='C:\path\to\z3-4.16.0-x64-win\include\z3.h'
mise run --skip-tools windows:build:x64

macOS build tools

Install Apple Command Line Tools before building locally:

xcode-select --install

Getting Started

# One-time trust
mise trust

# Run a standalone gateway for local development
mise run gateway

Building the openshell CLI

Inside this repository, openshell is a local shortcut script at scripts/bin/openshell. The script will

  1. Build openshell-cli if needed.
  2. Run the local debug CLI binary under target/debug/openshell.

Because mise adds scripts/bin to PATH for this project, you can run openshell directly from the repo.

openshell --help
openshell sandbox create -- codex

Rust build cache

Mise preserves an existing SCCACHE_DIR so each environment can choose where to store compiler cache entries. When SCCACHE_DIR is unset, OpenShell uses the worktree-local .cache/sccache directory. To make cache entries available to multiple worktrees on a workstation, set the variable to a user-level directory before activating mise. For example:

export SCCACHE_DIR="$HOME/.cache/openshell/sccache"

CI can select a different directory or configure a remote sccache backend without changing the workstation setting. Cargo output remains in each worktree's target/ directory.

OpenShell does not set SCCACHE_BASEDIRS. Sccache loads base directories when its machine-local daemon starts, but the correct workspace root differs for each worktree. Cache reuse therefore depends on the compiler inputs: outputs that embed absolute paths, including Rust dependencies in some builds, can still miss across worktrees.

Main Tasks

These are the primary mise tasks for day-to-day development:

Task Purpose
mise run gateway Run a standalone gateway for local development
mise run sandbox Create or reconnect to the dev sandbox
mise run test Default test suite
mise run e2e Default end-to-end test lane
mise run ci Full local CI checks (lint, compile/type checks, tests)
mise run docs Validate Fern docs locally
mise run helm:docs Regenerate the Helm chart README
mise run clean Clean build artifacts

Bazel targets (experimental)

Important

Bazel support is experimental and under evaluation via RFC 0012. It may be removed at any time depending on the RFC outcome. Feedback is welcome: open an issue or find us on CNCF Slack in #openshell-dev.

The following Bazel commands are available alongside the mise tasks above. Cargo and mise remain the primary build system.

Task Bazel command Notes
Build everything bazel build //... All crates and protos
Run all tests bazel test //... Unit tests only, no E2E
Build the CLI bazel build //crates/openshell-cli:openshell
Build the gateway bazel build //crates/openshell-server:openshell-gateway
Build the supervisor bazel build //crates/openshell-sandbox:openshell-sandbox-bin
Clean bazel clean

Bazel does not yet cover mise run gateway, mise run sandbox, mise run e2e, mise run docs, or mise run helm:docs. Those are runtime and infrastructure tasks that remain with mise. Additional Bazel targets will be added over time as the experiment progresses.

Project Structure

Path Purpose
crates/ Rust crates
python/ Python SDK and bindings
sdk/go/ Go SDK (types, gRPC clients, converters)
sdk/typescript/ TypeScript SDK (Connect client and generated protobuf bindings)
proto/ Protocol buffer definitions
tasks/ mise task definitions and build scripts
deploy/ Dockerfiles, Helm chart, Kubernetes manifests
docs/ Published Fern docs source, navigation, and content assets
fern/ Fern site config, components, and theme assets
architecture/ Architecture docs and plans
rfc/ Request for Comments proposals
.agents/ Agent skills and persona definitions

RFCs

New features always start as GitHub issues using the feature request template. For cross-cutting architectural decisions, API contract changes, or process proposals that need broad consensus, maintainers may ask for an RFC from the issue and assign an RFC number there. RFCs live in rfc/. See rfc/README.md for the full lifecycle and guidelines.

Documentation

If your change affects user-facing behavior (new flags, changed defaults, new features, bug fixes that contradict existing docs), update the relevant pages under docs/ in the same PR and adjust docs/index.yml if navigation changes. For explicit navigation entries, keep page: aligned with sidebar-title when present and put relative slug: values in docs/index.yml. Reserve frontmatter slug for folder-discovered pages or absolute URL overrides.

To ensure your doc changes follow NVIDIA documentation style, use the update-docs skill. It scans commits, identifies doc pages that need updates, and drafts content that follows the style guide in docs/CONTRIBUTING.mdx.

To preview Fern docs locally:

mise run docs:serve

To run non-interactive validation:

mise run docs

PRs that touch docs/** or fern/** are validated by .github/workflows/branch-docs.yml, and they get a preview when FERN_TOKEN is available to the workflow.

Fern docs publishing is handled by the publish-fern-docs job in .github/workflows/release-tag.yml when a release tag is created.

docs/ is the source-of-truth docs tree. fern/ contains the site config, components, and theme assets that publish those pages.

See docs/CONTRIBUTING.mdx for the current docs authoring guide.

Pull Requests

  1. Create a feature branch from main.
  2. Make your changes with tests.
  3. Run mise run ci to verify.
  4. Open a PR using the create-github-pr skill or manually following the PR template.

PRs for new features, user-visible behavior changes, public API changes, architecture changes, or multi-PR efforts must link an accepted issue. Small documentation fixes, mechanical maintenance, and obvious localized bug fixes may omit a separate issue when the PR contains enough context to review the decision and implementation together.

In the PR's Related Issue section, use Fixes #NNN or Closes #NNN when an issue is required. For an exempt change, write No issue required: followed by a brief reason. Security fixes follow the private disclosure process in SECURITY.md.

Commit Messages

This project uses Conventional Commits. All commit messages must follow the format:

<type>(<scope>): <description>

[optional body]

[optional footer(s)]

Types:

  • feat - New feature
  • fix - Bug fix
  • docs - Documentation only
  • chore - Maintenance tasks (dependencies, build config)
  • refactor - Code change that neither fixes a bug nor adds a feature
  • test - Adding or updating tests
  • ci - CI/CD changes
  • perf - Performance improvements

Examples:

feat(cli): add --verbose flag to openshell run
fix(sandbox): handle timeout errors gracefully
docs: update installation instructions
chore(deps): bump tokio to 1.40

DCO

All human contributions must include a Signed-off-by line in each commit message. This certifies you have the right to submit the work under the project license. See the Developer Certificate of Origin. Dependabot-authored dependency update PRs are allowlisted because the bot cannot sign commits.

git commit -s -m "feat(sandbox): add new capability"

DCO sign-off is separate from cryptographic commit signing. CI requires signing for org members so that copy-pr-bot can mirror your PR automatically; see CI.md for setup.

CI

How PR CI runs, the test:e2e, test:e2e-gpu, and test:e2e-kubernetes labels, copy-pr-bot, and commit-signing setup are documented in CI.md.