What it does
Xyne connects to your SaaS apps — Google Workspace, Slack, Jira, GitHub, Zoho Desk, Microsoft — indexes the content, and gives you a combined search and Q&A interface over all of it. Ask "what did we decide about the pricing model in Q3?" and it retrieves the relevant Slack threads, Docs, and emails, then synthesizes an answer with citations.
Why I starred it
The permission problem is the part most enterprise search tools quietly skip. They index everything and hope access control at the UI layer is enough. Xyne enforces permissions at query time — it checks connector status and integration presence before building the Vespa query, so a user never retrieves documents from apps they're not connected to.
The second thing: the model layer is genuinely agnostic. Not in the marketing-copy sense of "supports OpenAI and Anthropic" — the server/ai/provider/ directory has separate providers for Bedrock, Vertex AI, Gemini, Fireworks, Together, Ollama, LiteLLM, and OpenAI-compatible endpoints. The model config in server/ai/modelConfig.ts maps enum values like Models.Claude_3_7_Sonnet to provider-specific API names, so you can point the whole stack at a local DeepSeek with one config change.
How it works
The backend runs on Bun with Hono as the HTTP framework. Data is stored in Vespa (an open-source vector/text search engine from Yahoo), with PostgreSQL for application state via Drizzle ORM. The ingestion pipeline is queue-backed using pg-boss, with separate workers for files and PDFs.
The Vespa schemas are in server/vespa/schemas/. The file.sd schema is the most revealing piece of architecture. Documents are stored with both text chunks and vector embeddings, and the ranking profiles expose the explicit hybrid scoring formula:
rank-profile title_boosted_hybrid inherits initial {
first-phase {
expression: (query(alpha) * vector_score) + ((1 - query(alpha)) * (5 * nativeRank(title)+0.2*nativeRank(chunks)))
}
global-phase {
expression {
(
(query(alpha) * vector_score) +
((1 - query(alpha)) * (5 * nativeRank(title)+0.2*nativeRank(chunks)))
) * doc_recency
}
rerank-count: 1000
}
}
The query(alpha) parameter lets callers tune the vector/BM25 blend at query time. Title matches get a 5x boost over chunk matches. And the doc_recency multiplier in the global reranking pass means fresher documents float up without completely overriding relevance. That's a well-considered default — not just "cosine similarity + keyword fallback".
Permission enforcement lives in server/search/vespa.ts. Before building the Vespa query, it resolves which apps are actually connected for the requesting user:
const [driveConnector, gmailConnector, calendarConnector] =
await Promise.all([
getAppSyncJobsByEmail(db, Apps.GoogleDrive, config.CurrentAuthType, email),
getAppSyncJobsByEmail(db, Apps.Gmail, config.CurrentAuthType, email),
getAppSyncJobsByEmail(db, Apps.GoogleCalendar, config.CurrentAuthType, email),
])
isDriveConnected = Boolean(driveConnector && driveConnector.length > 0)
Connected app flags get passed into the Vespa query constraints. If you haven't connected Gmail, Gmail documents don't appear in results — regardless of what's indexed.
The context assembly layer in server/ai/context.ts is where it gets interesting. The answerContextMap function handles different document types with specific logic — when it encounters a spreadsheet (Excel, CSV, Google Sheets), it doesn't just dump the raw chunks. It runs the query against the sheet data using DuckDB (querySheetChunks from lib/duckdb), which means it can actually answer structured questions against tabular data instead of doing fuzzy text matching against flattened cells.
The AI provider abstraction in server/ai/provider/base.ts is a clean abstract class with converse and converseStream methods, plus a getModelParams that resolves model enum values to actual API names via MODEL_CONFIGURATIONS. The model config covers 25+ models across Bedrock, Vertex, Gemini, Ollama, Fireworks, and Together AI — with per-model flags for reasoning, websearch, and deepResearch capability.
Ingestion uses pg-boss for durable queuing. The worker.ts runs boss.work() with configurable batchSize for concurrent job processing — separate workers for regular files and PDFs. The pipeline.ts file is a three-line stub (class Pipeline {}), which tells you where active development is focused: the workers and integrations, not a formal pipeline abstraction.
Using it
The fastest path is Docker Compose:
docker-compose up -d
That starts Xyne, Vespa, and PostgreSQL together. You then go through the admin UI to connect a Google Workspace service account or OAuth flow. For Google Workspace, it needs domain-wide delegation to index other users' content in enterprise mode.
For local/personal use, the OAuth path is simpler — connect your own Google account and it indexes your Drive, Gmail, Calendar, and Contacts.
# Check what's indexed
curl http://localhost:3000/api/search?q=quarterly+review
The API is a standard Hono REST server. The server/server.ts entry point registers routes for search, chat, agent, ingestion, connectors, OAuth, and webhooks. It also runs an MCP server, so you can connect Cursor or Claude Desktop to it.
Rough edges
The Google Workspace integration is the most complete — the README calls out that it covers Drive, Gmail, Calendar, Contacts, and attachments, while most enterprise search tools only do Drive. Slack and Atlassian are listed as "NEXT" in the README, though the server/integrations/ directory shows Slack and Jira code already in place.
Test coverage is thin. There's a server/tests/ directory and some eval tooling in server/eval/, but the test suite doesn't appear comprehensive. For a tool handling sensitive work data, that's a gap worth noting.
The server/ai/context.ts file is 1,400+ lines handling context assembly for a dozen different document types. It works, but it's accumulated enough special cases that contributing to it requires reading carefully. The DuckDB integration for sheet queries is buried in there — good engineering, but you'd only find it by reading the file.
Vespa adds operational complexity. It's a powerful choice — WAND retrieval, first-phase/global-phase ranking, custom scoring expressions — but it means running a JVM-based search cluster alongside your app. The server/vespa/ directory has deployment scripts for both Docker and Kubernetes, but you're still taking on Vespa operations.
Bottom line
Xyne is a credible self-hosted alternative to Glean if you're on Google Workspace and want permission-aware AI search without sending your data to a third-party index. The Vespa ranking setup is genuinely sophisticated, the model abstraction is thorough, and the spreadsheet-via-DuckDB path shows someone thought carefully about what "search" means for structured data. If you have the ops capacity to run Vespa, it's worth a serious look.
