[WIP] A3: memfs memory read surface + core-memory blocks path fix (stacked on #170) — do not merge#172
Merged
Conversation
…ntainerRuntimeAdapter, fix overlay-fallback dispatch Phase 2 of the wizard-fork design (memory/specs/2026-07-18-letta-wizard-fork-design.md §1b, §5): - ContainerRuntimeAdapter gains the provisioning surface (serviceName, generateCompose, scaffold) and the CD image-ref surface (readImageRef, setImageRef, defaultImageRepo). hermesAdapter delegates to the same module functions the call sites used inline — zero behavior change. - Fix the `adapter ?? hermesAdapter` landmine: discover()'s overlay-only branch now dispatches on the overlay's PERSISTED runtime via adapterForRuntime(), not a blind Hermes fallback. Pre-seam rows (no runtime field) keep Hermes semantics. - HarnessService dispatch sites (resolveComposeTarget, currentImage, imageStatus, setAgentImage, duplicate, createOverlay, importFromDir) and the settings route now go through the adapter. - registerContainerAdapter() for future runtimes (claude-code-proxy/custom). 727/727 tests, tsc clean. New: runtime-adapter.test.ts (selection + delegation). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… Letta path
Slice A2 of Swarm Map v1 (spec §2.A2 'the core fork deliverable'; design
2026-07-18-letta-wizard-fork-design.md §3). A Letta deploy inverts the Hermes
layering: bring ONE shared Letta server up, then create each agent over REST —
no per-agent container/port/data-dir/compose.
New: lib/services/letta-deploy-templates.ts
- ensureLettaServer(): idempotent server bring-up (pull → compose up -d with
--env-file → poll GET /v1/agents). Server-wide provider keys written 0600 to
~/.hermes-swarm-map/letta/.env, merged (never blanked) across deploys.
- defaultAgentConfig(): the base CreateAgentConfig.
- deployLettaAgent(): ensure-server → name-clobber check → createAgent.
Deploy route: a runtime==='letta' branch after key resolution that skips the
entire Hermes path; validation is now runtime-aware.
Wizard: runtime toggle (Step 1), single model-handle input (Step 2), Platforms/
Keys degrade to Letta notes, summary + success copy branch. selectRuntime()
resets Hermes-only state on switch so it can't leak into a Letta payload.
docker.ts: start() gained an optional --env-file (top-level compose option) so
server keys flow without mutating process.env.
## memfs over memory blocks (Letta team feedback, 2026-07-21)
Letta's team flagged that the demo's memory_blocks is 'a very old style' and
modern agents use memfs (git-backed context files). defaultAgentConfig now
defaults agent_type to 'letta_v1_agent' and passes persona as `system`; the
legacy memory-blocks path is an explicit opt-in (airlock use-case, pending its
memfs remap). Exact modern payload stays a Phase-4 live-validation item.
## Addressed 9 findings from an adversarial review pass
- clobber check now uses the server-side ?name= filter (bare list is paginated)
- runtime switch resets Hermes-only state; Signal post-deploy connect + key
registry path are Hermes-gated (no stale key/surface leak into Letta)
- writeServerEnv chmods 0600 even on overwrite (writeFileSync mode is create-only)
- added tests: createAgent-reject 502, listAgents-fail 502, persona→system,
docker --env-file argv, name-filter clobber query
752/752 tests (6 new), tsc clean. Draft/do-not-merge; Letta create path mocked —
live validation is Phase 4.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-memory blocks path
Slice A3 of Swarm Map v1 (design §4a; grounded in the 2026-07-21 memfs research,
memory/specs/2026-07-21-letta-memfs-api-and-a3.md). Reshapes the spike's memory
surface around Letta's memfs model instead of the legacy memory-blocks-only view.
Bug fix (confirmed against docs.letta.com): the spike read blocks from
`/v1/agents/{id}/blocks`, which does NOT exist — the real path is
`/v1/agents/{id}/core-memory/blocks`. getMemoryBlocks now hits the correct path.
- letta.ts: correct blocks path; extend LettaBlock to the confirmed schema
(hidden/read_only/metadata/tags); add LettaFile + listFiles()
(GET /v1/agents/{id}/files — the memfs 'what's in context now' view).
- New route: /api/letta/agents/[id]/files proxies the file view same-origin.
- letta-agent-detail.tsx: 'Core memory (blocks)' pane (now with read-only/hidden
badges) + new 'Context files (memfs)' pane showing the file tree, with system/
files marked 'pinned' — framed explicitly as a context-loading indicator, NOT
an access boundary (memfs has no per-file ACL — see the airlock note in spec).
- provider: reframe persona-from-block as a display SEED, not the live source.
All read-only. Block/file WRITES, agent_type default, and the memfs↔REST
live-sync semantics need a live server (Phase 4, itemized in the spec).
Tests: new letta-client.test.ts pins the exact REST paths (guards the /blocks →
/core-memory/blocks regression) + the /files query params. 757/757, tsc + lint clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-07-21)
The /v1/agents/{id}/files endpoint returns { files, next_cursor, has_more },
NOT a bare array (confirmed against self-hosted letta/letta on Primo). listFiles
now unwraps to the files array, tolerating a bare-array shape defensively.
Two new tests pin both response shapes.
Also live-confirmed this slice's core claims: GET /core-memory/blocks -> 200
(old /blocks -> 404), so the path correction was real and necessary.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
juniperbevensee
force-pushed
the
dev/juniperbevensee/letta-a3-memory-ui
branch
from
July 22, 2026 01:22
64740dd to
5fa1ef3
Compare
juniperbevensee
changed the base branch from
dev/juniperbevensee/letta-a2-wizard-deploy
to
main
July 22, 2026 01:22
juniperbevensee
marked this pull request as ready for review
July 22, 2026 01:22
This was referenced Jul 22, 2026
juniperbevensee
added a commit
that referenced
this pull request
Jul 22, 2026
All merge gates satisfied: repo transferred to NimbleCoOrg, owner-derived publisher (#102) shipped the first nimblecoorg image, and the package is now PUBLIC (anon pull verified HTTP 200). Old nimblecoai package stays frozen as the grace window — never delete. Regenerated on post-A-track main (the original branch predated #172's merge and conflicted). Mechanical: 17 files, image path only; camofox + bare hermes-agent untouched. 766/766, tsc clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
juniperbevensee
added a commit
that referenced
this pull request
Jul 22, 2026
…-mt (#173) All merge gates satisfied: repo transferred to NimbleCoOrg, owner-derived publisher (#102) shipped the first nimblecoorg image, and the package is now PUBLIC (anon pull verified HTTP 200). Old nimblecoai package stays frozen as the grace window — never delete. Regenerated on post-A-track main (the original branch predated #172's merge and conflicted). Mechanical: 17 files, image path only; camofox + bare hermes-agent untouched. 766/766, tsc clean. Co-authored-by: Juniper Bevensee <juniperbevensee@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Slice A3 of Swarm Map v1 (design §4a). Stacked on #170 (A2) — do not merge until launch sequence.
Grounded in a 5-facet memfs research pass (43/44 load-bearing claims verified against fetched docs.letta.com / letta.com/blog; adversarially re-fetched). Full API map + Phase-4 items:
memory/specs/2026-07-21-letta-memfs-api-and-a3.md.Bug fix (confirmed)
The spike read core memory from
GET /v1/agents/{id}/blocks— that path does not exist. The real path is/v1/agents/{id}/core-memory/blocks.getMemoryBlocksnow hits the correct endpoint; a test pins it so the regression can't return.What A3 adds (all read-only, typed against confirmed REST)
letta.ts: correct blocks path;LettaBlockextended to the confirmed schema (hidden/read_only/metadata/tags); newLettaFile+listFiles()→GET /v1/agents/{id}/files(memfs 'what's in context now')./api/letta/agents/[id]/files— same-origin proxy for the file view.system/files marked pinned. That marker is framed explicitly as a context-loading indicator, not an access boundary — memfs has no per-file ACL (see the airlock finding).memfs findings that shaped this (and validated A2)
agent_type: 'letta_v1_agent'is the confirmed current/recommended type;systemis an accepted create field — so A2's create payload is validated (I setagent_typeexplicitly, not relying on the undocumented default).read_only; the real boundary must be app-layer. Recorded in the spec.Deferred to Phase 4 (needs a live server)
Block/file writes; the memfs↔REST live-sync question (do the block/file endpoints stay in sync once memfs is enabled?); self-hosted parity for the memfs git server (zero verified data); airlock end-to-end. All itemized in the spec.
Verification
757/757 vitest (5 new pinning the exact REST paths + query params);
tsc --noEmitclean; eslint clean on changed files. Draft/do-not-merge — read path only, live validation is Phase 4.🤖 Generated with Claude Code