Commits
Store agent ID in SQLite config table. First run creates the agent,
subsequent runs reuse it — no more creating a new agent each time.
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
Tools were only registered on the ephemeral createAgent session
(which gets closed immediately). The working createSession needs
tools + bypassPermissions too, otherwise the agent cant see or
call island_save.
Also simplified tool schema to flat strings (parsed on execute)
since nested array-of-objects schemas may not register reliably.
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
- Revert to runTurn() — stream() yields token deltas not messages
- Add allowedTools: [island_save] so agent cant use memory or other
built-in tools
- Remove unused SDKMessage import
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
Speeds up island:analyze significantly. Override with --model=claude-sonnet-4-20250514 etc.
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
Use session.send() + session.stream() instead of runTurn() so the
agent thinking, tool calls, and results are logged to stderr in
real-time as the analysis runs.
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
Replace rule-based keyword matching with LLM-powered analysis:
- Agent receives full island context (vertices, edges, domains, contributors)
- Agent calls island_save tool with structured analysis
- Produces real narrative synthesis instead of template strings
- Removes carry dependency — works from discovered component data alone
- Keeps same SQLite schema, CLI flags, and output format
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
- Extract shared island utilities (islandId, findComponents, etc.) into island-shared.ts
- All island:* commands now use 12-char lexmin hash as canonical island ID
- Regenerate lexicons/generated/ with org.latha.island namespace
- Fix island:analyze execFileSync import and null guards
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
- Frontend: /island/<12-hex-hash> replaces /strata?id=X, with legacy redirect
- Worker: SPA fallback includes /island/*, computes islandHash from lexmin
- island:analyze: fix execFileSync import (node:child_process), guard null takeaway/domain
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
Same component always gets the same rkey, so re-publishing updates
the existing record instead of creating duplicates.
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
- Extract shared carry query, URL normalization, and component logic into island-shared.ts
- island:detect: simplified, uses shared utils
- island:discover: improved component reporting
- island:explore: enhanced carry-backed scoring
- Add island:publish stub
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
Runs carry-backed analysis on connected components found by island:discover:
- Matches carry connections against island URLs to find bridge edges
- Extracts themes from island text corpus
- Flags hot edges connected to carry touchpoints
- Finds tensions from carry citations
- Generates synthesis and open questions
- Writes results to SQLite (not PDS — use island:publish for that)
Usage: bun island:analyze 1 | bun island:analyze --all
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
Lexicon namespace, source files, and generated types all updated.
Fixed hyphenated def IDs (web-search → webSearch, etc.) and
invalid schema types (json → unknown, inline object → ref).
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
- island:discover: BFS to find connected components in global network.cosmik.connection graph, caches in SQLite
- island:detect: match our carry graph against discovered components to find closest island
- island:explore: find fringe nodes and suggest bridge edges back into island
- connection:detect: compare our carry graph against individual DIDs' connection records
- vault:analyze: parse Obsidian vault notes into carry data via letta-code-sdk agent
Dependencies: @atproto/api, @letta-ai/letta-code-sdk, jsdom
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
The mapper was still trying to TF-IDF match carry entity names to
island vertex titles, producing garbage like "Vect" matching
"disinformation vector". The fundamental problem: carry entity URLs
and island vertex URLs have zero overlap — they track different things
(homepages vs articles).
Solution: follow carry semantics directly. A carry connection is
source → relation → target. If one side is in the island, create a
bridge. If neither side is in the island, emit as a "nearby" connection
that expands the knowledge graph. No keyword matching at all.
Changes:
- Remove TF-IDF scoring, tokenization, findBestVertexMatch
- Remove keywordOverlapSets, computeIdf, tfidfScore
- matchCarryData now only checks URL overlap and emits carry triples
- Fix synthesis to use deduped count (was showing 53, now shows 20)
Result: 20 connections, all real carry semantic triples:
- Cosmik Network → built → Semble
- Dialog DB → related-to → Ink and Switch
- Astera Institute → related-to → Arcadia Science
etc.
None bridge to the island (zero URL overlap) — honest result.
Tested locally with docker run -v carry-vault:/data/carry.
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
The mapper was producing garbage connections (e.g. "checkin" daily note
matched to mental health vertices) because it used raw keyword overlap
on vault note content. Carry already exports structured semantic triples
via `carry query --format json`.
Changes:
- Install carry binary in container (patchelf for Nix→Debian compat)
- Run `carry query` during sync to export JSON (entities, citations,
connections) — always runs, not just after git pull
- Rewrite mapper to use carry structured data:
1. Carry connections: match by URL overlap with island vertices,
resolve entity names to URLs via carry entity index
2. Carry entities/citations: match by TF-IDF name similarity
3. D1 carry data: same structured matching
- Remove vault note content matching entirely
- Remove Letta URL discovery (carry records already have URLs)
- Add TF-IDF weighting with IDF computed across carry + island corpus
- Expand stop words to filter common vault noise
- Add connections to D1 getCarryData (worker reads connection records)
- Remove Letta env vars from worker/container
Performance: 301-vertex island analysis drops from 3.5min to 0.26s.
Quality: all connections are real semantic triples, no more noise.
Tested locally with `docker run -v carry-vault:/data/carry`.
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
The O(n*m) keyword overlap between vault/carry notes and island
vertices was too slow for the full 301-vertex island. Now precomputes
token sets once and reuses them across all comparisons.
- tokenize() returns Set<string> for fast membership checks
- keywordOverlap(setA, textB) takes a precomputed set
- keywordOverlapSets(setA, setB) for set-set intersection
- findBestVertexMatch accepts precomputed vertexTokenSets
- All callers updated to pass precomputed sets
Full island analysis (301 vertices, 654 vault notes, 42 carry notes):
completes in ~3.5min (dominated by 10 Letta API calls for URL discovery).
Matching itself is now sub-second.
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
When a carry or vault note has no https:// URL, the analysis now calls
the Letta API (url-discoverer agent with tavily_search tool) to find
the canonical URL. This ensures all connection records have valid
https:// targets.
- Created url-discoverer agent (agent-36607e8a) on Letta Cloud
- Container calls Letta API for notes lacking URLs during analysis
- Secrets: LETTA_API_KEY, LETTA_AGENT_ID in Cloudflare
- Fixed async/await for findFilesystemTouchpoints (was returning Promise)
- Health endpoint shows lettaKeySet and lettaAgentId
Test: 14 connections found for single-vertex island, all with https://
targets discovered via Letta web search.
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
carry:// and vault:// URI schemes are not valid connection targets.
The graph only deals with https:// URLs.
- Filesystem touchpoints: extract the https:// URL from each carry/vault
note and use it as the target. Notes without URLs are skipped.
- D1 carry touchpoints: use the carry record url field (not atUri) as
the target for entities and reasonings. Citations already used url.
- Deleted 41 garbage connection records from PDS (carry://, vault://,
and at:// targets).
- Removed temporary cleanupConnections endpoint.
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
Container now syncs both vault (ob sync) and carry (git pull) on startup:
- Fixed obsidian-cli → obsidian-headless (correct npm package name)
- Fixed ob sync path syntax (--path flag, not positional arg)
- Container writes auth token + vault config to ~/.config/obsidian-headless/
- Secrets: OBSIDIAN_AUTH_TOKEN, OBSIDIAN_ENCRYPTION_KEY, OBSIDIAN_ENCRYPTION_SALT
- Container health endpoint shows vaultFiles, carryFiles, token status
- Sync endpoint returns vault/carry log output for debugging
Result: 654 vault files + 42 carry files synced in container.
Analysis now finds 279 touchpoints (vault + carry) against island vertices.
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
The strata analysis now cross-references island vertices against:
1. Carry data from filesystem (synced via git clone from GitHub)
2. Structured carry records from D1 (entities, citations, reasonings)
When semantic matches are found, new network.cosmik.connection records
are created on PDS linking island vertices to carry records.
Key changes:
- Container syncs carry vault from GitHub (CARRY_GITHUB_TOKEN secret)
- Analysis reads vault/carry markdown from filesystem
- findCarryTouchpoints matches carry records to island vertices
- findFilesystemTouchpoints matches vault/carry notes to vertices
- Worker creates connection records on PDS via createRecord
- Frontend sends vertex metadata (titles) with analyze request
- Added containerHealth and syncContainer endpoints
Test: 20 carry touchpoints found for AI island, matching citations
(Knowledge Synthesis, PROV-K), entities (Jacky Alciné, Dawn Foster),
and connections (Khayame-Vickers, Matt Akamatsu-Discourse Graphs).
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
Analysis now cross-references island vertices against carry data
(entities, citations, reasonings) from D1. When semantic matches are
found, new network.cosmik.connection records are created on PDS linking
the island vertex to the carry record.
Flow:
1. Worker reads carry records from D1 (records_entity, records_citation,
records_reasoning)
2. Passes carryData to container alongside island
3. Container matches carry records against island vertices by keyword
overlap (URL match, title overlap, takeaway overlap)
4. Returns newConnections array
5. Worker creates connection records on PDS via createRecord
6. New connection URIs added to highlights
No carry touchpoints found yet — carry data needs to be published to
AT Protocol first (entities, citations, reasonings) for contrail to
ingest into D1.
Removed carryRefs from frontend (replaced by real connection records).
Removed filesystem-based vault/carry reading from analysis.ts.
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
The old analysis.connections field contained fabricated connections
including Japanese text from a Bluesky post. Removed connections from
the PDS record and added cleanup to updateRecord to delete stale
fields. Restored unicode edge icons (the Japanese was in the data,
not the icons). Fixed pdsls.dev link (no encodeURIComponent).
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
Unicode arrows/symbols (U+2295, U+26D4, etc.) render as CJK in some
fonts. Replaced with ASCII equivalents. Edge title max-width 200px
was too short — now 60% of container.
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
- Highlights section shows only hot edges with star badges
- Edges section shows all edges without hot styling (no duplication)
- recordUri now pulled from getRecord response onto island object
- pdsls.dev link uses actual recordUri
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
Also persisted analysis highlights to D1/PDS for island 3mlrokf4gho2i.
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
Node --experimental-strip-types requires .ts extensions in imports,
not .js. Also adds better error reporting in callContainer.
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
Remove the separate connections/relationships layer from strata analysis.
All edges are now network.cosmik.connection records. The analysis flags
existing edges as "hot" (semantically significant) via a highlights array
of AT URIs. Frontend renders hot edges with a star badge and accent border.
Lexicon changes:
- Remove #connection def and token defs (supports, contradicts, etc.)
- Remove connections from structuredAnalysis
- Add highlights (at-uri[]) to structuredAnalysis
Analysis changes:
- Replace findConnections with findHotEdges — scores edges by
vault/carry overlap and connection type, returns top AT URIs
- Remove validateConnections (no longer needed)
- Remove Connection interface
Worker changes:
- Replace relationships flattening with highlights passthrough
Frontend changes:
- Remove Relationships section
- Hot edges get .hot-edge class, star badge, accent border
- Edges header shows "N hot" count
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
Was dropped in the edit-paradigm rewrite. Now rendered as editable
items with entity linking (Semble pages) and arrow styling.
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
Inline editing on all analysis fields (title, synthesis, themes,
tensions, questions). Theme tags are editable chips with add/remove.
Regenerate button re-runs analysis with diff highlighting (purple glow
on changed fields). Save to PDS button writes updated record via
com.atproto.repo.putRecord. Editable = human, generatable = machine,
symbiotic loop.
Also adds PUT /xrpc/org.latha.strata.updateRecord endpoint and
PDS_APP_PASSWORD worker secret for PDS writes.
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
pdsls.dev/at://did:plc:.../org.latha.strata/rkey#record
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
pdsls.dev links are /{handle}/{collection}/{rkey}, not /{at-uri}.
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
Links to pdsls.dev with the record AT URI so you can inspect
the raw lexicon record on the user's PDS.
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
Two strata records can share the same graph (same connected component)
but have different analyses. The lexmin hash conflated them. Now the ID
is the record rkey (e.g. 3mlrokf4gho2i), so each strata record is
distinct. Also simplifies getIsland — direct URI lookup instead of
scanning all records.
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
resolveVertexMeta was doing 14 sequential D1 queries (cards+citations,
chunked at 50). Now builds all statements upfront and runs them in a
single db.batch() call. Also removed ensureContrailReady from getIslands
entirely — it only needs D1.
940ms → 320ms cold, 200ms warm.
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
- Canvas height 200px → 320px (was cramming 301 nodes vertically)
- Remove hardcoded width/height HTML attrs — constructor sizes via
getBoundingClientRect + devicePixelRatio
- Repulsion force 300 → 500 for better spread
- Warmup ticks 120 → 200 for settled layout
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
Explore page fetches ?summary=true — skips vertexMeta (244KB saved)
and trims edges to source/target/connectionType. Full data loads
on demand when viewing an island detail. Also skips contrail init
in summary mode for faster cold starts.
523KB → 216KB, 1.2s → 230ms cold, 90ms warm.
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
Landing page only needs a json_extract query, not contrail schema
init. Cold start went from ~450ms to ~170ms.
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
Landing page only needs id, lexmin, title — extract those via
json_extract instead of pulling the full record (117KB each).
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
Lexicon: connections field now holds objects {uri, source, target,
connectionType, note} instead of bare AT URIs. The graph renders
straight from the record — no chunked D1 queries needed.
Backfilled both existing records via PDS putRecord.
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
- Lexicon: add connections field (array of AT URIs)
- getIslands: reconstruct island from stored connections in chunks of 50
- Backfill existing records with 341 connection URIs each
- Landing page loads summaries, frontend fetches full data via API
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
- Island interface: vertices, edges, vertexMeta now optional
- Card renderer shows title preview for summaries, graph for full data
- summarizeIsland checks title before vertices
- Fixed landing page to query D1 directly instead of internal fetch
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
- Dockerfile COPY paths relative to container/ build context
- Landing page now uses getIslands (lightweight) instead of listStrataIslands
- Removed listStrataIslands (dead code)
- wrangler.jsonc with container + DO + migrations config
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
- GET /xrpc/org.latha.strata.getIsland?id=<id>
- Accepts stable ID (sha256 of lexmin) or AT URI
- Returns full island with vertices, edges, metadata, and strata analysis
- Falls back to deriving island from seed if no strata record exists
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
- deriveIsland is now POST (procedure) — takes optional seed in body
- getIslands is a lightweight GET query — returns summaries from
records_strata without deriving islands on every request
- Added org.latha.strata.getIslands lexicon
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
- Container reads vault via ob sync, carry via git pull
- deriveIsland: with seed → single island, without → all islands
- analyze: cross-references island against vault+carry
- Lexicon methods: org.latha.strata.deriveIsland, org.latha.strata.analyze
- StrataContainer Durable Object manages container lifecycle
- carry-vault pushed to github.com/codegod100/carry-vault (private)
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
- Removed island_cache table and rebuildIslandCache cron
- Removed legacy PUT endpoints (putSummary, putAnalysis)
- Landing page and getIslands now query records_strata via listStrataIslands()
- Each strata record derives its island from lexmin on demand
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
- strata-analyze.py now creates org.latha.strata records on PDS
instead of pushing to API endpoints
- Added deriveIsland() with BFS traversal (recursive CTE OOMs in D1)
- New xrpc endpoints:
- GET /xrpc/org.latha.strata.getRecord?uri=<at-uri>
- GET /xrpc/org.latha.strata.listRecords
- GET /xrpc/org.latha.strata.getIslands
- Frontend supports AT URI as strata page ID
- Lexicon updated: added title field, relaxed source.uri format
- Migrated all /api/ routes to /xrpc/ namespace
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
LLM was paraphrasing/abbreviating titles which broke linking.
Added explicit instruction to use EXACT titles from island data.
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
- Links now point to semble.so/url/<encoded-uri> instead of raw URLs
- Excludes at:// titles from link map
- Added substring matching: short names from titles (before delimiters/
parens) are linked if they appear in relationship text
- Fixes most "leads to" etc being unlinked
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
LLM now produces a [Title] section — a single sentence capturing
the core argument rather than a fragmented topic list. Uploaded
as the island summary via PUT /api/strata/islands/:id/summary.
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
Cards and collections stored as at:// URIs were showing as raw
URIs with type "unknown". Now resolveVertexMeta also queries
records_card and records_collection by AT URI to get titles
and descriptions. Added card/collection type colors to force graph.
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
Removed DiscourseGraphRenderer class, types, CSS, and rendering.
The discourse_graph data is still computed and stored in strata
analysis — just not rendered on the page for now.
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
semble.so/url/<encoded-source-url> instead of profile/<did>.
Also: discourse graph now pan/zoom with fixed viewport.
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
- Nodes as rounded-rect cards with type badge + short title
- Canvas width scales with max nodes per layer (min 90px spacing)
- Canvas height scales with active layer count
- Hover tooltip shows full title
- Edges connect bottom/top of cards with bezier curves
- Legend moved to bottom-right
- Faded edges (44 alpha) by default, bright on hover
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
Layered graph modeled on Discourse Graphs (discoursegraphs.com):
4 node types — QUE (question), CLM (claim), EVD (evidence),
SRC (source) — arranged in horizontal layers top to bottom.
Edges typed as supports/opposes/informs/addresses with color
coding and curved bezier paths.
Computation: LLM classifies vertices and identifies discourse
relations during strata analysis. Data uploaded via API as
discourse_graph field in strata JSON.
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
Semble has no dedicated connection record page, so edges now link
to the connection author profile: semble.so/profile/<did>
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
Edge rows are now <a> links to semble.so/at/<record-uri>.
Removed vertex list — only edges shown. Removed "Strata" h2.
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
Layout: summary + graph at top, synthesis below, then themes,
relationships, tensions, open questions, and full node/edge lists
at the bottom.
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
On rebuild, compute the new lexmin-hash ID for each old island
and carry forward its summary and strata data to the new row.
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
Tools were only registered on the ephemeral createAgent session
(which gets closed immediately). The working createSession needs
tools + bypassPermissions too, otherwise the agent cant see or
call island_save.
Also simplified tool schema to flat strings (parsed on execute)
since nested array-of-objects schemas may not register reliably.
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
Replace rule-based keyword matching with LLM-powered analysis:
- Agent receives full island context (vertices, edges, domains, contributors)
- Agent calls island_save tool with structured analysis
- Produces real narrative synthesis instead of template strings
- Removes carry dependency — works from discovered component data alone
- Keeps same SQLite schema, CLI flags, and output format
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
- Extract shared island utilities (islandId, findComponents, etc.) into island-shared.ts
- All island:* commands now use 12-char lexmin hash as canonical island ID
- Regenerate lexicons/generated/ with org.latha.island namespace
- Fix island:analyze execFileSync import and null guards
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
- Frontend: /island/<12-hex-hash> replaces /strata?id=X, with legacy redirect
- Worker: SPA fallback includes /island/*, computes islandHash from lexmin
- island:analyze: fix execFileSync import (node:child_process), guard null takeaway/domain
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
- Extract shared carry query, URL normalization, and component logic into island-shared.ts
- island:detect: simplified, uses shared utils
- island:discover: improved component reporting
- island:explore: enhanced carry-backed scoring
- Add island:publish stub
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
Runs carry-backed analysis on connected components found by island:discover:
- Matches carry connections against island URLs to find bridge edges
- Extracts themes from island text corpus
- Flags hot edges connected to carry touchpoints
- Finds tensions from carry citations
- Generates synthesis and open questions
- Writes results to SQLite (not PDS — use island:publish for that)
Usage: bun island:analyze 1 | bun island:analyze --all
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
- island:discover: BFS to find connected components in global network.cosmik.connection graph, caches in SQLite
- island:detect: match our carry graph against discovered components to find closest island
- island:explore: find fringe nodes and suggest bridge edges back into island
- connection:detect: compare our carry graph against individual DIDs' connection records
- vault:analyze: parse Obsidian vault notes into carry data via letta-code-sdk agent
Dependencies: @atproto/api, @letta-ai/letta-code-sdk, jsdom
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
The mapper was still trying to TF-IDF match carry entity names to
island vertex titles, producing garbage like "Vect" matching
"disinformation vector". The fundamental problem: carry entity URLs
and island vertex URLs have zero overlap — they track different things
(homepages vs articles).
Solution: follow carry semantics directly. A carry connection is
source → relation → target. If one side is in the island, create a
bridge. If neither side is in the island, emit as a "nearby" connection
that expands the knowledge graph. No keyword matching at all.
Changes:
- Remove TF-IDF scoring, tokenization, findBestVertexMatch
- Remove keywordOverlapSets, computeIdf, tfidfScore
- matchCarryData now only checks URL overlap and emits carry triples
- Fix synthesis to use deduped count (was showing 53, now shows 20)
Result: 20 connections, all real carry semantic triples:
- Cosmik Network → built → Semble
- Dialog DB → related-to → Ink and Switch
- Astera Institute → related-to → Arcadia Science
etc.
None bridge to the island (zero URL overlap) — honest result.
Tested locally with docker run -v carry-vault:/data/carry.
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
The mapper was producing garbage connections (e.g. "checkin" daily note
matched to mental health vertices) because it used raw keyword overlap
on vault note content. Carry already exports structured semantic triples
via `carry query --format json`.
Changes:
- Install carry binary in container (patchelf for Nix→Debian compat)
- Run `carry query` during sync to export JSON (entities, citations,
connections) — always runs, not just after git pull
- Rewrite mapper to use carry structured data:
1. Carry connections: match by URL overlap with island vertices,
resolve entity names to URLs via carry entity index
2. Carry entities/citations: match by TF-IDF name similarity
3. D1 carry data: same structured matching
- Remove vault note content matching entirely
- Remove Letta URL discovery (carry records already have URLs)
- Add TF-IDF weighting with IDF computed across carry + island corpus
- Expand stop words to filter common vault noise
- Add connections to D1 getCarryData (worker reads connection records)
- Remove Letta env vars from worker/container
Performance: 301-vertex island analysis drops from 3.5min to 0.26s.
Quality: all connections are real semantic triples, no more noise.
Tested locally with `docker run -v carry-vault:/data/carry`.
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
The O(n*m) keyword overlap between vault/carry notes and island
vertices was too slow for the full 301-vertex island. Now precomputes
token sets once and reuses them across all comparisons.
- tokenize() returns Set<string> for fast membership checks
- keywordOverlap(setA, textB) takes a precomputed set
- keywordOverlapSets(setA, setB) for set-set intersection
- findBestVertexMatch accepts precomputed vertexTokenSets
- All callers updated to pass precomputed sets
Full island analysis (301 vertices, 654 vault notes, 42 carry notes):
completes in ~3.5min (dominated by 10 Letta API calls for URL discovery).
Matching itself is now sub-second.
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
When a carry or vault note has no https:// URL, the analysis now calls
the Letta API (url-discoverer agent with tavily_search tool) to find
the canonical URL. This ensures all connection records have valid
https:// targets.
- Created url-discoverer agent (agent-36607e8a) on Letta Cloud
- Container calls Letta API for notes lacking URLs during analysis
- Secrets: LETTA_API_KEY, LETTA_AGENT_ID in Cloudflare
- Fixed async/await for findFilesystemTouchpoints (was returning Promise)
- Health endpoint shows lettaKeySet and lettaAgentId
Test: 14 connections found for single-vertex island, all with https://
targets discovered via Letta web search.
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
carry:// and vault:// URI schemes are not valid connection targets.
The graph only deals with https:// URLs.
- Filesystem touchpoints: extract the https:// URL from each carry/vault
note and use it as the target. Notes without URLs are skipped.
- D1 carry touchpoints: use the carry record url field (not atUri) as
the target for entities and reasonings. Citations already used url.
- Deleted 41 garbage connection records from PDS (carry://, vault://,
and at:// targets).
- Removed temporary cleanupConnections endpoint.
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
Container now syncs both vault (ob sync) and carry (git pull) on startup:
- Fixed obsidian-cli → obsidian-headless (correct npm package name)
- Fixed ob sync path syntax (--path flag, not positional arg)
- Container writes auth token + vault config to ~/.config/obsidian-headless/
- Secrets: OBSIDIAN_AUTH_TOKEN, OBSIDIAN_ENCRYPTION_KEY, OBSIDIAN_ENCRYPTION_SALT
- Container health endpoint shows vaultFiles, carryFiles, token status
- Sync endpoint returns vault/carry log output for debugging
Result: 654 vault files + 42 carry files synced in container.
Analysis now finds 279 touchpoints (vault + carry) against island vertices.
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
The strata analysis now cross-references island vertices against:
1. Carry data from filesystem (synced via git clone from GitHub)
2. Structured carry records from D1 (entities, citations, reasonings)
When semantic matches are found, new network.cosmik.connection records
are created on PDS linking island vertices to carry records.
Key changes:
- Container syncs carry vault from GitHub (CARRY_GITHUB_TOKEN secret)
- Analysis reads vault/carry markdown from filesystem
- findCarryTouchpoints matches carry records to island vertices
- findFilesystemTouchpoints matches vault/carry notes to vertices
- Worker creates connection records on PDS via createRecord
- Frontend sends vertex metadata (titles) with analyze request
- Added containerHealth and syncContainer endpoints
Test: 20 carry touchpoints found for AI island, matching citations
(Knowledge Synthesis, PROV-K), entities (Jacky Alciné, Dawn Foster),
and connections (Khayame-Vickers, Matt Akamatsu-Discourse Graphs).
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
Analysis now cross-references island vertices against carry data
(entities, citations, reasonings) from D1. When semantic matches are
found, new network.cosmik.connection records are created on PDS linking
the island vertex to the carry record.
Flow:
1. Worker reads carry records from D1 (records_entity, records_citation,
records_reasoning)
2. Passes carryData to container alongside island
3. Container matches carry records against island vertices by keyword
overlap (URL match, title overlap, takeaway overlap)
4. Returns newConnections array
5. Worker creates connection records on PDS via createRecord
6. New connection URIs added to highlights
No carry touchpoints found yet — carry data needs to be published to
AT Protocol first (entities, citations, reasonings) for contrail to
ingest into D1.
Removed carryRefs from frontend (replaced by real connection records).
Removed filesystem-based vault/carry reading from analysis.ts.
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
The old analysis.connections field contained fabricated connections
including Japanese text from a Bluesky post. Removed connections from
the PDS record and added cleanup to updateRecord to delete stale
fields. Restored unicode edge icons (the Japanese was in the data,
not the icons). Fixed pdsls.dev link (no encodeURIComponent).
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
- Highlights section shows only hot edges with star badges
- Edges section shows all edges without hot styling (no duplication)
- recordUri now pulled from getRecord response onto island object
- pdsls.dev link uses actual recordUri
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
Remove the separate connections/relationships layer from strata analysis.
All edges are now network.cosmik.connection records. The analysis flags
existing edges as "hot" (semantically significant) via a highlights array
of AT URIs. Frontend renders hot edges with a star badge and accent border.
Lexicon changes:
- Remove #connection def and token defs (supports, contradicts, etc.)
- Remove connections from structuredAnalysis
- Add highlights (at-uri[]) to structuredAnalysis
Analysis changes:
- Replace findConnections with findHotEdges — scores edges by
vault/carry overlap and connection type, returns top AT URIs
- Remove validateConnections (no longer needed)
- Remove Connection interface
Worker changes:
- Replace relationships flattening with highlights passthrough
Frontend changes:
- Remove Relationships section
- Hot edges get .hot-edge class, star badge, accent border
- Edges header shows "N hot" count
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
Inline editing on all analysis fields (title, synthesis, themes,
tensions, questions). Theme tags are editable chips with add/remove.
Regenerate button re-runs analysis with diff highlighting (purple glow
on changed fields). Save to PDS button writes updated record via
com.atproto.repo.putRecord. Editable = human, generatable = machine,
symbiotic loop.
Also adds PUT /xrpc/org.latha.strata.updateRecord endpoint and
PDS_APP_PASSWORD worker secret for PDS writes.
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
Two strata records can share the same graph (same connected component)
but have different analyses. The lexmin hash conflated them. Now the ID
is the record rkey (e.g. 3mlrokf4gho2i), so each strata record is
distinct. Also simplifies getIsland — direct URI lookup instead of
scanning all records.
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
resolveVertexMeta was doing 14 sequential D1 queries (cards+citations,
chunked at 50). Now builds all statements upfront and runs them in a
single db.batch() call. Also removed ensureContrailReady from getIslands
entirely — it only needs D1.
940ms → 320ms cold, 200ms warm.
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
- Canvas height 200px → 320px (was cramming 301 nodes vertically)
- Remove hardcoded width/height HTML attrs — constructor sizes via
getBoundingClientRect + devicePixelRatio
- Repulsion force 300 → 500 for better spread
- Warmup ticks 120 → 200 for settled layout
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
Explore page fetches ?summary=true — skips vertexMeta (244KB saved)
and trims edges to source/target/connectionType. Full data loads
on demand when viewing an island detail. Also skips contrail init
in summary mode for faster cold starts.
523KB → 216KB, 1.2s → 230ms cold, 90ms warm.
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
Lexicon: connections field now holds objects {uri, source, target,
connectionType, note} instead of bare AT URIs. The graph renders
straight from the record — no chunked D1 queries needed.
Backfilled both existing records via PDS putRecord.
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
- Lexicon: add connections field (array of AT URIs)
- getIslands: reconstruct island from stored connections in chunks of 50
- Backfill existing records with 341 connection URIs each
- Landing page loads summaries, frontend fetches full data via API
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
- Island interface: vertices, edges, vertexMeta now optional
- Card renderer shows title preview for summaries, graph for full data
- summarizeIsland checks title before vertices
- Fixed landing page to query D1 directly instead of internal fetch
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
- Dockerfile COPY paths relative to container/ build context
- Landing page now uses getIslands (lightweight) instead of listStrataIslands
- Removed listStrataIslands (dead code)
- wrangler.jsonc with container + DO + migrations config
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
- GET /xrpc/org.latha.strata.getIsland?id=<id>
- Accepts stable ID (sha256 of lexmin) or AT URI
- Returns full island with vertices, edges, metadata, and strata analysis
- Falls back to deriving island from seed if no strata record exists
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
- deriveIsland is now POST (procedure) — takes optional seed in body
- getIslands is a lightweight GET query — returns summaries from
records_strata without deriving islands on every request
- Added org.latha.strata.getIslands lexicon
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
- Container reads vault via ob sync, carry via git pull
- deriveIsland: with seed → single island, without → all islands
- analyze: cross-references island against vault+carry
- Lexicon methods: org.latha.strata.deriveIsland, org.latha.strata.analyze
- StrataContainer Durable Object manages container lifecycle
- carry-vault pushed to github.com/codegod100/carry-vault (private)
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
- Removed island_cache table and rebuildIslandCache cron
- Removed legacy PUT endpoints (putSummary, putAnalysis)
- Landing page and getIslands now query records_strata via listStrataIslands()
- Each strata record derives its island from lexmin on demand
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
- strata-analyze.py now creates org.latha.strata records on PDS
instead of pushing to API endpoints
- Added deriveIsland() with BFS traversal (recursive CTE OOMs in D1)
- New xrpc endpoints:
- GET /xrpc/org.latha.strata.getRecord?uri=<at-uri>
- GET /xrpc/org.latha.strata.listRecords
- GET /xrpc/org.latha.strata.getIslands
- Frontend supports AT URI as strata page ID
- Lexicon updated: added title field, relaxed source.uri format
- Migrated all /api/ routes to /xrpc/ namespace
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
- Links now point to semble.so/url/<encoded-uri> instead of raw URLs
- Excludes at:// titles from link map
- Added substring matching: short names from titles (before delimiters/
parens) are linked if they appear in relationship text
- Fixes most "leads to" etc being unlinked
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
Cards and collections stored as at:// URIs were showing as raw
URIs with type "unknown". Now resolveVertexMeta also queries
records_card and records_collection by AT URI to get titles
and descriptions. Added card/collection type colors to force graph.
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
- Nodes as rounded-rect cards with type badge + short title
- Canvas width scales with max nodes per layer (min 90px spacing)
- Canvas height scales with active layer count
- Hover tooltip shows full title
- Edges connect bottom/top of cards with bezier curves
- Legend moved to bottom-right
- Faded edges (44 alpha) by default, bright on hover
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>
Layered graph modeled on Discourse Graphs (discoursegraphs.com):
4 node types — QUE (question), CLM (claim), EVD (evidence),
SRC (source) — arranged in horizontal layers top to bottom.
Edges typed as supports/opposes/informs/addresses with color
coding and curved bezier paths.
Computation: LLM classifies vertices and identifies discourse
relations during strata analysis. Data uploaded via API as
discourse_graph field in strata JSON.
👾 Generated with [Letta Code](https://letta.com)
Co-Authored-By: Letta Code <noreply@letta.com>