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-code-acp

v0.0.3

Published

Agent Client Protocol adapter for Letta Code (headless stream-json)

Readme

letta-code-acp

Agent Client Protocol (ACP) adapter for Letta Code. Wraps letta -p --output-format stream-json so any ACP-capable client (Zed, Emacs using agent-shell.el, …) can drive Letta agents.

Status: Working prototype. Streams s with results, permission round-trips, and memory diffs. Verified end-to-end against Letta Cloud on the Letta auto (GLM-5.1 and Minimax 2.7 mostly) "model" from Emacs.

Letta agent in Emacs posting to Bluesky via social-cli skill Expanded tool call body showing console output and memory diff

Architecture

ACP client (Zed / agent-shell)
        │  JSON-RPC over stdio
        ▼
   letta-code-acp  (this package - Node ≥18)
        │  --input-format stream-json / --output-format stream-json
        ▼
   letta -p  (Letta Code CLI; runs the agent, owns tools + MemFS + skills)
        │
        ▼
   Letta Cloud or local backend

Design: treat Letta Code as the harness/runtime, expose only the ACP protocol glue. The adapter does not duplicate filesystem/terminal/permission surfaces — those stay inside Letta Code.

What works

  • initialize returns agentCapabilities + two auth methods (letta-cloud, letta-local).
  • newSession spawns letta -p --input-format stream-json --output-format stream-json --include-partial-messages. Honors _meta:
    • lettaAgentId / env LETTA_AGENT_ID — pin a specific agent.
    • lettaModel / env LETTA_MODEL — e.g. "auto", "claude-opus-4-8", "openai-codex/gpt-5.4".
    • lettaPermissionMode / env LETTA_PERMISSION_MODEstandard | acceptEdits | unrestricted | memory.
    • lettaConversationId / env LETTA_CONVERSATION_ID — resume a conversation by id; "default" attaches to the agent's primary chat.
    • lettaReflectionTrigger + lettaReflectionStepCount — enable dreaming/reflection.
    • newConversation: true — force a fresh conversation.
  • prompt streams:
    • agent_message_chunk — assistant text.
    • agent_thought_chunk — reasoning/thinking blocks.
    • tool_call + tool_call_update — per-tool lifecycle with descriptive titles (Read ~/foo.ts, Bash <description>, memory · str_replace · system/prefs.md).
    • Memory tool diffs: tool_call_update carries {type: "diff", oldText, newText, path} for memory edits.
    • plan notification when the agent uses TodoWrite.
    • Resolves with stopReason: end_turn | cancelled | refusal.
  • cancel sends control_request: interrupt — no fallback SIGINT (keeps session alive for the next prompt).
  • request_permission round-trip: Letta can_use_tool control requests translate to ACP session/request_permission; selected/cancelled outcomes flow back as control_response { behavior: allow | deny }.
  • Real tool errors (status: error in tool_return_message, or success-with-error-body heuristic) surface as failed updates with the error text in content.

Not yet

  • session/load + session/list (need persistent sessionId ↔ agent mapping).
  • fs/* and terminal/* client-side routing. Letta executes tools itself; ACP host sees them passively.
  • MemFS conflict surfacing (#808) as a clean ACP error.
  • Mid-session setSessionModel / setSessionMode — spawn-time flags work, live swap requires upstream support.
  • Image input (multipart prompt) — needs upstream headless parser change.

Build

bun install   # or npm install
bun run build

Run the adapter (for ACP clients to spawn)

node ./dist/cli.js

The adapter speaks ACP on stdio. It expects letta to be on $PATH and either:

  • LETTA_API_KEY set (Cloud backend), or
  • a connected local provider (letta connect <provider> once).

Store your key in a .env file at the repo root (gitignored):

LETTA_API_KEY=letta-...

Smoke test

LETTA_API_KEY=... \
  bun run smoke -- "Reply with exactly: pong" agent-<id>

Drives the adapter as a child process, prints every session/update to stderr, prints the final PromptResponse.

Wire-protocol notes

Captured against letta 0.26.x with the auto model:

  • System init arrives as { type: "system", subtype: "init", session_id, agent_id, conversation_id, model, tools, cwd, ... }. Adapter uses this as source of truth for sessionId.
  • Assistant + reasoning content arrive inside stream_event with event.message_type in {assistant_message, reasoning_message, …_delta}.
  • Tool calls stream via stream_event + event.message_type = "approval_request_message". First delta carries tool_call.name + tool_call.tool_call_id; subsequent deltas stream the arguments string. An auto_approval top-level wire message follows for auto-approved tools; can_use_tool fires for tools requiring human approval.
  • The terminating result message carries subtype: success | interrupted | error plus duration_ms, num_turns, final result text, and aggregated usage.

See src/wire.ts for the typed subset and src/mapping.ts for the translation.

Use from Emacs (agent-shell)

This repo ships agent-shell-letta.el, an adapter for xenodium/agent-shell.

Prerequisites

  • Doom Emacs (or vanilla Emacs with use-package).
  • agent-shell installed.
  • letta CLI on your $PATH. See letta-ai/letta-code for current install instructions. Note: this adapter requires two patches not yet merged upstream (see Upstream fork below). Without them, tool results won't appear and interrupt may be unreliable.
  • This repo cloned somewhere, built (bun run build).
  • A Letta Cloud account and an agent ID. Sign up at letta.com, create an agent, and copy its ID from the agent settings.

packages.el (Doom)

(package! acp)
(package! agent-shell
  :recipe (:host github :repo "xenodium/agent-shell"))
;; Optional: macOS notifications + file-copy helpers
(package! agent-shell-macext
  :recipe (:host github :repo "cxa/agent-shell-macext" :branch "main"))

config.el / agent-shell.el

;; Add letta-code-acp to load-path so agent-shell-letta is available.
(add-to-list 'load-path "~/path/to/letta-code-acp")
(require 'agent-shell-letta)

(after! agent-shell
  ;; Expand reasoning and tool-use folds by default — Letta turns can be long.
  (setq agent-shell-thought-process-expand-by-default t
        agent-shell-tool-use-expand-by-default t)

  ;; Letta adapter — use expand-file-name; tilde won't expand in a process command list.
  (setq agent-shell-letta-acp-command
        (list (expand-file-name "~/path/to/letta-code-acp/bin/letta-code-acp")))

  ;; Load LETTA_API_KEY from .env and inherit shell PATH (so `letta` resolves).
  (setq agent-shell-letta-environment
        (agent-shell-make-environment-variables
         :inherit-env t
         :load-env (expand-file-name "~/path/to/letta-code-acp/.env")))

  ;; Model and permission mode (agent-id goes in secrets.el, see below).
  (setq agent-shell-letta-model "auto")
  (setq agent-shell-letta-permission-mode "standard")

  ;; Set t to trace adapter ↔ Letta wire traffic in *Messages*.
  (setq agent-shell-letta-debug nil))

Store the agent ID in secrets (keep it out of version control)

Find your agent ID in the Letta Cloud UI (agent settings page) — it looks like agent-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx. Store it in a file that isn't committed to your dotfiles repo:

;; secrets.el or similar — not in version control
(setq agent-shell-letta-agent-id "agent-<your-id-here>")

Then M-x agent-shell-letta-start-main-chat to open a session attached to the agent's main chat, or M-x agent-shell-letta-start-conversation to spawn a fresh standalone conversation.

Useful commands

| Command | What it does | |---|---| | M-x agent-shell-letta-start-main-chat | Open a session attached to the agent's main chat | | M-x agent-shell-letta-start-conversation | Open a session in a fresh conversation | | M-x agent-shell-letta-set-reflection | Configure dreaming trigger + step count | | C-c C-c (in agent-shell buffer) | Cancel the current turn |

Dev loop

Adapter (TypeScript) side:

cd letta-code-acp
bun run build:watch   # rebuilds dist/ on every save

After a rebuild, restart the Emacs session: M-x agent-shell-letta-start-main-chat (or -start-conversation) in a fresh buffer.

Elisp side:

  • Edit agent-shell-letta.el.
  • M-x eval-buffer to reload.
  • Restart the shell session to apply config-affecting changes.

Troubleshooting

  • Shell hangs after prompt, nothing happens: check *Messages* for adapter stderr. Letta prints Missing LETTA_API_KEY when the env var is absent.
  • letta not found: Emacs may not inherit your login shell's $PATH. Set :inherit-env t in agent-shell-make-environment-variables, or load exec-path-from-shell before starting agent-shell.
  • Cancel doesn't abort: C-c C-c sends control_request: interrupt. If the turn was already finishing server-side, Letta cloud may still be processing — the next prompt will get a CONFLICT error. A runs/<id>/cancel API call after interrupt is a planned fix.

Upstream fork

The adapter depends on two patches to letta-ai/letta-code not yet merged upstream. The patched fork is at codeluggage/letta-code, branch feat/headless-emit-tool-return:

  1. Emit tool_return_message in headless stream-json — wires onChunk into executeApprovalBatch so tool results appear on the wire.
  2. Fast-path interrupt + pre-controller latch — synchronous interrupt handling so a runaway thinking turn aborts cleanly.

Both are isolated to src/headless.ts. To build and use the fork:

git clone -b feat/headless-emit-tool-return https://github.com/codeluggage/letta-code
cd letta-code
bun install && bun run build   # produces letta.js

Then point the adapter at it:

LETTA_BIN=/path/to/letta-code/letta.js bun run smoke -- "ping" agent-<id>

Or set LETTA_BIN in your .env file. The adapter falls back to letta on $PATH if LETTA_BIN is not set.

Acknowledgements

License

Apache-2.0. Letta Code itself is Apache-2.0.