A real-time chat app with AI agents built with SolidStart, PowerSync, Neon, and Mastra.
- π¬ Real-time chat channels
- π€ AI agents you can @mention in channels
- π Document management - agents can create and manage markdown documents
- π Document mentions - use #documentTitle to reference documents
- π₯ Channel sidebar - view members, agents, and documents
- π Document viewer - click documents to view full content
- π€ Agent viewer - click agents to view their description and instructions
- π± Offline-first with PowerSync local-first sync
- β‘ Instant UI updates via client-side writes
- π Anonymous sessions (no signup required for MVP)
- Frontend: SolidStart + Solid Router
- Database: Neon Postgres
- Sync: PowerSync (Web SDK + Service)
- AI: Mastra with OpenAI GPT-5
- Auth: Anonymous cookie-based sessions
bun installThe database has already been created and seeded via Neon MCP:
- Project:
powerchat(ID:morning-tree-55202603) - Database:
neondb - Tables:
users,agents,channels,channel_members,messages,message_mentions,documents - Demo agents:
Assistant,Analyst,Researcher,Writer
Connection string should be set in .env.local (see Environment Variables section below).
PowerSync setup instructions are in db/replication.sql. You need to:
- Enable logical replication in Neon
- Create a replication role for PowerSync Service
- Connect PowerSync Service to your Neon database
- Configure sync rules to filter by user membership
Create a .env.local file with the following variables:
NEON_DATABASE_URL="postgresql://..."
POWERSYNC_SERVICE_URL=https://your-instance.powersync.com
POWERSYNC_JWT_SECRET=your-secret-min-32-chars
POWERSYNC_JWT_KID=your-key-id
OPENAI_API_KEY=sk-your-key
AI_MODEL=gpt-5Note: POWERSYNC_JWT_SECRET should be a Base64URL-encoded secret (minimum 32 characters). POWERSYNC_JWT_KID is the key ID used in the JWT header.
Also add for client (Vite):
VITE_POWERSYNC_SERVICE_URL=https://your-instance.powersync.combun devVisit http://localhost:3000
- Create a Channel: Use the form in the sidebar
- Invite an Agent: Use the agent invite UI in a channel
- Send Messages: Type in the input box
- Mention Agents: Use
@AgentNamein your message to trigger AI reply - Mention Documents: Use
#DocumentTitleto reference documents created by agents - View Documents: Click on documents in the right sidebar to view full content
- View Agent Details: Click on agents in the right sidebar to see their description and instructions
PowerChat uses vertical slice architecture to organize features by domain rather than by technical layer. Each feature is a self-contained "slice" that includes all the code needed for that feature.
Every slice lives in src/slices/{feature-name}/ and contains:
index.tsx- The component/hook implementationindex.test.tsx- Test file (see Testing below)
Slices are categorized by their primary responsibility:
Query Slices (read-only):
- Fetch and display data
- Use TanStack DB
useLiveQueryover PowerSync-backed collections - Examples:
channel-list,chat-messages,username-check,channel-header,channel-member-list,channel-agents-list,channel-documents-list,document-viewer,agent-viewer,mention-autocomplete
Mutation Slices (write operations):
- Handle user actions that modify data
- Use collection
insert/update/deleteoperations or server actions - Examples:
create-channel,chat-input,username-registration,channel-invite,create-agent,delete-channel
Key Principle: Each slice is either a query OR a mutation, never both. This ensures clear separation of concerns.
Slices are completely independent - they never import or depend on other slices. This means:
- Slices can be developed, tested, and refactored in isolation
- No circular dependencies between features
- Easy to understand what each slice does without reading other code
- Route components orchestrate multiple slices together
Route components (src/routes/) compose multiple slices together:
// Example: src/routes/(chat).tsx
import { UsernameCheck } from "~/slices/username-check";
import { UsernameRegistration } from "~/slices/username-registration";
import { ChannelList } from "~/slices/channel-list";
export default function ChatLayout() {
const usernameCheck = UsernameCheck(); // Query slice
// ... conditionally render UsernameRegistration based on query state
// ... render ChannelList and other slices
}Every slice must have a test file (index.test.tsx). Tests can be simple but should verify basic functionality:
- Query slices: Test that data renders correctly, loading states work, and empty states are handled
- Mutation slices: Test that user interactions trigger the correct mutations and callbacks
Tests use Vitest and @solidjs/testing-library:
import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen } from "@solidjs/testing-library";
import { MySlice } from "./index";
// Mock dependencies
vi.mock("@tanstack/solid-db", () => ({
useLiveQuery: vi.fn(() => Object.assign(() => [], { isLoading: false, isReady: true })),
}));
describe("MySlice", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("renders correctly", () => {
render(() => <MySlice prop="value" />);
expect(screen.getByText("Expected text")).toBeInTheDocument();
});
});bun test- Keep tests simple - Focus on basic functionality, not edge cases
- Mock external dependencies - Mock PowerSync queries, server actions, etc.
- Test behavior, not implementation - Verify what users see and experience
- Every slice gets a test - Even if it's just a few basic assertions
- Messages are written to local PowerSync SQLite DB instantly
- PowerSync queues uploads to the service
- Server confirms and agent triggers run server-side
- Replies sync back automatically
- Client detects
@agentin message - Calls
triggerAgentserver function - Server fetches recent context from Neon (messages, users, agents, documents)
- Server provides agent with:
- List of other agents in channel (encourages delegation)
- List of available documents (for knowledge transfer)
- Document management tools (list, create, read)
- Mastra calls OpenAI GPT-5
- Agent reply inserted into Neon
- PowerSync syncs reply to all clients
- Agent interactions logged to
logs/agent-{id}-{timestamp}.log
Agents can create, list, and read markdown documents within channels:
- Create Document: Agents use
createDocumenttool to store long-form content - List Documents: Agents use
listDocumentsto see available documents - Read Document: Agents use
readDocumentto access document content - Document Mentions: Users and agents can reference documents with
#DocumentTitle - Document Viewer: Click documents in sidebar to view full content (replaces chat view)
- Agent Instructions: Agents are instructed to keep responses concise (2-4 sentences) and use documents for detailed information
Agents are encouraged to delegate tasks to specialized agents:
- Agents receive a list of other agents in the channel as context
- Instructions guide agents to delegate when another agent's expertise matches the task
- Delegation syntax: Use
@agentnameonly when explicitly delegating (not when describing) - Sequential delegation: Agents delegate sequentially when tasks depend on each other
src/middleware.ts- Sets anonymous user cookiesrc/lib/powersync.ts- PowerSync client + schemasrc/lib/tanstack-db.ts- TanStack DB collections over PowerSync tablessrc/server/powersync.ts- PowerSync JWT token generation (getPowerSyncToken) and upload handler (uploadData)src/server/db.ts- Neon connection poolsrc/server/agent.ts- Mastra agent execution with document tools and loggingsrc/server/tools/documents.ts- Document management tools for agentssrc/routes/(chat).tsx- Layout with sidebar (orchestrates slices)src/routes/channel/[id].tsx- Messages view with document/agent viewers (orchestrates slices)src/slices/- All feature slices (query and mutation)logs/- Agent interaction logs (plain text format, git-ignored)
- No server-side validation of mentions/membership
- Anonymous users only (no proper auth)
- Single agent response per mention (no multi-agent parallel execution)
- No streaming (simple text replies)
- In-memory idempotency (resets on server restart)
- Documents can only be created by agents (no client-side uploads)
- No document editing or deletion
- Add user/agent invite UI
- Server-side membership validation
- Streaming agent responses
- Rate limiting
- Persistent idempotency via DB
- Display names/avatars
- Rich message formatting
- Typing indicators
- Document editing and deletion
- Client-side document creation
- Document search and filtering