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

FeatureGuest ModeLogged-In Mode
Chat HistoryIn-Memory / sessionStorage (tab scoped)PostgreSQL (Drizzle ORM)
API CredentialssessionStorage (sent in payload body)PostgreSQL (AES-GCM encrypted)
Rate Limiter ScopeIP Address (in-memory map)User ID (in-memory sliding window)
Telemetry & CostsComputed in-memory per message turnSaved 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:

typescript
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.