SynapSynapDocs
Development

Building AI Agents

Building AI Agents

How to Add Intelligence to Synap

:::note Some sections below reference LangGraph as an internal agent option. LangGraph was evaluated and deferred — the current Intelligence Hub uses a peer-agent network built on the Vercel AI SDK. The external Hub Protocol pattern described here remains the recommended integration path. :::


The Intelligence Services Model

Synap's AI architecture is different: AI runs as external services, not embedded code.

Why External?

Traditional (Built-in)Synap (External Services)
One AI modelYour choice of model
Vendor lock-inSwap anytime
Can't use proprietary modelsKeep your IP
Scales with appScale independently

The Flow

User → Data Pod → Hub Protocol → Your Intelligence Service

                                  ANY AI Model
                                  (GPT-4, Claude, Llama, Custom)

                                  Entities/Proposals

                        ← Hub Protocol ← Data Pod

Quick Decision Tree

Building AI for Synap?
├─ Need proprietary AI? → Intelligence Service (recommended)
├─ Need GPU infrastructure? → Intelligence Service
├─ Want to use local models? → Intelligence Service
├─ Simple utility? → Internal Agent (LangGraph)
└─ Just testing? → tRPC mutation with AI SDK

Best for: Production AI, proprietary models, independent scaling

1. Create Your Service

mkdir my-intelligence-service
cd my-intelligence-service
npm init -y
npm install @synap/hub-protocol hono

2. Implement the Service

// src/index.ts
import { Hono } from 'hono';
import { HubProtocolClient } from '@synap/hub-protocol';
import OpenAI from 'openai'; // or any AI library
 
const app = new Hono();
 
app.post('/analyze', async (c) => {
  const { threadId, dataPodUrl, apiKey } = await c.req.json();
  
  // 1. Connect to user's Data Pod
  const hub = new HubProtocolClient(dataPodUrl, apiKey);
  
  // 2. Read context
  const thread = await hub.getThreadContext({ threadId });
  const userNotes = await hub.queryEntities({ 
    type: "note",
    filters: { tags: ["project-x"] }
  });
  
  // 3. Process with YOUR AI
  const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
  const completion = await openai.chat.completions.create({
    model: "gpt-4",
    messages: [
      { role: "system", content: "You are a project analyzer." },
      { role: "user", content: `Analyze: ${JSON.stringify(userNotes)}` }
    ]
  });
  
  const analysis = completion.choices[0].message.content;
  
  // 4. Submit insights as proposals
  await hub.createEntity({
    type: "note",
    title: "Project Analysis",
    content: analysis,
    metadata: {
      aiGenerated: true,
      model: "gpt-4",
      confidence: 0.9,
      reasoning: "Analyzed project notes for insights"
    }
  });
  
  return c.json({ success: true });
});
 
export default app;

3. Deploy Anywhere

  • Cloudflare Workers: Serverless, global edge
  • AWS Lambda: Serverless, AWS ecosystem
  • Railway: One-click deploy
  • Your VPS: Full control
  • Local (ngrok): Development

4. Register with Data Pod

// Service startup
await fetch(`${dataPodUrl}/trpc/intelligenceRegistry.register`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    serviceId: 'my-analyzer',
    name: 'Project Analyzer',
    capabilities: ['entity_creation', 'semantic_analysis'],
    webhookUrl: 'https://my-service.com/analyze'
  })
});

5. Frontend Integration

Users can invoke your service:

// Frontend
const response = await client.intelligenceServices.invoke({
  serviceId: 'my-analyzer',
  threadId: currentThread.id
});

Option 2: Internal Agent (LangGraph)

Best for: Simple utilities, tight integration, open-source contributions

When to Use Internal

  • No proprietary logic
  • Simple AI calls
  • Contributing to core
  • Low latency critical

Example: Note Summarizer

// packages/ai/src/agents/summarizer.ts
import { StateGraph, Annotation } from '@langchain/langgraph';
import { generateObject } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';
 
const SummarizerState = Annotation.Root({
  noteId: Annotation<string>(),
  content: Annotation<string>(),
  summary: Annotation<string>()
});
 
export function createSummarizer() {
  const graph = new StateGraph(SummarizerState);
  
  graph.addNode('fetch', async (state) => {
    // Fetch note from DB
    const note = await db.query.entities.findFirst({
      where: eq(entities.id, state.noteId)
    });
    return { ...state, content: note.content };
  });
  
  graph.addNode('summarize', async (state) => {
    const result = await generateObject({
      model: anthropic('claude-3-haiku-20240307'),
      schema: z.object({
        summary: z.string(),
        keyPoints: z.array(z.string())
      }),
      prompt: `Summarize: ${state.content}`
    });
    
    return { ...state, summary: result.object.summary };
  });
  
  graph.addNode('save', async (state) => {
    // Emit event to create summary entity
    await eventBus.emit('entity.creation.requested', {
      type: 'note',
      title: `Summary: ${state.noteId}`,
      content: state.summary,
      metadata: { aiGenerated: true }
    });
    
    return state;
  });
  
  graph.setEntryPoint('fetch');
  graph.addEdge('fetch', 'summarize');
  graph.addEdge('summarize', 'save');
  
  return graph.compile();
}

Register in Router

// packages/api/src/routers/notes.ts
import { createSummarizer } from '@synap/ai';
 
export const notesRouter = router({
  summarize: protectedProcedure
    .input(z.object({ noteId: z.string() }))
    .mutation(async ({ input }) => {
      const agent = createSummarizer();
      await agent.invoke({ noteId: input.noteId });
      
      return { success: true };
    })
});

Option 3: Simple AI Call (Vercel AI SDK)

Best for: One-off AI features, prototyping

Example: Smart Tags

// packages/api/src/routers/entities.ts
import { generateObject } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';
 
export const entitiesRouter = router({
  generateTags: protectedProcedure
    .input(z.object({ content: z.string() }))
    .mutation(async ({ input }) => {
      const result = await generateObject({
        model: anthropic('claude-3-haiku-20240307'),
        schema: z.object({
          tags: z.array(z.string()),
          confidence: z.number()
        }),
        prompt: `Suggest tags for: ${input.content}`
      });
      
      // Emit event for each tag
      for (const tag of result.object.tags) {
        await ctx.events.emit('tag.creation.requested', {
          name: tag,
          metadata: { aiGenerated: true }
        });
      }
      
      return result.object;
    })
});

Proposals: Human-in-the-Loop

All AI-created entities start as proposals awaiting user approval.

The Flow

1. Intelligence Service → hubProtocol.createEntity()
2. Backend → Creates Proposal (status: pending)
3. User → Reviews in UI
4. User → Approves → Real entity created
5. User → Rejects → Stays for audit

Why Proposals?

  • Review before commit: See what AI wants to create
  • Confidence scores: AI explains its reasoning
  • Audit trail: Track all AI suggestions
  • Undo capability: Reject bad suggestions

Proposal API

// Intelligence Service creates proposal
await hubProtocol.createEntity({
  type: "task",
  title: "Follow up with client",
  metadata: {
    aiGenerated: true,
    confidence: 0.75,
    reasoning: "Detected action item in meeting notes",
    source: "meeting-note-123"
  }
});
 
// User sees in proposal inbox
{
  id: "prop-456",
  status: "pending",
  targetType: "entity",
  request: { /* entity data */ },
  createdAt: "2024-01-15T10:00:00Z"
}
 
// User approves
await client.proposals.approve({ proposalId: "prop-456" });
 
// Now it's a real entity

AI Metadata Standard

Always include AI context in metadata:

metadata: {
  aiGenerated: true,              // Required: marks as AI-created
  model: "gpt-4",                  // Which model
  confidence: 0.85,                // 0-1 confidence score
  reasoning: "Found in notes...",  // Explain why created
  source: "entity-123",            // What triggered it
  timestamp: "2024-01-15T10:00:00Z"
}

This enables:

  • UI badges ("AI-suggested")
  • Filtering (show only high-confidence)
  • Debugging (trace AI reasoning)
  • Analytics (which AI performs best)

Real Examples

1. Meeting Notes Analyzer

Intelligence Service that:

  1. Reads meeting notes
  2. Extracts action items → Tasks
  3. Identifies attendees → Person entities
  4. Creates relations (Task → Person)

All as proposals for user review.

2. Knowledge Graph Builder

Intelligence Service that:

  1. Reads all user notes
  2. Finds semantic connections
  3. Proposes relations between notes
  4. Generates summary notes

User reviews and approves connections.

3. Smart Inbox

Internal Agent that:

  1. Monitors inbox items
  2. Classifies by type
  3. Suggests tags
  4. Routes to projects

Runs on every inbox item creation.


Deployment Patterns

Development

# Run service locally
npm run dev
 
# Expose via ngrok
ngrok http 3000
 
# Register with Data Pod
curl -X POST http://localhost:3001/trpc/intelligenceRegistry.register \
  -d '{"webhookUrl": "https://xyz.ngrok.io/analyze"}'

Production

Cloudflare Workers

npm install wrangler -g
wrangler init
wrangler publish

Docker

FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
CMD ["npm", "start"]
docker build -t my-intelligence-service .
docker run -p 3000:3000 my-intelligence-service

Testing

Test Intelligence Service Locally

// test/analyze.test.ts
import { HubProtocolClient } from '@synap/hub-protocol';
 
describe('Analyzer Service', () => {
  it('creates proposals', async () => {
    const hub = new HubProtocolClient(
      'http://localhost:3001',
      'test-api-key'
    );
    
    const response = await fetch('http://localhost:3000/analyze', {
      method: 'POST',
      body: JSON.stringify({
        threadId: 'test-thread',
        dataPodUrl: 'http://localhost:3001',
        apiKey: 'test-api-key'
      })
    });
    
    expect(response.status).toBe(200);
    
    // Check proposals were created
    const proposals = await hub.getProposals();
    expect(proposals.length).toBeGreaterThan(0);
  });
});

Best Practices

  1. Use Intelligence Services for production - Better separation, scaling
  2. Use Internal Agents sparingly - Only for simple utilities
  3. Always create proposals - Never write directly
  4. Include metadata - Confidence, reasoning, model
  5. Test with real Data Pods - Use Hub Protocol SDK
  6. Version your APIs - Service endpoints can change
  7. Monitor confidence scores - Track AI performance

Next Steps

  1. Choose your approach (Intelligence Service recommended)
  2. Read Hub Protocol docsHub and spoke
  3. See composabilityComposable architecture
  4. Build and deploy

Resources: