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..860ff3d5 --- /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 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, 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/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..dc3ce8a0 --- /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 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" + }, + "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..4c294c75 --- /dev/null +++ b/skills/dreaming/README.md @@ -0,0 +1,44 @@ +# Dreaming + +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 + +This skill is activated by: + +- `/dreaming:setup` + +## How it works + +- 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`. + +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 over my 'payment refactor' conversation for the +> `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..59743b15 --- /dev/null +++ b/skills/dreaming/SKILL.md @@ -0,0 +1,297 @@ +--- +name: dreaming +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 +--- + +# Dreaming Automation + +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 +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. + +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`)"* + +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" && 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. + +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 - Pick the conversation, doc path, and schedule + +Ask: *"Which conversation should Dreaming reflect over? Give me its name as +shown in OpenHands (or part of it)."* + +Users know conversation names, not ids. Resolve the name yourself: each local +conversation directory has a `meta.json` with a `title`. List them: + +```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'))]" +``` + +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`. + +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. + +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: daily at 04:00 UTC -> `0 4 * * *`. +Use any cron expression for a different cadence)"* + +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 + +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` | +| `__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). + +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}" --conversations-dir "{conversations_dir}" +``` + +`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 -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 +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 -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: \\\"{conversation_title}\\\" -> {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 -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 -sm 60 "${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. 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 +subsequent run so its memory accumulates. + +### Step 9 - Confirm + +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}` +> - The dreamer agent's memory lives in its Letta memory filesystem and +> accumulates across runs. +> +> 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 ... --conversations-dir +... --conversation-id ... --json`: + +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 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. + +--- + +## 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 | +| 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 new file mode 100644 index 00000000..a71dafc3 --- /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 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. + +$ARGUMENTS diff --git a/skills/dreaming/scripts/run.sh b/skills/dreaming/scripts/run.sh new file mode 100644 index 00000000..ec8c2bf8 --- /dev/null +++ b/skills/dreaming/scripts/run.sh @@ -0,0 +1,20 @@ +#!/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__" \ + --conversations-dir "__CONVERSATIONS_DIR__" \ + --conversation-id "__CONVERSATION_ID__" \ + --json diff --git a/skills/dreaming/scripts/setup.sh b/skills/dreaming/scripts/setup.sh new file mode 100644 index 00000000..9c647fe1 --- /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: 0.1.3) +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:-0.1.3}" + +# 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 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 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", "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.",