Guest vs. Logged-In Mode
A breakdown of session boundaries, storage targets, and rate limiting profiles.
Arbiter supports a stateless Guest Mode that allows users to test concurrent queries without creating a database profile. This is not a secondary code pipeline: both modes share the exact same orchestrator and providers. The distinction lies entirely in where state is stored and how rate limits are calculated.
Functional Differences
| Feature | Guest Mode | Logged-In Mode |
|---|---|---|
| Chat History | In-Memory / sessionStorage (tab scoped) | PostgreSQL (Drizzle ORM) |
| API Credentials | sessionStorage (sent in payload body) | PostgreSQL (AES-GCM encrypted) |
| Rate Limiter Scope | IP Address (in-memory map) | User ID (in-memory sliding window) |
| Telemetry & Costs | Computed in-memory per message turn | Saved to Postgres (aggregate tracking) |
Guest Storage Tradeoffs
We store guest keys in sessionStorage on purpose — losing them on tab close is the point, not a bug. By keeping keys out of persistent local storage, we guarantee that closed browser windows cannot leak API keys even if a physical machine is compromised.
When a guest user sends a request, the keys are passed inside the POST request body to the stateless /api/guest/message endpoint. The backend processes the LLM streams but never logs the query, response, or metadata to PostgreSQL.
IP-Based Rate Limiting
Since guest routes cannot verify user identities, they are gated by an IP extraction filter. In the guest route, we resolve the client IP via headers:
const ip = request.headers.get('x-forwarded-for') || '127.0.0.1';
if (isIpRateLimited(ip)) {
return NextResponse.json({ error: 'Too many requests' }, { status: 429 });
}This prevents denial-of-service (DoS) attempts against downstream LLMs. Both guest and logged-in rate limit maps are stored in memory and reset after 60,000ms.