Documentation Index Fetch the complete documentation index at: https://mintlify.com/ghuntley/loom/llms.txt
Use this file to discover all available pages before exploring further.
Thread System
Loom’s thread system provides persistent conversation history with bidirectional synchronization between local storage and the server. Every coding session is a thread that captures:
Complete message history (user, assistant, tool calls)
Agent state (waiting, executing tools, error)
Git context (branch, commits, remote URL)
Workspace metadata
Architecture
┌─────────────────┐
│ loom CLI │
│ ┌───────────┐ │
│ │ Thread │ │
│ └─────┬─────┘ │
│ │ │
└────────┼────────┘
│
▼
┌────────────────────┐ ┌──────────────────┐
│ SyncingThreadStore │◀─────▶│ LocalThreadStore │
└────────┬───────────┘ └──────────────────┘
│ ~/.local/share/loom/threads/
▼
┌────────────────────┐
│ ThreadSyncClient │
└────────┬───────────┘
│ HTTPS
▼
┌────────────────────┐
│ loom-server │
│ /api/threads │
└────────┬───────────┘
│
▼
┌────────────────────┐
│ PostgreSQL │
│ threads table │
└────────────────────┘
Components:
Thread: Core data model (defined in loom-common-thread/src/model.rs)
LocalThreadStore: XDG-compliant local storage
SyncingThreadStore: Hybrid store with background sync
ThreadSyncClient: HTTP client for server communication
PendingSyncQueue: Offline-first sync queue
Thread Data Model
Thread Structure
pub struct Thread {
// Identity
pub id : ThreadId , // T-<uuidv7>
pub version : u64 , // Optimistic locking
pub created_at : String , // ISO 8601
pub updated_at : String ,
pub last_activity_at : String ,
// Workspace context
pub workspace_root : Option < String >, // /home/user/project
pub cwd : Option < String >, // /home/user/project/src
pub loom_version : Option < String >, // 0.1.0
// Git metadata
pub git_branch : Option < String >, // main
pub git_remote_url : Option < String >, // github.com/user/repo
pub git_initial_branch : Option < String >,
pub git_initial_commit_sha : Option < String >,
pub git_current_commit_sha : Option < String >,
pub git_commits : Vec < String >, // [sha1, sha2, ...]
pub git_start_dirty : Option < bool >,
pub git_end_dirty : Option < bool >,
// LLM configuration
pub provider : Option < String >, // anthropic
pub model : Option < String >, // claude-3-5-sonnet-20241022
// Conversation data
pub conversation : ConversationSnapshot ,
pub agent_state : AgentStateSnapshot ,
pub metadata : ThreadMetadata ,
// Sync metadata
pub is_private : bool , // Never sync if true
pub visibility : ThreadVisibility , // organization, private, public
pub is_shared_with_support : bool ,
}
Thread IDs use UUIDv7 (time-ordered) with a T- prefix:
T-018e2b3c-4d5e-7f8a-9b0c-1d2e3f4a5b6c
│ └────────────────┬──────────────────┘
│ └─ UUIDv7 (sortable by creation time)
└─ Prefix for visual identification
Benefits:
Sortable by creation time
No collision risk (globally unique)
Human-readable prefix
Compatible with databases (indexed strings)
Message Types
{
"role" : "user" ,
"content" : "Add error handling to login function"
}
{
"role" : "assistant" ,
"content" : "I'll add error handling to the login function." ,
"tool_calls" : [
{
"id" : "call_abc123" ,
"tool_name" : "read_file" ,
"arguments_json" : { "path" : "src/auth.rs" }
}
]
}
{
"role" : "tool" ,
"tool_call_id" : "call_abc123" ,
"tool_name" : "read_file" ,
"content" : "{ \" path \" : \" src/auth.rs \" , \" contents \" : \" ... \" }"
}
Agent States
pub enum AgentStateKind {
WaitingForUserInput , // Idle, ready for input
CallingLlm , // Sending request to LLM
ProcessingLlmResponse , // Parsing LLM response
ExecutingTools , // Running tool calls
PostToolsHook , // Post-tool processing (e.g., auto-commit)
Error , // Recoverable error (retries possible)
ShuttingDown , // Graceful shutdown in progress
}
Each state includes:
Retry count (for error recovery)
Last error message (if applicable)
Pending tool calls (for ExecutingTools)
Thread Visibility
pub enum ThreadVisibility {
Organization , // Visible to all org members (default)
Private , // Synced but only owner can access
Public , // Publicly visible (use with caution)
}
Sharing workflow:
# Share with organization
loom share < thread-i d > --visibility organization
# Share with support
loom share < thread-i d > --support
# Make private
loom share < thread-i d > --visibility private
Local Storage
XDG Directory
Threads are stored in:
~/.local/share/loom/threads/
├── T-018e2b3c-4d5e-7f8a-9b0c-1d2e3f4a5b6c.json
├── T-018e2b3c-5e6f-8a9b-0c1d-2e3f4a5b6c7d.json
└── ...
File format: Pretty-printed JSON (human-readable)
LocalThreadStore API
pub trait ThreadStore : Send + Sync {
async fn load ( & self , id : & ThreadId ) -> Result < Option < Thread >>;
async fn save ( & self , thread : & Thread ) -> Result <()>;
async fn list ( & self , limit : u32 ) -> Result < Vec < ThreadSummary >>;
async fn delete ( & self , id : & ThreadId ) -> Result <()>;
async fn save_and_sync ( & self , thread : & Thread ) -> Result <()>;
}
save_and_sync() blocks until server sync completes. Use for commands that exit immediately (e.g., loom share).
Local Search
Fallback when server is unavailable:
pub async fn search ( & self , query : & str , limit : usize ) -> Result < Vec < ThreadSummary >>
Searches:
Thread title
Git branch name
Git remote URL
Commit SHAs (prefix match)
Tags
Message content (full text)
Server Synchronization
Sync Strategy
Loom uses offline-first synchronization:
Save locally first
local_store . save ( thread ) . await ? ;
Queue for sync
pending_sync_queue . enqueue ( SyncOperation :: Upsert ( thread . id));
Background sync
Spawned task processes queue: sync_client . upsert_thread ( thread ) . await ? ;
Handle conflicts
Server returns latest version on conflict. Client merges or retries.
Benefits:
Works offline (local-first)
No blocking on network
Automatic retry on failure
Conflict detection via versioning
SyncingThreadStore
pub struct SyncingThreadStore {
local : LocalThreadStore ,
sync_client : Option < ThreadSyncClient >,
pending_sync : Arc < PendingSyncQueue >,
}
Usage:
let local_store = LocalThreadStore :: from_xdg () ? ;
let sync_client = ThreadSyncClient :: new ( server_url , http_client )
. with_auth_token ( token );
let store = SyncingThreadStore :: with_sync ( local_store , sync_client );
Sync API Endpoints
Upsert Thread
Fetch Thread
List Threads
Search Threads
POST /api/threads
PUT /api/threads/:id
Creates or updates a thread. Server validates:
Thread ownership (via auth token)
Version number (optimistic locking)
Visibility rules
Returns thread if:
User owns thread, OR
Thread visibility allows access
GET /api/threads?limit= 50
Returns user’s threads, sorted by last_activity_at DESC. GET /api/threads/search?q=query & limit = 20
Full-text search powered by PostgreSQL ts_vector.
Git Integration
Loom automatically captures git state:
Snapshot Workflow
fn snapshot_git_state ( thread : & mut Thread , workspace_path : & Path ) {
match detect_repo_status ( workspace_path ) {
Ok ( Some ( status )) => {
// Initial state (first snapshot)
if thread . git_initial_branch . is_none () {
thread . git_initial_branch = status . branch . clone ();
thread . git_initial_commit_sha = status . head . as_ref () . map ( | h | h . sha . clone ());
thread . git_start_dirty = status . is_dirty;
}
// Current state (every save)
thread . git_branch = status . branch;
thread . git_current_commit_sha = status . head . as_ref () . map ( | h | h . sha . clone ());
thread . git_end_dirty = status . is_dirty;
// Track commits
if let Some ( ref sha ) = status . head . as_ref () . map ( | h | h . sha . clone ()) {
if ! thread . git_commits . contains ( sha ) {
thread . git_commits . push ( sha . clone ());
}
}
// Remote URL (normalized)
if thread . git_remote_url . is_none () {
thread . git_remote_url = status . remote_slug;
}
}
Ok ( None ) => { /* Not a git repo */ }
Err ( e ) => { /* Git unavailable */ }
}
}
Called:
On thread creation
After every tool execution
On graceful shutdown (Ctrl+C, EOF)
# Find threads for a branch
loom search "feature/auth"
# Find threads for a repository
loom search "github.com/user/project"
# Find threads with specific commit
loom search "a1b2c3d"
Private Sessions
Create local-only sessions that never sync:
Sets:
is_private = true
visibility = Private
The SyncingThreadStore skips sync for private threads:
if thread . is_private {
tracing :: debug! ( "skipping sync for private thread" );
return Ok (());
}
Use cases:
Sensitive codebases
Offline development
Personal experiments
Thread Lifecycle
Creation
let mut thread = Thread :: new ();
thread . workspace_root = Some ( workspace . display () . to_string ());
snapshot_git_state ( & mut thread , & workspace );
store . save ( & thread ) . await ? ;
User Input
let user_message = Message :: user ( input );
thread . conversation . messages . push ( MessageSnapshot :: from ( & user_message ));
LLM Response
let assistant_message = Message :: assistant_with_tool_calls ( content , tool_calls );
thread . conversation . messages . push ( MessageSnapshot :: from ( & assistant_message ));
Tool Execution
for tool_call in & tool_calls {
let outcome = execute_tool ( registry , tool_call , & ctx ) . await ;
let tool_message = Message :: tool ( & tool_call . id, & tool_call . tool_name, & result );
thread . conversation . messages . push ( MessageSnapshot :: from ( & tool_message ));
}
State Update
thread . agent_state = AgentStateSnapshot {
kind : AgentStateKind :: WaitingForUserInput ,
retries : 0 ,
last_error : None ,
pending_tool_calls : Vec :: new (),
};
snapshot_git_state ( & mut thread , & workspace );
thread . touch (); // Update last_activity_at
store . save ( & thread ) . await ? ;
Resume Behavior
Restores:
Full conversation history
Agent state (resumes from last checkpoint)
Workspace context (warns if workspace changed)
Git state (shows branch/commit changes)
Example output:
Resuming thread: T-018e2b3c-4d5e-7f8a-9b0c-1d2e3f4a5b6c
Title: "Add authentication to API"
Messages: 23
Last activity: 2 hours ago
Git: main @ a1b2c3d (dirty)
Search Implementation
Server-Side Search
PostgreSQL full-text search:
CREATE INDEX threads_search_idx ON threads USING gin(to_tsvector( 'english' ,
coalesce (title, '' ) || ' ' ||
coalesce (git_branch, '' ) || ' ' ||
coalesce ( conversation :: text , '' )
));
Query:
SELECT * FROM threads
WHERE to_tsvector( 'english' , ...) @@ plainto_tsquery( 'english' , $ 1 )
ORDER BY ts_rank(...) DESC
LIMIT $ 2 ;
Local Search Fallback
Substring matching across all fields:
fn matches_query ( & self , thread : & Thread , query : & str ) -> bool {
// Title
if thread . metadata . title . as_ref () . map ( | t | t . to_lowercase () . contains ( query )) . unwrap_or ( false ) {
return true ;
}
// Git metadata
if thread . git_branch . as_ref () . map ( | b | b . to_lowercase () . contains ( query )) . unwrap_or ( false ) {
return true ;
}
// Commit SHAs (prefix)
for sha in & thread . git_commits {
if sha . to_lowercase () . starts_with ( query ) {
return true ;
}
}
// Messages
for msg in & thread . conversation . messages {
if msg . content . to_lowercase () . contains ( query ) {
return true ;
}
}
false
}
Lazy Loading Thread summaries load minimal data. Full threads fetched on demand.
Background Sync Non-blocking sync prevents UI lag. Errors retried automatically.
Local Caching Recently accessed threads cached in memory (future enhancement).
Indexed Search PostgreSQL GIN indexes for fast full-text queries.
Troubleshooting
Check network connectivity: curl https://loom.ghuntley.com/health
Verify authentication: The thread is still saved locally. Sync retries automatically.
List all threads: Search by content: Check local storage: ls ~/.local/share/loom/threads/
Happens when thread modified on multiple devices simultaneously. The server returns HTTP 409. Client should:
Fetch latest version from server
Merge changes (manual or automatic)
Retry with updated version
Search returns no results
Try:
Broader query terms
Different keywords (branch name, repo URL)
Local search if server is down
Verify thread exists: loom list | grep < keywor d >