{"id":47680620,"url":"https://github.com/giselles-ai/sandkit","last_synced_at":"2026-04-02T13:58:30.875Z","repository":{"id":346250883,"uuid":"1189065526","full_name":"giselles-ai/sandkit","owner":"giselles-ai","description":"Workspace state, session management, and durable command execution for Vercel Sandbox.","archived":false,"fork":false,"pushed_at":"2026-03-23T14:05:17.000Z","size":468,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-23T20:57:06.074Z","etag":null,"topics":["durable-execution","state-management","typescript","vercel-sandbox"],"latest_commit_sha":null,"homepage":"https://github.com/giselles-ai/sandkit","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/giselles-ai.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-03-22T23:59:09.000Z","updated_at":"2026-03-23T14:05:19.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/giselles-ai/sandkit","commit_stats":null,"previous_names":["giselles-ai/sandkit"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/giselles-ai/sandkit","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/giselles-ai%2Fsandkit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/giselles-ai%2Fsandkit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/giselles-ai%2Fsandkit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/giselles-ai%2Fsandkit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/giselles-ai","download_url":"https://codeload.github.com/giselles-ai/sandkit/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/giselles-ai%2Fsandkit/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31307427,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-02T12:59:32.332Z","status":"ssl_error","status_checked_at":"2026-04-02T12:54:48.875Z","response_time":89,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["durable-execution","state-management","typescript","vercel-sandbox"],"created_at":"2026-04-02T13:58:27.953Z","updated_at":"2026-04-02T13:58:30.850Z","avatar_url":"https://github.com/giselles-ai.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Sandkit\n\nSandkit makes Vercel Sandbox stateful.\n\nSandkit adds workspace state, session management, durable command execution, and resumable sandbox workflows to Vercel Sandbox.\n\nIt keeps two paths explicit:\n\n- `workspace.sandbox.runCommand(...)` for durable, one-command-at-a-time work\n- `openSession()` / `attachSession()` for a live leased sandbox when you need an interactive process\n\nAn active session is an exclusive workspace lease. While a live session is open, `runCommand()` is unavailable until you attach to that session or commit it.\n\nProvider-specific behavior still matters, but the public API stays centered on workspaces, policies, and durable state.\n\n## Problem\n\nVercel Sandbox is ephemeral by design. It does not give you durable workspaces, session lifecycle, or a clear boundary between one-shot commands and live attached execution.\n\n- No built-in workspace identity or durable workspace state\n- No session management abstraction for live attach / resume\n- No durable command boundary for one-command-at-a-time work\n- Teams end up rebuilding the same sandbox state and lifecycle layer around jobs, agents, and recovery flows\n\n## Solution\n\nSandkit adds a workspace state layer on top of Vercel Sandbox:\n\n- Persistent workspaces for sandbox state management\n- Live session lifecycle with explicit attach / commit semantics\n- Durable command execution through `runCommand(...)` for committed work\n- Policy controls that stay part of workspace state\n- Resumable sandbox workflows for long-running apps and control planes\n\n## Positioning\n\n| Tool                                  | Responsibility                                                                                                           |\n| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |\n| Vercel Sandbox                        | Ephemeral execution environment                                                                                          |\n| Sandkit                               | State and lifecycle layer for sandboxed execution: workspaces, session lifecycle, durable command boundaries, and resume |\n| Workflow engines / app control planes | Decide when and why work runs                                                                                            |\n\n## Primary Use Case: Persistent Workspaces for AI Coding Agents\n\nSandkit is especially useful when an agent or long-running sandbox app needs to keep a workspace alive across runs, attach to a live process, expose a public URL, and commit progress durably.\n\n## Install\n\n```sh\nnpm install @giselles-ai/sandkit\n```\n\nWith Drizzle:\n\n```sh\nnpm install @giselles-ai/sandkit drizzle-orm\n```\n\n## Quick Start\n\n\u003e Migration note: `sandkit(...)` was renamed to `createSandkit(...)` and this package is not yet aliased.\n\u003e Callers must update imports and call sites from `sandkit` to `createSandkit` together.\n\n```ts\nimport { Database } from \"bun:sqlite\";\n\nimport { createSandkit } from \"@giselles-ai/sandkit\";\nimport { createBunSqliteAdapter } from \"@giselles-ai/sandkit/adapters/sqlite-bun\";\nimport { vercelSandbox } from \"@giselles-ai/sandkit/integrations/vercel\";\n\nconst database = new Database(\"./sandkit.sqlite\");\nconst workspaceAdapter = createBunSqliteAdapter(database);\n\nconst sandkit = createSandkit({\n  database: workspaceAdapter,\n  sandbox: vercelSandbox({\n    defaultTimeout: 60_000,\n  }),\n});\n\nconst workspace = await sandkit.createWorkspace({\n  name: \"hello-sandkit\",\n});\n\nawait workspace.sandbox.runCommand({\n  command: \"sh\",\n  args: [\"-lc\", \"echo 'hello world' \u003e ./hello.txt\"],\n});\n\nconst result = await workspace.sandbox.runCommand({\n  command: \"cat\",\n  args: [\"./hello.txt\"],\n});\n\nconsole.log(result.stdout.trim());\n```\n\nSet `VERCEL_OIDC_TOKEN` for local runs or `VERCEL_ACCESS_TOKEN` in CI before creating a Vercel-backed sandbox.\n\n## Policies\n\nService presets read default credentials from the environment when the policy is applied:\n\n- `npm()` allows the public npm registry host `registry.npmjs.org`\n- `bun()` allows Bun install/distribution hosts `bun.sh` and `bun.com`\n- `codex()` reads `CODEX_API_KEY`\n- `gemini()` reads `GEMINI_API_KEY`\n- `github()` reads `GITHUB_TOKEN` and maps it through Vercel Sandbox firewall transforms:\n  - `Authorization: Basic \u003cbase64(x-access-token:\u003ctoken\u003e)\u003e` on requests to `github.com`, intended for Git-over-HTTPS operations\n  - `Authorization: Bearer \u003ctoken\u003e` on requests to `api.github.com`\n  - no Authorization header for `*.githubusercontent.com`\n\nFor JavaScript package bootstrap, prefer explicit service presets over `allowAll()`:\n\n```ts\nimport { allowServices, bun, npm } from \"@giselles-ai/sandkit\";\n\nconst policy = allowServices([bun(), npm()]);\n```\n\nFor one-off overrides, pass the secret only on that run:\n\n```ts\nawait workspace.sandbox.runCommand({\n  command: \"node\",\n  args: [\"./script.js\"],\n  policy: allowServices([codex({ apiKey: process.env.RUN_SCOPED_CODEX_API_KEY! })]),\n});\n```\n\nDurable default policy belongs to the workspace. Set it when creating the workspace or update it later:\n\n```ts\nconst workspace = await sandkit.createWorkspace({\n  policy: allowServices([codex()]),\n});\n\nawait workspace.setPolicy(allowServices([codex()]));\n```\n\n## Setup Bootstrap\n\n`setup` is the shared bootstrap definition, not the materialized artifact.\nIt is optional.\nWhen `setup` is provided, it is the shared bootstrap command, args, and required durable `policy`.\nThis produces adapter-scoped shared bootstrap state keyed by `adapter.id` + setup definition fingerprint.\nMultiple Sandkit instances using the same adapter and setup definition can reuse the same shared bootstrap state.\n\n`sandkit.bootstrap()` is an optional eager materialization step:\n\n- it creates shared bootstrap state if missing,\n- it leaves existing shared bootstrap state untouched,\n- it does not open or validate a long-lived sandbox runtime for an existing shared bootstrap state.\n\nWithout `bootstrap()`, shared setup is still materialized lazily on first workspace use (first `runCommand(...)` or `openSession(...)` that needs it).\nStale or unusable shared bootstrap artifacts are detected and rebuilt in those workspace flows, not by `bootstrap()` alone.\n\n`setup` durability is adapter-backed. With a persistent adapter such as Bun SQLite or Drizzle, the shared bootstrap survives process restarts. With the default in-memory adapter, it does not.\n\n```ts\nimport { createSandkit, allowAll } from \"@giselles-ai/sandkit\";\nimport { vercelSandbox } from \"@giselles-ai/sandkit/integrations/vercel\";\n\nconst sandkit = createSandkit({\n  sandbox: vercelSandbox(),\n  setup: {\n    command: \"sh\",\n    args: [\"-lc\", \"npm ci\"],\n    policy: allowAll(),\n  },\n});\n\nawait sandkit.bootstrap();\n// Optional: omit bootstrap() and let setup run lazily on first workspace use.\nconst workspace = await sandkit.createWorkspace({\n  name: \"bootstrapped-workspace\",\n});\n```\n\n## Live Sessions\n\nUse a session only when you need a running process or a public URL:\n\n```ts\nconst workspace = await sandkit.createWorkspace({\n  sandbox: {\n    exposedPorts: [3000],\n  },\n});\n\nconst session = await workspace.sandbox.openSession();\n\nawait session.exec({\n  command: \"sh\",\n  args: [\"-lc\", \"python3 -m http.server 3000\"],\n});\n\nconst url = await session.url(3000);\nawait session.commit();\n```\n\nDeclare `exposedPorts` on the workspace only when you intend to publish a live session URL. Keep `defaultTimeout` as the provider-level lease default, and use `openSession({ timeoutMs })` when a specific live session needs a different timeout.\n\n`runCommand()` and a live session are intentionally separate. If a session is active, attach to it or commit it before running another durable command.\n\n## Configuration\n\nProvide a `sandbox` provider explicitly (for example `vercelSandbox(...)`).\nIf you omit `database`, Sandkit defaults to the in-memory adapter.\n\n## Drizzle Adapter\n\nThe generated schema exports the canonical workspace table as `sandkitWorkspaces`.\n\n```ts\nimport { createSandkit, allowServices, codex } from \"@giselles-ai/sandkit\";\nimport { drizzleAdapter } from \"@giselles-ai/sandkit/adapters/drizzle\";\nimport { vercelSandbox } from \"@giselles-ai/sandkit/integrations/vercel\";\nimport { db, schema } from \"@/db\";\n\nconst sandkit = createSandkit({\n  database: drizzleAdapter(db, {\n    provider: \"sqlite\",\n    workspaces: schema.sandkitWorkspaces,\n  }),\n  sandbox: vercelSandbox(),\n});\n```\n\nGenerate schema:\n\n```sh\nnpx @giselles-ai/sandkit generate --adapter drizzle --provider sqlite\nnpx drizzle-kit generate\n```\n\nIf you already have a Drizzle repo, provider discovery can infer the dialect:\n\n```sh\nnpx @giselles-ai/sandkit generate\n```\n\n## Examples\n\n- [`examples/workflow-hello-git`](examples/workflow-hello-git) shows a minimal Next.js + Workflow DevKit flow where Workflow orchestrates durable `workspace.sandbox.runCommand(...)` steps.\n- [`examples/sandbox-openclaw`](examples/sandbox-openclaw) shows a production-oriented live session flow with OpenClaw on Vercel Sandbox.\n- [`smoke/drizzle-sample`](smoke/drizzle-sample) shows schema generation and Drizzle integration.\n\n## Status\n\nSandkit is still early, but the core paths are already exercised in local smoke coverage:\n\n- workspace create and reload\n- durable `runCommand()` execution\n- workspace policy and per-run override\n- Drizzle schema generation\n- live session lifecycle on Vercel Sandbox\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgiselles-ai%2Fsandkit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgiselles-ai%2Fsandkit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgiselles-ai%2Fsandkit/lists"}