npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@letta-ai/openhands-dreaming

v0.1.5

Published

OpenHands automation CLI that distills coding-session transcripts into a repo's AGENTS.md via Letta reflection

Readme

openhands-dreaming

@letta-ai/openhands-dreaming — an OpenHands automation CLI that runs background Letta dreaming over your local Agent Canvas coding sessions: it distills them into durable memory, projects what it learns into the target repo's AGENTS.md, and opens/updates a pull request.

bun add --global @letta-ai/openhands-dreaming

MVP scope

Local Agent Canvas only ("poll locally on your laptop" automation mode):

  • reads conversations from ~/.openhands/agent-canvas/dev_conversations/<conversation-id>/;
  • uses one persistent local Letta dreamer agent per target repo (found/created by tags openhands-dreaming + repo:<owner>/<repo>; memory accumulates in its memfs across runs);
  • passes conversation directories directly to letta dream;
  • opens/updates a GitHub PR when AGENTS.md changes.

OpenHands Cloud sources (--from openhands:<cloud-conversation-id>) and a native dream() SDK integration (letta-agent-sdk) are deliberate follow-ups.

Commands

openhands-dreaming run [flags]      # the sweep (cron entrypoint)
openhands-dreaming doctor           # read-only preflight of the environment
openhands-dreaming cursor get|set|reset   # inspect / mutate the sweep cursor
openhands-dreaming --version

run flags: --repo <owner/repo>, --model <handle>, --to <path> (default AGENTS.md), --base-branch <name> (default: auto-detected from the repo), --conversations-dir <path>, --lookback <minutes> (first-run bound, default 1440), --max-conversations <n> (default 3), --dream-timeout <seconds> (per-conversation letta dream timeout, default 900), --conversation-id <id> (pinned mode: sweep exactly that conversation every run, bypassing the cursor window and exclusion rules — letta's dedupe makes re-sweeps free; a missing conversation fails the run loudly), --dry-run (read-only plan), --json.

doctor checks: repo configured + reachable + push permission, model + its provider API key present, conversations dir exists, letta binary resolves and runs, state store reachable. Every failure mode you would hit in a cron run surfaces there instead.

How a sweep works

  1. Loads config (flags > DREAMING_* env > defaults) and the cursor state from OpenHands Automation KV (AUTOMATION_API_URL/AUTOMATION_KV_TOKEN), or a durable local fallback file when KV is not configured.
  2. Acquires a lock lease so two runs never dream against the same agent. The lock is REPO-scoped (KV key dreaming-lock:<owner>__<repo>), shared across all automations targeting the repo — pinned and sweep automations mutually exclude even though each keeps its own cursor record.
  3. Selects conversations by the global exclusive cursor: events/-dir mtime > processed_through and <= now - 5s (first run bounded by --lookback).
  4. Finds or creates the repo's dreamer agent (dreamer:<repo>; the model is set at agent creation — letta dream takes no model flag).
  5. Shallow-clones the target repo; reuses the open dream PR's branch if one exists, else the remote dream/local/<owner>__<repo> branch, else cuts it fresh from the base branch.
  6. Runs letta dream --memory <agent-id> --from openhands:<conversation-dir> --to <checkout>/AGENTS.md --timeout 900 --json per conversation. A no_payload result (nothing new since the last reflection) is a skip, not a failure.
  7. If AGENTS.md changed: commits as the token owner with a Co-authored-by: Letta Code <[email protected]> trailer, pushes the dream branch (lease-protected force push), and opens or updates the PR. The deterministic title/body summarize the doc diff (changed headings, or the added guidance lines themselves) and name the source conversation; the body ends with 👾 Generated with [Letta Code](https://www.letta.com). When the doc changed AND an OpenHands agent server is reachable (AGENT_SERVER_URL + session key, present in the automation runtime), the CLI additionally dispatches a PR-writer conversation: an OpenHands agent that reads the PR diff and rewrites the title/body as real prose, PATCHing the PR itself via GITHUB_TOKEN. Fire-and-forget and best-effort - the deterministic text remains the fallback. Disable with DREAMING_PR_WRITER=off. Never dispatched for noop runs.
  8. Fires the automation completion callback and advances processed_through only after the entire run succeeds — failed runs are safely retried (Letta dedupes re-fed transcripts by source message id).

Configuration

Non-secret config is passed as flags (baked into the automation entrypoint by the OpenHands dreaming extension). Secrets are read from the environment first; when missing (the local automation runtime does not export named secrets into the subprocess), the CLI fetches them from the Agent Server secret store (GET $AGENT_SERVER_URL/api/settings/secrets/<name> with X-Session-API-Key) and injects them for itself and the letta subprocess.

| Variable | Meaning | |---|---| | GITHUB_TOKEN | required; repo write access (clone, push, PR) | | provider API key | required; matches the model, e.g. OPENAI_API_KEY for openai/* | | DREAMING_TARGET_REPO / DREAMING_MODEL | env alternatives to --repo / --model | | DREAMING_TO / DREAMING_TARGET_BASE_BRANCH | env alternatives to --to / --base-branch | | DREAMING_CONVERSATIONS_DIR | default ~/.openhands/agent-canvas/dev_conversations | | DREAMING_CONVERSATION_ID | pinned mode: sweep exactly this conversation id | | DREAMING_LOOKBACK_MINUTES / DREAMING_MAX_CONVERSATIONS | selection bounds | | DREAMING_TIMEOUT | per-conversation letta dream --timeout seconds (default 900) | | DREAMING_PR_WRITER | off disables the PR-writer conversation dispatch | | DREAMING_STATE_FILE | local fallback state path (default ~/.cache/openhands-dreaming/<owner>__<repo>.json) | | DREAMING_LOCK_TTL_SECONDS | lock lease TTL (default 3600) | | AUTOMATION_API_URL / AUTOMATION_KV_TOKEN | OpenHands KV state store (key dreaming-cursor:<automation-id>:<owner>__<repo>) | | AUTOMATION_CALLBACK_URL / AUTOMATION_CALLBACK_API_KEY / AUTOMATION_RUN_ID | completion callback | | LETTA_BIN | override the letta binary (defaults to the pinned @letta-ai/letta-code dependency) |

Using it from OpenHands

The dreaming extension (skill + automations-catalog entry in OpenHands/extensions) is the intended consumer: /dreaming:setup collects config, bakes it into a two-file tarball (setup.sh installs bun + this CLI; run.sh execs openhands-dreaming run … --json), and registers a cron automation.

Try it without OpenHands

fixtures/conversations/ contains a small fake Agent Canvas conversation (conv-demo) so you can exercise the full pipeline with nothing but this repo, a model API key, and (for the PR step) a GitHub token.

  1. Point a sweep at the fixtures and check the plan (read-only):

    OPENAI_API_KEY=... \
    bun src/cli.ts run --repo <you>/<scratch-repo> --model openai/gpt-5.5 \
      --conversations-dir ./fixtures/conversations --dry-run

    You should see conv-demo in wouldProcess.

  2. Run it for real against a scratch repo you can push to (GITHUB_TOKEN needs write access). Dreaming reflects the fixture conversation and opens a PR whose AGENTS.md captures the conventions planted in the transcript (bun over npm, bun run check before PRs, co-located *.test.ts):

    GITHUB_TOKEN=... OPENAI_API_KEY=... \
    bun src/cli.ts run --repo <you>/<scratch-repo> --model openai/gpt-5.5 \
      --conversations-dir ./fixtures/conversations --json
  3. To test a change you expect to see in AGENTS.md: add a new event file to fixtures/conversations/conv-demo/events/ with the next sequence number and a NEW id — e.g. copy event-00008-*.json, bump the filename to event-00009-<new-uuid>.json, set "id" to the same new uuid, a later "timestamp", and put your convention in the message text. Then re-run step 2 and confirm the PR updates with your addition.

    Two mechanics to know:

    • letta dedupes by event id — editing an existing event's text in place is treated as already-reflected and ignored; new content needs a new id;
    • creating the file bumps the events-dir mtime, which is what makes the next sweep re-select the conversation. If a sweep already advanced past your edit, rewind with bun src/cli.ts cursor reset --repo <you>/<scratch-repo>.

Importing Claude Code sessions

scripts/import-claude.ts converts a Claude Code session transcript into an OpenHands-format conversation directory so dreaming can sweep it too:

bun scripts/import-claude.ts \
  --from ~/.claude/projects/<project>/<session-id>.jsonl \
  --out ~/.openhands/claude-transcripts

Re-run it any time the session grows — it is incremental (a .claude-import.json sidecar tracks the last converted message) and idempotent (event ids are the Claude message uuids, so letta's source_message_id dedupe absorbs any overlap). Known secret patterns (GitHub/OpenAI/Anthropic/Slack tokens, bearer headers) are redacted before anything is written to disk.

To sweep imports on a schedule, register a second dreaming automation whose run.sh bakes --conversations-dir ~/.openhands/claude-transcripts — the KV cursor key is namespaced per automation id, so it cannot collide with the Agent Canvas automation. For a one-off:

GITHUB_TOKEN=... OPENAI_API_KEY=... \
bun src/cli.ts run --repo <owner>/<repo> --model <handle> \
  --conversations-dir ~/.openhands/claude-transcripts --json

Handoff: swapping to the letta-agent-sdk dream()

The planned next step is replacing the letta dream subprocess with the TypeScript dream() from letta-agent-sdk, which also enables multi-conversation reflection (one call over N sources with internal parallelism, instead of this repo's sequential one-conversation-per-call loop). Notes for that change:

The seam is src/letta.ts — nothing else talks to letta. Discovery, state/cursor, git/PR, and the orchestrator are transport-agnostic. The swap should replace findOrCreateDreamerAgent/runDream internals and leave their call sites in src/run.ts mostly intact (the per-conversation loop collapses into one dream({ sources: [...] }) call).

Invariants that must survive (the regression contract):

  1. One real Letta agent per target repo, found by tags (openhands-dreaming + repo:<owner>/<repo>), model set at creation. The SDK takes a memoryDir instead of an agent id — pass the agent's memfs (~/.letta/agents/<agent-id>/memory); do NOT switch to a standalone memory dir (later work assumes a real letta memfs).
  2. Doc projection (--to): today the subprocess owns seeding AGENTS.md into memfs (frontmatter added) and exporting it back out (frontmatter stripped). dream() has no target option — this glue must be re-implemented around the SDK call or added to the SDK itself.
  3. Letta-side dedupe is the correctness boundary. The sweep cursor here is only a performance filter; re-feeding an already-reflected conversation must stay cheap and safe (today: no_payload → skip, never throw). Convergence across cron ticks (a killed run's completed work is skipped quickly next run) depends on letta advancing its own per-conversation cursor — verify the SDK preserves per-source cursoring.
  4. Cursor advance stays all-or-nothing (processed_through moves only after full success). The capped-run prefix rule in src/run.ts cursorAdvanceTarget assumes oldest-first prefix processing; with one batched dream() call decide explicitly whether a partial batch can succeed, and keep the never-skip property (step back on mtime ties; re-feed rather than skip).
  5. Lock lease refresh: today it refreshes between conversations. One long dream() call needs a different cadence (timer or SDK progress callback) so the lease outlives the reflection.
  6. PR conventions: commit author = token owner, Letta Code co-author trailer, docs: titles derived from the doc diff, body diffed against the base branch, 👾 footer.

Validation recipe (no OpenHands needed): bun run check (unit suite encodes the argv/protocol contracts you are replacing — update src/letta.test.ts deliberately, not incidentally), then the fixture flow above with BOTH fixture conversations: fixtures/conversations contains conv-demo and conv-demo-2 with disjoint conventions precisely so multi-conversation reflection can be proven — one sweep should produce one PR whose AGENTS.md captures conventions from both (bun/bun run check from the first; conventional commits/PR-only-workflow from the second). Re-run the sweep afterwards and confirm a NOOP (dedupe + cursor intact).

Development

bun install
bun run check        # typecheck + unit tests
bun src/cli.ts doctor --repo <owner/repo> --model <handle>

Releases are tag-driven: bump version in package.json, tag v<version>, push the tag — .github/workflows/publish.yml runs the checks and publishes to npm (requires the NPM_TOKEN repo secret).

main.py, setup.sh, and deploy.sh are the retired Python prototype, kept as a behavioral reference until the extension-side E2E completes.