https://github.com/mateffy/sandkasten
Sandkasten wraps multiple sandbox providers like Daytona, Fly.io Sprites or simple SSH boxes into a unified API for provisioning machines, preparing environments and executing code in a secure, isolated environment.
https://github.com/mateffy/sandkasten
Last synced: 1 day ago
JSON representation
Sandkasten wraps multiple sandbox providers like Daytona, Fly.io Sprites or simple SSH boxes into a unified API for provisioning machines, preparing environments and executing code in a secure, isolated environment.
- Host: GitHub
- URL: https://github.com/mateffy/sandkasten
- Owner: mateffy
- Created: 2026-05-25T22:11:46.000Z (about 1 month ago)
- Default Branch: main
- Last Pushed: 2026-05-25T22:12:18.000Z (about 1 month ago)
- Last Synced: 2026-05-26T00:25:04.430Z (about 1 month ago)
- Size: 6.84 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Sandkasten – Advanced sandbox SDK for AI coding agent environments
Sandkasten wraps multiple sandbox providers like Daytona, Fly.io Sprites or simple SSH boxes into a unified API for provisioning machines, preparing environments and executing code in a secure, isolated environment.
> [!WARNING]
> This project is currently in private alpha and not available for public use yet (which is why its currently only re-exporting a private @grips package). The full implementation and source will be available here soon. If you're interested in early access or contributing, please reach out!
## Table of Contents
- [Overview](#overview)
- [Packages](#packages)
- [`@grips/format`](#gripsformat)
- [`@grips/harness`](#gripsharness)
- [`@grips/sdk`](#gripssdk)
- [`@grips/cli`](#gripscli)
- [`@grips/react`](#gripsreact)
- [`website`](#website)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [CLI](#cli)
- [SDK](#sdk)
- [React Components](#react-components)
- [Architecture](#architecture)
- [Development](#development)
- [Contributing](#contributing)
---
## Overview
Modern AI coding agents all store session history differently. GRIPS unifies them under one schema and one API:
- **One schema** for nodes (messages, tool calls, compactions, checkpoints, config changes, and custom events)
- **One tree model** for branching, forking, and navigating session history
- **One streaming format** for real-time output (text, reasoning, tool calls, tool execution stdout/stderr)
- **One SDK** to list, read, create, fork, send messages to, and listen to sessions across every supported harness
- **One CLI** to inspect and manage sessions from the terminal
- **One React library** to render session trees, tables, paths, and streaming UIs
---
## Packages
### Package Dependency Graph
```
cli ──> sdk ──> harness ──> format
react ───> format
```
| Package | Purpose | Tech |
|---------|---------|------|
| `@grips/format` | Pure types and schemas — zero I/O | Effect-TS Schema |
| `@grips/harness` | Per-agent adapters (Pi, OpenCode, Claude Code, Codex, Cursor) | Effect-TS |
| `@grips/sdk` | Unified consumer API via Effect `Layer` and `Service` | Effect-TS |
| `@grips/cli` | Terminal interface and TUI | Effect-TS + citty |
| `@grips/react` | React components and hooks for rendering sessions | React + Tailwind |
| `website` | Marketing and documentation site | Vite + React |
---
### `@grips/format`
> Pure types and schemas. No I/O, no Effect runtime.
Defines the canonical data model used by every other package.
**Core types:**
- `Node` — Discriminated union of all node types:
- `message` — User / assistant / system messages with usage metadata
- `tool_execution` — Tool call with arguments, output, and exit code
- `compaction` — Session compaction / summarization event
- `branch_summary` — Summary of a branched conversation path
- `checkpoint` — Code snapshot checkpoint
- `config_change` — Agent configuration change
- `custom` — Extensible custom event type
- `Session` — Session metadata (id, title, cwd, source harness, timestamps)
- `SessionListItem` — Lightweight session list entry
- `Tree` — Built from an array of nodes; provides `getPath`, `getChildren`, `findLeaves`, `validate`
- `StreamEvent` — Real-time streaming events (`text.start`, `text`, `tool_call.delta`, `tool_execution.stdout`, `done`, `error`, etc.)
**Validation:**
```ts
import { buildTree } from "@grips/format";
const tree = buildTree(nodes);
const result = tree.validate(); // { valid, brokenReferences, orphanedNodes, cycles }
```
---
### `@grips/harness`
> Per-harness adapters that bridge native agent storage and runtime APIs into the GRIPS format.
Each supported agent has two implementations:
- **SessionSDK** — Read/write session storage (`list`, `get`, `create`, `append`, `delete`, `fork`)
- **RuntimeSDK** — Drive a live agent (`send`, `listen`, `stop`)
**Supported harnesses:**
| Harness | SessionSDK | RuntimeSDK |
|---------|------------|------------|
| Pi (`pi`) | `PiSessionSDK` | `PiRuntimeSDK` |
| OpenCode (`opencode`) | `OpenCodeSessionSDK` | `OpenCodeRuntimeSDK` |
| Claude Code (`claude-code`) | `ClaudeCodeSessionSDK` | `ClaudeCodeRuntimeSDK` |
| Codex (`codex`) | `CodexSessionSDK` | `CodexRuntimeSDK` |
| Cursor (`cursor`) | `CursorSessionSDK` | `CursorRuntimeSDK` |
**ACP Harness base class:**
Agents that expose a JSON-RPC stdio interface can subclass `AcpHarness` in `runtime-sdk/acp.ts` to inherit standardized process handling.
**Error types:**
- `SessionSDKError` / `RuntimeSDKError` — Tagged errors from adapter operations
- `AdapterError` — Harness not found or misconfigured
---
### `@grips/sdk`
> Unified consumer API. All harnesses, one interface.
The `GripsClient` is an Effect `Service` that aggregates every registered harness and routes calls to the right adapter automatically.
**Registry services:**
- `SessionSDKRegistry` — Holds `SessionSDK` instances
- `RuntimeSDKRegistry` — Holds `RuntimeSDK` instances
**Client API:**
```ts
import { GripsClient } from "@grips/sdk";
// List sessions across all harnesses
yield* GripsClient.list({ cwd: "/project", harness: "opencode", limit: 10 });
// Get a session and its tree
const { session, tree } = yield* GripsClient.get("opencode:session-123");
// Create a new session
yield* GripsClient.create({ harness: "claude-code", title: "Fix login bug" });
// Send a message and receive a stream of events
const stream = yield* GripsClient.send("pi:session-456", { content: "Refactor auth.ts" });
// Listen to an already-running session
const liveStream = yield* GripsClient.listen("codex:session-789");
// Fork a session from a specific node
yield* GripsClient.fork("cursor:session-abc", "node-xyz", { title: "Exploration branch" });
// Stop a running session
yield* GripsClient.stop("opencode:session-123");
```
Session IDs are namespaced: `{harness}:{nativeId}`. The client parses the harness prefix and routes to the correct adapter.
---
### `@grips/cli`
> Terminal interface and interactive TUI for managing sessions.
**Commands:**
```bash
# Interactive TUI (default when no args provided)
grips
# List sessions
grips sessions list [--cwd DIR] [--harness NAME] [--json]
# Show a session
grips sessions get [--json | --tree | --nodes] [--thinking]
# Create a session
grips sessions create [--harness NAME] [--cwd DIR] [--title TITLE] [--json]
# Delete a session
grips sessions delete
# Fork a session from a node
grips sessions fork [--title TITLE] [--json]
# Send a message to a running session
grips send --content "MESSAGE"
# Listen to a running session
grips listen
# Stop a running session
grips stop
```
**Auto-detection:** The CLI automatically detects installed agents by checking for their executables (`opencode`, `claude`, `codex`, `cursor-agent`) or home-directory markers (`~/.pi`) and wires only the available harnesses into the Effect dependency layer.
---
### `@grips/react`
> React components, hooks, and headless primitives for rendering GRIPS sessions.
**Exports:**
- `/` — Components + hooks + mock data
- `/components` — Styled UI components
- `/headless` — Unstyled, stateful primitives
- `/hooks` — Reusable state and stream logic
**Components:**
| Component | Description |
|-----------|-------------|
| `SessionTree` | Static hierarchical tree of nodes with type legend |
| `InteractiveSessionTree` | Expandable / collapsible interactive tree |
| `SessionTable` | Tabular session list with sorting and filtering |
| `SessionHeader` | Session metadata header (title, harness, timestamps) |
| `SessionPath` | Breadcrumb path from root to selected node |
| `NodeDetail` | Detail view for any node type with type-specific rendering |
| `SessionView` | Composite view combining tree, path, and detail |
**Hooks:**
| Hook | Purpose |
|------|---------|
| `useTree` | Tree state (expand/collapse, select, paths, visibility) |
| `useStreamEvents` | Collect stream events into state |
| `useStreamContent` | Accumulate streaming text / reasoning content |
| `useStreamingNodes` | Merge committed nodes with live draft nodes from a stream |
| `useSelection` | Single / multi selection state |
| `usePath` | Compute ancestor path for a node ID |
**Headless primitives:**
| Primitive | Purpose |
|-----------|---------|
| `Tree` | Render-agnostic tree component (pass your own node renderer) |
| `SessionList` | Render-agnostic list component |
**Peer dependencies:** `react >=19`, `react-dom >=19`, `tailwindcss >=4`
---
### `website`
> Vite + React marketing and documentation site.
Uses TanStack Router and Tailwind CSS. Built with `vite build` and previewed with `vite preview`.
---
## Installation
This repository uses **Bun** workspaces.
```bash
# Install dependencies
bun install
# Type-check all packages
bun run typecheck
# Run tests
bun test
```
### Using packages in your own project
```bash
# Install the SDK
bun add @grips/sdk
# Install React components
bun add @grips/react
```
---
## Quick Start
### CLI
```bash
# Start the interactive TUI
cd /your/project && grips
# List all sessions across all agents
grips sessions list
# View a session transcript
grips sessions get opencode:abc123
# View the branching tree
grips sessions get opencode:abc123 --tree
# Create a session
grips sessions create --harness claude-code --title "Bugfix"
# Send a message
grips send claude-code:xyz789 --content "Fix the race condition in queue.ts"
```
### SDK
```ts
import { Effect } from "effect";
import { GripsClient, SessionSDKRegistry, RuntimeSDKRegistry } from "@grips/sdk";
import { OpenCodeSessionSDK, OpenCodeRuntimeSDK } from "@grips/sdk";
const layer = Layer.mergeAll(
Layer.succeed(SessionSDKRegistry, { sdks: [new OpenCodeSessionSDK()] }),
Layer.succeed(RuntimeSDKRegistry, { sdks: [new OpenCodeRuntimeSDK()] })
).pipe(Layer.provide(GripsClient.Default));
const program = Effect.gen(function* () {
const sessions = yield* GripsClient.list({ limit: 5 });
const { session, tree } = yield* GripsClient.get(sessions[0].id);
const stream = yield* GripsClient.send(session.id, { content: "Hello" });
yield* Stream.runForEach(stream, (event) =>
Effect.sync(() => console.log(event.type))
);
});
Effect.runPromise(Effect.provide(program, layer));
```
### React Components
```tsx
import { SessionTree, SessionView, useStreamingNodes } from "@grips/react";
import { mockNodes } from "@grips/react/data/mock";
// Static tree
// Full interactive view with streaming
function MyApp({ nodes, streamEvents }) {
const { nodes: liveNodes, draftNodeIds } = useStreamingNodes(nodes, streamEvents);
return ;
}
```
---
## Architecture
### The Streaming Draft Node Lifecycle
When driving a running agent through `RuntimeSDK`, the event stream merges live output into the tree:
1. **Stream Intake** — `useStreamingNodes` merges committed nodes with incoming `StreamEvent`s.
2. **Draft Node Initialization** — When an active event starts (e.g., `text.start`), a transient "draft node" is created with `_draft: true`.
3. **Delta Merging** — Incremental events continuously merge into the draft node's payload.
4. **Live Auto-Expansion** — Draft node IDs are returned as `draftNodeIds` and mapped to `autoExpandIds` in the `Tree` component, keeping the active streaming turn visible.
5. **Commitment** — On completion events (`text.done`, `tool_execution.end`, `done`), the draft node closes. If the runtime appends it to storage, it becomes a permanent committed node.
### Node Format
Every node shares a common shape:
```ts
interface Node {
id: string;
parentId: string | null;
timestamp: number;
type: "message" | "tool_execution" | "compaction" | "branch_summary" | "checkpoint" | "config_change" | "custom";
version?: number;
payload: /* type-specific */;
metadata: Record;
}
```
The `parentId` enables a full directed acyclic graph of session history, supporting branching, forking, and non-linear exploration.
---
## Development
### Scripts
| Script | Description |
|--------|-------------|
| `bun run dev` | Start dev servers (Turbo) |
| `bun run typecheck` | TypeScript check all packages |
| `bun test` | Run all tests |
| `bun run fmt` | Format with oxfmt |
| `bun run lint` | Lint with oxlint |
| `bun run grips` | Run the CLI locally |
### Testing Conventions
- **Colocation required** — Test files (`*.test.ts`, `*.test.tsx`) and Storybook stories (`*.stories.tsx`) must live in the same directory as the source file they cover. No `__tests__/` or `stories/` directories.
- Unit tests: `vitest run` in `packages/react/` and `packages/website/`; `bun test` elsewhere.
- Storybook `play()` tests are real tests and must pass.
### Adding a New Harness Adapter
See [`AGENTS.md`](./AGENTS.md) for the full workflow. The short version:
1. Implement `SessionSDK` in `packages/harness/src/session-sdk/my-agent.ts`
2. Implement `RuntimeSDK` in `packages/harness/src/runtime-sdk/my-agent.ts` (or subclass `AcpHarness`)
3. Export both from `packages/harness/src/index.ts`
4. Register in `packages/sdk/src/registry.ts`
5. Add auto-detection in `packages/cli/src/layer.ts`
6. Add colocated tests
### Adding a New Node Type
See [`AGENTS.md`](./AGENTS.md) for the full workflow. The short version:
1. Define the payload schema in `packages/format/src/node.ts`
2. Add streaming events to `packages/format/src/stream.ts` if applicable
3. Add colors, background, and symbol in `packages/react/src/lib/node-rendering.tsx`
4. Create a detail component in `packages/react/src/components/NodeDetail/details/`
5. Wire it into `NodeDetail.tsx`
6. Add tests and stories
---
## Contributing
1. Follow the existing code style (oxfmt, oxlint).
2. Keep tests and stories colocated with source files.
3. Ensure all tests pass before submitting changes.
4. Make minimal changes — prefer small, focused PRs.
---
## License
MIT