Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
476 changes: 476 additions & 0 deletions cookbook/archive-sandbox.mdx

Large diffs are not rendered by default.

172 changes: 172 additions & 0 deletions cookbook/clone-and-attach.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
---
title: Clone and Attach
description: Clone a repository and run its setup script in a sandbox, then attach a conversation to the prepared environment.
icon: code-branch
---

{/* GENERATED from OpenHands/enterprise-cookbook@126c20e4a3becac898c541aac120277a9c2835b3 (clone-and-attach/README.md). Edit the source, not this file. */}

<Card title="View source on GitHub" icon="github" href="https://github.com/OpenHands/enterprise-cookbook/tree/126c20e4a3becac898c541aac120277a9c2835b3/clone-and-attach" horizontal />

This example provisions a sandbox **yourself** — shallow-cloning a git repo and
running its setup script — and only *then* hands it to an OpenHands agent by
**attaching a conversation** to that already-prepared sandbox.

It builds directly on [`start-sandbox`](/cookbook/start-sandbox), which shows the bare
sandbox lifecycle. Read that one first if the sandbox/agent-server split is new
to you.

## How It Works

```mermaid
sequenceDiagram
participant You
participant Cloud as Cloud app server
participant Agent as Sandbox agent server
You->>Cloud: POST /api/v1/sandboxes (1. start a sandbox, no conversation)
You->>Cloud: GET /api/v1/sandboxes?id=#lt;id#gt; (2. poll until status == RUNNING)
You->>Agent: POST /api/bash/execute_bash_command (3. git clone --depth 1 #lt;repo#gt;)
You->>Agent: POST /api/bash/execute_bash_command (4. bash .openhands/setup.sh)
You->>Cloud: POST /api/v1/app-conversations (5. attach a conversation, sandbox_id=#lt;id#gt;)
You->>Cloud: GET /api/v1/app-conversations/start-tasks (5b. poll for the app_conversation_id)
```

Steps 1–2 use the **Cloud app server** (auth header `X-Session-API-Key: <OH_API_KEY>`).
Steps 3–4 use the sandbox's **agent server** (auth header
`X-Session-API-Key: <session_api_key>`, returned by the create call). Step 5 is
back on the Cloud app server. See [`start-sandbox`](/cookbook/start-sandbox) for more
on the two-server split.

### Where does `setup.sh` live?

In the repository, at `.openhands/setup.sh`. That is the exact location
OpenHands itself runs every time it starts working with a repo — see
[Repository Customization](/openhands/usage/customization/repository).
This example runs that same file so the sandbox you hand off is set up the way
the agent would expect. If a repo has no `.openhands/setup.sh`, the step is
skipped with a note. (This repo ships a tiny one so the default run does
something visible.)

### Attaching is asynchronous

`POST /api/v1/app-conversations` returns a **start task**, not the conversation
itself. Poll `GET /api/v1/app-conversations/start-tasks?ids=<task_id>` until it
reports an `app_conversation_id`, then open
`https://app.all-hands.dev/conversations/<app_conversation_id>`.

## Run It

```bash
export OH_API_KEY=... # your https://app.all-hands.dev API key
pip install requests

# Zero-config: clones this repo (it has a .openhands/setup.sh) and attaches
# a conversation that summarizes it.
python attach_conversation.py
```

Sample output:

```text
sandbox: 1ho9eZpt4m27CC23XPdGcN
sandbox status: RUNNING
agent: https://ahhygodzefollslv.prod-runtime.all-hands.dev

=== shallow clone https://github.com/OpenHands/enterprise-cookbook -> /workspace/enterprise-cookbook ===
$ git clone (exit=0)

=== run .openhands/setup.sh ===
$ setup script (exit=0)
[enterprise-cookbook setup.sh] running in /workspace/enterprise-cookbook
[enterprise-cookbook setup.sh] python: Python 3.13.13
[enterprise-cookbook setup.sh] done

=== attach conversation ===
start-task status: STARTING_CONVERSATION
start-task status: READY

Conversation attached to your prepared sandbox:
https://app.all-hands.dev/conversations/f041a2e252cf45b39a46a3189b2efce7
```

Open that URL and you'll find the agent already in a workspace where your repo
is cloned and set up.

## Why Would I Do This?

Normally you start a conversation and OpenHands clones your selected repository
for you. Sometimes you want more control *before* the agent gets involved:

- pre-warm an environment so the agent starts instantly on an expensive setup,
- check out a specific commit, tag, or a sub-path of a monorepo,

Check warning on line 101 in cookbook/clone-and-attach.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

cookbook/clone-and-attach.mdx#L101

Did you really mean 'monorepo'?
- clone from a mirror or run custom bootstrapping the default flow doesn't do,
- reuse one prepared sandbox for several scripted conversations.

The trick is a single field: `POST /api/v1/app-conversations` accepts a
`sandbox_id`. Pass the id of a sandbox you already prepared and the new
conversation attaches to it instead of creating a fresh one.

## Point It at Your Own Repo

Every input is a flag with an environment-variable fallback, so the script is
safe to drop into your own automation unchanged:

| Flag | Env var | Default | Purpose |
| ------------------- | ----------------- | --------------------------- | ----------------------------------------------- |
| `--api-key` | `OH_API_KEY` | — (required) | Cloud API key |
| `--base-url` | `OH_API_BASE` | `https://app.all-hands.dev` | Cloud app server |
| `--repo` | `REPO_URL` | this repo | Git URL to shallow-clone |
| `--branch` | `REPO_BRANCH` | repo default | Branch to check out |
| `--depth` | `CLONE_DEPTH` | `1` | `git clone --depth` |
| `--workdir` | `WORKDIR` | `/workspace` | Where the repo is cloned |
| `--setup-script` | `SETUP_SCRIPT` | `.openhands/setup.sh` | Script to run after clone |
| `--message` | `INITIAL_MESSAGE` | a summarize prompt | First message to the agent |
| `--sandbox-id` | `SANDBOX_ID` | none | Reuse a RUNNING sandbox instead of creating one |
| `--sandbox-spec-id` | `SANDBOX_SPEC_ID` | account default | Runtime image to start |
| `--poll-timeout` | `POLL_TIMEOUT` | `240` | Seconds to wait for readiness |

```bash
python attach_conversation.py \
--repo https://github.com/your-org/your-repo \
--branch main \
--message "Run the test suite and fix any failures."
```

> Cloning a **private** repo? Start the sandbox with the appropriate git
> credentials available (e.g. via sandbox secrets) or clone over an
> authenticated URL. This example targets public repositories to stay simple.

## Cleanup

The sandbox is intentionally left running because a live conversation is now
attached to it — deleting the sandbox ends that conversation. Delete it from the
conversation UI, or via the API (the id goes in **both** the path and a required
`sandbox_id` query parameter):

```bash
SID=<sandbox_id>
curl -X DELETE "https://app.all-hands.dev/api/v1/sandboxes/${SID}?sandbox_id=${SID}" \
-H "X-Session-API-Key: $OH_API_KEY"
```

## APIs Used

| Endpoint | Method | Purpose |
| --------------------------------------- | ------ | -------------------------------------------------------- |
| `/api/v1/sandboxes` | POST | Start a sandbox (no conversation) |
| `/api/v1/sandboxes` | GET | Poll until status == RUNNING |
| `{agent}/api/bash/execute_bash_command` | POST | Run `git clone` and `.openhands/setup.sh` in the sandbox |
| `/api/v1/app-conversations` | POST | Attach a conversation (`sandbox_id`) |
| `/api/v1/app-conversations/start-tasks` | GET | Poll for the `app_conversation_id` |

## Related

<CardGroup cols={2}>
<Card title="start-sandbox" href="/cookbook/start-sandbox" icon="play">
The bare sandbox lifecycle and the sandbox/agent-server split
</Card>

<Card title="Repository Customization" href="/openhands/usage/customization/repository" icon="book-open">
Where .openhands/setup.sh lives
</Card>
</CardGroup>
22 changes: 11 additions & 11 deletions cookbook/command-blacklist.mdx
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
---
title: Command blacklist
title: Command Blacklist
description: Block known-dangerous shell commands with a PreToolUse hook bundled in a plugin. Everything not on the blocklist runs normally.
icon: shield-halved
---

{/* GENERATED from OpenHands/enterprise-cookbook@main (command-blacklist/README.md). Edit the source, not this file. */}
{/* GENERATED from OpenHands/enterprise-cookbook@126c20e4a3becac898c541aac120277a9c2835b3 (command-blacklist/README.md). Edit the source, not this file. */}

<Card title="View source on GitHub" icon="github" href="https://github.com/OpenHands/enterprise-cookbook/tree/main/command-blacklist" horizontal />
<Card title="View source on GitHub" icon="github" href="https://github.com/OpenHands/enterprise-cookbook/tree/126c20e4a3becac898c541aac120277a9c2835b3/command-blacklist" horizontal />

A self-contained example showing how to use **PreToolUse hooks** in a plugin to **blacklist dangerous shell commands**. When the agent tries to execute a risky command, the hook blocks it with helpful (and slightly snarky) feedback.

This example demonstrates the **blacklist approach**: block known dangerous patterns while allowing everything else to proceed normally.

## What's in the Box

The [`safety-guardian/`](https://github.com/OpenHands/enterprise-cookbook/tree/main/command-blacklist/safety-guardian) plugin bundles:
The [`safety-guardian/`](https://github.com/OpenHands/enterprise-cookbook/tree/126c20e4a3becac898c541aac120277a9c2835b3/command-blacklist/safety-guardian) plugin bundles:

- **Hooks** (`hooks/hooks.json`) - PreToolUse hook that intercepts terminal commands
- **Skill** (`skills/safety-guardian/SKILL.md`) - Documentation about what's protected
Expand All @@ -41,10 +41,10 @@
| Pattern | Why It's Dangerous | Example Block Message |
| ------------------ | ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `rm -rf /...` | Recursive deletion of system directories | "Whoa there, friend! Trying to rm -rf a system directory is like playing Russian Roulette with all chambers loaded..." |
| `chmod 777 /...` | Overly permissive file permissions | "chmod 777? Really? That's the security equivalent of leaving your front door open with a 'FREE STUFF' sign..." |

Check warning on line 44 in cookbook/command-blacklist.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

cookbook/command-blacklist.mdx#L44

Did you really mean 'chmod'?
| `dd of=/dev/sd*` | Writing to raw block devices | "Attempting to dd directly to a device? Bold move! But I'm not about to let you accidentally turn your storage into modern art..." |
| `:(){:\|:&};:` | Fork bombs (process explosion) | "Nice try with the fork bomb! I appreciate the creativity, but I'm not going to help you DOS yourself..." |
| `curl ... \| bash` | Piping untrusted scripts to shell | "Piping unknown scripts directly to bash? That's like accepting candy from strangers on the internet..." |

Check warning on line 47 in cookbook/command-blacklist.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

cookbook/command-blacklist.mdx#L47

Did you really mean 'untrusted'?

All other commands work normally - only these specific dangerous patterns are blocked.

Expand All @@ -56,11 +56,11 @@
(So `rm -rf /tmp` is **not** blocked; use the `curl … | bash` demo below to see a block.)
</Note>

## Try It
## Run It

<Tabs>
<Tab title="Load via API">
Use the companion [`load-plugin`](https://github.com/OpenHands/enterprise-cookbook/tree/main/load-plugin) example:
Use the companion [`load-plugin`](https://github.com/OpenHands/enterprise-cookbook/tree/126c20e4a3becac898c541aac120277a9c2835b3/load-plugin) example:

```bash
cd ../load-plugin
Expand Down Expand Up @@ -96,7 +96,7 @@

## The Hook

The magic happens in [`hooks/hooks.json`](https://github.com/OpenHands/enterprise-cookbook/blob/main/command-blacklist/safety-guardian/hooks/hooks.json):
The magic happens in [`hooks/hooks.json`](https://github.com/OpenHands/enterprise-cookbook/blob/126c20e4a3becac898c541aac120277a9c2835b3/command-blacklist/safety-guardian/hooks/hooks.json):

```json safety-guardian/hooks/hooks.json
{
Expand Down Expand Up @@ -144,7 +144,7 @@
`hooks/scripts/*.sh`: when this runs as a **plugin**, hooks execute with the
working directory set to the agent's workspace (not the plugin directory) and
there is no plugin-root path variable, so a relative script path won't resolve.
Inlining a plain POSIX-sh script avoids both traps.

Check warning on line 147 in cookbook/command-blacklist.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

cookbook/command-blacklist.mdx#L147

Did you really mean 'Inlining'?
</Accordion>

## Blacklist vs. Whitelist
Expand All @@ -156,7 +156,7 @@
- ❌ **Con:** Can't catch every dangerous pattern
- ❌ **Con:** Clever variations might slip through

For high-security scenarios, see the companion [`command-whitelist`](https://github.com/OpenHands/enterprise-cookbook/tree/main/command-whitelist) example that shows the **whitelist** approach (only allow explicitly approved commands).
For high-security scenarios, see the companion [`command-whitelist`](https://github.com/OpenHands/enterprise-cookbook/tree/126c20e4a3becac898c541aac120277a9c2835b3/command-whitelist) example that shows the **whitelist** approach (only allow explicitly approved commands).

## Hook Types

Expand Down Expand Up @@ -201,15 +201,15 @@
How plugins work
</Card>

<Card title="load-plugin" href="https://github.com/OpenHands/enterprise-cookbook/tree/main/load-plugin" icon="arrow-up-right-from-square">
<Card title="load-plugin" href="https://github.com/OpenHands/enterprise-cookbook/tree/126c20e4a3becac898c541aac120277a9c2835b3/load-plugin" icon="arrow-up-right-from-square">
Programmatic plugin loading
</Card>

<Card title="launch-plugin-badge" href="https://github.com/OpenHands/enterprise-cookbook/tree/main/launch-plugin-badge" icon="arrow-up-right-from-square">
<Card title="launch-plugin-badge" href="https://github.com/OpenHands/enterprise-cookbook/tree/126c20e4a3becac898c541aac120277a9c2835b3/launch-plugin-badge" icon="arrow-up-right-from-square">
No-code plugin launcher
</Card>

<Card title="command-whitelist" href="https://github.com/OpenHands/enterprise-cookbook/tree/main/command-whitelist" icon="arrow-up-right-from-square">
<Card title="command-whitelist" href="https://github.com/OpenHands/enterprise-cookbook/tree/126c20e4a3becac898c541aac120277a9c2835b3/command-whitelist" icon="arrow-up-right-from-square">
Whitelist approach (opposite strategy)
</Card>
</CardGroup>
Expand All @@ -236,7 +236,7 @@
}
EOF
exit 2
fi

Check warning on line 239 in cookbook/command-blacklist.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

cookbook/command-blacklist.mdx#L239

Did you really mean 'fi'?
```

The inline bash makes it easy to iterate without rebuilding images or restarting servers.
8 changes: 4 additions & 4 deletions cookbook/conversation-tags.mdx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
---
title: Conversation tags
title: Conversation Tags
description: Attach key-value metadata to a conversation with tags and read it back from AppConversation.tags.
icon: tags
---

{/* GENERATED from OpenHands/enterprise-cookbook@main (conversation-tags/README.md). Edit the source, not this file. */}
{/* GENERATED from OpenHands/enterprise-cookbook@126c20e4a3becac898c541aac120277a9c2835b3 (conversation-tags/README.md). Edit the source, not this file. */}

<Card title="View source on GitHub" icon="github" href="https://github.com/OpenHands/enterprise-cookbook/tree/main/conversation-tags" horizontal />
<Card title="View source on GitHub" icon="github" href="https://github.com/OpenHands/enterprise-cookbook/tree/126c20e4a3becac898c541aac120277a9c2835b3/conversation-tags" horizontal />

Stash your own key-value metadata on an OpenHands conversation — for example an
external `environment_url` or `environment_conversation_id` — and read it back
Expand Down Expand Up @@ -49,7 +49,7 @@ and then *polls* the Cloud read instead of reading once.
> the agent server is the authoritative place to write them, and the Cloud
> reflects the result. The agent `POST /api/conversations` also accepts `tags`
> at creation time if you provision the sandbox yourself (see
> [`clone-and-attach`](https://github.com/OpenHands/enterprise-cookbook/tree/main/clone-and-attach)).
> [`clone-and-attach`](/cookbook/clone-and-attach)).

## Tag rules

Expand Down
Loading
Loading