Skip to content

[WIP] A3: memfs memory read surface + core-memory blocks path fix (stacked on #170) — do not merge#172

Merged
juniperbevensee merged 4 commits into
mainfrom
dev/juniperbevensee/letta-a3-memory-ui
Jul 22, 2026
Merged

[WIP] A3: memfs memory read surface + core-memory blocks path fix (stacked on #170) — do not merge#172
juniperbevensee merged 4 commits into
mainfrom
dev/juniperbevensee/letta-a3-memory-ui

Conversation

@juniperbevensee

Copy link
Copy Markdown
Collaborator

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}/blocksthat path does not exist. The real path is /v1/agents/{id}/core-memory/blocks. getMemoryBlocks now 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; LettaBlock extended to the confirmed schema (hidden/read_only/metadata/tags); new LettaFile + listFiles()GET /v1/agents/{id}/files (memfs 'what's in context now').
  • New route /api/letta/agents/[id]/files — same-origin proxy for the file view.
  • Detail view: 'Core memory (blocks)' pane (read-only/hidden badges) + new 'Context files (memfs)' pane rendering the git-backed file tree, with 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).
  • provider: persona-from-block reframed as a display seed, not the live source.

memfs findings that shaped this (and validated A2)

  • agent_type: 'letta_v1_agent' is the confirmed current/recommended type; system is an accepted create field — so A2's create payload is validated (I set agent_type explicitly, not relying on the undocumented default).
  • Airlock (v1.1) constraint: no per-file ACL; sharing is block-level with global 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 --noEmit clean; eslint clean on changed files. Draft/do-not-merge — read path only, live validation is Phase 4.

🤖 Generated with Claude Code

juniperbevensee and others added 4 commits July 22, 2026 13:20
…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
juniperbevensee force-pushed the dev/juniperbevensee/letta-a3-memory-ui branch from 64740dd to 5fa1ef3 Compare July 22, 2026 01:22
@juniperbevensee
juniperbevensee changed the base branch from dev/juniperbevensee/letta-a2-wizard-deploy to main July 22, 2026 01:22
@juniperbevensee
juniperbevensee marked this pull request as ready for review July 22, 2026 01:22
@juniperbevensee
juniperbevensee merged commit c1b41f8 into main Jul 22, 2026
1 check passed
@juniperbevensee
juniperbevensee deleted the dev/juniperbevensee/letta-a3-memory-ui branch July 22, 2026 01:23
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant