From bb4b97ca029b36e3a5e32c69bddf90bdcefdbad9 Mon Sep 17 00:00:00 2001 From: Caren Thomas Date: Thu, 9 Jul 2026 09:54:48 -0700 Subject: [PATCH 1/4] feat: add dreaming skill and Agent Canvas automation catalog entry --- README.md | 5 +- automations/catalog/dreaming.json | 13 + .../catalog/research-brief-writer.json | 2 +- automations/index.js | 2 + marketplaces/openhands-extensions.json | 26 +- skills/dreaming/.claude-plugin | 1 + skills/dreaming/.codex-plugin | 1 + skills/dreaming/.plugin/plugin.json | 20 ++ skills/dreaming/README.md | 42 +++ skills/dreaming/SKILL.md | 297 ++++++++++++++++++ skills/dreaming/commands/dreaming-setup.md | 8 + skills/dreaming/scripts/run.sh | 19 ++ skills/dreaming/scripts/setup.sh | 42 +++ skills/index.js | 8 + 14 files changed, 477 insertions(+), 9 deletions(-) create mode 100644 automations/catalog/dreaming.json create mode 120000 skills/dreaming/.claude-plugin create mode 120000 skills/dreaming/.codex-plugin create mode 100644 skills/dreaming/.plugin/plugin.json create mode 100644 skills/dreaming/README.md create mode 100644 skills/dreaming/SKILL.md create mode 100644 skills/dreaming/commands/dreaming-setup.md create mode 100644 skills/dreaming/scripts/run.sh create mode 100644 skills/dreaming/scripts/setup.sh diff --git a/README.md b/README.md index 6938eb71..eebbea97 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ The JS and Python versions are kept in lock-step by `release-please` and guarded ## Extensions Catalog -This repository contains **2 marketplace(s)** with **62 extensions** (52 skills, 10 plugins). +This repository contains **2 marketplace(s)** with **63 extensions** (53 skills, 10 plugins). ### large-codebase @@ -106,7 +106,7 @@ OpenHands skills for interacting, improving, and refactoring large codebases Official skills and plugins for OpenHands — the open-source AI software engineer. -**58 extensions** (50 skills, 8 plugins) +**59 extensions** (51 skills, 8 plugins) | Name | Type | Description | Commands | |------|------|-------------|----------| @@ -126,6 +126,7 @@ Official skills and plugins for OpenHands — the open-source AI software engine | deno | skill | Common project operations using Deno (tasks, run/test/lint/fmt, and dependency management). | — | | discord | skill | Build and automate Discord integrations (bots, webhooks, slash commands, and REST API workflows). Use when the user m... | — | | docker | skill | Run Docker commands within a container environment, including starting the Docker daemon and managing containers. Use... | — | +| dreaming | skill | Create an automation that periodically distills your local OpenHands Agent Canvas coding sessions into a target repos... | `/dreaming:setup` | | evidence-based-citations | skill | Back factual claims and field values with official, verifiable sources. Use when the user asks to fill fields, answer... | — | | flarglebargle | skill | A test skill that responds to the magic word 'flarglebargle' with a compliment. Use for testing skill activation and ... | — | | frontend-design | skill | Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks ... | — | diff --git a/automations/catalog/dreaming.json b/automations/catalog/dreaming.json new file mode 100644 index 00000000..9513b18b --- /dev/null +++ b/automations/catalog/dreaming.json @@ -0,0 +1,13 @@ +{ + "id": "dreaming", + "name": "Letta dreaming agent", + "category": "Agent memory", + "description": "Give your coding agents a memory that compounds: Letta reflects over your OpenHands sessions in the background, learning your codebase, conventions, and preferences over time.", + "requiredIntegrationIds": [ + "github" + ], + "popularityRank": 84, + "estimatedSetupMinutes": 5, + "prompt": "/dreaming:setup", + "exampleImplementation": "Trigger: cron, weekly by default (configurable)\nRequired secrets: GITHUB_TOKEN (repo write) and the model provider API key\n\n1. On each cron run, sweep local Agent Canvas conversations newer than the stored cursor.\n2. Feed them to a persistent Letta dreamer agent; letta dream reflects them into AGENTS.md in a temporary shallow checkout of the target repo.\n3. If the doc changed, commit as the token owner with a Letta Code co-author trailer and push the dream branch.\n4. Open or update the pull request with a summary of the doc changes.\n5. Advance the cursor only on full end-to-end success, so failed runs are retried." +} diff --git a/automations/catalog/research-brief-writer.json b/automations/catalog/research-brief-writer.json index 656bed01..905c3d6f 100644 --- a/automations/catalog/research-brief-writer.json +++ b/automations/catalog/research-brief-writer.json @@ -7,7 +7,7 @@ "tavily", "notion" ], - "popularityRank": 84, + "popularityRank": 83, "estimatedSetupMinutes": 7, "prompt": "/research-brief:setup", "exampleImplementation": "Trigger: cron, weekly or daily\nRequired MCPs: Tavily, Notion\n\n1. Run focused Tavily searches for configured topics and competitors.\n2. Deduplicate sources and rank them by freshness and authority.\n3. TODO: confirm citation format and destination Notion database schema.\n4. Write an executive summary, implications, and recommended actions.\n5. Create or update a Notion page with citations and source links." diff --git a/automations/index.js b/automations/index.js index fdb57631..83e3eb9a 100644 --- a/automations/index.js +++ b/automations/index.js @@ -6,6 +6,7 @@ import linear_triage_assistant from "./catalog/linear-triage-assistant.json" wit import research_brief_writer from "./catalog/research-brief-writer.json" with { type: "json" }; import incident_retrospective_drafter from "./catalog/incident-retrospective-drafter.json" with { type: "json" }; import jira_issue_to_pr from "./catalog/jira-issue-to-pr.json" with { type: "json" }; +import dreaming from "./catalog/dreaming.json" with { type: "json" }; export const AUTOMATION_CATALOG = [ github_pr_reviewer, @@ -16,5 +17,6 @@ export const AUTOMATION_CATALOG = [ research_brief_writer, incident_retrospective_drafter, jira_issue_to_pr, + dreaming, ]; export default AUTOMATION_CATALOG; diff --git a/marketplaces/openhands-extensions.json b/marketplaces/openhands-extensions.json index f266842f..44286f73 100644 --- a/marketplaces/openhands-extensions.json +++ b/marketplaces/openhands-extensions.json @@ -5,7 +5,7 @@ "email": "contact@all-hands.dev" }, "metadata": { - "description": "Official skills and plugins for OpenHands \u2014 the open-source AI software engineer.", + "description": "Official skills and plugins for OpenHands — the open-source AI software engineer.", "maintainer": "OpenHands", "homepage": "https://github.com/OpenHands/extensions" }, @@ -13,7 +13,7 @@ { "name": "agent-creator", "source": "./skills/agent-creator", - "description": "Create file-based sub-agents as Markdown files \u2014 no Python code required. Guides the user through a structured interview and generates a ready-to-deploy .md agent file following the OpenHands SDK specification.", + "description": "Create file-based sub-agents as Markdown files — no Python code required. Guides the user through a structured interview and generates a ready-to-deploy .md agent file following the OpenHands SDK specification.", "category": "development", "keywords": [ "agent", @@ -429,7 +429,7 @@ { "name": "openhands", "source": "./plugins/openhands", - "description": "Unified OpenHands plugin \u2014 bundles Cloud CLI, REST API (openhands-api), and Automations (openhands-automation) into a single plugin.", + "description": "Unified OpenHands plugin — bundles Cloud CLI, REST API (openhands-api), and Automations (openhands-automation) into a single plugin.", "category": "openhands", "keywords": [ "openhands", @@ -470,7 +470,7 @@ { "name": "pr-review", "source": "./plugins/pr-review", - "description": "Automated PR code review \u2014 analyzes diffs and posts inline review comments via the GitHub API.", + "description": "Automated PR code review — analyzes diffs and posts inline review comments via the GitHub API.", "category": "code-quality", "keywords": [ "pr-review", @@ -482,7 +482,7 @@ { "name": "qa-changes", "source": "./plugins/qa-changes", - "description": "Validate pull request changes by actually running the code \u2014 setting up the environment, exercising changed behavior, and posting a structured QA report.", + "description": "Validate pull request changes by actually running the code — setting up the environment, exercising changed behavior, and posting a structured QA report.", "category": "quality-assurance", "keywords": [ "qa", @@ -631,7 +631,7 @@ { "name": "iterate", "source": "./skills/iterate", - "description": "Iterate on a GitHub pull request \u2014 drive it through CI, code review, and QA until merge-ready. Monitors state, fixes failures, addresses review feedback, retries flaky checks, and pushes fixes in one continuous loop.", + "description": "Iterate on a GitHub pull request — drive it through CI, code review, and QA until merge-ready. Monitors state, fixes failures, addresses review feedback, retries flaky checks, and pushes fixes in one continuous loop.", "category": "productivity", "keywords": [ "github", @@ -747,6 +747,20 @@ "jira", "atlassian" ] + }, + { + "name": "dreaming", + "source": "./skills/dreaming", + "description": "Create an automation that periodically distills your local OpenHands Agent Canvas coding sessions into a target repository's AGENTS.md via Letta memory reflection and opens or updates a pull request with the result.", + "category": "productivity", + "keywords": [ + "letta", + "memory", + "reflection", + "agents-md", + "dreaming", + "automation" + ] } ] } diff --git a/skills/dreaming/.claude-plugin b/skills/dreaming/.claude-plugin new file mode 120000 index 00000000..665797f0 --- /dev/null +++ b/skills/dreaming/.claude-plugin @@ -0,0 +1 @@ +.plugin \ No newline at end of file diff --git a/skills/dreaming/.codex-plugin b/skills/dreaming/.codex-plugin new file mode 120000 index 00000000..665797f0 --- /dev/null +++ b/skills/dreaming/.codex-plugin @@ -0,0 +1 @@ +.plugin \ No newline at end of file diff --git a/skills/dreaming/.plugin/plugin.json b/skills/dreaming/.plugin/plugin.json new file mode 100644 index 00000000..e43cdea2 --- /dev/null +++ b/skills/dreaming/.plugin/plugin.json @@ -0,0 +1,20 @@ +{ + "name": "dreaming", + "version": "1.0.0", + "description": "Create an automation that periodically distills your local OpenHands Agent Canvas coding sessions into a target repository's AGENTS.md via Letta memory reflection and opens or updates a pull request with the result.", + "author": { + "name": "OpenHands", + "email": "contact@all-hands.dev" + }, + "homepage": "https://github.com/OpenHands/extensions", + "repository": "https://github.com/OpenHands/extensions", + "license": "MIT", + "keywords": [ + "letta", + "memory", + "reflection", + "agents-md", + "dreaming", + "automation" + ] +} diff --git a/skills/dreaming/README.md b/skills/dreaming/README.md new file mode 100644 index 00000000..bdf16ad5 --- /dev/null +++ b/skills/dreaming/README.md @@ -0,0 +1,42 @@ +# Dreaming + +Create an automation that periodically distills your local OpenHands Agent +Canvas coding sessions into a target repository's `AGENTS.md` via Letta +memory reflection, opening or updating a pull request with what it learned. + +## Trigger + +This skill is activated by: + +- `/dreaming:setup` + +## How it works + +- A cron automation sweeps new local Agent Canvas conversations since the + last run. +- A persistent Letta "dreamer" agent reflects on them and updates the doc in + a temporary checkout of the target repo. +- If the doc changed, the automation commits and opens (or updates) a PR. +- The automation tarball is a thin wrapper (two small scripts); all logic + lives in the pinned npm package `@letta-ai/openhands-dreaming`. + +MVP scope: **local Agent Canvas conversations only** (the automation polls on +your laptop). Cloud conversation sources are not supported yet. + +## Prerequisites + +Set these in OpenHands Settings -> Secrets: + +- `GITHUB_TOKEN` with write access to the target repo +- The API key for the chosen model's provider (e.g. `ANTHROPIC_API_KEY`) - + note this is separate from OpenHands' own LLM configuration + +## Quick Start + +Ask OpenHands: + +> "Set up Dreaming for my `myorg/backend` repo using `anthropic/opus-4.8`." + +## See Also + +- [SKILL.md](SKILL.md) - Full setup workflow reference diff --git a/skills/dreaming/SKILL.md b/skills/dreaming/SKILL.md new file mode 100644 index 00000000..616e75d9 --- /dev/null +++ b/skills/dreaming/SKILL.md @@ -0,0 +1,297 @@ +--- +name: dreaming +description: > + Create an automation that periodically distills your local OpenHands Agent + Canvas coding sessions into a target repository's AGENTS.md via Letta memory + reflection and opens or updates a pull request with the result. +triggers: + - /dreaming:setup +--- + +# Dreaming Automation + +Create a cron automation that sweeps your **local OpenHands Agent Canvas** +conversations, feeds the new ones to a persistent Letta "dreamer" agent for +memory reflection, and turns what it learned into a pull request against the +target repository's `AGENTS.md` (or another doc path you choose). + +The extension is a thin wrapper: the automation tarball contains only two +small scripts (`setup.sh` + `run.sh`). All logic lives in the pinned npm +package `@letta-ai/openhands-dreaming` (commands: `run`, `doctor`, +`cursor get|set|reset`), which `setup.sh` installs at run time. + +MVP scope: local Agent Canvas conversations only (the automation polls on +your laptop). Cloud conversation sources are not supported yet. + +--- + +## Prerequisites + +### Required secrets + +Verify these are set in **OpenHands Settings -> Secrets** (the CLI reads +them from the environment or fetches them from the Agent Server secret store +at run time): + +| Secret name | Purpose | Minimum permissions | +|---|---|---| +| `GITHUB_TOKEN` | Push the dream branch and open/update the PR | Write access to the target repo (Contents: Read and Write, Pull requests: Read and Write, Metadata: Read) | +| Provider API key (e.g. `ANTHROPIC_API_KEY`) | The Letta reflection model | Valid key for the chosen model's provider (see Step 2) | + +> **Important:** OpenHands' own LLM configuration is SEPARATE from named +> secrets. Even if the chosen model already works in OpenHands chat, its API +> key must be added as a named secret for the automation to use it. + +--- + +## Setup Workflow + +Follow these steps in order. + +### Step 1 - Collect and validate the target repository + +Ask: *"Which GitHub repository should Dreaming open AGENTS.md PRs against? +(Format: `owner/repo`, e.g. `myorg/backend`)"* + +First confirm `GITHUB_TOKEN` is present: + +```bash +test -n "$GITHUB_TOKEN" && echo "GITHUB_TOKEN set" || echo "MISSING_GITHUB_TOKEN" +``` + +If missing: *"GITHUB_TOKEN is not set. Please add it in OpenHands Settings -> +Secrets."* Stop. + +Then validate access and push permission (single pipeline; prints three lines): + +```bash +curl -s "https://api.github.com/repos/{owner}/{repo}" -H "Authorization: Bearer $GITHUB_TOKEN" -H "Accept: application/vnd.github+json" | python3 -c "import json,sys; d=json.load(sys.stdin); print('message:', d.get('message')); print('private:', d.get('private')); print('push:', (d.get('permissions') or {}).get('push'))" +``` + +- `message:` non-None (e.g. `Not Found`, `Bad credentials`): report it and stop. +- `push:` must print `True`; otherwise tell the user the token cannot push to + this repo (Dreaming needs to push a branch and open a PR) and stop. + +Record `TARGET_REPO = "{owner}/{repo}"`. + +### Step 2 - Collect the model handle and verify the provider secret + +Ask: *"Which model should the Letta dreamer agent use for reflection? +(Format: `provider/model`, e.g. `openai/gpt-5.5` or `anthropic/opus-4.8`)"* + +Record the answer as `MODEL`. For KNOWN provider prefixes, verify the matching +secret exists as an environment variable: + +| Model prefix | Required secret | +|---|---| +| `anthropic/` | `ANTHROPIC_API_KEY` | +| `openai/` | `OPENAI_API_KEY` | +| `google/` | `GOOGLE_GENERATIVE_AI_API_KEY` | +| `groq/` | `GROQ_API_KEY` | +| `deepseek/` | `DEEPSEEK_API_KEY` | +| `openrouter/` | `OPENROUTER_API_KEY` | +| `ollama-cloud/` | `OLLAMA_API_KEY` | +| `ollama/` | `OLLAMA_LOCAL_API_KEY` | + +- Known prefix, secret missing: tell the user which secret to add in + OpenHands Settings -> Secrets, then stop until it is added. +- Unknown prefix: warn that Letta will validate the model handle and its + provider key at run time, and continue (do not block). + +Note: the model is baked into the dreamer agent when it is first created. +Changing `MODEL` later does not re-model an existing dreamer agent. + +### Step 3 - Collect doc path and schedule, compute lookback + +Ask: *"Which doc file should Dreaming maintain in the repo? +(Press Enter for the default: `AGENTS.md`)"* + +Record as `TO_PATH` (default `AGENTS.md`). + +Ask: *"How often should Dreaming run? +(Press Enter for the default: weekly, Mondays at 04:00 -> `0 4 * * 1`. +Use any cron expression for a different cadence, e.g. `0 4 * * *` = daily)"* + +Record as `CRON_SCHEDULE` (default `0 4 * * 1`). + +**Compute `LOOKBACK_MINUTES` - do not ask the user for it.** Derive +`cadence_minutes` (the interval between consecutive runs of `CRON_SCHEDULE`), +then: + +``` +LOOKBACK_MINUTES = min(cadence_minutes + 5, 1440) +``` + +Example: the default weekly `0 4 * * 1` has a 10080-minute cadence, so +`LOOKBACK_MINUTES = 1440` (the cap). + +The lookback only bounds the first sweep (before a cursor exists); after that +the automation resumes from its stored cursor. + +### Step 4 - Build the payload + +Copy this skill's two scripts into a temporary build directory and substitute +the placeholders in `run.sh`: + +```bash +mkdir -p /tmp/dreaming-build +cp {skill_dir}/scripts/setup.sh /tmp/dreaming-build/setup.sh +cp {skill_dir}/scripts/run.sh /tmp/dreaming-build/run.sh +``` + +| Placeholder in `run.sh` | Replace with | +|---|---| +| `__TARGET_REPO__` | `TARGET_REPO` | +| `__MODEL__` | `MODEL` | +| `__TO_PATH__` | `TO_PATH` | +| `__LOOKBACK_MINUTES__` | computed `LOOKBACK_MINUTES` | + +Validate each substituted value against `^[A-Za-z0-9._/-]+$` and re-ask on +mismatch (the placeholders sit inside double quotes in a bash script). + +Do NOT bake secrets into either script: the CLI resolves `GITHUB_TOKEN` and +the provider key from the environment or the Agent Server secret store at +run time, and `AUTOMATION_*` values are provided by the automation runtime. + +Validate both scripts before packaging: + +```bash +bash -n /tmp/dreaming-build/setup.sh && bash -n /tmp/dreaming-build/run.sh && echo "Syntax OK" +``` + +Fix any syntax errors before proceeding. + +### Step 5 - Preflight with `doctor` + +If the current environment can run bash and reach the network, dry-run the +install and the read-only preflight from the build directory: + +```bash +cd /tmp/dreaming-build +bash setup.sh +./openhands-dreaming doctor --repo "{owner}/{repo}" --model "{model}" +``` + +`doctor` mutates nothing; it checks the token, repo write access, the +provider key for the model, the conversations directory, and the Letta +binary. If it fails, show the doctor output to the user and stop until the +underlying issue is fixed. If the environment cannot run the preflight, +tell the user it was skipped and why. + +### Step 6 - Package and upload + +Determine the Automation backend URL and auth from the `` +block in your system context: +- **OPENHANDS_HOST**: the Automation backend `url_from_agent` +- **Auth**: `X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY` + +```bash +tar -czf /tmp/dreaming.tar.gz -C /tmp/dreaming-build setup.sh run.sh + +TARBALL_PATH=$(curl -s -X POST \ + "${OPENHANDS_HOST}/api/automation/v1/uploads?name=dreaming" \ + -H "X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY" \ + -H "Content-Type: application/gzip" \ + --data-binary @/tmp/dreaming.tar.gz \ + | python3 -c "import json,sys; print(json.load(sys.stdin)['tarball_path'])") + +echo "Uploaded: $TARBALL_PATH" +``` + +Note: the tarball intentionally contains only `setup.sh` and `run.sh` (the +Step 5 preflight leaves extra files such as the wrapper in the build dir, +which must not be packaged). + +### Step 7 - Register the automation + +The entrypoint must contain no shell metacharacters (hence `bash run.sh`). +There is no arbitrary `env` field: configuration is baked into `run.sh`. + +```bash +curl -s -X POST "${OPENHANDS_HOST}/api/automation/v1" \ + -H "X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY" \ + -H "Content-Type: application/json" \ + -d "{ + \"name\": \"Dreaming: {owner}/{repo}\", + \"trigger\": {\"type\": \"cron\", \"schedule\": \"{cron_schedule}\"}, + \"tarball_path\": \"$TARBALL_PATH\", + \"entrypoint\": \"bash run.sh\", + \"timeout\": 1800 + }" | python3 -m json.tool +``` + +Record the returned `id` as `AUTOMATION_ID`. + +### Step 8 - Dispatch a first run and surface the PR + +```bash +curl -s -X POST "${OPENHANDS_HOST}/api/automation/v1/${AUTOMATION_ID}/dispatch" \ + -H "X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY" | python3 -m json.tool +``` + +Watch the run until it completes: + +```bash +curl -s "${OPENHANDS_HOST}/api/automation/v1/${AUTOMATION_ID}/runs" \ + -H "X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY" | python3 -m json.tool +``` + +The CLI emits structured `--json` output; surface the PR link to the user if +one was opened or updated. If no recent conversations were found, tell the +user that is normal on a quiet first run. + +On the first run the CLI auto-creates the dreamer Letta agent (tagged +`openhands-dreaming` and `repo:{owner}/{repo}`); it is reused on every +subsequent run so its memory accumulates. + +### Step 9 - Confirm + +Tell the user: + +> **Dreaming** is set up! +> +> - Automation ID: `{automation_id}` +> - Target repo: `{owner}/{repo}` +> - Model: `{model}` +> - Schedule: `{cron_schedule}` +> - Doc path: `{to_path}` +> - State: sweep cursor lives in the OpenHands KV store under +> `dreaming-cursor:{automation_id}:{owner__repo}`; the dreamer agent's +> memory lives in its Letta memory filesystem and accumulates across runs. +> +> Each run sweeps new local Agent Canvas conversations, reflects on them, and +> opens or updates a PR when `{to_path}` changes. Nothing happens on runs +> with no new conversations or no doc changes. + +--- + +## Runtime behaviour (per run) + +Each cron run executes `bash run.sh`, which execs +`openhands-dreaming run --repo ... --model ... --to ... --lookback ... --json`: + +1. Reads the sweep cursor from the OpenHands KV store and acquires a lock. +2. Discovers local Agent Canvas conversations newer than the cursor (first + run bounded by the computed lookback). +3. Finds or creates the dreamer Letta agent by tags. +4. Shallow-clones the target repo and runs `letta dream` to reflect the new + conversations into the doc. +5. If the doc changed: commits as the token owner (with a + `Co-authored-by: Letta Code ` trailer), pushes the + dream branch, and opens or updates the PR. +6. Advances the cursor only on full end-to-end success. + +--- + +## Troubleshooting + +Run these from the automation working directory (or anywhere the CLI is +installed) with the same env the automation uses: + +| Symptom | Try | +|---|---| +| Run fails at preflight or setup | `openhands-dreaming doctor --repo {owner}/{repo} --model {model}` - read-only report of token, repo access, provider key, dirs, and Letta binary | +| Want to see what the sweep will pick up next | `openhands-dreaming cursor get` | +| Need to replay conversations after a bad run | `openhands-dreaming cursor reset` (safe: Letta dedupes re-fed conversations) | +| Model provider errors at run time | Confirm the provider API key is a named secret (OpenHands chat LLM config is separate) | +| PR never appears | Check the run logs for the `--json` result; no doc change means no PR by design | diff --git a/skills/dreaming/commands/dreaming-setup.md b/skills/dreaming/commands/dreaming-setup.md new file mode 100644 index 00000000..f3fbd47d --- /dev/null +++ b/skills/dreaming/commands/dreaming-setup.md @@ -0,0 +1,8 @@ +--- +# auto-generated by sync_extensions.py +description: Create an automation that periodically distills your local OpenHands Agent Canvas coding sessions into a target repository's AGENTS.md via Letta memory reflection and opens or updates a pull request with the result. +--- + +Read and follow the complete instructions in the SKILL.md file located in this skill's directory. + +$ARGUMENTS diff --git a/skills/dreaming/scripts/run.sh b/skills/dreaming/scripts/run.sh new file mode 100644 index 00000000..cb3c3419 --- /dev/null +++ b/skills/dreaming/scripts/run.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +# Entrypoint TEMPLATE for the Dreaming automation. +# +# The /dreaming:setup flow substitutes the __PLACEHOLDER__ values below before +# packaging the tarball. This file is a thin launcher only: all logic lives in +# the pinned @letta-ai/openhands-dreaming npm package, installed by setup.sh, +# which also writes the ./openhands-dreaming wrapper this script execs. +# +# Secrets (GITHUB_TOKEN + the model provider API key) are never baked into +# this file: the CLI resolves them from the environment or fetches them from +# the Agent Server secret store at run time. +set -euo pipefail + +exec ./openhands-dreaming run \ + --repo "__TARGET_REPO__" \ + --model "__MODEL__" \ + --to "__TO_PATH__" \ + --lookback "__LOOKBACK_MINUTES__" \ + --json diff --git a/skills/dreaming/scripts/setup.sh b/skills/dreaming/scripts/setup.sh new file mode 100644 index 00000000..1f15a36a --- /dev/null +++ b/skills/dreaming/scripts/setup.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +# Install-time script for the Dreaming automation tarball. +# +# Installs bun (if missing) and the @letta-ai/openhands-dreaming CLI, then +# writes an absolute-path wrapper `./openhands-dreaming` into the automation +# working directory. The wrapper is required because PATH exports made here +# do NOT survive into the entrypoint (`bash run.sh`). +# +# Env knob (optional): +# OPENHANDS_DREAMING_VERSION npm version to pin (default: latest) +set -euo pipefail + +if ! command -v bun >/dev/null 2>&1 && [ ! -x "$HOME/.bun/bin/bun" ]; then + # Tradeoff acknowledged: the automation sandbox has no preinstalled bun, so + # we use the official installer, pinned to a known-good release. + curl -fsSL https://bun.sh/install | bash -s "bun-v1.3.4" +fi +export PATH="$HOME/.bun/bin:$PATH" + +DREAMING_VERSION="${OPENHANDS_DREAMING_VERSION:-latest}" + +# Absolute paths baked into the wrapper below (bun may be preinstalled +# somewhere other than ~/.bun, e.g. via a system package). +BUN_BIN="$(command -v bun || echo "$HOME/.bun/bin/bun")" +BUN_GLOBAL_BIN="${BUN_INSTALL:-$HOME/.bun}/bin" + +bun add --global "@letta-ai/openhands-dreaming@${DREAMING_VERSION}" + +# Wrapper in the extracted automation directory so run.sh can invoke +# `./openhands-dreaming` by relative path regardless of PATH. All paths are +# baked in as absolute at install time. +cat > openhands-dreaming < /tmp/docker.log 2>&1 &\n\n# Wait for Docker to initialize\nsleep 5\n```\n\nOn Windows, start Docker Desktop or the Docker service instead of running `sudo dockerd`; then run Docker commands from PowerShell without `sudo`.\n\n## Verifying Docker Installation\n\nTo verify Docker is working correctly, run the hello-world container:\n\n```bash\nsudo docker run hello-world\n```\n\nPowerShell equivalent after Docker Desktop is running: `docker run hello-world`." }, + { + "name": "dreaming", + "description": "Create an automation that periodically distills your local OpenHands Agent Canvas coding sessions into a target repository's AGENTS.md via Letta memory reflection and opens or updates a pull request with the result.", + "triggers": [ + "/dreaming:setup" + ], + "content": "# Dreaming Automation\n\nCreate a cron automation that sweeps your **local OpenHands Agent Canvas**\nconversations, feeds the new ones to a persistent Letta \"dreamer\" agent for\nmemory reflection, and turns what it learned into a pull request against the\ntarget repository's `AGENTS.md` (or another doc path you choose).\n\nThe extension is a thin wrapper: the automation tarball contains only two\nsmall scripts (`setup.sh` + `run.sh`). All logic lives in the pinned npm\npackage `@letta-ai/openhands-dreaming` (commands: `run`, `doctor`,\n`cursor get|set|reset`), which `setup.sh` installs at run time.\n\nMVP scope: local Agent Canvas conversations only (the automation polls on\nyour laptop). Cloud conversation sources are not supported yet.\n\n---\n\n## Prerequisites\n\n### Required secrets\n\nVerify these are set in **OpenHands Settings -> Secrets** (the CLI reads\nthem from the environment or fetches them from the Agent Server secret store\nat run time):\n\n| Secret name | Purpose | Minimum permissions |\n|---|---|---|\n| `GITHUB_TOKEN` | Push the dream branch and open/update the PR | Write access to the target repo (Contents: Read and Write, Pull requests: Read and Write, Metadata: Read) |\n| Provider API key (e.g. `ANTHROPIC_API_KEY`) | The Letta reflection model | Valid key for the chosen model's provider (see Step 2) |\n\n> **Important:** OpenHands' own LLM configuration is SEPARATE from named\n> secrets. Even if the chosen model already works in OpenHands chat, its API\n> key must be added as a named secret for the automation to use it.\n\n---\n\n## Setup Workflow\n\nFollow these steps in order.\n\n### Step 1 - Collect and validate the target repository\n\nAsk: *\"Which GitHub repository should Dreaming open AGENTS.md PRs against?\n(Format: `owner/repo`, e.g. `myorg/backend`)\"*\n\nFirst confirm `GITHUB_TOKEN` is present:\n\n```bash\ntest -n \"$GITHUB_TOKEN\" && echo \"GITHUB_TOKEN set\" || echo \"MISSING_GITHUB_TOKEN\"\n```\n\nIf missing: *\"GITHUB_TOKEN is not set. Please add it in OpenHands Settings ->\nSecrets.\"* Stop.\n\nThen validate access and push permission (single pipeline; prints three lines):\n\n```bash\ncurl -s \"https://api.github.com/repos/{owner}/{repo}\" -H \"Authorization: Bearer $GITHUB_TOKEN\" -H \"Accept: application/vnd.github+json\" | python3 -c \"import json,sys; d=json.load(sys.stdin); print('message:', d.get('message')); print('private:', d.get('private')); print('push:', (d.get('permissions') or {}).get('push'))\"\n```\n\n- `message:` non-None (e.g. `Not Found`, `Bad credentials`): report it and stop.\n- `push:` must print `True`; otherwise tell the user the token cannot push to\n this repo (Dreaming needs to push a branch and open a PR) and stop.\n\nRecord `TARGET_REPO = \"{owner}/{repo}\"`.\n\n### Step 2 - Collect the model handle and verify the provider secret\n\nAsk: *\"Which model should the Letta dreamer agent use for reflection?\n(Format: `provider/model`, e.g. `openai/gpt-5.5` or `anthropic/opus-4.8`)\"*\n\nRecord the answer as `MODEL`. For KNOWN provider prefixes, verify the matching\nsecret exists as an environment variable:\n\n| Model prefix | Required secret |\n|---|---|\n| `anthropic/` | `ANTHROPIC_API_KEY` |\n| `openai/` | `OPENAI_API_KEY` |\n| `google/` | `GOOGLE_GENERATIVE_AI_API_KEY` |\n| `groq/` | `GROQ_API_KEY` |\n| `deepseek/` | `DEEPSEEK_API_KEY` |\n| `openrouter/` | `OPENROUTER_API_KEY` |\n| `ollama-cloud/` | `OLLAMA_API_KEY` |\n| `ollama/` | `OLLAMA_LOCAL_API_KEY` |\n\n- Known prefix, secret missing: tell the user which secret to add in\n OpenHands Settings -> Secrets, then stop until it is added.\n- Unknown prefix: warn that Letta will validate the model handle and its\n provider key at run time, and continue (do not block).\n\nNote: the model is baked into the dreamer agent when it is first created.\nChanging `MODEL` later does not re-model an existing dreamer agent.\n\n### Step 3 - Collect doc path and schedule, compute lookback\n\nAsk: *\"Which doc file should Dreaming maintain in the repo?\n(Press Enter for the default: `AGENTS.md`)\"*\n\nRecord as `TO_PATH` (default `AGENTS.md`).\n\nAsk: *\"How often should Dreaming run?\n(Press Enter for the default: weekly, Mondays at 04:00 -> `0 4 * * 1`.\nUse any cron expression for a different cadence, e.g. `0 4 * * *` = daily)\"*\n\nRecord as `CRON_SCHEDULE` (default `0 4 * * 1`).\n\n**Compute `LOOKBACK_MINUTES` - do not ask the user for it.** Derive\n`cadence_minutes` (the interval between consecutive runs of `CRON_SCHEDULE`),\nthen:\n\n```\nLOOKBACK_MINUTES = min(cadence_minutes + 5, 1440)\n```\n\nExample: the default weekly `0 4 * * 1` has a 10080-minute cadence, so\n`LOOKBACK_MINUTES = 1440` (the cap).\n\nThe lookback only bounds the first sweep (before a cursor exists); after that\nthe automation resumes from its stored cursor.\n\n### Step 4 - Build the payload\n\nCopy this skill's two scripts into a temporary build directory and substitute\nthe placeholders in `run.sh`:\n\n```bash\nmkdir -p /tmp/dreaming-build\ncp {skill_dir}/scripts/setup.sh /tmp/dreaming-build/setup.sh\ncp {skill_dir}/scripts/run.sh /tmp/dreaming-build/run.sh\n```\n\n| Placeholder in `run.sh` | Replace with |\n|---|---|\n| `__TARGET_REPO__` | `TARGET_REPO` |\n| `__MODEL__` | `MODEL` |\n| `__TO_PATH__` | `TO_PATH` |\n| `__LOOKBACK_MINUTES__` | computed `LOOKBACK_MINUTES` |\n\nValidate each substituted value against `^[A-Za-z0-9._/-]+$` and re-ask on\nmismatch (the placeholders sit inside double quotes in a bash script).\n\nDo NOT bake secrets into either script: the CLI resolves `GITHUB_TOKEN` and\nthe provider key from the environment or the Agent Server secret store at\nrun time, and `AUTOMATION_*` values are provided by the automation runtime.\n\nValidate both scripts before packaging:\n\n```bash\nbash -n /tmp/dreaming-build/setup.sh && bash -n /tmp/dreaming-build/run.sh && echo \"Syntax OK\"\n```\n\nFix any syntax errors before proceeding.\n\n### Step 5 - Preflight with `doctor`\n\nIf the current environment can run bash and reach the network, dry-run the\ninstall and the read-only preflight from the build directory:\n\n```bash\ncd /tmp/dreaming-build\nbash setup.sh\n./openhands-dreaming doctor --repo \"{owner}/{repo}\" --model \"{model}\"\n```\n\n`doctor` mutates nothing; it checks the token, repo write access, the\nprovider key for the model, the conversations directory, and the Letta\nbinary. If it fails, show the doctor output to the user and stop until the\nunderlying issue is fixed. If the environment cannot run the preflight,\ntell the user it was skipped and why.\n\n### Step 6 - Package and upload\n\nDetermine the Automation backend URL and auth from the ``\nblock in your system context:\n- **OPENHANDS_HOST**: the Automation backend `url_from_agent`\n- **Auth**: `X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY`\n\n```bash\ntar -czf /tmp/dreaming.tar.gz -C /tmp/dreaming-build setup.sh run.sh\n\nTARBALL_PATH=$(curl -s -X POST \\\n \"${OPENHANDS_HOST}/api/automation/v1/uploads?name=dreaming\" \\\n -H \"X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY\" \\\n -H \"Content-Type: application/gzip\" \\\n --data-binary @/tmp/dreaming.tar.gz \\\n | python3 -c \"import json,sys; print(json.load(sys.stdin)['tarball_path'])\")\n\necho \"Uploaded: $TARBALL_PATH\"\n```\n\nNote: the tarball intentionally contains only `setup.sh` and `run.sh` (the\nStep 5 preflight leaves extra files such as the wrapper in the build dir,\nwhich must not be packaged).\n\n### Step 7 - Register the automation\n\nThe entrypoint must contain no shell metacharacters (hence `bash run.sh`).\nThere is no arbitrary `env` field: configuration is baked into `run.sh`.\n\n```bash\ncurl -s -X POST \"${OPENHANDS_HOST}/api/automation/v1\" \\\n -H \"X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d \"{\n \\\"name\\\": \\\"Dreaming: {owner}/{repo}\\\",\n \\\"trigger\\\": {\\\"type\\\": \\\"cron\\\", \\\"schedule\\\": \\\"{cron_schedule}\\\"},\n \\\"tarball_path\\\": \\\"$TARBALL_PATH\\\",\n \\\"entrypoint\\\": \\\"bash run.sh\\\",\n \\\"timeout\\\": 1800\n }\" | python3 -m json.tool\n```\n\nRecord the returned `id` as `AUTOMATION_ID`.\n\n### Step 8 - Dispatch a first run and surface the PR\n\n```bash\ncurl -s -X POST \"${OPENHANDS_HOST}/api/automation/v1/${AUTOMATION_ID}/dispatch\" \\\n -H \"X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY\" | python3 -m json.tool\n```\n\nWatch the run until it completes:\n\n```bash\ncurl -s \"${OPENHANDS_HOST}/api/automation/v1/${AUTOMATION_ID}/runs\" \\\n -H \"X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY\" | python3 -m json.tool\n```\n\nThe CLI emits structured `--json` output; surface the PR link to the user if\none was opened or updated. If no recent conversations were found, tell the\nuser that is normal on a quiet first run.\n\nOn the first run the CLI auto-creates the dreamer Letta agent (tagged\n`openhands-dreaming` and `repo:{owner}/{repo}`); it is reused on every\nsubsequent run so its memory accumulates.\n\n### Step 9 - Confirm\n\nTell the user:\n\n> **Dreaming** is set up!\n>\n> - Automation ID: `{automation_id}`\n> - Target repo: `{owner}/{repo}`\n> - Model: `{model}`\n> - Schedule: `{cron_schedule}`\n> - Doc path: `{to_path}`\n> - State: sweep cursor lives in the OpenHands KV store under\n> `dreaming-cursor:{automation_id}:{owner__repo}`; the dreamer agent's\n> memory lives in its Letta memory filesystem and accumulates across runs.\n>\n> Each run sweeps new local Agent Canvas conversations, reflects on them, and\n> opens or updates a PR when `{to_path}` changes. Nothing happens on runs\n> with no new conversations or no doc changes.\n\n---\n\n## Runtime behaviour (per run)\n\nEach cron run executes `bash run.sh`, which execs\n`openhands-dreaming run --repo ... --model ... --to ... --lookback ... --json`:\n\n1. Reads the sweep cursor from the OpenHands KV store and acquires a lock.\n2. Discovers local Agent Canvas conversations newer than the cursor (first\n run bounded by the computed lookback).\n3. Finds or creates the dreamer Letta agent by tags.\n4. Shallow-clones the target repo and runs `letta dream` to reflect the new\n conversations into the doc.\n5. If the doc changed: commits as the token owner (with a\n `Co-authored-by: Letta Code ` trailer), pushes the\n dream branch, and opens or updates the PR.\n6. Advances the cursor only on full end-to-end success.\n\n---\n\n## Troubleshooting\n\nRun these from the automation working directory (or anywhere the CLI is\ninstalled) with the same env the automation uses:\n\n| Symptom | Try |\n|---|---|\n| Run fails at preflight or setup | `openhands-dreaming doctor --repo {owner}/{repo} --model {model}` - read-only report of token, repo access, provider key, dirs, and Letta binary |\n| Want to see what the sweep will pick up next | `openhands-dreaming cursor get` |\n| Need to replay conversations after a bad run | `openhands-dreaming cursor reset` (safe: Letta dedupes re-fed conversations) |\n| Model provider errors at run time | Confirm the provider API key is a named secret (OpenHands chat LLM config is separate) |\n| PR never appears | Check the run logs for the `--json` result; no doc change means no PR by design |" + }, { "name": "evidence-based-citations", "description": "Back factual claims and field values with official, verifiable sources. Use when the user asks to fill fields, answer questions, or make claims that must be supported by an exact quote and an official link.", From 3fc27ef0d4d7b4796f33d03699b0c8991ece11b3 Mon Sep 17 00:00:00 2001 From: Caren Thomas Date: Thu, 9 Jul 2026 11:07:16 -0700 Subject: [PATCH 2/4] docs: note the one-command-per-invocation shell constraint --- skills/dreaming/SKILL.md | 5 +++++ skills/index.js | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/skills/dreaming/SKILL.md b/skills/dreaming/SKILL.md index 616e75d9..32f2ca6e 100644 --- a/skills/dreaming/SKILL.md +++ b/skills/dreaming/SKILL.md @@ -48,6 +48,11 @@ at run time): Follow these steps in order. +Note: the automation shell executes one command per invocation. Combine +snippets with `&&` or `;` when needed, but never combine a heredoc +(`python3 - <<'PY'`) with another command - run heredocs alone or rewrite +them as a `python3 -c` one-liner. + ### Step 1 - Collect and validate the target repository Ask: *"Which GitHub repository should Dreaming open AGENTS.md PRs against? diff --git a/skills/index.js b/skills/index.js index 5ab2ec3a..b11d591f 100644 --- a/skills/index.js +++ b/skills/index.js @@ -151,7 +151,7 @@ export const SKILLS_CATALOG = [ "triggers": [ "/dreaming:setup" ], - "content": "# Dreaming Automation\n\nCreate a cron automation that sweeps your **local OpenHands Agent Canvas**\nconversations, feeds the new ones to a persistent Letta \"dreamer\" agent for\nmemory reflection, and turns what it learned into a pull request against the\ntarget repository's `AGENTS.md` (or another doc path you choose).\n\nThe extension is a thin wrapper: the automation tarball contains only two\nsmall scripts (`setup.sh` + `run.sh`). All logic lives in the pinned npm\npackage `@letta-ai/openhands-dreaming` (commands: `run`, `doctor`,\n`cursor get|set|reset`), which `setup.sh` installs at run time.\n\nMVP scope: local Agent Canvas conversations only (the automation polls on\nyour laptop). Cloud conversation sources are not supported yet.\n\n---\n\n## Prerequisites\n\n### Required secrets\n\nVerify these are set in **OpenHands Settings -> Secrets** (the CLI reads\nthem from the environment or fetches them from the Agent Server secret store\nat run time):\n\n| Secret name | Purpose | Minimum permissions |\n|---|---|---|\n| `GITHUB_TOKEN` | Push the dream branch and open/update the PR | Write access to the target repo (Contents: Read and Write, Pull requests: Read and Write, Metadata: Read) |\n| Provider API key (e.g. `ANTHROPIC_API_KEY`) | The Letta reflection model | Valid key for the chosen model's provider (see Step 2) |\n\n> **Important:** OpenHands' own LLM configuration is SEPARATE from named\n> secrets. Even if the chosen model already works in OpenHands chat, its API\n> key must be added as a named secret for the automation to use it.\n\n---\n\n## Setup Workflow\n\nFollow these steps in order.\n\n### Step 1 - Collect and validate the target repository\n\nAsk: *\"Which GitHub repository should Dreaming open AGENTS.md PRs against?\n(Format: `owner/repo`, e.g. `myorg/backend`)\"*\n\nFirst confirm `GITHUB_TOKEN` is present:\n\n```bash\ntest -n \"$GITHUB_TOKEN\" && echo \"GITHUB_TOKEN set\" || echo \"MISSING_GITHUB_TOKEN\"\n```\n\nIf missing: *\"GITHUB_TOKEN is not set. Please add it in OpenHands Settings ->\nSecrets.\"* Stop.\n\nThen validate access and push permission (single pipeline; prints three lines):\n\n```bash\ncurl -s \"https://api.github.com/repos/{owner}/{repo}\" -H \"Authorization: Bearer $GITHUB_TOKEN\" -H \"Accept: application/vnd.github+json\" | python3 -c \"import json,sys; d=json.load(sys.stdin); print('message:', d.get('message')); print('private:', d.get('private')); print('push:', (d.get('permissions') or {}).get('push'))\"\n```\n\n- `message:` non-None (e.g. `Not Found`, `Bad credentials`): report it and stop.\n- `push:` must print `True`; otherwise tell the user the token cannot push to\n this repo (Dreaming needs to push a branch and open a PR) and stop.\n\nRecord `TARGET_REPO = \"{owner}/{repo}\"`.\n\n### Step 2 - Collect the model handle and verify the provider secret\n\nAsk: *\"Which model should the Letta dreamer agent use for reflection?\n(Format: `provider/model`, e.g. `openai/gpt-5.5` or `anthropic/opus-4.8`)\"*\n\nRecord the answer as `MODEL`. For KNOWN provider prefixes, verify the matching\nsecret exists as an environment variable:\n\n| Model prefix | Required secret |\n|---|---|\n| `anthropic/` | `ANTHROPIC_API_KEY` |\n| `openai/` | `OPENAI_API_KEY` |\n| `google/` | `GOOGLE_GENERATIVE_AI_API_KEY` |\n| `groq/` | `GROQ_API_KEY` |\n| `deepseek/` | `DEEPSEEK_API_KEY` |\n| `openrouter/` | `OPENROUTER_API_KEY` |\n| `ollama-cloud/` | `OLLAMA_API_KEY` |\n| `ollama/` | `OLLAMA_LOCAL_API_KEY` |\n\n- Known prefix, secret missing: tell the user which secret to add in\n OpenHands Settings -> Secrets, then stop until it is added.\n- Unknown prefix: warn that Letta will validate the model handle and its\n provider key at run time, and continue (do not block).\n\nNote: the model is baked into the dreamer agent when it is first created.\nChanging `MODEL` later does not re-model an existing dreamer agent.\n\n### Step 3 - Collect doc path and schedule, compute lookback\n\nAsk: *\"Which doc file should Dreaming maintain in the repo?\n(Press Enter for the default: `AGENTS.md`)\"*\n\nRecord as `TO_PATH` (default `AGENTS.md`).\n\nAsk: *\"How often should Dreaming run?\n(Press Enter for the default: weekly, Mondays at 04:00 -> `0 4 * * 1`.\nUse any cron expression for a different cadence, e.g. `0 4 * * *` = daily)\"*\n\nRecord as `CRON_SCHEDULE` (default `0 4 * * 1`).\n\n**Compute `LOOKBACK_MINUTES` - do not ask the user for it.** Derive\n`cadence_minutes` (the interval between consecutive runs of `CRON_SCHEDULE`),\nthen:\n\n```\nLOOKBACK_MINUTES = min(cadence_minutes + 5, 1440)\n```\n\nExample: the default weekly `0 4 * * 1` has a 10080-minute cadence, so\n`LOOKBACK_MINUTES = 1440` (the cap).\n\nThe lookback only bounds the first sweep (before a cursor exists); after that\nthe automation resumes from its stored cursor.\n\n### Step 4 - Build the payload\n\nCopy this skill's two scripts into a temporary build directory and substitute\nthe placeholders in `run.sh`:\n\n```bash\nmkdir -p /tmp/dreaming-build\ncp {skill_dir}/scripts/setup.sh /tmp/dreaming-build/setup.sh\ncp {skill_dir}/scripts/run.sh /tmp/dreaming-build/run.sh\n```\n\n| Placeholder in `run.sh` | Replace with |\n|---|---|\n| `__TARGET_REPO__` | `TARGET_REPO` |\n| `__MODEL__` | `MODEL` |\n| `__TO_PATH__` | `TO_PATH` |\n| `__LOOKBACK_MINUTES__` | computed `LOOKBACK_MINUTES` |\n\nValidate each substituted value against `^[A-Za-z0-9._/-]+$` and re-ask on\nmismatch (the placeholders sit inside double quotes in a bash script).\n\nDo NOT bake secrets into either script: the CLI resolves `GITHUB_TOKEN` and\nthe provider key from the environment or the Agent Server secret store at\nrun time, and `AUTOMATION_*` values are provided by the automation runtime.\n\nValidate both scripts before packaging:\n\n```bash\nbash -n /tmp/dreaming-build/setup.sh && bash -n /tmp/dreaming-build/run.sh && echo \"Syntax OK\"\n```\n\nFix any syntax errors before proceeding.\n\n### Step 5 - Preflight with `doctor`\n\nIf the current environment can run bash and reach the network, dry-run the\ninstall and the read-only preflight from the build directory:\n\n```bash\ncd /tmp/dreaming-build\nbash setup.sh\n./openhands-dreaming doctor --repo \"{owner}/{repo}\" --model \"{model}\"\n```\n\n`doctor` mutates nothing; it checks the token, repo write access, the\nprovider key for the model, the conversations directory, and the Letta\nbinary. If it fails, show the doctor output to the user and stop until the\nunderlying issue is fixed. If the environment cannot run the preflight,\ntell the user it was skipped and why.\n\n### Step 6 - Package and upload\n\nDetermine the Automation backend URL and auth from the ``\nblock in your system context:\n- **OPENHANDS_HOST**: the Automation backend `url_from_agent`\n- **Auth**: `X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY`\n\n```bash\ntar -czf /tmp/dreaming.tar.gz -C /tmp/dreaming-build setup.sh run.sh\n\nTARBALL_PATH=$(curl -s -X POST \\\n \"${OPENHANDS_HOST}/api/automation/v1/uploads?name=dreaming\" \\\n -H \"X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY\" \\\n -H \"Content-Type: application/gzip\" \\\n --data-binary @/tmp/dreaming.tar.gz \\\n | python3 -c \"import json,sys; print(json.load(sys.stdin)['tarball_path'])\")\n\necho \"Uploaded: $TARBALL_PATH\"\n```\n\nNote: the tarball intentionally contains only `setup.sh` and `run.sh` (the\nStep 5 preflight leaves extra files such as the wrapper in the build dir,\nwhich must not be packaged).\n\n### Step 7 - Register the automation\n\nThe entrypoint must contain no shell metacharacters (hence `bash run.sh`).\nThere is no arbitrary `env` field: configuration is baked into `run.sh`.\n\n```bash\ncurl -s -X POST \"${OPENHANDS_HOST}/api/automation/v1\" \\\n -H \"X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d \"{\n \\\"name\\\": \\\"Dreaming: {owner}/{repo}\\\",\n \\\"trigger\\\": {\\\"type\\\": \\\"cron\\\", \\\"schedule\\\": \\\"{cron_schedule}\\\"},\n \\\"tarball_path\\\": \\\"$TARBALL_PATH\\\",\n \\\"entrypoint\\\": \\\"bash run.sh\\\",\n \\\"timeout\\\": 1800\n }\" | python3 -m json.tool\n```\n\nRecord the returned `id` as `AUTOMATION_ID`.\n\n### Step 8 - Dispatch a first run and surface the PR\n\n```bash\ncurl -s -X POST \"${OPENHANDS_HOST}/api/automation/v1/${AUTOMATION_ID}/dispatch\" \\\n -H \"X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY\" | python3 -m json.tool\n```\n\nWatch the run until it completes:\n\n```bash\ncurl -s \"${OPENHANDS_HOST}/api/automation/v1/${AUTOMATION_ID}/runs\" \\\n -H \"X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY\" | python3 -m json.tool\n```\n\nThe CLI emits structured `--json` output; surface the PR link to the user if\none was opened or updated. If no recent conversations were found, tell the\nuser that is normal on a quiet first run.\n\nOn the first run the CLI auto-creates the dreamer Letta agent (tagged\n`openhands-dreaming` and `repo:{owner}/{repo}`); it is reused on every\nsubsequent run so its memory accumulates.\n\n### Step 9 - Confirm\n\nTell the user:\n\n> **Dreaming** is set up!\n>\n> - Automation ID: `{automation_id}`\n> - Target repo: `{owner}/{repo}`\n> - Model: `{model}`\n> - Schedule: `{cron_schedule}`\n> - Doc path: `{to_path}`\n> - State: sweep cursor lives in the OpenHands KV store under\n> `dreaming-cursor:{automation_id}:{owner__repo}`; the dreamer agent's\n> memory lives in its Letta memory filesystem and accumulates across runs.\n>\n> Each run sweeps new local Agent Canvas conversations, reflects on them, and\n> opens or updates a PR when `{to_path}` changes. Nothing happens on runs\n> with no new conversations or no doc changes.\n\n---\n\n## Runtime behaviour (per run)\n\nEach cron run executes `bash run.sh`, which execs\n`openhands-dreaming run --repo ... --model ... --to ... --lookback ... --json`:\n\n1. Reads the sweep cursor from the OpenHands KV store and acquires a lock.\n2. Discovers local Agent Canvas conversations newer than the cursor (first\n run bounded by the computed lookback).\n3. Finds or creates the dreamer Letta agent by tags.\n4. Shallow-clones the target repo and runs `letta dream` to reflect the new\n conversations into the doc.\n5. If the doc changed: commits as the token owner (with a\n `Co-authored-by: Letta Code ` trailer), pushes the\n dream branch, and opens or updates the PR.\n6. Advances the cursor only on full end-to-end success.\n\n---\n\n## Troubleshooting\n\nRun these from the automation working directory (or anywhere the CLI is\ninstalled) with the same env the automation uses:\n\n| Symptom | Try |\n|---|---|\n| Run fails at preflight or setup | `openhands-dreaming doctor --repo {owner}/{repo} --model {model}` - read-only report of token, repo access, provider key, dirs, and Letta binary |\n| Want to see what the sweep will pick up next | `openhands-dreaming cursor get` |\n| Need to replay conversations after a bad run | `openhands-dreaming cursor reset` (safe: Letta dedupes re-fed conversations) |\n| Model provider errors at run time | Confirm the provider API key is a named secret (OpenHands chat LLM config is separate) |\n| PR never appears | Check the run logs for the `--json` result; no doc change means no PR by design |" + "content": "# Dreaming Automation\n\nCreate a cron automation that sweeps your **local OpenHands Agent Canvas**\nconversations, feeds the new ones to a persistent Letta \"dreamer\" agent for\nmemory reflection, and turns what it learned into a pull request against the\ntarget repository's `AGENTS.md` (or another doc path you choose).\n\nThe extension is a thin wrapper: the automation tarball contains only two\nsmall scripts (`setup.sh` + `run.sh`). All logic lives in the pinned npm\npackage `@letta-ai/openhands-dreaming` (commands: `run`, `doctor`,\n`cursor get|set|reset`), which `setup.sh` installs at run time.\n\nMVP scope: local Agent Canvas conversations only (the automation polls on\nyour laptop). Cloud conversation sources are not supported yet.\n\n---\n\n## Prerequisites\n\n### Required secrets\n\nVerify these are set in **OpenHands Settings -> Secrets** (the CLI reads\nthem from the environment or fetches them from the Agent Server secret store\nat run time):\n\n| Secret name | Purpose | Minimum permissions |\n|---|---|---|\n| `GITHUB_TOKEN` | Push the dream branch and open/update the PR | Write access to the target repo (Contents: Read and Write, Pull requests: Read and Write, Metadata: Read) |\n| Provider API key (e.g. `ANTHROPIC_API_KEY`) | The Letta reflection model | Valid key for the chosen model's provider (see Step 2) |\n\n> **Important:** OpenHands' own LLM configuration is SEPARATE from named\n> secrets. Even if the chosen model already works in OpenHands chat, its API\n> key must be added as a named secret for the automation to use it.\n\n---\n\n## Setup Workflow\n\nFollow these steps in order.\n\nNote: the automation shell executes one command per invocation. Combine\nsnippets with `&&` or `;` when needed, but never combine a heredoc\n(`python3 - <<'PY'`) with another command - run heredocs alone or rewrite\nthem as a `python3 -c` one-liner.\n\n### Step 1 - Collect and validate the target repository\n\nAsk: *\"Which GitHub repository should Dreaming open AGENTS.md PRs against?\n(Format: `owner/repo`, e.g. `myorg/backend`)\"*\n\nFirst confirm `GITHUB_TOKEN` is present:\n\n```bash\ntest -n \"$GITHUB_TOKEN\" && echo \"GITHUB_TOKEN set\" || echo \"MISSING_GITHUB_TOKEN\"\n```\n\nIf missing: *\"GITHUB_TOKEN is not set. Please add it in OpenHands Settings ->\nSecrets.\"* Stop.\n\nThen validate access and push permission (single pipeline; prints three lines):\n\n```bash\ncurl -s \"https://api.github.com/repos/{owner}/{repo}\" -H \"Authorization: Bearer $GITHUB_TOKEN\" -H \"Accept: application/vnd.github+json\" | python3 -c \"import json,sys; d=json.load(sys.stdin); print('message:', d.get('message')); print('private:', d.get('private')); print('push:', (d.get('permissions') or {}).get('push'))\"\n```\n\n- `message:` non-None (e.g. `Not Found`, `Bad credentials`): report it and stop.\n- `push:` must print `True`; otherwise tell the user the token cannot push to\n this repo (Dreaming needs to push a branch and open a PR) and stop.\n\nRecord `TARGET_REPO = \"{owner}/{repo}\"`.\n\n### Step 2 - Collect the model handle and verify the provider secret\n\nAsk: *\"Which model should the Letta dreamer agent use for reflection?\n(Format: `provider/model`, e.g. `openai/gpt-5.5` or `anthropic/opus-4.8`)\"*\n\nRecord the answer as `MODEL`. For KNOWN provider prefixes, verify the matching\nsecret exists as an environment variable:\n\n| Model prefix | Required secret |\n|---|---|\n| `anthropic/` | `ANTHROPIC_API_KEY` |\n| `openai/` | `OPENAI_API_KEY` |\n| `google/` | `GOOGLE_GENERATIVE_AI_API_KEY` |\n| `groq/` | `GROQ_API_KEY` |\n| `deepseek/` | `DEEPSEEK_API_KEY` |\n| `openrouter/` | `OPENROUTER_API_KEY` |\n| `ollama-cloud/` | `OLLAMA_API_KEY` |\n| `ollama/` | `OLLAMA_LOCAL_API_KEY` |\n\n- Known prefix, secret missing: tell the user which secret to add in\n OpenHands Settings -> Secrets, then stop until it is added.\n- Unknown prefix: warn that Letta will validate the model handle and its\n provider key at run time, and continue (do not block).\n\nNote: the model is baked into the dreamer agent when it is first created.\nChanging `MODEL` later does not re-model an existing dreamer agent.\n\n### Step 3 - Collect doc path and schedule, compute lookback\n\nAsk: *\"Which doc file should Dreaming maintain in the repo?\n(Press Enter for the default: `AGENTS.md`)\"*\n\nRecord as `TO_PATH` (default `AGENTS.md`).\n\nAsk: *\"How often should Dreaming run?\n(Press Enter for the default: weekly, Mondays at 04:00 -> `0 4 * * 1`.\nUse any cron expression for a different cadence, e.g. `0 4 * * *` = daily)\"*\n\nRecord as `CRON_SCHEDULE` (default `0 4 * * 1`).\n\n**Compute `LOOKBACK_MINUTES` - do not ask the user for it.** Derive\n`cadence_minutes` (the interval between consecutive runs of `CRON_SCHEDULE`),\nthen:\n\n```\nLOOKBACK_MINUTES = min(cadence_minutes + 5, 1440)\n```\n\nExample: the default weekly `0 4 * * 1` has a 10080-minute cadence, so\n`LOOKBACK_MINUTES = 1440` (the cap).\n\nThe lookback only bounds the first sweep (before a cursor exists); after that\nthe automation resumes from its stored cursor.\n\n### Step 4 - Build the payload\n\nCopy this skill's two scripts into a temporary build directory and substitute\nthe placeholders in `run.sh`:\n\n```bash\nmkdir -p /tmp/dreaming-build\ncp {skill_dir}/scripts/setup.sh /tmp/dreaming-build/setup.sh\ncp {skill_dir}/scripts/run.sh /tmp/dreaming-build/run.sh\n```\n\n| Placeholder in `run.sh` | Replace with |\n|---|---|\n| `__TARGET_REPO__` | `TARGET_REPO` |\n| `__MODEL__` | `MODEL` |\n| `__TO_PATH__` | `TO_PATH` |\n| `__LOOKBACK_MINUTES__` | computed `LOOKBACK_MINUTES` |\n\nValidate each substituted value against `^[A-Za-z0-9._/-]+$` and re-ask on\nmismatch (the placeholders sit inside double quotes in a bash script).\n\nDo NOT bake secrets into either script: the CLI resolves `GITHUB_TOKEN` and\nthe provider key from the environment or the Agent Server secret store at\nrun time, and `AUTOMATION_*` values are provided by the automation runtime.\n\nValidate both scripts before packaging:\n\n```bash\nbash -n /tmp/dreaming-build/setup.sh && bash -n /tmp/dreaming-build/run.sh && echo \"Syntax OK\"\n```\n\nFix any syntax errors before proceeding.\n\n### Step 5 - Preflight with `doctor`\n\nIf the current environment can run bash and reach the network, dry-run the\ninstall and the read-only preflight from the build directory:\n\n```bash\ncd /tmp/dreaming-build\nbash setup.sh\n./openhands-dreaming doctor --repo \"{owner}/{repo}\" --model \"{model}\"\n```\n\n`doctor` mutates nothing; it checks the token, repo write access, the\nprovider key for the model, the conversations directory, and the Letta\nbinary. If it fails, show the doctor output to the user and stop until the\nunderlying issue is fixed. If the environment cannot run the preflight,\ntell the user it was skipped and why.\n\n### Step 6 - Package and upload\n\nDetermine the Automation backend URL and auth from the ``\nblock in your system context:\n- **OPENHANDS_HOST**: the Automation backend `url_from_agent`\n- **Auth**: `X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY`\n\n```bash\ntar -czf /tmp/dreaming.tar.gz -C /tmp/dreaming-build setup.sh run.sh\n\nTARBALL_PATH=$(curl -s -X POST \\\n \"${OPENHANDS_HOST}/api/automation/v1/uploads?name=dreaming\" \\\n -H \"X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY\" \\\n -H \"Content-Type: application/gzip\" \\\n --data-binary @/tmp/dreaming.tar.gz \\\n | python3 -c \"import json,sys; print(json.load(sys.stdin)['tarball_path'])\")\n\necho \"Uploaded: $TARBALL_PATH\"\n```\n\nNote: the tarball intentionally contains only `setup.sh` and `run.sh` (the\nStep 5 preflight leaves extra files such as the wrapper in the build dir,\nwhich must not be packaged).\n\n### Step 7 - Register the automation\n\nThe entrypoint must contain no shell metacharacters (hence `bash run.sh`).\nThere is no arbitrary `env` field: configuration is baked into `run.sh`.\n\n```bash\ncurl -s -X POST \"${OPENHANDS_HOST}/api/automation/v1\" \\\n -H \"X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d \"{\n \\\"name\\\": \\\"Dreaming: {owner}/{repo}\\\",\n \\\"trigger\\\": {\\\"type\\\": \\\"cron\\\", \\\"schedule\\\": \\\"{cron_schedule}\\\"},\n \\\"tarball_path\\\": \\\"$TARBALL_PATH\\\",\n \\\"entrypoint\\\": \\\"bash run.sh\\\",\n \\\"timeout\\\": 1800\n }\" | python3 -m json.tool\n```\n\nRecord the returned `id` as `AUTOMATION_ID`.\n\n### Step 8 - Dispatch a first run and surface the PR\n\n```bash\ncurl -s -X POST \"${OPENHANDS_HOST}/api/automation/v1/${AUTOMATION_ID}/dispatch\" \\\n -H \"X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY\" | python3 -m json.tool\n```\n\nWatch the run until it completes:\n\n```bash\ncurl -s \"${OPENHANDS_HOST}/api/automation/v1/${AUTOMATION_ID}/runs\" \\\n -H \"X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY\" | python3 -m json.tool\n```\n\nThe CLI emits structured `--json` output; surface the PR link to the user if\none was opened or updated. If no recent conversations were found, tell the\nuser that is normal on a quiet first run.\n\nOn the first run the CLI auto-creates the dreamer Letta agent (tagged\n`openhands-dreaming` and `repo:{owner}/{repo}`); it is reused on every\nsubsequent run so its memory accumulates.\n\n### Step 9 - Confirm\n\nTell the user:\n\n> **Dreaming** is set up!\n>\n> - Automation ID: `{automation_id}`\n> - Target repo: `{owner}/{repo}`\n> - Model: `{model}`\n> - Schedule: `{cron_schedule}`\n> - Doc path: `{to_path}`\n> - State: sweep cursor lives in the OpenHands KV store under\n> `dreaming-cursor:{automation_id}:{owner__repo}`; the dreamer agent's\n> memory lives in its Letta memory filesystem and accumulates across runs.\n>\n> Each run sweeps new local Agent Canvas conversations, reflects on them, and\n> opens or updates a PR when `{to_path}` changes. Nothing happens on runs\n> with no new conversations or no doc changes.\n\n---\n\n## Runtime behaviour (per run)\n\nEach cron run executes `bash run.sh`, which execs\n`openhands-dreaming run --repo ... --model ... --to ... --lookback ... --json`:\n\n1. Reads the sweep cursor from the OpenHands KV store and acquires a lock.\n2. Discovers local Agent Canvas conversations newer than the cursor (first\n run bounded by the computed lookback).\n3. Finds or creates the dreamer Letta agent by tags.\n4. Shallow-clones the target repo and runs `letta dream` to reflect the new\n conversations into the doc.\n5. If the doc changed: commits as the token owner (with a\n `Co-authored-by: Letta Code ` trailer), pushes the\n dream branch, and opens or updates the PR.\n6. Advances the cursor only on full end-to-end success.\n\n---\n\n## Troubleshooting\n\nRun these from the automation working directory (or anywhere the CLI is\ninstalled) with the same env the automation uses:\n\n| Symptom | Try |\n|---|---|\n| Run fails at preflight or setup | `openhands-dreaming doctor --repo {owner}/{repo} --model {model}` - read-only report of token, repo access, provider key, dirs, and Letta binary |\n| Want to see what the sweep will pick up next | `openhands-dreaming cursor get` |\n| Need to replay conversations after a bad run | `openhands-dreaming cursor reset` (safe: Letta dedupes re-fed conversations) |\n| Model provider errors at run time | Confirm the provider API key is a named secret (OpenHands chat LLM config is separate) |\n| PR never appears | Check the run logs for the `--json` result; no doc change means no PR by design |" }, { "name": "evidence-based-citations", From 02cf93db8ed03f69810c643aaa33cede59d8a65f Mon Sep 17 00:00:00 2001 From: Caren Thomas Date: Thu, 9 Jul 2026 11:10:02 -0700 Subject: [PATCH 3/4] docs: bound skill API calls with curl --max-time --- skills/dreaming/SKILL.md | 10 +++++----- skills/index.js | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/skills/dreaming/SKILL.md b/skills/dreaming/SKILL.md index 32f2ca6e..53b7cbbd 100644 --- a/skills/dreaming/SKILL.md +++ b/skills/dreaming/SKILL.md @@ -70,7 +70,7 @@ Secrets."* Stop. Then validate access and push permission (single pipeline; prints three lines): ```bash -curl -s "https://api.github.com/repos/{owner}/{repo}" -H "Authorization: Bearer $GITHUB_TOKEN" -H "Accept: application/vnd.github+json" | python3 -c "import json,sys; d=json.load(sys.stdin); print('message:', d.get('message')); print('private:', d.get('private')); print('push:', (d.get('permissions') or {}).get('push'))" +curl -sm 60 "https://api.github.com/repos/{owner}/{repo}" -H "Authorization: Bearer $GITHUB_TOKEN" -H "Accept: application/vnd.github+json" | python3 -c "import json,sys; d=json.load(sys.stdin); print('message:', d.get('message')); print('private:', d.get('private')); print('push:', (d.get('permissions') or {}).get('push'))" ``` - `message:` non-None (e.g. `Not Found`, `Bad credentials`): report it and stop. @@ -193,7 +193,7 @@ block in your system context: ```bash tar -czf /tmp/dreaming.tar.gz -C /tmp/dreaming-build setup.sh run.sh -TARBALL_PATH=$(curl -s -X POST \ +TARBALL_PATH=$(curl -sm 60 -X POST \ "${OPENHANDS_HOST}/api/automation/v1/uploads?name=dreaming" \ -H "X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY" \ -H "Content-Type: application/gzip" \ @@ -213,7 +213,7 @@ The entrypoint must contain no shell metacharacters (hence `bash run.sh`). There is no arbitrary `env` field: configuration is baked into `run.sh`. ```bash -curl -s -X POST "${OPENHANDS_HOST}/api/automation/v1" \ +curl -sm 60 -X POST "${OPENHANDS_HOST}/api/automation/v1" \ -H "X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY" \ -H "Content-Type: application/json" \ -d "{ @@ -230,14 +230,14 @@ Record the returned `id` as `AUTOMATION_ID`. ### Step 8 - Dispatch a first run and surface the PR ```bash -curl -s -X POST "${OPENHANDS_HOST}/api/automation/v1/${AUTOMATION_ID}/dispatch" \ +curl -sm 60 -X POST "${OPENHANDS_HOST}/api/automation/v1/${AUTOMATION_ID}/dispatch" \ -H "X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY" | python3 -m json.tool ``` Watch the run until it completes: ```bash -curl -s "${OPENHANDS_HOST}/api/automation/v1/${AUTOMATION_ID}/runs" \ +curl -sm 60 "${OPENHANDS_HOST}/api/automation/v1/${AUTOMATION_ID}/runs" \ -H "X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY" | python3 -m json.tool ``` diff --git a/skills/index.js b/skills/index.js index b11d591f..fea7658c 100644 --- a/skills/index.js +++ b/skills/index.js @@ -151,7 +151,7 @@ export const SKILLS_CATALOG = [ "triggers": [ "/dreaming:setup" ], - "content": "# Dreaming Automation\n\nCreate a cron automation that sweeps your **local OpenHands Agent Canvas**\nconversations, feeds the new ones to a persistent Letta \"dreamer\" agent for\nmemory reflection, and turns what it learned into a pull request against the\ntarget repository's `AGENTS.md` (or another doc path you choose).\n\nThe extension is a thin wrapper: the automation tarball contains only two\nsmall scripts (`setup.sh` + `run.sh`). All logic lives in the pinned npm\npackage `@letta-ai/openhands-dreaming` (commands: `run`, `doctor`,\n`cursor get|set|reset`), which `setup.sh` installs at run time.\n\nMVP scope: local Agent Canvas conversations only (the automation polls on\nyour laptop). Cloud conversation sources are not supported yet.\n\n---\n\n## Prerequisites\n\n### Required secrets\n\nVerify these are set in **OpenHands Settings -> Secrets** (the CLI reads\nthem from the environment or fetches them from the Agent Server secret store\nat run time):\n\n| Secret name | Purpose | Minimum permissions |\n|---|---|---|\n| `GITHUB_TOKEN` | Push the dream branch and open/update the PR | Write access to the target repo (Contents: Read and Write, Pull requests: Read and Write, Metadata: Read) |\n| Provider API key (e.g. `ANTHROPIC_API_KEY`) | The Letta reflection model | Valid key for the chosen model's provider (see Step 2) |\n\n> **Important:** OpenHands' own LLM configuration is SEPARATE from named\n> secrets. Even if the chosen model already works in OpenHands chat, its API\n> key must be added as a named secret for the automation to use it.\n\n---\n\n## Setup Workflow\n\nFollow these steps in order.\n\nNote: the automation shell executes one command per invocation. Combine\nsnippets with `&&` or `;` when needed, but never combine a heredoc\n(`python3 - <<'PY'`) with another command - run heredocs alone or rewrite\nthem as a `python3 -c` one-liner.\n\n### Step 1 - Collect and validate the target repository\n\nAsk: *\"Which GitHub repository should Dreaming open AGENTS.md PRs against?\n(Format: `owner/repo`, e.g. `myorg/backend`)\"*\n\nFirst confirm `GITHUB_TOKEN` is present:\n\n```bash\ntest -n \"$GITHUB_TOKEN\" && echo \"GITHUB_TOKEN set\" || echo \"MISSING_GITHUB_TOKEN\"\n```\n\nIf missing: *\"GITHUB_TOKEN is not set. Please add it in OpenHands Settings ->\nSecrets.\"* Stop.\n\nThen validate access and push permission (single pipeline; prints three lines):\n\n```bash\ncurl -s \"https://api.github.com/repos/{owner}/{repo}\" -H \"Authorization: Bearer $GITHUB_TOKEN\" -H \"Accept: application/vnd.github+json\" | python3 -c \"import json,sys; d=json.load(sys.stdin); print('message:', d.get('message')); print('private:', d.get('private')); print('push:', (d.get('permissions') or {}).get('push'))\"\n```\n\n- `message:` non-None (e.g. `Not Found`, `Bad credentials`): report it and stop.\n- `push:` must print `True`; otherwise tell the user the token cannot push to\n this repo (Dreaming needs to push a branch and open a PR) and stop.\n\nRecord `TARGET_REPO = \"{owner}/{repo}\"`.\n\n### Step 2 - Collect the model handle and verify the provider secret\n\nAsk: *\"Which model should the Letta dreamer agent use for reflection?\n(Format: `provider/model`, e.g. `openai/gpt-5.5` or `anthropic/opus-4.8`)\"*\n\nRecord the answer as `MODEL`. For KNOWN provider prefixes, verify the matching\nsecret exists as an environment variable:\n\n| Model prefix | Required secret |\n|---|---|\n| `anthropic/` | `ANTHROPIC_API_KEY` |\n| `openai/` | `OPENAI_API_KEY` |\n| `google/` | `GOOGLE_GENERATIVE_AI_API_KEY` |\n| `groq/` | `GROQ_API_KEY` |\n| `deepseek/` | `DEEPSEEK_API_KEY` |\n| `openrouter/` | `OPENROUTER_API_KEY` |\n| `ollama-cloud/` | `OLLAMA_API_KEY` |\n| `ollama/` | `OLLAMA_LOCAL_API_KEY` |\n\n- Known prefix, secret missing: tell the user which secret to add in\n OpenHands Settings -> Secrets, then stop until it is added.\n- Unknown prefix: warn that Letta will validate the model handle and its\n provider key at run time, and continue (do not block).\n\nNote: the model is baked into the dreamer agent when it is first created.\nChanging `MODEL` later does not re-model an existing dreamer agent.\n\n### Step 3 - Collect doc path and schedule, compute lookback\n\nAsk: *\"Which doc file should Dreaming maintain in the repo?\n(Press Enter for the default: `AGENTS.md`)\"*\n\nRecord as `TO_PATH` (default `AGENTS.md`).\n\nAsk: *\"How often should Dreaming run?\n(Press Enter for the default: weekly, Mondays at 04:00 -> `0 4 * * 1`.\nUse any cron expression for a different cadence, e.g. `0 4 * * *` = daily)\"*\n\nRecord as `CRON_SCHEDULE` (default `0 4 * * 1`).\n\n**Compute `LOOKBACK_MINUTES` - do not ask the user for it.** Derive\n`cadence_minutes` (the interval between consecutive runs of `CRON_SCHEDULE`),\nthen:\n\n```\nLOOKBACK_MINUTES = min(cadence_minutes + 5, 1440)\n```\n\nExample: the default weekly `0 4 * * 1` has a 10080-minute cadence, so\n`LOOKBACK_MINUTES = 1440` (the cap).\n\nThe lookback only bounds the first sweep (before a cursor exists); after that\nthe automation resumes from its stored cursor.\n\n### Step 4 - Build the payload\n\nCopy this skill's two scripts into a temporary build directory and substitute\nthe placeholders in `run.sh`:\n\n```bash\nmkdir -p /tmp/dreaming-build\ncp {skill_dir}/scripts/setup.sh /tmp/dreaming-build/setup.sh\ncp {skill_dir}/scripts/run.sh /tmp/dreaming-build/run.sh\n```\n\n| Placeholder in `run.sh` | Replace with |\n|---|---|\n| `__TARGET_REPO__` | `TARGET_REPO` |\n| `__MODEL__` | `MODEL` |\n| `__TO_PATH__` | `TO_PATH` |\n| `__LOOKBACK_MINUTES__` | computed `LOOKBACK_MINUTES` |\n\nValidate each substituted value against `^[A-Za-z0-9._/-]+$` and re-ask on\nmismatch (the placeholders sit inside double quotes in a bash script).\n\nDo NOT bake secrets into either script: the CLI resolves `GITHUB_TOKEN` and\nthe provider key from the environment or the Agent Server secret store at\nrun time, and `AUTOMATION_*` values are provided by the automation runtime.\n\nValidate both scripts before packaging:\n\n```bash\nbash -n /tmp/dreaming-build/setup.sh && bash -n /tmp/dreaming-build/run.sh && echo \"Syntax OK\"\n```\n\nFix any syntax errors before proceeding.\n\n### Step 5 - Preflight with `doctor`\n\nIf the current environment can run bash and reach the network, dry-run the\ninstall and the read-only preflight from the build directory:\n\n```bash\ncd /tmp/dreaming-build\nbash setup.sh\n./openhands-dreaming doctor --repo \"{owner}/{repo}\" --model \"{model}\"\n```\n\n`doctor` mutates nothing; it checks the token, repo write access, the\nprovider key for the model, the conversations directory, and the Letta\nbinary. If it fails, show the doctor output to the user and stop until the\nunderlying issue is fixed. If the environment cannot run the preflight,\ntell the user it was skipped and why.\n\n### Step 6 - Package and upload\n\nDetermine the Automation backend URL and auth from the ``\nblock in your system context:\n- **OPENHANDS_HOST**: the Automation backend `url_from_agent`\n- **Auth**: `X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY`\n\n```bash\ntar -czf /tmp/dreaming.tar.gz -C /tmp/dreaming-build setup.sh run.sh\n\nTARBALL_PATH=$(curl -s -X POST \\\n \"${OPENHANDS_HOST}/api/automation/v1/uploads?name=dreaming\" \\\n -H \"X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY\" \\\n -H \"Content-Type: application/gzip\" \\\n --data-binary @/tmp/dreaming.tar.gz \\\n | python3 -c \"import json,sys; print(json.load(sys.stdin)['tarball_path'])\")\n\necho \"Uploaded: $TARBALL_PATH\"\n```\n\nNote: the tarball intentionally contains only `setup.sh` and `run.sh` (the\nStep 5 preflight leaves extra files such as the wrapper in the build dir,\nwhich must not be packaged).\n\n### Step 7 - Register the automation\n\nThe entrypoint must contain no shell metacharacters (hence `bash run.sh`).\nThere is no arbitrary `env` field: configuration is baked into `run.sh`.\n\n```bash\ncurl -s -X POST \"${OPENHANDS_HOST}/api/automation/v1\" \\\n -H \"X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d \"{\n \\\"name\\\": \\\"Dreaming: {owner}/{repo}\\\",\n \\\"trigger\\\": {\\\"type\\\": \\\"cron\\\", \\\"schedule\\\": \\\"{cron_schedule}\\\"},\n \\\"tarball_path\\\": \\\"$TARBALL_PATH\\\",\n \\\"entrypoint\\\": \\\"bash run.sh\\\",\n \\\"timeout\\\": 1800\n }\" | python3 -m json.tool\n```\n\nRecord the returned `id` as `AUTOMATION_ID`.\n\n### Step 8 - Dispatch a first run and surface the PR\n\n```bash\ncurl -s -X POST \"${OPENHANDS_HOST}/api/automation/v1/${AUTOMATION_ID}/dispatch\" \\\n -H \"X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY\" | python3 -m json.tool\n```\n\nWatch the run until it completes:\n\n```bash\ncurl -s \"${OPENHANDS_HOST}/api/automation/v1/${AUTOMATION_ID}/runs\" \\\n -H \"X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY\" | python3 -m json.tool\n```\n\nThe CLI emits structured `--json` output; surface the PR link to the user if\none was opened or updated. If no recent conversations were found, tell the\nuser that is normal on a quiet first run.\n\nOn the first run the CLI auto-creates the dreamer Letta agent (tagged\n`openhands-dreaming` and `repo:{owner}/{repo}`); it is reused on every\nsubsequent run so its memory accumulates.\n\n### Step 9 - Confirm\n\nTell the user:\n\n> **Dreaming** is set up!\n>\n> - Automation ID: `{automation_id}`\n> - Target repo: `{owner}/{repo}`\n> - Model: `{model}`\n> - Schedule: `{cron_schedule}`\n> - Doc path: `{to_path}`\n> - State: sweep cursor lives in the OpenHands KV store under\n> `dreaming-cursor:{automation_id}:{owner__repo}`; the dreamer agent's\n> memory lives in its Letta memory filesystem and accumulates across runs.\n>\n> Each run sweeps new local Agent Canvas conversations, reflects on them, and\n> opens or updates a PR when `{to_path}` changes. Nothing happens on runs\n> with no new conversations or no doc changes.\n\n---\n\n## Runtime behaviour (per run)\n\nEach cron run executes `bash run.sh`, which execs\n`openhands-dreaming run --repo ... --model ... --to ... --lookback ... --json`:\n\n1. Reads the sweep cursor from the OpenHands KV store and acquires a lock.\n2. Discovers local Agent Canvas conversations newer than the cursor (first\n run bounded by the computed lookback).\n3. Finds or creates the dreamer Letta agent by tags.\n4. Shallow-clones the target repo and runs `letta dream` to reflect the new\n conversations into the doc.\n5. If the doc changed: commits as the token owner (with a\n `Co-authored-by: Letta Code ` trailer), pushes the\n dream branch, and opens or updates the PR.\n6. Advances the cursor only on full end-to-end success.\n\n---\n\n## Troubleshooting\n\nRun these from the automation working directory (or anywhere the CLI is\ninstalled) with the same env the automation uses:\n\n| Symptom | Try |\n|---|---|\n| Run fails at preflight or setup | `openhands-dreaming doctor --repo {owner}/{repo} --model {model}` - read-only report of token, repo access, provider key, dirs, and Letta binary |\n| Want to see what the sweep will pick up next | `openhands-dreaming cursor get` |\n| Need to replay conversations after a bad run | `openhands-dreaming cursor reset` (safe: Letta dedupes re-fed conversations) |\n| Model provider errors at run time | Confirm the provider API key is a named secret (OpenHands chat LLM config is separate) |\n| PR never appears | Check the run logs for the `--json` result; no doc change means no PR by design |" + "content": "# Dreaming Automation\n\nCreate a cron automation that sweeps your **local OpenHands Agent Canvas**\nconversations, feeds the new ones to a persistent Letta \"dreamer\" agent for\nmemory reflection, and turns what it learned into a pull request against the\ntarget repository's `AGENTS.md` (or another doc path you choose).\n\nThe extension is a thin wrapper: the automation tarball contains only two\nsmall scripts (`setup.sh` + `run.sh`). All logic lives in the pinned npm\npackage `@letta-ai/openhands-dreaming` (commands: `run`, `doctor`,\n`cursor get|set|reset`), which `setup.sh` installs at run time.\n\nMVP scope: local Agent Canvas conversations only (the automation polls on\nyour laptop). Cloud conversation sources are not supported yet.\n\n---\n\n## Prerequisites\n\n### Required secrets\n\nVerify these are set in **OpenHands Settings -> Secrets** (the CLI reads\nthem from the environment or fetches them from the Agent Server secret store\nat run time):\n\n| Secret name | Purpose | Minimum permissions |\n|---|---|---|\n| `GITHUB_TOKEN` | Push the dream branch and open/update the PR | Write access to the target repo (Contents: Read and Write, Pull requests: Read and Write, Metadata: Read) |\n| Provider API key (e.g. `ANTHROPIC_API_KEY`) | The Letta reflection model | Valid key for the chosen model's provider (see Step 2) |\n\n> **Important:** OpenHands' own LLM configuration is SEPARATE from named\n> secrets. Even if the chosen model already works in OpenHands chat, its API\n> key must be added as a named secret for the automation to use it.\n\n---\n\n## Setup Workflow\n\nFollow these steps in order.\n\nNote: the automation shell executes one command per invocation. Combine\nsnippets with `&&` or `;` when needed, but never combine a heredoc\n(`python3 - <<'PY'`) with another command - run heredocs alone or rewrite\nthem as a `python3 -c` one-liner.\n\n### Step 1 - Collect and validate the target repository\n\nAsk: *\"Which GitHub repository should Dreaming open AGENTS.md PRs against?\n(Format: `owner/repo`, e.g. `myorg/backend`)\"*\n\nFirst confirm `GITHUB_TOKEN` is present:\n\n```bash\ntest -n \"$GITHUB_TOKEN\" && echo \"GITHUB_TOKEN set\" || echo \"MISSING_GITHUB_TOKEN\"\n```\n\nIf missing: *\"GITHUB_TOKEN is not set. Please add it in OpenHands Settings ->\nSecrets.\"* Stop.\n\nThen validate access and push permission (single pipeline; prints three lines):\n\n```bash\ncurl -sm 60 \"https://api.github.com/repos/{owner}/{repo}\" -H \"Authorization: Bearer $GITHUB_TOKEN\" -H \"Accept: application/vnd.github+json\" | python3 -c \"import json,sys; d=json.load(sys.stdin); print('message:', d.get('message')); print('private:', d.get('private')); print('push:', (d.get('permissions') or {}).get('push'))\"\n```\n\n- `message:` non-None (e.g. `Not Found`, `Bad credentials`): report it and stop.\n- `push:` must print `True`; otherwise tell the user the token cannot push to\n this repo (Dreaming needs to push a branch and open a PR) and stop.\n\nRecord `TARGET_REPO = \"{owner}/{repo}\"`.\n\n### Step 2 - Collect the model handle and verify the provider secret\n\nAsk: *\"Which model should the Letta dreamer agent use for reflection?\n(Format: `provider/model`, e.g. `openai/gpt-5.5` or `anthropic/opus-4.8`)\"*\n\nRecord the answer as `MODEL`. For KNOWN provider prefixes, verify the matching\nsecret exists as an environment variable:\n\n| Model prefix | Required secret |\n|---|---|\n| `anthropic/` | `ANTHROPIC_API_KEY` |\n| `openai/` | `OPENAI_API_KEY` |\n| `google/` | `GOOGLE_GENERATIVE_AI_API_KEY` |\n| `groq/` | `GROQ_API_KEY` |\n| `deepseek/` | `DEEPSEEK_API_KEY` |\n| `openrouter/` | `OPENROUTER_API_KEY` |\n| `ollama-cloud/` | `OLLAMA_API_KEY` |\n| `ollama/` | `OLLAMA_LOCAL_API_KEY` |\n\n- Known prefix, secret missing: tell the user which secret to add in\n OpenHands Settings -> Secrets, then stop until it is added.\n- Unknown prefix: warn that Letta will validate the model handle and its\n provider key at run time, and continue (do not block).\n\nNote: the model is baked into the dreamer agent when it is first created.\nChanging `MODEL` later does not re-model an existing dreamer agent.\n\n### Step 3 - Collect doc path and schedule, compute lookback\n\nAsk: *\"Which doc file should Dreaming maintain in the repo?\n(Press Enter for the default: `AGENTS.md`)\"*\n\nRecord as `TO_PATH` (default `AGENTS.md`).\n\nAsk: *\"How often should Dreaming run?\n(Press Enter for the default: weekly, Mondays at 04:00 -> `0 4 * * 1`.\nUse any cron expression for a different cadence, e.g. `0 4 * * *` = daily)\"*\n\nRecord as `CRON_SCHEDULE` (default `0 4 * * 1`).\n\n**Compute `LOOKBACK_MINUTES` - do not ask the user for it.** Derive\n`cadence_minutes` (the interval between consecutive runs of `CRON_SCHEDULE`),\nthen:\n\n```\nLOOKBACK_MINUTES = min(cadence_minutes + 5, 1440)\n```\n\nExample: the default weekly `0 4 * * 1` has a 10080-minute cadence, so\n`LOOKBACK_MINUTES = 1440` (the cap).\n\nThe lookback only bounds the first sweep (before a cursor exists); after that\nthe automation resumes from its stored cursor.\n\n### Step 4 - Build the payload\n\nCopy this skill's two scripts into a temporary build directory and substitute\nthe placeholders in `run.sh`:\n\n```bash\nmkdir -p /tmp/dreaming-build\ncp {skill_dir}/scripts/setup.sh /tmp/dreaming-build/setup.sh\ncp {skill_dir}/scripts/run.sh /tmp/dreaming-build/run.sh\n```\n\n| Placeholder in `run.sh` | Replace with |\n|---|---|\n| `__TARGET_REPO__` | `TARGET_REPO` |\n| `__MODEL__` | `MODEL` |\n| `__TO_PATH__` | `TO_PATH` |\n| `__LOOKBACK_MINUTES__` | computed `LOOKBACK_MINUTES` |\n\nValidate each substituted value against `^[A-Za-z0-9._/-]+$` and re-ask on\nmismatch (the placeholders sit inside double quotes in a bash script).\n\nDo NOT bake secrets into either script: the CLI resolves `GITHUB_TOKEN` and\nthe provider key from the environment or the Agent Server secret store at\nrun time, and `AUTOMATION_*` values are provided by the automation runtime.\n\nValidate both scripts before packaging:\n\n```bash\nbash -n /tmp/dreaming-build/setup.sh && bash -n /tmp/dreaming-build/run.sh && echo \"Syntax OK\"\n```\n\nFix any syntax errors before proceeding.\n\n### Step 5 - Preflight with `doctor`\n\nIf the current environment can run bash and reach the network, dry-run the\ninstall and the read-only preflight from the build directory:\n\n```bash\ncd /tmp/dreaming-build\nbash setup.sh\n./openhands-dreaming doctor --repo \"{owner}/{repo}\" --model \"{model}\"\n```\n\n`doctor` mutates nothing; it checks the token, repo write access, the\nprovider key for the model, the conversations directory, and the Letta\nbinary. If it fails, show the doctor output to the user and stop until the\nunderlying issue is fixed. If the environment cannot run the preflight,\ntell the user it was skipped and why.\n\n### Step 6 - Package and upload\n\nDetermine the Automation backend URL and auth from the ``\nblock in your system context:\n- **OPENHANDS_HOST**: the Automation backend `url_from_agent`\n- **Auth**: `X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY`\n\n```bash\ntar -czf /tmp/dreaming.tar.gz -C /tmp/dreaming-build setup.sh run.sh\n\nTARBALL_PATH=$(curl -sm 60 -X POST \\\n \"${OPENHANDS_HOST}/api/automation/v1/uploads?name=dreaming\" \\\n -H \"X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY\" \\\n -H \"Content-Type: application/gzip\" \\\n --data-binary @/tmp/dreaming.tar.gz \\\n | python3 -c \"import json,sys; print(json.load(sys.stdin)['tarball_path'])\")\n\necho \"Uploaded: $TARBALL_PATH\"\n```\n\nNote: the tarball intentionally contains only `setup.sh` and `run.sh` (the\nStep 5 preflight leaves extra files such as the wrapper in the build dir,\nwhich must not be packaged).\n\n### Step 7 - Register the automation\n\nThe entrypoint must contain no shell metacharacters (hence `bash run.sh`).\nThere is no arbitrary `env` field: configuration is baked into `run.sh`.\n\n```bash\ncurl -sm 60 -X POST \"${OPENHANDS_HOST}/api/automation/v1\" \\\n -H \"X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d \"{\n \\\"name\\\": \\\"Dreaming: {owner}/{repo}\\\",\n \\\"trigger\\\": {\\\"type\\\": \\\"cron\\\", \\\"schedule\\\": \\\"{cron_schedule}\\\"},\n \\\"tarball_path\\\": \\\"$TARBALL_PATH\\\",\n \\\"entrypoint\\\": \\\"bash run.sh\\\",\n \\\"timeout\\\": 1800\n }\" | python3 -m json.tool\n```\n\nRecord the returned `id` as `AUTOMATION_ID`.\n\n### Step 8 - Dispatch a first run and surface the PR\n\n```bash\ncurl -sm 60 -X POST \"${OPENHANDS_HOST}/api/automation/v1/${AUTOMATION_ID}/dispatch\" \\\n -H \"X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY\" | python3 -m json.tool\n```\n\nWatch the run until it completes:\n\n```bash\ncurl -sm 60 \"${OPENHANDS_HOST}/api/automation/v1/${AUTOMATION_ID}/runs\" \\\n -H \"X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY\" | python3 -m json.tool\n```\n\nThe CLI emits structured `--json` output; surface the PR link to the user if\none was opened or updated. If no recent conversations were found, tell the\nuser that is normal on a quiet first run.\n\nOn the first run the CLI auto-creates the dreamer Letta agent (tagged\n`openhands-dreaming` and `repo:{owner}/{repo}`); it is reused on every\nsubsequent run so its memory accumulates.\n\n### Step 9 - Confirm\n\nTell the user:\n\n> **Dreaming** is set up!\n>\n> - Automation ID: `{automation_id}`\n> - Target repo: `{owner}/{repo}`\n> - Model: `{model}`\n> - Schedule: `{cron_schedule}`\n> - Doc path: `{to_path}`\n> - State: sweep cursor lives in the OpenHands KV store under\n> `dreaming-cursor:{automation_id}:{owner__repo}`; the dreamer agent's\n> memory lives in its Letta memory filesystem and accumulates across runs.\n>\n> Each run sweeps new local Agent Canvas conversations, reflects on them, and\n> opens or updates a PR when `{to_path}` changes. Nothing happens on runs\n> with no new conversations or no doc changes.\n\n---\n\n## Runtime behaviour (per run)\n\nEach cron run executes `bash run.sh`, which execs\n`openhands-dreaming run --repo ... --model ... --to ... --lookback ... --json`:\n\n1. Reads the sweep cursor from the OpenHands KV store and acquires a lock.\n2. Discovers local Agent Canvas conversations newer than the cursor (first\n run bounded by the computed lookback).\n3. Finds or creates the dreamer Letta agent by tags.\n4. Shallow-clones the target repo and runs `letta dream` to reflect the new\n conversations into the doc.\n5. If the doc changed: commits as the token owner (with a\n `Co-authored-by: Letta Code ` trailer), pushes the\n dream branch, and opens or updates the PR.\n6. Advances the cursor only on full end-to-end success.\n\n---\n\n## Troubleshooting\n\nRun these from the automation working directory (or anywhere the CLI is\ninstalled) with the same env the automation uses:\n\n| Symptom | Try |\n|---|---|\n| Run fails at preflight or setup | `openhands-dreaming doctor --repo {owner}/{repo} --model {model}` - read-only report of token, repo access, provider key, dirs, and Letta binary |\n| Want to see what the sweep will pick up next | `openhands-dreaming cursor get` |\n| Need to replay conversations after a bad run | `openhands-dreaming cursor reset` (safe: Letta dedupes re-fed conversations) |\n| Model provider errors at run time | Confirm the provider API key is a named secret (OpenHands chat LLM config is separate) |\n| PR never appears | Check the run logs for the `--json` result; no doc change means no PR by design |" }, { "name": "evidence-based-citations", From 52dad9697820fafccdaaa9910f96873a8b5a8153 Mon Sep 17 00:00:00 2001 From: Caren Thomas Date: Thu, 9 Jul 2026 14:22:53 -0700 Subject: [PATCH 4/4] feat: pin dreaming to a chosen conversation Reshape the skill from cursor-swept conversations to pinned-conversation mode for the MVP: the user names a conversation, the setup flow resolves it to an id from local conversation metadata, and each run reflects over whatever is new in that one conversation (already-reflected messages are deduplicated, so quiet runs are no-ops). Automations register as 'Dreaming: "" -> /'. Every snippet is a single chained invocation (the automation shell runs one command per call), and the CLI pin moves to 0.1.3 (repo-scoped lock + release fix). --- automations/catalog/dreaming.json | 4 +- skills/dreaming/.plugin/plugin.json | 2 +- skills/dreaming/README.md | 18 +-- skills/dreaming/SKILL.md | 143 ++++++++++----------- skills/dreaming/commands/dreaming-setup.md | 2 +- skills/dreaming/scripts/run.sh | 3 +- skills/dreaming/scripts/setup.sh | 4 +- skills/index.js | 4 +- 8 files changed, 89 insertions(+), 91 deletions(-) diff --git a/automations/catalog/dreaming.json b/automations/catalog/dreaming.json index 9513b18b..860ff3d5 100644 --- a/automations/catalog/dreaming.json +++ b/automations/catalog/dreaming.json @@ -2,12 +2,12 @@ "id": "dreaming", "name": "Letta dreaming agent", "category": "Agent memory", - "description": "Give your coding agents a memory that compounds: Letta reflects over your OpenHands sessions in the background, learning your codebase, conventions, and preferences over time.", + "description": "Give your coding agents a memory that compounds: Letta reflects over a chosen OpenHands conversation on a schedule, learning your codebase, conventions, and preferences over time.", "requiredIntegrationIds": [ "github" ], "popularityRank": 84, "estimatedSetupMinutes": 5, "prompt": "/dreaming:setup", - "exampleImplementation": "Trigger: cron, weekly by default (configurable)\nRequired secrets: GITHUB_TOKEN (repo write) and the model provider API key\n\n1. On each cron run, sweep local Agent Canvas conversations newer than the stored cursor.\n2. Feed them to a persistent Letta dreamer agent; letta dream reflects them into AGENTS.md in a temporary shallow checkout of the target repo.\n3. If the doc changed, commit as the token owner with a Letta Code co-author trailer and push the dream branch.\n4. Open or update the pull request with a summary of the doc changes.\n5. Advance the cursor only on full end-to-end success, so failed runs are retried." + "exampleImplementation": "Trigger: cron, daily by default (configurable)\nRequired secrets: GITHUB_TOKEN (repo write) and the model provider API key\n\n1. At setup, the user names a conversation; the skill resolves it to a conversation id from local conversation metadata.\n2. On each cron run, feed whatever is new in that conversation to a persistent Letta dreamer agent; letta dream reflects it into AGENTS.md in a temporary shallow checkout of the target repo. Already-reflected messages are deduplicated, so quiet runs are cheap no-ops.\n3. If the doc changed, commit as the token owner with a Letta Code co-author trailer and push the dream branch.\n4. Open or update the pull request with a summary of the doc changes.\n5. To dream over several conversations, set up one automation per conversation; automations targeting the same repo share a lock and never overlap." } diff --git a/skills/dreaming/.plugin/plugin.json b/skills/dreaming/.plugin/plugin.json index e43cdea2..dc3ce8a0 100644 --- a/skills/dreaming/.plugin/plugin.json +++ b/skills/dreaming/.plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "dreaming", "version": "1.0.0", - "description": "Create an automation that periodically distills your local OpenHands Agent Canvas coding sessions into a target repository's AGENTS.md via Letta memory reflection and opens or updates a pull request with the result.", + "description": "Create an automation that periodically distills a chosen local OpenHands conversation into a target repository's AGENTS.md via Letta memory reflection and opens or updates a pull request with the result.", "author": { "name": "OpenHands", "email": "contact@all-hands.dev" diff --git a/skills/dreaming/README.md b/skills/dreaming/README.md index bdf16ad5..4c294c75 100644 --- a/skills/dreaming/README.md +++ b/skills/dreaming/README.md @@ -1,8 +1,8 @@ # Dreaming -Create an automation that periodically distills your local OpenHands Agent -Canvas coding sessions into a target repository's `AGENTS.md` via Letta -memory reflection, opening or updating a pull request with what it learned. +Create an automation that periodically distills a chosen local OpenHands +conversation into a target repository's `AGENTS.md` via Letta memory +reflection, opening or updating a pull request with what it learned. ## Trigger @@ -12,10 +12,11 @@ This skill is activated by: ## How it works -- A cron automation sweeps new local Agent Canvas conversations since the - last run. -- A persistent Letta "dreamer" agent reflects on them and updates the doc in - a temporary checkout of the target repo. +- At setup you name a conversation; the skill resolves it to a conversation + id from local conversation metadata. +- On each cron run, a persistent Letta "dreamer" agent reflects on whatever + is new in that conversation and updates the doc in a temporary checkout of + the target repo (already-reflected messages are deduplicated). - If the doc changed, the automation commits and opens (or updates) a PR. - The automation tarball is a thin wrapper (two small scripts); all logic lives in the pinned npm package `@letta-ai/openhands-dreaming`. @@ -35,7 +36,8 @@ Set these in OpenHands Settings -> Secrets: Ask OpenHands: -> "Set up Dreaming for my `myorg/backend` repo using `anthropic/opus-4.8`." +> "Set up Dreaming over my 'payment refactor' conversation for the +> `myorg/backend` repo using `anthropic/opus-4.8`." ## See Also diff --git a/skills/dreaming/SKILL.md b/skills/dreaming/SKILL.md index 53b7cbbd..59743b15 100644 --- a/skills/dreaming/SKILL.md +++ b/skills/dreaming/SKILL.md @@ -1,8 +1,8 @@ --- name: dreaming description: > - Create an automation that periodically distills your local OpenHands Agent - Canvas coding sessions into a target repository's AGENTS.md via Letta memory + Create an automation that periodically distills a chosen local OpenHands + conversation into a target repository's AGENTS.md via Letta memory reflection and opens or updates a pull request with the result. triggers: - /dreaming:setup @@ -10,10 +10,13 @@ triggers: # Dreaming Automation -Create a cron automation that sweeps your **local OpenHands Agent Canvas** -conversations, feeds the new ones to a persistent Letta "dreamer" agent for -memory reflection, and turns what it learned into a pull request against the -target repository's `AGENTS.md` (or another doc path you choose). +Create a cron automation that dreams over **one chosen local OpenHands +conversation**: on each run it feeds whatever is new in that conversation to a +persistent Letta "dreamer" agent for memory reflection and turns what it +learned into a pull request against the target repository's `AGENTS.md` (or +another doc path you choose). Users pick the conversation by NAME; this skill +resolves it to a conversation id. To dream over several conversations, set up +one automation per conversation. The extension is a thin wrapper: the automation tarball contains only two small scripts (`setup.sh` + `run.sh`). All logic lives in the pinned npm @@ -48,31 +51,25 @@ at run time): Follow these steps in order. -Note: the automation shell executes one command per invocation. Combine -snippets with `&&` or `;` when needed, but never combine a heredoc -(`python3 - <<'PY'`) with another command - run heredocs alone or rewrite -them as a `python3 -c` one-liner. +Note: the shell executes ONE command per tool call. Every fenced block in +this skill is already a single `&&`-chained invocation - run each block as +its own call, never paste two blocks together, and never combine a heredoc +with anything else (rewrite heredocs as `python3 -c` one-liners). ### Step 1 - Collect and validate the target repository Ask: *"Which GitHub repository should Dreaming open AGENTS.md PRs against? (Format: `owner/repo`, e.g. `myorg/backend`)"* -First confirm `GITHUB_TOKEN` is present: +Validate the token and push permission in one call (prints three lines on +success, `MISSING_GITHUB_TOKEN_OR_API_ERROR` otherwise): ```bash -test -n "$GITHUB_TOKEN" && echo "GITHUB_TOKEN set" || echo "MISSING_GITHUB_TOKEN" -``` - -If missing: *"GITHUB_TOKEN is not set. Please add it in OpenHands Settings -> -Secrets."* Stop. - -Then validate access and push permission (single pipeline; prints three lines): - -```bash -curl -sm 60 "https://api.github.com/repos/{owner}/{repo}" -H "Authorization: Bearer $GITHUB_TOKEN" -H "Accept: application/vnd.github+json" | python3 -c "import json,sys; d=json.load(sys.stdin); print('message:', d.get('message')); print('private:', d.get('private')); print('push:', (d.get('permissions') or {}).get('push'))" +test -n "$GITHUB_TOKEN" && curl -sm 60 "https://api.github.com/repos/{owner}/{repo}" -H "Authorization: Bearer $GITHUB_TOKEN" -H "Accept: application/vnd.github+json" | python3 -c "import json,sys; d=json.load(sys.stdin); print('message:', d.get('message')); print('private:', d.get('private')); print('push:', (d.get('permissions') or {}).get('push'))" || echo "MISSING_GITHUB_TOKEN_OR_API_ERROR" ``` +- `MISSING_GITHUB_TOKEN_OR_API_ERROR`: tell the user to add `GITHUB_TOKEN` in + OpenHands Settings -> Secrets (or report the network error) and stop. - `message:` non-None (e.g. `Not Found`, `Bad credentials`): report it and stop. - `push:` must print `True`; otherwise tell the user the token cannot push to this repo (Dreaming needs to push a branch and open a PR) and stop. @@ -106,32 +103,41 @@ secret exists as an environment variable: Note: the model is baked into the dreamer agent when it is first created. Changing `MODEL` later does not re-model an existing dreamer agent. -### Step 3 - Collect doc path and schedule, compute lookback +### Step 3 - Pick the conversation, doc path, and schedule -Ask: *"Which doc file should Dreaming maintain in the repo? -(Press Enter for the default: `AGENTS.md`)"* +Ask: *"Which conversation should Dreaming reflect over? Give me its name as +shown in OpenHands (or part of it)."* -Record as `TO_PATH` (default `AGENTS.md`). +Users know conversation names, not ids. Resolve the name yourself: each local +conversation directory has a `meta.json` with a `title`. List them: -Ask: *"How often should Dreaming run? -(Press Enter for the default: weekly, Mondays at 04:00 -> `0 4 * * 1`. -Use any cron expression for a different cadence, e.g. `0 4 * * *` = daily)"* +```bash +python3 -c "import json,glob,os; [print(os.path.basename(os.path.dirname(p)), '|', (json.load(open(p)).get('title') or '(untitled)')) for p in glob.glob(os.path.expanduser('~/.openhands/agent-canvas/dev_conversations/*/meta.json'))]" +``` -Record as `CRON_SCHEDULE` (default `0 4 * * 1`). +Match the user's answer against the titles (case-insensitive substring). If +several match, show the candidates and ask the user to pick one. Record the +directory basename as `CONVERSATION_ID` and the title as `CONVERSATION_TITLE`; +record `CONVERSATIONS_DIR` as the ABSOLUTE conversations root (expand `~` - +e.g. `/Users/you/.openhands/agent-canvas/dev_conversations`): tildes do not +expand inside the double quotes in `run.sh`. -**Compute `LOOKBACK_MINUTES` - do not ask the user for it.** Derive -`cadence_minutes` (the interval between consecutive runs of `CRON_SCHEDULE`), -then: +If the user's conversation lives in a different root (for example imported +Claude Code sessions under `~/.openhands/claude-transcripts`), run the same +listing against that root and record it as `CONVERSATIONS_DIR` instead. -``` -LOOKBACK_MINUTES = min(cadence_minutes + 5, 1440) -``` +Ask: *"Which doc file should Dreaming maintain in the repo? +(Press Enter for the default: `AGENTS.md`)"* + +Record as `TO_PATH` (default `AGENTS.md`). -Example: the default weekly `0 4 * * 1` has a 10080-minute cadence, so -`LOOKBACK_MINUTES = 1440` (the cap). +Ask: *"How often should Dreaming run? +(Press Enter for the default: daily at 04:00 UTC -> `0 4 * * *`. +Use any cron expression for a different cadence)"* -The lookback only bounds the first sweep (before a cursor exists); after that -the automation resumes from its stored cursor. +Record as `CRON_SCHEDULE` (default `0 4 * * *`). Every run re-checks the +pinned conversation; runs where nothing new happened are cheap no-ops (Letta +deduplicates already-reflected messages). ### Step 4 - Build the payload @@ -139,9 +145,7 @@ Copy this skill's two scripts into a temporary build directory and substitute the placeholders in `run.sh`: ```bash -mkdir -p /tmp/dreaming-build -cp {skill_dir}/scripts/setup.sh /tmp/dreaming-build/setup.sh -cp {skill_dir}/scripts/run.sh /tmp/dreaming-build/run.sh +mkdir -p /tmp/dreaming-build && cp {skill_dir}/scripts/setup.sh /tmp/dreaming-build/setup.sh && cp {skill_dir}/scripts/run.sh /tmp/dreaming-build/run.sh ``` | Placeholder in `run.sh` | Replace with | @@ -149,7 +153,8 @@ cp {skill_dir}/scripts/run.sh /tmp/dreaming-build/run.sh | `__TARGET_REPO__` | `TARGET_REPO` | | `__MODEL__` | `MODEL` | | `__TO_PATH__` | `TO_PATH` | -| `__LOOKBACK_MINUTES__` | computed `LOOKBACK_MINUTES` | +| `__CONVERSATIONS_DIR__` | `CONVERSATIONS_DIR` | +| `__CONVERSATION_ID__` | `CONVERSATION_ID` | Validate each substituted value against `^[A-Za-z0-9._/-]+$` and re-ask on mismatch (the placeholders sit inside double quotes in a bash script). @@ -172,9 +177,7 @@ If the current environment can run bash and reach the network, dry-run the install and the read-only preflight from the build directory: ```bash -cd /tmp/dreaming-build -bash setup.sh -./openhands-dreaming doctor --repo "{owner}/{repo}" --model "{model}" +cd /tmp/dreaming-build && bash setup.sh && ./openhands-dreaming doctor --repo "{owner}/{repo}" --model "{model}" --conversations-dir "{conversations_dir}" ``` `doctor` mutates nothing; it checks the token, repo write access, the @@ -191,16 +194,7 @@ block in your system context: - **Auth**: `X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY` ```bash -tar -czf /tmp/dreaming.tar.gz -C /tmp/dreaming-build setup.sh run.sh - -TARBALL_PATH=$(curl -sm 60 -X POST \ - "${OPENHANDS_HOST}/api/automation/v1/uploads?name=dreaming" \ - -H "X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY" \ - -H "Content-Type: application/gzip" \ - --data-binary @/tmp/dreaming.tar.gz \ - | python3 -c "import json,sys; print(json.load(sys.stdin)['tarball_path'])") - -echo "Uploaded: $TARBALL_PATH" +tar -czf /tmp/dreaming.tar.gz -C /tmp/dreaming-build setup.sh run.sh && TARBALL_PATH=$(curl -sm 60 -X POST "${OPENHANDS_HOST}/api/automation/v1/uploads?name=dreaming" -H "X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY" -H "Content-Type: application/gzip" --data-binary @/tmp/dreaming.tar.gz | python3 -c "import json,sys; print(json.load(sys.stdin)['tarball_path'])") && echo "Uploaded: $TARBALL_PATH" ``` Note: the tarball intentionally contains only `setup.sh` and `run.sh` (the @@ -217,7 +211,7 @@ curl -sm 60 -X POST "${OPENHANDS_HOST}/api/automation/v1" \ -H "X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY" \ -H "Content-Type: application/json" \ -d "{ - \"name\": \"Dreaming: {owner}/{repo}\", + \"name\": \"Dreaming: \\\"{conversation_title}\\\" -> {owner}/{repo}\", \"trigger\": {\"type\": \"cron\", \"schedule\": \"{cron_schedule}\"}, \"tarball_path\": \"$TARBALL_PATH\", \"entrypoint\": \"bash run.sh\", @@ -242,8 +236,8 @@ curl -sm 60 "${OPENHANDS_HOST}/api/automation/v1/${AUTOMATION_ID}/runs" \ ``` The CLI emits structured `--json` output; surface the PR link to the user if -one was opened or updated. If no recent conversations were found, tell the -user that is normal on a quiet first run. +one was opened or updated. A `noop` PR result means the reflection produced +no doc changes - normal when the conversation has little durable content yet. On the first run the CLI auto-creates the dreamer Letta agent (tagged `openhands-dreaming` and `repo:{owner}/{repo}`); it is reused on every @@ -256,35 +250,37 @@ Tell the user: > **Dreaming** is set up! > > - Automation ID: `{automation_id}` +> - Automation name: `Dreaming: "{conversation_title}" -> {owner}/{repo}` +> - Conversation: `{conversation_title}` (`{conversation_id}`) > - Target repo: `{owner}/{repo}` > - Model: `{model}` > - Schedule: `{cron_schedule}` > - Doc path: `{to_path}` -> - State: sweep cursor lives in the OpenHands KV store under -> `dreaming-cursor:{automation_id}:{owner__repo}`; the dreamer agent's -> memory lives in its Letta memory filesystem and accumulates across runs. +> - The dreamer agent's memory lives in its Letta memory filesystem and +> accumulates across runs. > -> Each run sweeps new local Agent Canvas conversations, reflects on them, and -> opens or updates a PR when `{to_path}` changes. Nothing happens on runs -> with no new conversations or no doc changes. +> Each run reflects over whatever is new in the conversation and opens or +> updates a PR when `{to_path}` changes. Runs with nothing new or no doc +> changes are cheap no-ops. --- ## Runtime behaviour (per run) Each cron run executes `bash run.sh`, which execs -`openhands-dreaming run --repo ... --model ... --to ... --lookback ... --json`: +`openhands-dreaming run --repo ... --model ... --to ... --conversations-dir +... --conversation-id ... --json`: -1. Reads the sweep cursor from the OpenHands KV store and acquires a lock. -2. Discovers local Agent Canvas conversations newer than the cursor (first - run bounded by the computed lookback). +1. Acquires the repo-scoped lock (automations sharing a repo never overlap). +2. Selects the pinned conversation (a missing conversation fails the run + loudly - it is a configuration error, not an empty sweep). 3. Finds or creates the dreamer Letta agent by tags. -4. Shallow-clones the target repo and runs `letta dream` to reflect the new - conversations into the doc. +4. Shallow-clones the target repo and runs `letta dream` to reflect whatever + is new in the conversation into the doc (Letta deduplicates + already-reflected messages, so quiet runs are cheap no-ops). 5. If the doc changed: commits as the token owner (with a `Co-authored-by: Letta Code ` trailer), pushes the dream branch, and opens or updates the PR. -6. Advances the cursor only on full end-to-end success. --- @@ -296,7 +292,6 @@ installed) with the same env the automation uses: | Symptom | Try | |---|---| | Run fails at preflight or setup | `openhands-dreaming doctor --repo {owner}/{repo} --model {model}` - read-only report of token, repo access, provider key, dirs, and Letta binary | -| Want to see what the sweep will pick up next | `openhands-dreaming cursor get` | -| Need to replay conversations after a bad run | `openhands-dreaming cursor reset` (safe: Letta dedupes re-fed conversations) | +| Run fails with "pinned conversation not found" | The conversation dir moved or the id is wrong - re-run the Step 3 listing and update `run.sh` | | Model provider errors at run time | Confirm the provider API key is a named secret (OpenHands chat LLM config is separate) | | PR never appears | Check the run logs for the `--json` result; no doc change means no PR by design | diff --git a/skills/dreaming/commands/dreaming-setup.md b/skills/dreaming/commands/dreaming-setup.md index f3fbd47d..a71dafc3 100644 --- a/skills/dreaming/commands/dreaming-setup.md +++ b/skills/dreaming/commands/dreaming-setup.md @@ -1,6 +1,6 @@ --- # auto-generated by sync_extensions.py -description: Create an automation that periodically distills your local OpenHands Agent Canvas coding sessions into a target repository's AGENTS.md via Letta memory reflection and opens or updates a pull request with the result. +description: Create an automation that periodically distills a chosen local OpenHands conversation into a target repository's AGENTS.md via Letta memory reflection and opens or updates a pull request with the result. --- Read and follow the complete instructions in the SKILL.md file located in this skill's directory. diff --git a/skills/dreaming/scripts/run.sh b/skills/dreaming/scripts/run.sh index cb3c3419..ec8c2bf8 100644 --- a/skills/dreaming/scripts/run.sh +++ b/skills/dreaming/scripts/run.sh @@ -15,5 +15,6 @@ exec ./openhands-dreaming run \ --repo "__TARGET_REPO__" \ --model "__MODEL__" \ --to "__TO_PATH__" \ - --lookback "__LOOKBACK_MINUTES__" \ + --conversations-dir "__CONVERSATIONS_DIR__" \ + --conversation-id "__CONVERSATION_ID__" \ --json diff --git a/skills/dreaming/scripts/setup.sh b/skills/dreaming/scripts/setup.sh index 1f15a36a..9c647fe1 100644 --- a/skills/dreaming/scripts/setup.sh +++ b/skills/dreaming/scripts/setup.sh @@ -7,7 +7,7 @@ # do NOT survive into the entrypoint (`bash run.sh`). # # Env knob (optional): -# OPENHANDS_DREAMING_VERSION npm version to pin (default: latest) +# OPENHANDS_DREAMING_VERSION npm version to pin (default: 0.1.3) set -euo pipefail if ! command -v bun >/dev/null 2>&1 && [ ! -x "$HOME/.bun/bin/bun" ]; then @@ -17,7 +17,7 @@ if ! command -v bun >/dev/null 2>&1 && [ ! -x "$HOME/.bun/bin/bun" ]; then fi export PATH="$HOME/.bun/bin:$PATH" -DREAMING_VERSION="${OPENHANDS_DREAMING_VERSION:-latest}" +DREAMING_VERSION="${OPENHANDS_DREAMING_VERSION:-0.1.3}" # Absolute paths baked into the wrapper below (bun may be preinstalled # somewhere other than ~/.bun, e.g. via a system package). diff --git a/skills/index.js b/skills/index.js index fea7658c..9af73701 100644 --- a/skills/index.js +++ b/skills/index.js @@ -147,11 +147,11 @@ export const SKILLS_CATALOG = [ }, { "name": "dreaming", - "description": "Create an automation that periodically distills your local OpenHands Agent Canvas coding sessions into a target repository's AGENTS.md via Letta memory reflection and opens or updates a pull request with the result.", + "description": "Create an automation that periodically distills a chosen local OpenHands conversation into a target repository's AGENTS.md via Letta memory reflection and opens or updates a pull request with the result.", "triggers": [ "/dreaming:setup" ], - "content": "# Dreaming Automation\n\nCreate a cron automation that sweeps your **local OpenHands Agent Canvas**\nconversations, feeds the new ones to a persistent Letta \"dreamer\" agent for\nmemory reflection, and turns what it learned into a pull request against the\ntarget repository's `AGENTS.md` (or another doc path you choose).\n\nThe extension is a thin wrapper: the automation tarball contains only two\nsmall scripts (`setup.sh` + `run.sh`). All logic lives in the pinned npm\npackage `@letta-ai/openhands-dreaming` (commands: `run`, `doctor`,\n`cursor get|set|reset`), which `setup.sh` installs at run time.\n\nMVP scope: local Agent Canvas conversations only (the automation polls on\nyour laptop). Cloud conversation sources are not supported yet.\n\n---\n\n## Prerequisites\n\n### Required secrets\n\nVerify these are set in **OpenHands Settings -> Secrets** (the CLI reads\nthem from the environment or fetches them from the Agent Server secret store\nat run time):\n\n| Secret name | Purpose | Minimum permissions |\n|---|---|---|\n| `GITHUB_TOKEN` | Push the dream branch and open/update the PR | Write access to the target repo (Contents: Read and Write, Pull requests: Read and Write, Metadata: Read) |\n| Provider API key (e.g. `ANTHROPIC_API_KEY`) | The Letta reflection model | Valid key for the chosen model's provider (see Step 2) |\n\n> **Important:** OpenHands' own LLM configuration is SEPARATE from named\n> secrets. Even if the chosen model already works in OpenHands chat, its API\n> key must be added as a named secret for the automation to use it.\n\n---\n\n## Setup Workflow\n\nFollow these steps in order.\n\nNote: the automation shell executes one command per invocation. Combine\nsnippets with `&&` or `;` when needed, but never combine a heredoc\n(`python3 - <<'PY'`) with another command - run heredocs alone or rewrite\nthem as a `python3 -c` one-liner.\n\n### Step 1 - Collect and validate the target repository\n\nAsk: *\"Which GitHub repository should Dreaming open AGENTS.md PRs against?\n(Format: `owner/repo`, e.g. `myorg/backend`)\"*\n\nFirst confirm `GITHUB_TOKEN` is present:\n\n```bash\ntest -n \"$GITHUB_TOKEN\" && echo \"GITHUB_TOKEN set\" || echo \"MISSING_GITHUB_TOKEN\"\n```\n\nIf missing: *\"GITHUB_TOKEN is not set. Please add it in OpenHands Settings ->\nSecrets.\"* Stop.\n\nThen validate access and push permission (single pipeline; prints three lines):\n\n```bash\ncurl -sm 60 \"https://api.github.com/repos/{owner}/{repo}\" -H \"Authorization: Bearer $GITHUB_TOKEN\" -H \"Accept: application/vnd.github+json\" | python3 -c \"import json,sys; d=json.load(sys.stdin); print('message:', d.get('message')); print('private:', d.get('private')); print('push:', (d.get('permissions') or {}).get('push'))\"\n```\n\n- `message:` non-None (e.g. `Not Found`, `Bad credentials`): report it and stop.\n- `push:` must print `True`; otherwise tell the user the token cannot push to\n this repo (Dreaming needs to push a branch and open a PR) and stop.\n\nRecord `TARGET_REPO = \"{owner}/{repo}\"`.\n\n### Step 2 - Collect the model handle and verify the provider secret\n\nAsk: *\"Which model should the Letta dreamer agent use for reflection?\n(Format: `provider/model`, e.g. `openai/gpt-5.5` or `anthropic/opus-4.8`)\"*\n\nRecord the answer as `MODEL`. For KNOWN provider prefixes, verify the matching\nsecret exists as an environment variable:\n\n| Model prefix | Required secret |\n|---|---|\n| `anthropic/` | `ANTHROPIC_API_KEY` |\n| `openai/` | `OPENAI_API_KEY` |\n| `google/` | `GOOGLE_GENERATIVE_AI_API_KEY` |\n| `groq/` | `GROQ_API_KEY` |\n| `deepseek/` | `DEEPSEEK_API_KEY` |\n| `openrouter/` | `OPENROUTER_API_KEY` |\n| `ollama-cloud/` | `OLLAMA_API_KEY` |\n| `ollama/` | `OLLAMA_LOCAL_API_KEY` |\n\n- Known prefix, secret missing: tell the user which secret to add in\n OpenHands Settings -> Secrets, then stop until it is added.\n- Unknown prefix: warn that Letta will validate the model handle and its\n provider key at run time, and continue (do not block).\n\nNote: the model is baked into the dreamer agent when it is first created.\nChanging `MODEL` later does not re-model an existing dreamer agent.\n\n### Step 3 - Collect doc path and schedule, compute lookback\n\nAsk: *\"Which doc file should Dreaming maintain in the repo?\n(Press Enter for the default: `AGENTS.md`)\"*\n\nRecord as `TO_PATH` (default `AGENTS.md`).\n\nAsk: *\"How often should Dreaming run?\n(Press Enter for the default: weekly, Mondays at 04:00 -> `0 4 * * 1`.\nUse any cron expression for a different cadence, e.g. `0 4 * * *` = daily)\"*\n\nRecord as `CRON_SCHEDULE` (default `0 4 * * 1`).\n\n**Compute `LOOKBACK_MINUTES` - do not ask the user for it.** Derive\n`cadence_minutes` (the interval between consecutive runs of `CRON_SCHEDULE`),\nthen:\n\n```\nLOOKBACK_MINUTES = min(cadence_minutes + 5, 1440)\n```\n\nExample: the default weekly `0 4 * * 1` has a 10080-minute cadence, so\n`LOOKBACK_MINUTES = 1440` (the cap).\n\nThe lookback only bounds the first sweep (before a cursor exists); after that\nthe automation resumes from its stored cursor.\n\n### Step 4 - Build the payload\n\nCopy this skill's two scripts into a temporary build directory and substitute\nthe placeholders in `run.sh`:\n\n```bash\nmkdir -p /tmp/dreaming-build\ncp {skill_dir}/scripts/setup.sh /tmp/dreaming-build/setup.sh\ncp {skill_dir}/scripts/run.sh /tmp/dreaming-build/run.sh\n```\n\n| Placeholder in `run.sh` | Replace with |\n|---|---|\n| `__TARGET_REPO__` | `TARGET_REPO` |\n| `__MODEL__` | `MODEL` |\n| `__TO_PATH__` | `TO_PATH` |\n| `__LOOKBACK_MINUTES__` | computed `LOOKBACK_MINUTES` |\n\nValidate each substituted value against `^[A-Za-z0-9._/-]+$` and re-ask on\nmismatch (the placeholders sit inside double quotes in a bash script).\n\nDo NOT bake secrets into either script: the CLI resolves `GITHUB_TOKEN` and\nthe provider key from the environment or the Agent Server secret store at\nrun time, and `AUTOMATION_*` values are provided by the automation runtime.\n\nValidate both scripts before packaging:\n\n```bash\nbash -n /tmp/dreaming-build/setup.sh && bash -n /tmp/dreaming-build/run.sh && echo \"Syntax OK\"\n```\n\nFix any syntax errors before proceeding.\n\n### Step 5 - Preflight with `doctor`\n\nIf the current environment can run bash and reach the network, dry-run the\ninstall and the read-only preflight from the build directory:\n\n```bash\ncd /tmp/dreaming-build\nbash setup.sh\n./openhands-dreaming doctor --repo \"{owner}/{repo}\" --model \"{model}\"\n```\n\n`doctor` mutates nothing; it checks the token, repo write access, the\nprovider key for the model, the conversations directory, and the Letta\nbinary. If it fails, show the doctor output to the user and stop until the\nunderlying issue is fixed. If the environment cannot run the preflight,\ntell the user it was skipped and why.\n\n### Step 6 - Package and upload\n\nDetermine the Automation backend URL and auth from the ``\nblock in your system context:\n- **OPENHANDS_HOST**: the Automation backend `url_from_agent`\n- **Auth**: `X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY`\n\n```bash\ntar -czf /tmp/dreaming.tar.gz -C /tmp/dreaming-build setup.sh run.sh\n\nTARBALL_PATH=$(curl -sm 60 -X POST \\\n \"${OPENHANDS_HOST}/api/automation/v1/uploads?name=dreaming\" \\\n -H \"X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY\" \\\n -H \"Content-Type: application/gzip\" \\\n --data-binary @/tmp/dreaming.tar.gz \\\n | python3 -c \"import json,sys; print(json.load(sys.stdin)['tarball_path'])\")\n\necho \"Uploaded: $TARBALL_PATH\"\n```\n\nNote: the tarball intentionally contains only `setup.sh` and `run.sh` (the\nStep 5 preflight leaves extra files such as the wrapper in the build dir,\nwhich must not be packaged).\n\n### Step 7 - Register the automation\n\nThe entrypoint must contain no shell metacharacters (hence `bash run.sh`).\nThere is no arbitrary `env` field: configuration is baked into `run.sh`.\n\n```bash\ncurl -sm 60 -X POST \"${OPENHANDS_HOST}/api/automation/v1\" \\\n -H \"X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d \"{\n \\\"name\\\": \\\"Dreaming: {owner}/{repo}\\\",\n \\\"trigger\\\": {\\\"type\\\": \\\"cron\\\", \\\"schedule\\\": \\\"{cron_schedule}\\\"},\n \\\"tarball_path\\\": \\\"$TARBALL_PATH\\\",\n \\\"entrypoint\\\": \\\"bash run.sh\\\",\n \\\"timeout\\\": 1800\n }\" | python3 -m json.tool\n```\n\nRecord the returned `id` as `AUTOMATION_ID`.\n\n### Step 8 - Dispatch a first run and surface the PR\n\n```bash\ncurl -sm 60 -X POST \"${OPENHANDS_HOST}/api/automation/v1/${AUTOMATION_ID}/dispatch\" \\\n -H \"X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY\" | python3 -m json.tool\n```\n\nWatch the run until it completes:\n\n```bash\ncurl -sm 60 \"${OPENHANDS_HOST}/api/automation/v1/${AUTOMATION_ID}/runs\" \\\n -H \"X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY\" | python3 -m json.tool\n```\n\nThe CLI emits structured `--json` output; surface the PR link to the user if\none was opened or updated. If no recent conversations were found, tell the\nuser that is normal on a quiet first run.\n\nOn the first run the CLI auto-creates the dreamer Letta agent (tagged\n`openhands-dreaming` and `repo:{owner}/{repo}`); it is reused on every\nsubsequent run so its memory accumulates.\n\n### Step 9 - Confirm\n\nTell the user:\n\n> **Dreaming** is set up!\n>\n> - Automation ID: `{automation_id}`\n> - Target repo: `{owner}/{repo}`\n> - Model: `{model}`\n> - Schedule: `{cron_schedule}`\n> - Doc path: `{to_path}`\n> - State: sweep cursor lives in the OpenHands KV store under\n> `dreaming-cursor:{automation_id}:{owner__repo}`; the dreamer agent's\n> memory lives in its Letta memory filesystem and accumulates across runs.\n>\n> Each run sweeps new local Agent Canvas conversations, reflects on them, and\n> opens or updates a PR when `{to_path}` changes. Nothing happens on runs\n> with no new conversations or no doc changes.\n\n---\n\n## Runtime behaviour (per run)\n\nEach cron run executes `bash run.sh`, which execs\n`openhands-dreaming run --repo ... --model ... --to ... --lookback ... --json`:\n\n1. Reads the sweep cursor from the OpenHands KV store and acquires a lock.\n2. Discovers local Agent Canvas conversations newer than the cursor (first\n run bounded by the computed lookback).\n3. Finds or creates the dreamer Letta agent by tags.\n4. Shallow-clones the target repo and runs `letta dream` to reflect the new\n conversations into the doc.\n5. If the doc changed: commits as the token owner (with a\n `Co-authored-by: Letta Code ` trailer), pushes the\n dream branch, and opens or updates the PR.\n6. Advances the cursor only on full end-to-end success.\n\n---\n\n## Troubleshooting\n\nRun these from the automation working directory (or anywhere the CLI is\ninstalled) with the same env the automation uses:\n\n| Symptom | Try |\n|---|---|\n| Run fails at preflight or setup | `openhands-dreaming doctor --repo {owner}/{repo} --model {model}` - read-only report of token, repo access, provider key, dirs, and Letta binary |\n| Want to see what the sweep will pick up next | `openhands-dreaming cursor get` |\n| Need to replay conversations after a bad run | `openhands-dreaming cursor reset` (safe: Letta dedupes re-fed conversations) |\n| Model provider errors at run time | Confirm the provider API key is a named secret (OpenHands chat LLM config is separate) |\n| PR never appears | Check the run logs for the `--json` result; no doc change means no PR by design |" + "content": "# Dreaming Automation\n\nCreate a cron automation that dreams over **one chosen local OpenHands\nconversation**: on each run it feeds whatever is new in that conversation to a\npersistent Letta \"dreamer\" agent for memory reflection and turns what it\nlearned into a pull request against the target repository's `AGENTS.md` (or\nanother doc path you choose). Users pick the conversation by NAME; this skill\nresolves it to a conversation id. To dream over several conversations, set up\none automation per conversation.\n\nThe extension is a thin wrapper: the automation tarball contains only two\nsmall scripts (`setup.sh` + `run.sh`). All logic lives in the pinned npm\npackage `@letta-ai/openhands-dreaming` (commands: `run`, `doctor`,\n`cursor get|set|reset`), which `setup.sh` installs at run time.\n\nMVP scope: local Agent Canvas conversations only (the automation polls on\nyour laptop). Cloud conversation sources are not supported yet.\n\n---\n\n## Prerequisites\n\n### Required secrets\n\nVerify these are set in **OpenHands Settings -> Secrets** (the CLI reads\nthem from the environment or fetches them from the Agent Server secret store\nat run time):\n\n| Secret name | Purpose | Minimum permissions |\n|---|---|---|\n| `GITHUB_TOKEN` | Push the dream branch and open/update the PR | Write access to the target repo (Contents: Read and Write, Pull requests: Read and Write, Metadata: Read) |\n| Provider API key (e.g. `ANTHROPIC_API_KEY`) | The Letta reflection model | Valid key for the chosen model's provider (see Step 2) |\n\n> **Important:** OpenHands' own LLM configuration is SEPARATE from named\n> secrets. Even if the chosen model already works in OpenHands chat, its API\n> key must be added as a named secret for the automation to use it.\n\n---\n\n## Setup Workflow\n\nFollow these steps in order.\n\nNote: the shell executes ONE command per tool call. Every fenced block in\nthis skill is already a single `&&`-chained invocation - run each block as\nits own call, never paste two blocks together, and never combine a heredoc\nwith anything else (rewrite heredocs as `python3 -c` one-liners).\n\n### Step 1 - Collect and validate the target repository\n\nAsk: *\"Which GitHub repository should Dreaming open AGENTS.md PRs against?\n(Format: `owner/repo`, e.g. `myorg/backend`)\"*\n\nValidate the token and push permission in one call (prints three lines on\nsuccess, `MISSING_GITHUB_TOKEN_OR_API_ERROR` otherwise):\n\n```bash\ntest -n \"$GITHUB_TOKEN\" && curl -sm 60 \"https://api.github.com/repos/{owner}/{repo}\" -H \"Authorization: Bearer $GITHUB_TOKEN\" -H \"Accept: application/vnd.github+json\" | python3 -c \"import json,sys; d=json.load(sys.stdin); print('message:', d.get('message')); print('private:', d.get('private')); print('push:', (d.get('permissions') or {}).get('push'))\" || echo \"MISSING_GITHUB_TOKEN_OR_API_ERROR\"\n```\n\n- `MISSING_GITHUB_TOKEN_OR_API_ERROR`: tell the user to add `GITHUB_TOKEN` in\n OpenHands Settings -> Secrets (or report the network error) and stop.\n- `message:` non-None (e.g. `Not Found`, `Bad credentials`): report it and stop.\n- `push:` must print `True`; otherwise tell the user the token cannot push to\n this repo (Dreaming needs to push a branch and open a PR) and stop.\n\nRecord `TARGET_REPO = \"{owner}/{repo}\"`.\n\n### Step 2 - Collect the model handle and verify the provider secret\n\nAsk: *\"Which model should the Letta dreamer agent use for reflection?\n(Format: `provider/model`, e.g. `openai/gpt-5.5` or `anthropic/opus-4.8`)\"*\n\nRecord the answer as `MODEL`. For KNOWN provider prefixes, verify the matching\nsecret exists as an environment variable:\n\n| Model prefix | Required secret |\n|---|---|\n| `anthropic/` | `ANTHROPIC_API_KEY` |\n| `openai/` | `OPENAI_API_KEY` |\n| `google/` | `GOOGLE_GENERATIVE_AI_API_KEY` |\n| `groq/` | `GROQ_API_KEY` |\n| `deepseek/` | `DEEPSEEK_API_KEY` |\n| `openrouter/` | `OPENROUTER_API_KEY` |\n| `ollama-cloud/` | `OLLAMA_API_KEY` |\n| `ollama/` | `OLLAMA_LOCAL_API_KEY` |\n\n- Known prefix, secret missing: tell the user which secret to add in\n OpenHands Settings -> Secrets, then stop until it is added.\n- Unknown prefix: warn that Letta will validate the model handle and its\n provider key at run time, and continue (do not block).\n\nNote: the model is baked into the dreamer agent when it is first created.\nChanging `MODEL` later does not re-model an existing dreamer agent.\n\n### Step 3 - Pick the conversation, doc path, and schedule\n\nAsk: *\"Which conversation should Dreaming reflect over? Give me its name as\nshown in OpenHands (or part of it).\"*\n\nUsers know conversation names, not ids. Resolve the name yourself: each local\nconversation directory has a `meta.json` with a `title`. List them:\n\n```bash\npython3 -c \"import json,glob,os; [print(os.path.basename(os.path.dirname(p)), '|', (json.load(open(p)).get('title') or '(untitled)')) for p in glob.glob(os.path.expanduser('~/.openhands/agent-canvas/dev_conversations/*/meta.json'))]\"\n```\n\nMatch the user's answer against the titles (case-insensitive substring). If\nseveral match, show the candidates and ask the user to pick one. Record the\ndirectory basename as `CONVERSATION_ID` and the title as `CONVERSATION_TITLE`;\nrecord `CONVERSATIONS_DIR` as the ABSOLUTE conversations root (expand `~` -\ne.g. `/Users/you/.openhands/agent-canvas/dev_conversations`): tildes do not\nexpand inside the double quotes in `run.sh`.\n\nIf the user's conversation lives in a different root (for example imported\nClaude Code sessions under `~/.openhands/claude-transcripts`), run the same\nlisting against that root and record it as `CONVERSATIONS_DIR` instead.\n\nAsk: *\"Which doc file should Dreaming maintain in the repo?\n(Press Enter for the default: `AGENTS.md`)\"*\n\nRecord as `TO_PATH` (default `AGENTS.md`).\n\nAsk: *\"How often should Dreaming run?\n(Press Enter for the default: daily at 04:00 UTC -> `0 4 * * *`.\nUse any cron expression for a different cadence)\"*\n\nRecord as `CRON_SCHEDULE` (default `0 4 * * *`). Every run re-checks the\npinned conversation; runs where nothing new happened are cheap no-ops (Letta\ndeduplicates already-reflected messages).\n\n### Step 4 - Build the payload\n\nCopy this skill's two scripts into a temporary build directory and substitute\nthe placeholders in `run.sh`:\n\n```bash\nmkdir -p /tmp/dreaming-build && cp {skill_dir}/scripts/setup.sh /tmp/dreaming-build/setup.sh && cp {skill_dir}/scripts/run.sh /tmp/dreaming-build/run.sh\n```\n\n| Placeholder in `run.sh` | Replace with |\n|---|---|\n| `__TARGET_REPO__` | `TARGET_REPO` |\n| `__MODEL__` | `MODEL` |\n| `__TO_PATH__` | `TO_PATH` |\n| `__CONVERSATIONS_DIR__` | `CONVERSATIONS_DIR` |\n| `__CONVERSATION_ID__` | `CONVERSATION_ID` |\n\nValidate each substituted value against `^[A-Za-z0-9._/-]+$` and re-ask on\nmismatch (the placeholders sit inside double quotes in a bash script).\n\nDo NOT bake secrets into either script: the CLI resolves `GITHUB_TOKEN` and\nthe provider key from the environment or the Agent Server secret store at\nrun time, and `AUTOMATION_*` values are provided by the automation runtime.\n\nValidate both scripts before packaging:\n\n```bash\nbash -n /tmp/dreaming-build/setup.sh && bash -n /tmp/dreaming-build/run.sh && echo \"Syntax OK\"\n```\n\nFix any syntax errors before proceeding.\n\n### Step 5 - Preflight with `doctor`\n\nIf the current environment can run bash and reach the network, dry-run the\ninstall and the read-only preflight from the build directory:\n\n```bash\ncd /tmp/dreaming-build && bash setup.sh && ./openhands-dreaming doctor --repo \"{owner}/{repo}\" --model \"{model}\" --conversations-dir \"{conversations_dir}\"\n```\n\n`doctor` mutates nothing; it checks the token, repo write access, the\nprovider key for the model, the conversations directory, and the Letta\nbinary. If it fails, show the doctor output to the user and stop until the\nunderlying issue is fixed. If the environment cannot run the preflight,\ntell the user it was skipped and why.\n\n### Step 6 - Package and upload\n\nDetermine the Automation backend URL and auth from the ``\nblock in your system context:\n- **OPENHANDS_HOST**: the Automation backend `url_from_agent`\n- **Auth**: `X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY`\n\n```bash\ntar -czf /tmp/dreaming.tar.gz -C /tmp/dreaming-build setup.sh run.sh && TARBALL_PATH=$(curl -sm 60 -X POST \"${OPENHANDS_HOST}/api/automation/v1/uploads?name=dreaming\" -H \"X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY\" -H \"Content-Type: application/gzip\" --data-binary @/tmp/dreaming.tar.gz | python3 -c \"import json,sys; print(json.load(sys.stdin)['tarball_path'])\") && echo \"Uploaded: $TARBALL_PATH\"\n```\n\nNote: the tarball intentionally contains only `setup.sh` and `run.sh` (the\nStep 5 preflight leaves extra files such as the wrapper in the build dir,\nwhich must not be packaged).\n\n### Step 7 - Register the automation\n\nThe entrypoint must contain no shell metacharacters (hence `bash run.sh`).\nThere is no arbitrary `env` field: configuration is baked into `run.sh`.\n\n```bash\ncurl -sm 60 -X POST \"${OPENHANDS_HOST}/api/automation/v1\" \\\n -H \"X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY\" \\\n -H \"Content-Type: application/json\" \\\n -d \"{\n \\\"name\\\": \\\"Dreaming: \\\\\\\"{conversation_title}\\\\\\\" -> {owner}/{repo}\\\",\n \\\"trigger\\\": {\\\"type\\\": \\\"cron\\\", \\\"schedule\\\": \\\"{cron_schedule}\\\"},\n \\\"tarball_path\\\": \\\"$TARBALL_PATH\\\",\n \\\"entrypoint\\\": \\\"bash run.sh\\\",\n \\\"timeout\\\": 1800\n }\" | python3 -m json.tool\n```\n\nRecord the returned `id` as `AUTOMATION_ID`.\n\n### Step 8 - Dispatch a first run and surface the PR\n\n```bash\ncurl -sm 60 -X POST \"${OPENHANDS_HOST}/api/automation/v1/${AUTOMATION_ID}/dispatch\" \\\n -H \"X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY\" | python3 -m json.tool\n```\n\nWatch the run until it completes:\n\n```bash\ncurl -sm 60 \"${OPENHANDS_HOST}/api/automation/v1/${AUTOMATION_ID}/runs\" \\\n -H \"X-Session-API-Key: $OPENHANDS_AUTOMATION_API_KEY\" | python3 -m json.tool\n```\n\nThe CLI emits structured `--json` output; surface the PR link to the user if\none was opened or updated. A `noop` PR result means the reflection produced\nno doc changes - normal when the conversation has little durable content yet.\n\nOn the first run the CLI auto-creates the dreamer Letta agent (tagged\n`openhands-dreaming` and `repo:{owner}/{repo}`); it is reused on every\nsubsequent run so its memory accumulates.\n\n### Step 9 - Confirm\n\nTell the user:\n\n> **Dreaming** is set up!\n>\n> - Automation ID: `{automation_id}`\n> - Automation name: `Dreaming: \"{conversation_title}\" -> {owner}/{repo}`\n> - Conversation: `{conversation_title}` (`{conversation_id}`)\n> - Target repo: `{owner}/{repo}`\n> - Model: `{model}`\n> - Schedule: `{cron_schedule}`\n> - Doc path: `{to_path}`\n> - The dreamer agent's memory lives in its Letta memory filesystem and\n> accumulates across runs.\n>\n> Each run reflects over whatever is new in the conversation and opens or\n> updates a PR when `{to_path}` changes. Runs with nothing new or no doc\n> changes are cheap no-ops.\n\n---\n\n## Runtime behaviour (per run)\n\nEach cron run executes `bash run.sh`, which execs\n`openhands-dreaming run --repo ... --model ... --to ... --conversations-dir\n... --conversation-id ... --json`:\n\n1. Acquires the repo-scoped lock (automations sharing a repo never overlap).\n2. Selects the pinned conversation (a missing conversation fails the run\n loudly - it is a configuration error, not an empty sweep).\n3. Finds or creates the dreamer Letta agent by tags.\n4. Shallow-clones the target repo and runs `letta dream` to reflect whatever\n is new in the conversation into the doc (Letta deduplicates\n already-reflected messages, so quiet runs are cheap no-ops).\n5. If the doc changed: commits as the token owner (with a\n `Co-authored-by: Letta Code ` trailer), pushes the\n dream branch, and opens or updates the PR.\n\n---\n\n## Troubleshooting\n\nRun these from the automation working directory (or anywhere the CLI is\ninstalled) with the same env the automation uses:\n\n| Symptom | Try |\n|---|---|\n| Run fails at preflight or setup | `openhands-dreaming doctor --repo {owner}/{repo} --model {model}` - read-only report of token, repo access, provider key, dirs, and Letta binary |\n| Run fails with \"pinned conversation not found\" | The conversation dir moved or the id is wrong - re-run the Step 3 listing and update `run.sh` |\n| Model provider errors at run time | Confirm the provider API key is a named secret (OpenHands chat LLM config is separate) |\n| PR never appears | Check the run logs for the `--json` result; no doc change means no PR by design |" }, { "name": "evidence-based-citations",