Xyncra Server
A distributed messaging backend with built-in AI agent support.
Bidirectional RPC over WebSocket Β· Multi-device sync Β· Offline resilience Β· Streaming Β· Human-in-the-Loop
Architecture Β· Quick Start Β· Protocol Β· Agent System Β· Documentation Β· Contributing Β· License
Why Xyncra?
Most messaging systems force you to choose between real-time infrastructure and AI agent integration. Xyncra gives you both in a single, zero-config server:
| You need... |
Xyncra delivers |
| Real-time messaging |
WebSocket-based bidirectional RPC with server-initiated pushes |
| AI agents that talk to users |
Built-in agent runtime with streaming, tool calls, and HITL |
| Multi-device everywhere |
Per-device connection tracking with offline sync and gap filling |
| Production resilience |
Redis-backed distributed state, MQ task queue, fail-open design |
| Zero operational overhead |
SQLite by default, one binary, sensible defaults for everything |
Architecture
βββββββββββββββββββββββββββββββββββ
β Your Application β
β (reverse proxy + auth layer) β
ββββββββββββββββ¬βββββββββββββββββββ
β injects user_id
ββββββββββββββββΌβββββββββββββββββββ
β Xyncra Server β
β β
βββββββββ WebSocketβ βββββββββββββ βββββββββββββ β βββββββββ
β User AββββββββββββΊβ β Conn β β Agent β ββββΊβ Redis β
β Deviceβ RPC+Push β β Store β β Runtime β β βββββ¬ββββ
βββββββββ β βββββββ¬ββββββ βββββββ¬ββββββ β β
β β β β βββββΌββββ
βββββββββ WebSocketβ βββββββΌββββββ βββββββΌββββββ β β MQ β
β User BββββββββββββΊβ β Handler β β Tool β β β(Asynq)β
βDevice1β RPC+Push β β Registry β β Provider β β βββββββββ
βββββββββ β βββββββ¬ββββββ βββββββββββββ β
β β β
βββββββββ WebSocketβ βββββββΌββββββββββββββββββββββ β
β User BββββββββββββΊβ β Store Layer β β
βDevice2β RPC+Push β β (SQLite/PostgreSQL/MySQL)β β
βββββββββ β βββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββ
Three layers, one binary:
- Connection Layer β WebSocket server with per-(user, device) connection tracking, Redis Pub/Sub for cross-node broadcasting, and device replacement protocol
- Messaging Layer β Bidirectional RPC (clientβserver), persistent update sync with sequence-based gap filling, ephemeral pushes for typing/streaming/presence
- Agent Layer β YAML-configured AI agents with multi-LLM support (OpenAI, Claude, Ollama, Qwen), MCP tool integration, client-side tool invocation via ReverseRPC, sub-agent delegation, and human-in-the-loop checkpoints
Features
π¬ Messaging Core
- Bidirectional RPC β Both client and server can initiate calls over a single WebSocket connection
- Persistent updates β Sequence-numbered update log with cursor-based pagination (
sync_updates)
- Offline resilience β Clients reconnect and fetch missed updates; gap placeholders ensure no silent data loss
- Multi-device sync β Per-device connection tracking (
user_id + device_id + conn_id)
- Soft delete β Conversations and messages support delete/restore with cascade semantics
π€ AI Agent Runtime
- Declarative agents β Define agents in a single Markdown file with YAML front matter (model, tools, middleware, system prompt)
- Multi-LLM β Pluggable providers: OpenAI, Anthropic Claude, Ollama, Qwen β or bring your own
- Streaming responses β Real-time text streaming via ephemeral push (
stream_text), cumulative snapshot model
- Tool execution β Server-side tools (code, search) + client-side tools (ReverseRPC to device) + MCP server integration
- Human-in-the-Loop β Agents pause and ask users for confirmation via
ask_user; questions persist to database, multi-device sync via pull-notification pattern (conversation update as lightweight signal), idempotent agent_resume with 409 conflict detection, and crash recovery (answers in DB, checkpoints in Redis with 24h TTL, stale conversation cleanup)
- Sub-agent delegation β Agents can invoke other agents, each with isolated context
- Context management β Token-aware truncation with optional summarization middleware
ποΈ Infrastructure
- Zero-config startup β SQLite + Redis localhost defaults, one command to run
- Flexible storage β SQLite (embedded), PostgreSQL, or MySQL via GORM
- Distributed-ready β Redis Pub/Sub for cross-node push, Asynq for async task queue with priority levels
- Fail-open design β MQ enqueue failures don't block message persistence; Redis outages don't crash agents
- Ephemeral events β Typing indicators, streaming text, agent status β never persisted, never replayed, always real-time
π Observability
All features are opt-in via environment variables (zero overhead when disabled):
- Structured logging β
slog-based structured logging with JSON/text format and automatic log rotation (lumberjack)
- Prometheus metrics β 36 metrics covering system, connections, messages, agents, business operations, and Redis health
- Performance profiling β pprof HTTP server (localhost-only) and Pyroscope continuous profiling integration
- Docker Compose stack β Complete observability profile with Prometheus, AlertManager, Loki, Promtail, Pyroscope, and Grafana
- Alert rules β 11 pre-defined alerts for goroutine leaks, connection storms, LLM errors, Redis failures, and queue backlogs
Quick Start
Prerequisites
- Go 1.26+
- Redis running on
localhost:6379 (default)
Build & Run
# Clone
git clone https://github.com/PineappleBond/xyncra-server.git
cd xyncra-server
# Build
make build
# Start server (zero-config: SQLite + Redis localhost:6379)
./bin/xyncra-server
That's it. The server is listening on :8080.
Docker
docker compose -f deploy/docker-compose.yml up -d
Configuration
Override defaults via CLI flags or XYNCRA_ environment variables:
| Flag |
Env Var |
Default |
Description |
-addr |
XYNCRA_ADDR |
:8080 |
WebSocket listen address |
-redis-addr |
XYNCRA_REDIS_ADDR |
localhost:6379 |
Redis address |
-redis-password |
XYNCRA_REDIS_PASSWORD |
|
Redis AUTH password |
-db-driver |
XYNCRA_DB_DRIVER |
sqlite |
sqlite / postgres / mysql |
-db-dsn |
XYNCRA_DB_DSN |
xyncra.db |
Database connection string |
-max-conns |
XYNCRA_MAX_CONNS_PER_USER |
0 (unlimited) |
Max connections per user |
-redis-db |
XYNCRA_REDIS_DB |
0 |
Redis database number |
-agents-dir |
XYNCRA_AGENTS_DIR |
agents |
Path to agent definitions directory |
-max-functions-per-device |
XYNCRA_MAX_FUNCTIONS_PER_DEVICE |
200 |
Max registered functions per device |
Client CLI
Xyncra includes a full-featured CLI client (xyncra-client) for interacting with the server.
# Start the daemon (maintains persistent WebSocket connection)
./bin/xyncra-client listen --user-id alice --device-id laptop
# Create a conversation
./bin/xyncra-client create-conversation --peer-id bob
# Send a message
./bin/xyncra-client send --conversation-id <conv-id> --content "Hello!"
# Query local data (offline-capable, reads from local SQLite)
./bin/xyncra-client list-conversations
./bin/xyncra-client get-messages --conversation-id <conv-id>
./bin/xyncra-client search-messages --conversation-id <conv-id> --query "hello"
The daemon auto-registers built-in functions (ping, get_device_info, get_time) that agents can invoke via ReverseRPC. Custom device metadata can be attached via --device-info.
Protocol
All communication uses a 3-level envelope over WebSocket:
// Client β Server (Request, type=0)
{"type": 0, "data": {"id": "req-1", "method": "send_message", "params": {...}}}
// Server β Client (Response, type=1)
{"type": 1, "data": {"id": "req-1", "code": 0, "msg": "ok", "data": {...}}}
// Server β Client (Push Updates, type=2)
{"type": 2, "data": {"updates": [{"seq": 1, "type": "message", "payload": {...}}]}}
RPC Methods
| Method |
Description |
heartbeat |
Keep-alive, refreshes connection TTL |
send_message |
Send a message (idempotent via client_message_id) |
sync_updates |
Cursor-based update sync with gap filling |
create_conversation |
Find-or-create 1-on-1 conversation |
get_conversation |
Get a single conversation with unread count and HITL questions |
list_conversations |
List conversations (ordered by last_message_at DESC) |
get_messages |
Paginated message history |
search_messages |
Text search (LIKE-based) within a conversation |
mark_as_read |
Update read cursor (MAX semantics) |
delete_conversation |
Soft-delete conversation + messages |
restore_conversation |
Restore soft-deleted conversation |
delete_message |
Soft-delete message (sender only) |
set_typing |
Ephemeral typing indicator (Seq=0) |
stream_text |
Ephemeral streaming text (Seq=0, cumulative snapshot) |
agent_resume |
Resume a HITL-interrupted agent |
reload_agents |
Hot-reload agent configurations |
system.register_functions |
Register device function capabilities (ReverseRPC) |
system.reconnect |
Reconnect handshake with request replay |
Push Update Types
Persisted (Seq > 0, delivered via sync_updates):
| Type |
Description |
message |
New message |
delete_message |
Message deleted |
mark_read |
Read cursor updated |
conversation |
Conversation state changed (incl. HITL) |
gap |
Synthetic gap filler (runtime only) |
Ephemeral (Seq = 0, real-time only, never replayed):
| Type |
Description |
typing |
User typing indicator |
streaming |
Cumulative text stream from agent |
agent_status |
Agent state: thinking / tool_calling / generating / idle / asking_user |
agent_timeout |
Agent timed out |
π Full protocol specification: docs/API.md
Agent System
Agents are defined as single Markdown files with YAML front matter β no code required.
Example: Weather Bot
---
id: weather-bot
name: Weather Bot
model: qwen3.7-plus
api_key_env: DASHSCOPE_API_KEY
tools:
- get_weather
- get_current_time
middleware:
enable_client_tools: true
enable_summarization: true
---
You are a helpful weather assistant. Provide current weather
information, forecasts, and weather-related advice.
Drop this file in the agents/ directory and hot-reload with reload_agents RPC.
Agent Capabilities
| Feature |
Description |
| Multi-LLM |
OpenAI, Claude, Ollama, Qwen β pluggable LLMProvider interface |
| Tool calling |
Server-side tools, client-side tools (ReverseRPC), MCP servers |
| Streaming |
Real-time text streaming with cumulative snapshot model |
| HITL |
Persistent questions with multi-device sync, offline resilience, crash recovery via checkpoint + DB |
| Sub-agents |
Delegate to other agents with isolated contexts |
| Middleware |
Client tools, tool-call patching, summarization, tool result reduction |
| Context management |
Token-aware truncation, message count fallback, configurable limits |
Agent Interaction Flow
User Server Agent LLM
β send_message β β β
βββββββββββββββββββββΊβ enqueue MQ task β β
β βββββββββββββββββββββΊβ prompt + context β
β β βββββββββββββββββββΊβ
β typing (Seq=0) βββββ ephemeral βββββ β
ββββββββββββββββββββββ β tool calls β
β agent_status β βββββββββββββββββββΊβ
ββββββββββββββββββββββββββ ephemeral βββββ β
β streaming (Seq=0) β β response β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β message (Seq=N) β β β
βββββββββββββββββββββββββ persisted βββββββ β
HITL Resilience
The agent runtime implements a conversation state machine with 6 defined states (idle, thinking, tool_calling, generating, asking_user, timeout). Only asking_user and idle are persisted to the database β intermediate states (thinking, tool_calling, generating) are ephemeral WebSocket broadcasts for UI display only:
ββββββββββββββββββββββββββββββββββββββββββββββββ
β Conversation State Machine β
β β
β Ephemeral (broadcast only, not persisted): β
β thinking β tool_calling β generating β
β β
β Persisted (database): β
idle βββββββββββββββββββββββββββββββββββββββββββΊ asking_user β
β β
β resume β
β (all β
β answered) β
ββββββββββββββ
timeout (background cleanup resets to idle)
Pull-notification pattern β When an agent pauses for HITL:
- Questions are persisted to the
Question table (DB)
- Conversation
agent_status transitions to asking_user
- A lightweight
conversation update is broadcast (just conversation_id + updated_at)
- Clients pull the full conversation state β questions, status, checkpoint β on demand
- Ephemeral
agent_status pushes are also sent for online clients
- CLI daemon's
OnConversation handler detects agent_status == "asking_user" and displays HITL info as [hitl] format (checkpoint_id, interrupt_id, question_text)
Crash recovery β Answers live in DB, checkpoints in Redis (24h TTL). Server restarts during HITL wait are safe: the user can still answer, and the resume handler reads answers from DB to rebuild the targets map.
Idempotency β agent_resume uses UPDATE ... WHERE status='pending' for atomic answer claiming. If another device already answered, returns 409 (question_already_answered). Multi-question checkpoints track partial progress β resume only fires when ALL questions are answered.
Stale cleanup β A background task (Redis distributed lock per conversation) detects conversations stuck in asking_user past a configurable threshold, resets them to idle, and cleans up pending questions.
π Full scenario analysis: docs/design/DESIGN_HITL_RESILIENCE.md
π Agent configuration details: docs/decisions/PRODUCT_DECISIONS.md (D-054 through D-124) and docs/decisions/PRODUCT_DECISIONS_DETAILS.md
Deployment Model
Xyncra is designed for internal network deployment behind a reverse proxy:
Internet
β
ββββββββΌβββββββ
β Nginx / β β TLS termination, CORS, Rate Limit
β Envoy β
ββββββββ¬βββββββ
β Internal Network
ββββββββΌβββββββ
β Your App β β Authentication, business logic
β Server β Injects authenticated user_id
ββββββββ¬βββββββ
β
ββββββββΌβββββββ
β Xyncra β β Messaging + Agents
β Server β
βββββββββββββββ
What Xyncra intentionally does NOT include:
- β Authentication β handled by your app server via reverse proxy
- β TLS termination β handled by your reverse proxy
- β CORS / Rate Limiting β handled by your reverse proxy
- β CSRF protection β not needed in internal deployment
What you get out of the box:
- β
user_id query parameter auth (dev default, override via WSWithAuthenticate)
- β
Accepts any Origin (internal deployment model)
- β
Functional options for all configuration overrides
π Design rationale: docs/decisions/PRODUCT_DECISIONS.md (D-001 through D-005)
Project Structure
xyncra-server/
βββ cmd/
β βββ xyncra-server/ # Server entry point
β βββ xyncra-client/ # CLI client entry point
βββ agents/ # Agent definitions (Markdown with YAML front matter)
βββ internal/
β βββ server/ # WebSocket server, connection lifecycle
β βββ handler/ # RPC method handlers
β βββ agent/ # Agent runtime, executor, tool providers
β β βββ tools/ # Built-in tool implementations
β βββ cli/ # CLI client implementation (commands, output)
β βββ mq/ # Message queue (Asynq/Redis)
β βββ store/ # Persistence layer (GORM)
β β βββ model/ # Data models
β βββ cleanup/ # Expired update cleanup
β βββ e2e/ # End-to-end integration tests
βββ pkg/
β βββ protocol/ # Wire protocol types (importable)
β βββ client/ # Go client SDK
β βββ store/ # Client-side local storage (SQLite via GORM)
β βββ model/ # Client data models
βββ scripts/ # Shell scripts
βββ docs/
β βββ API.md # WebSocket protocol reference
β βββ decisions/
β β βββ PRODUCT_DECISIONS.md # Architecture decisions
β β βββ PRODUCT_DECISIONS_DETAILS.md # Detailed decision specs
β βββ design/
β β βββ DESIGN_HITL_RESILIENCE.md # HITL failure scenarios & recovery
β β βββ DESIGN_CLIENT_FUNCTION_AGENT_TOOLS.md # Client function agent tools design
β β βββ DESIGN_TYPING_EPHEMERAL_PUSH.md # Typing/ephemeral push design
β βββ guides/
β β βββ DEVELOPER_GUIDE.md # Developer onboarding guide
β β βββ DEVELOPER_REFERENCE.md # Developer reference
β βββ reviews/
β β βββ CLIENT_REVIEW.md # Client code review report
β β βββ REVIEW_CLIENT_QUERY_ARCHITECTURE.md # Client query architecture review
β βββ CLI_E2E_TEST_STRATEGY.md # CLI E2E test strategy
β βββ CLI_E2E_TEST_STRATEGY_ROUND2.md # CLI E2E test strategy round 2
β βββ IMPLEMENTATION_PHASES.md # Implementation phases
β βββ manual-test-cases/ # Manual test case documents
β βββ plans/ # Design plans
β βββ testing/ # Test documents & reports
β βββ superpowers/ # AI-generated specs
βββ deploy/ # Docker & deployment configurations
β βββ Dockerfile
β βββ docker-compose.yml
β βββ docker-compose.e2e.yml # E2E test environment
β βββ docker-compose.multi-node.yml # Multi-node distributed test
β βββ alertmanager/ # AlertManager config
β βββ grafana/ # Grafana dashboards & provisioning
β βββ loki/ # Loki config
β βββ prometheus/ # Prometheus rules & config
β βββ promtail/ # Promtail config
βββ wiki/ # Project wiki
Development
# Unit tests (no Redis required)
make test
# E2E tests (Redis on port 16379 required)
make test-e2e
# All tests
make test-all
# Format & lint
make fmt
make vet
Documentation
Contributing
Contributions are welcome! Here's how to get started:
- Report bugs β Open an issue with reproduction steps
- Suggest features β Open an issue describing your use case
- Submit PRs β Fork, branch, implement, test, submit
When contributing code:
- Follow existing patterns and naming conventions (see Developer Guide)
- Reference product decision IDs in comments (e.g.,
D-011)
- Write tests β handler tests use in-memory stores, E2E tests require Redis
- Use
fmt.Errorf("context: %w", err) for error wrapping
License
MIT β Copyright (c) 2026 PineappleBond