Rowboat includes background services that continuously monitor your data sources and maintain your knowledge graph. These agents work autonomously to keep your information up-to-date without manual intervention.
Service Architecture
Rowboat runs multiple independent services, each responsible for a specific data source or workflow:
Gmail Sync Monitors your Gmail inbox for new threads and syncs them to markdown files Interval: Every 5 minutes
Fireflies Sync Pulls meeting transcripts from Fireflies and saves them locally Interval: Every 5 minutes
Granola Sync Syncs meeting notes from Granola Interval: Every 5 minutes
Knowledge Graph Builder Processes all synced content to extract entities and update your knowledge base Interval: Every 30 seconds
How Services Work
1. Data Source Services
Each data source (Gmail, Fireflies, Granola) runs as an independent service:
export async function init () {
console . log ( "Starting Gmail Sync..." );
console . log ( `Will sync every ${ SYNC_INTERVAL_MS / 1000 } seconds.` );
while ( true ) {
try {
const hasCredentials = await checkCredentials ();
if ( ! hasCredentials ) {
console . log ( "OAuth credentials not available. Sleeping..." );
} else {
await performSync ();
}
} catch ( error ) {
console . error ( "Error in main loop:" , error );
}
await sleep ( SYNC_INTERVAL_MS );
}
}
Gmail Sync Flow:
Check for changes
Uses Gmail History API with stored historyId to detect new messages efficiently
Fetch thread details
For each new thread, fetches full message content including attachments
Convert to markdown
Converts HTML emails to clean markdown format
Save locally
Writes thread to ~/.rowboat/gmail_sync/{threadId}.md
Update state
Saves new historyId for next sync
Incremental vs Full Sync:
First run: Full sync of last 30 days
Subsequent runs: Incremental sync using Gmail History API
Fallback: If historyId expires (404), falls back to full sync
2. Knowledge Graph Builder
The graph builder service monitors all source folders:
const SOURCE_FOLDERS = [
'gmail_sync' ,
'fireflies_transcripts' ,
'granola_notes' ,
];
export async function init () {
console . log ( '[GraphBuilder] Starting Knowledge Graph Builder Service...' );
console . log ( `[GraphBuilder] Monitoring folders: ${ SOURCE_FOLDERS . join ( ', ' ) } ` );
// Initial run
await processAllSources ();
// Periodic processing
while ( true ) {
await sleep ( SYNC_INTERVAL_MS );
await processAllSources ();
}
}
The graph builder checks for new files every 30 seconds, processes them in batches, and updates your knowledge base incrementally.
Service Logging
All services log their activity to a central log file for debugging and monitoring:
const run = await serviceLogger . startRun ({
service: 'gmail' ,
message: 'Syncing Gmail' ,
trigger: 'timer' ,
});
await serviceLogger . log ({
type: 'changes_identified' ,
service: run . service ,
runId: run . runId ,
level: 'info' ,
message: `Found ${ threadIds . length } threads to sync` ,
counts: { threads: threadIds . length },
});
await serviceLogger . log ({
type: 'run_complete' ,
service: run . service ,
runId: run . runId ,
level: 'info' ,
message: `Gmail sync complete: ${ threadIds . length } threads` ,
durationMs: Date . now () - run . startedAt ,
outcome: 'ok' ,
summary: { threads: threadIds . length },
});
Logs are stored at: ~/.rowboat/logs/services.jsonl
Agent Runtime
The agent runtime handles executing AI workflows for note creation:
Processing Batches
When new files need processing:
Build knowledge index
Scans existing notes to build in-memory index of all entities
Create agent run
Spawns a new agent instance with the note_creation agent
Build context
Prepares a message with:
Knowledge base index
Batch of files to process
Instructions for entity extraction
Execute agent
Agent reads files, extracts entities, creates/updates notes
Track changes
Monitors which notes were created or modified
Update state
Marks files as processed and commits changes to git
Agent Configuration
Agents are configured using markdown files with YAML frontmatter:
---
model : claude-3-5-sonnet-20241022
tools :
workspace-readFile :
type : builtin
name : readFile
workspace-writeFile :
type : builtin
name : writeFile
workspace-edit :
type : builtin
name : edit
---
# Agent Instructions
You are a note creation agent...
The runtime loads agents dynamically:
export async function loadAgent ( id : string ) : Promise < Agent > {
if ( id === 'note_creation' ) {
const strictness = getNoteCreationStrictness ();
let raw = '' ;
switch ( strictness ) {
case 'medium' : raw = noteCreationMediumRaw ; break ;
case 'low' : raw = noteCreationLowRaw ; break ;
case 'high' : raw = noteCreationHighRaw ; break ;
}
// Parse frontmatter and return agent config
}
// Load from ~/.rowboat/agents/ directory
}
The note creation agent has three variants (high/medium/low) based on your configured strictness level.
Error Handling
Services include robust error handling:
Automatic Retry
If a sync fails, the service:
Logs the error
Continues to the next scheduled run
Will retry on the next iteration
Partial Progress
The knowledge graph builder saves progress after each batch:
for ( let i = 0 ; i < contentFiles . length ; i += BATCH_SIZE ) {
const batch = contentFiles . slice ( i , i + BATCH_SIZE );
try {
await createNotesFromBatch ( batch , batchNumber , indexForPrompt );
// Mark files as processed
for ( const file of batch ) {
markFileAsProcessed ( file . path , state );
}
// Save state after successful batch
saveState ( state );
} catch ( error ) {
console . error ( `Error processing batch ${ batchNumber } :` , error );
// Continue with next batch
}
}
This ensures that if processing fails mid-way, already-processed files won’t be repeated.
OAuth Expiration
If OAuth credentials expire (401 error):
if ( error . response ?. status === 401 ) {
console . log ( "401 Unauthorized, clearing cache" );
GoogleClientFactory . clearCache ();
}
The service clears cached credentials and prompts for re-authentication on next run.
Manual Triggers
You can trigger syncs manually without waiting for the scheduled interval:
import { triggerSync } from './sync_gmail.js' ;
triggerSync (); // Wakes up the sync service immediately
This uses an interruptible sleep pattern:
function interruptibleSleep ( ms : number ) : Promise < void > {
return new Promise ( resolve => {
const timeout = setTimeout (() => {
wakeResolve = null ;
resolve ();
}, ms );
wakeResolve = () => {
clearTimeout ( timeout );
resolve ();
};
});
}
export function triggerSync () : void {
if ( wakeResolve ) {
wakeResolve ();
wakeResolve = null ;
}
}
Source Code Reference
Key implementation files:
apps/x/packages/core/src/knowledge/build_graph.ts:522-657 - Main graph builder service
apps/x/packages/core/src/knowledge/sync_gmail.ts:443-467 - Gmail sync service
apps/x/packages/core/src/agents/runtime.ts:36-148 - Agent runtime
apps/x/packages/core/src/services/service_logger.ts - Service logging infrastructure