diff --git a/playbooks/supplemental/openhands-getting-started/README.md b/playbooks/supplemental/openhands-getting-started/README.md index ee609f36..22851255 100644 --- a/playbooks/supplemental/openhands-getting-started/README.md +++ b/playbooks/supplemental/openhands-getting-started/README.md @@ -51,21 +51,26 @@ at that model, and run your first coding task against a real project folder. | Agent Canvas | The browser UI and backend that runs OpenHands conversations and shows tool calls and file changes. | Launches the stack and hosts your conversation. | | Workspace | The project folder the agent is allowed to read and modify. | The target of the agent's edits and commands. | - + > [!NOTE] > Coding-agent workflows benefit from a larger model and context window. Use at > least 32 GB of system memory, and prefer 64 GB or more for larger GGUF models. +## Setting the Memory Configuration + + + + +## Check for Software Updates + + + + ## Prerequisites - - - - - You need: @@ -78,9 +83,52 @@ You need: - A project folder to work in. This can be any local git repository or code directory you want the agent to work on. - - + + + +```bash +set -euo pipefail +export PATH="$HOME/.npm-global/bin:$HOME/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$PATH" + +lemonade --version +node -v +npm -v + +# uv is a required prerequisite (agent-canvas uses it to build its Python env). +# Install it if missing, exactly as this playbook's prerequisites instruct. +if ! command -v uv >/dev/null 2>&1; then + curl -LsSf https://astral.sh/uv/install.sh | sh +fi +export PATH="$HOME/.local/bin:$PATH" +uv --version + +echo "OK: lemonade, node, npm, and uv are all available" +``` + + + + + +```powershell +$ErrorActionPreference = "Stop" + +lemonade --version +node -v +npm -v + +# uv is a required prerequisite (agent-canvas uses it to build its Python env). +# Install it if missing, exactly as this playbook's prerequisites instruct. +if (-not (Get-Command uv -ErrorAction SilentlyContinue)) { + powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex" + $env:Path = "$env:USERPROFILE\.local\bin;$env:Path" +} +uv --version + +Write-Host "OK: lemonade, node, npm, and uv are all available" +``` + + ## 1. Start Lemonade Server @@ -92,6 +140,10 @@ lemonade config set ctx_size=65536 lemonade run "Qwen3.6-35B-A3B-GGUF" ``` +> **Choose a model that fits your hardware.** `Qwen3.6-35B-A3B-GGUF` (~20 GB) is a strong coding model but needs a large memory pool. If your device has limited memory or GPU VRAM, pick a smaller GGUF model from the Lemonade model library instead and use that model ID throughout this playbook. + +> **Note:** The first `lemonade run` downloads the model if it isn't already present, which can take a while depending on the model size and your connection. + Lemonade exposes an OpenAI-compatible API at: ```text @@ -125,6 +177,125 @@ curl -sS "http://127.0.0.1:13305/api/v1/chat/completions" \ If this returns a `choices` array, Lemonade is ready for Agent Canvas. + + +```bash +set -euo pipefail + +models_json="" +for i in $(seq 1 120); do + models_json="$(curl -s --max-time 2 http://127.0.0.1:13305/api/v1/models || true)" + if [ -n "$models_json" ]; then + break + fi + sleep 1 +done + +if [ -z "$models_json" ]; then + echo "Lemonade server not ready on http://127.0.0.1:13305" + exit 1 +fi +echo "OK: Lemonade server is responding" + +export MODELS_JSON="$models_json" + +python3 - <<'PY' +import json +import os +import sys + +data = json.loads(os.environ["MODELS_JSON"]) +model_id = "${lemonade_model}" + +entry = None +for item in data.get("data", []): + if item.get("id") == model_id: + entry = item + break + +if entry is None: + print(f"Model {model_id} is not present in Lemonade /api/v1/models.") + sys.exit(1) + +if not entry.get("downloaded", False): + print(f"Model {model_id} is present but not downloaded in Lemonade. Please download it before running CI.") + sys.exit(1) + +print(f"OK: {model_id} model is downloaded in Lemonade") +PY + +body='{ + "model": "${lemonade_model}", + "messages": [{"role": "user", "content": "Reply with exactly: OK"}], + "temperature": 0, + "max_tokens": 32 +}' + +out="$(curl -sS --fail-with-body --max-time 300 http://127.0.0.1:13305/api/v1/chat/completions \ + -H "Content-Type: application/json" \ + -d "$body")" + +if [ -z "$out" ]; then + echo "Empty response from Lemonade chat/completions" + exit 1 +fi + +echo "OK: Lemonade chat/completions returned a response" +``` + + + + + +```powershell +$ErrorActionPreference = "Stop" + +$modelsJson = $null +for ($i = 0; $i -lt 120; $i++) { + $modelsJson = curl.exe -s --max-time 2 http://127.0.0.1:13305/api/v1/models + if ($modelsJson) { break } + Start-Sleep -Seconds 1 +} + +if (-not $modelsJson) {throw "Lemonade server not ready on http://127.0.0.1:13305"} +Write-Host "OK: Lemonade server is responding" + +$parsed = $modelsJson | ConvertFrom-Json +$entry = $parsed.data | Where-Object { $_.id -eq "${lemonade_model}" } | Select-Object -First 1 + +if (-not $entry) {throw "Model ${lemonade_model} is not present in Lemonade /api/v1/models."} +if (-not $entry.downloaded) {throw "Model ${lemonade_model} is present but not downloaded in Lemonade. Please download it before running CI."} +Write-Host "OK: ${lemonade_model} model is downloaded in Lemonade" + +$body = @{ + model = "${lemonade_model}" + messages = @( + @{ + role = "user" + content = "Reply with exactly: OK" + } + ) + temperature = 0 + max_tokens = 32 +} | ConvertTo-Json -Depth 5 + +$tmpBody = Join-Path $env:TEMP "openhands-lemonade-chat-body.json" +[System.IO.File]::WriteAllText($tmpBody, $body, [System.Text.UTF8Encoding]::new($false)) + +try { + $out = curl.exe -sS --fail-with-body --max-time 300 http://127.0.0.1:13305/api/v1/chat/completions ` + -H "Content-Type: application/json" ` + --data-binary "@$tmpBody" + if (-not $out) {throw "Empty response from Lemonade chat/completions"} + Write-Host "OK: Lemonade chat/completions returned a response" +} +finally { + Remove-Item $tmpBody -Force -ErrorAction SilentlyContinue +} +``` + + + ## 3. Install and Launch Agent Canvas Install the published Agent Canvas package globally: @@ -133,6 +304,48 @@ Install the published Agent Canvas package globally: npm install -g @openhands/agent-canvas ``` + + +```bash +set -euo pipefail + +# Use a user-owned global npm prefix so the install needs no root (matches the +# Troubleshooting section of this playbook). +mkdir -p "$HOME/.npm-global" +npm config set prefix "$HOME/.npm-global" +export PATH="$HOME/.npm-global/bin:$HOME/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$PATH" + +# Install agent-canvas only if the runner doesn't already have it. +if ! command -v agent-canvas >/dev/null 2>&1; then + npm install -g @openhands/agent-canvas +fi + +# Prefer --version; fall back to --help if this build has no --version flag. +agent-canvas --version || agent-canvas --help + +echo "OK: agent-canvas CLI is on PATH" +``` + + + + + +```powershell +$ErrorActionPreference = "Stop" + +# Install agent-canvas only if the runner doesn't already have it. +if (-not (Get-Command agent-canvas -ErrorAction SilentlyContinue)) { + npm install -g @openhands/agent-canvas +} + +# Prefer --version; fall back to --help if this build has no --version flag. +try { agent-canvas --version } catch { agent-canvas --help } + +Write-Host "OK: agent-canvas CLI is on PATH" +``` + + + Then start the full stack from a terminal: ```bash @@ -140,14 +353,14 @@ agent-canvas ``` By default, Agent Canvas starts on `http://localhost:8000`. Open that URL in -your browser. If port 8000 is already in use, pass `--port` (or `-p`) when you -launch Agent Canvas: +your browser. The port is not special — if 8000 is already in use, pass any +free port with `--port` (or `-p`) when you launch Agent Canvas: ```bash agent-canvas --port 3000 ``` -The same command works in PowerShell on Windows. Then open +Then open `http://localhost:3000` instead. The default local backend should show as healthy on the home screen. @@ -155,6 +368,100 @@ The `agent-canvas` command starts the agent server, the automation backend, and the web frontend together. You only need this one command to run OpenHands locally. + + +```bash +set -euo pipefail +export PATH="$HOME/.npm-global/bin:$HOME/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$PATH" + +log="/tmp/agent-canvas-ci.log" +p="" +cleanup() { + if [ -n "${p:-}" ] && kill -0 "$p" 2>/dev/null; then + kill "$p" 2>/dev/null || true + sleep 2 + kill -9 "$p" 2>/dev/null || true + fi +} +trap cleanup EXIT + +rm -f "$log" + +# First launch builds the agent server's uv-managed Python env, so allow a generous startup window. +agent-canvas >"$log" 2>&1 & +p=$! + +ok=false +for i in $(seq 1 300); do + code="$(curl -s -o /dev/null -w "%{http_code}" --max-time 2 http://127.0.0.1:8000/ || true)" + if [ "$code" = "200" ]; then + ok=true + break + fi + if ! kill -0 "$p" 2>/dev/null; then + echo "agent-canvas process exited before it finished starting" + break + fi + sleep 1 +done + +if [ "$ok" != "true" ]; then + echo "agent-canvas not ready on http://127.0.0.1:8000/" + echo "---- agent-canvas log ----" + cat "$log" || true + exit 1 +fi + +echo "OK: agent-canvas server is responding" +``` + + + + + +```powershell +$ErrorActionPreference = "Stop" + +# Ensure npm-global and uv (installed to ~\.local\bin) are visible to the launched process. +$env:Path = "$env:APPDATA\npm;$env:USERPROFILE\.local\bin;$env:Path" + +$log = Join-Path $env:TEMP "agent-canvas-ci.log" +if (Test-Path $log) { Remove-Item $log -Force } + +# agent-canvas installs as a .cmd shim (npm global), which Start-Process cannot +# launch directly ("%1 is not a valid Win32 application"). Run it through cmd.exe, +# same pattern as the n8n playbook. +$AGENT_CANVAS_CMD = "$env:APPDATA\npm\agent-canvas.cmd" +if (-not (Test-Path $AGENT_CANVAS_CMD)) { throw "agent-canvas.cmd not found at $AGENT_CANVAS_CMD" } + +# First launch builds the agent server's uv-managed Python env, so allow a generous startup window. +$p = Start-Process -FilePath "cmd.exe" -ArgumentList "/c `"$AGENT_CANVAS_CMD`"" -NoNewWindow -PassThru -RedirectStandardOutput $log -RedirectStandardError "$log.err" +try { + $ok = $false + for ($i = 0; $i -lt 300; $i++) { + $code = curl.exe -s -o NUL -w "%{http_code}" --max-time 2 http://127.0.0.1:8000/ + if ($LASTEXITCODE -eq 0 -and $code -eq "200") { $ok = $true; break } + if ($p.HasExited) { Write-Host "agent-canvas process exited before it finished starting"; break } + Start-Sleep -Seconds 1 + } + if (-not $ok) { + Write-Host "agent-canvas not ready on http://127.0.0.1:8000/" + Write-Host "---- agent-canvas log ----" + if (Test-Path $log) { Get-Content $log } + throw "agent-canvas not ready on http://127.0.0.1:8000/" + } + Write-Host "OK: agent-canvas server is responding" +} +finally { + # Kill whatever is listening on 8000, then the wrapper process. + $conn = Get-NetTCPConnection -LocalPort 8000 -State Listen -ErrorAction SilentlyContinue | Select-Object -First 1 + if ($conn) { Stop-Process -Id $conn.OwningProcess -Force -ErrorAction SilentlyContinue } + if ($p -and -not $p.HasExited) { Stop-Process -Id $p.Id -Force -ErrorAction SilentlyContinue } +} +``` + + + ## 4. Configure the Local LLM On first launch, Agent Canvas opens an onboarding flow. In that flow: @@ -277,9 +584,10 @@ the file again—all in the same conversation. - **Lemonade chat requests fail with a connection error:** confirm `curl -fsS "http://127.0.0.1:13305/api/v1/health"` succeeds and that Lemonade is still serving the model with `lemonade status`. -- **The agent errors with a context-length or token-limit message:** restart - Lemonade with a larger `ctx_size` (for example `ctx_size=65536`), and start a - fresh conversation so the agent does not carry an oversized history. +- **The agent errors with a context-length or token-limit message:** start a + fresh conversation so the agent does not carry an oversized history. If it + keeps happening, restart Lemonade with a larger `ctx_size` than the default + 65536 (for example `ctx_size=131072`), memory permitting. - **The agent produces low-quality or incomplete edits:** switch to a larger model in Lemonade, or give the agent a smaller, more concrete task and let it finish before asking for the next change. @@ -305,3 +613,22 @@ the file again—all in the same conversation. - [Agent Canvas setup](https://docs.openhands.dev/openhands/usage/agent-canvas/setup) - [LLM profiles and model configuration](https://docs.openhands.dev/openhands/usage/agent-canvas/llm-profiles) - [Lemonade Server documentation](https://lemonade-server.ai/docs) + + + +```bash +# CI cleanup: unload the model so the GPU pool is free +lemonade unload || true +``` + + + + + +```powershell +# CI cleanup: unload the model so the GPU pool is free +lemonade unload +exit 0 +``` + + diff --git a/playbooks/supplemental/openhands-getting-started/playbook.json b/playbooks/supplemental/openhands-getting-started/playbook.json index 1ebc6789..bc748f71 100644 --- a/playbooks/supplemental/openhands-getting-started/playbook.json +++ b/playbooks/supplemental/openhands-getting-started/playbook.json @@ -5,32 +5,88 @@ "time": 20, "supported_platforms": { "halo_box": [ - "linux", - "windows" + "linux" ], "halo": [ - "linux", - "windows" + "linux" ], "stx": [ - "linux", - "windows" + "linux" ], "krk": [ - "linux", - "windows" + "linux" ], "rx7900xt": [ - "linux", - "windows" + "linux" ], "rx9070xt": [ - "linux", - "windows" + "linux" ], "r9700": [ - "linux", - "windows" + "linux" + ] + }, + "tested_platforms": { + "halo": [ + "linux" + ], + "stx": [ + "linux" + ], + "krk": [ + "linux" + ], + "rx7900xt": [ + "linux" + ], + "rx9070xt": [ + "linux" + ], + "r9700": [ + "linux" + ] + }, + "required_platforms": { + "halo": [ + "linux" + ], + "stx": [ + "linux" + ], + "krk": [ + "linux" + ], + "rx7900xt": [ + "linux" + ], + "rx9070xt": [ + "linux" + ], + "r9700": [ + "linux" + ] + }, + "published_platforms": { + "halo_box": [ + "linux" + ], + "halo": [ + "linux" + ], + "stx": [ + "linux" + ], + "krk": [ + "linux" + ], + "rx7900xt": [ + "linux" + ], + "rx9070xt": [ + "linux" + ], + "r9700": [ + "linux" ] }, "difficulty": "beginner",