Skip to main content
Sorat is a single Go binary that embeds a Next.js static frontend and runs as an HTTP server on your machine.

Package structure

sorat/
├── main.go              # Entry point, flag parsing, embedded frontend
├── config/              # Configuration manager (atomic read/write)
├── server/              # HTTP server setup, graceful shutdown, ephemeral agents
├── api/                 # HTTP handlers
│   ├── openai.go        # OpenAI-compatible chat completions
│   ├── agui.go          # AG-UI streaming protocol
│   ├── admin.go         # Admin REST endpoints
│   ├── auth.go          # Authentication (bcrypt + tokens)
│   └── oauth.go         # OAuth flows (PKCE + callback)
├── agent/               # ADK agent wiring
│   ├── agent.go         # Holder, lazy init, tool assembly, model routing
│   ├── openai_model.go  # model.LLM for OpenAI-compatible APIs
│   ├── antigravity_model.go  # model.LLM for Google Antigravity
│   ├── resilient_model.go    # Retry wrapper with backoff + context trimming
│   ├── error_classify.go     # Error classification for retry decisions
│   ├── summarize.go          # Session summarization when messages exceed threshold
│   └── tools/           # ADK FunctionTools
├── store/               # Filesystem persistence (sessions, memory, user)
├── skills/              # Skills loader, installer, registry
├── cron/                # Job scheduler (tick-based with persistence)
├── auth/                # OAuth credential store, PKCE helpers
├── daemon/              # PID file management (background mode)
├── cli/                 # Bubble Tea interactive TUI
└── frontend/            # Next.js 16 static export (embedded in binary)

Model routing

The buildModel function in agent/agent.go selects the model.LLM implementation based on the active provider:
1

OAuth + Google Antigravity

auth_method == "oauth" and oauth_provider == "google-antigravity"Uses newAntigravityModel — connects to cloudcode-pa.googleapis.com, handles ThoughtSignature parts, custom auth headers.
2

OAuth (other providers)

auth_method == "oauth" with other providers.Uses newOpenAIModelWithTokenSource — OpenAI-compatible with auto-refreshing OAuth token callback.
3

Default (API key)

Standard API key authentication.Uses newOpenAIModel — works with any OpenAI-compatible endpoint and a static API key.
All three implement the ADK model.LLM interface and are wrapped in resilientModel for automatic retry with exponential backoff and context trimming on overflow errors:
type LLM interface {
    Name() string
    GenerateContent(ctx context.Context, req *LLMRequest, stream bool) iter.Seq2[*LLMResponse, error]
}
The OpenAI model handles conversion between ADK’s genai.Content format and OpenAI’s message format, including role mapping ("model" to "assistant"), tool call assembly from streamed SSE chunks, and schema type case conversion.

ADK integration

Sorat uses Google ADK v0.4.0:
ComponentUsage
Agentllmagent.New() with model, instruction, and tools
Runnerrunner.New() with app name, agent, and session service
Session servicesession.InMemoryService() — ephemeral, reset on reinit
Toolsfunctiontool.New() with typed Go structs for args/results
Streamingagent.RunConfig{StreamingMode: agent.StreamingModeSSE}
The agent is held in a thread-safe Holder struct that supports lazy initialization and reinit.

Config hot-reload

When configuration changes via the admin API, reinitAgent() creates an entirely new ADK agent:
  1. New runner with updated model, instruction, and tools
  2. New in-memory session service (all active ADK sessions are lost)
  3. Persisted session messages on disk are preserved
  4. New tool instances with updated configuration
Triggers: PUT /api/config, PUT /api/persona, PUT /api/soul, PUT /api/tools, skills changes.

Ephemeral agents

Cron jobs use ephemeral agents — temporary instances with:
  • Full tool set
  • Loaded persona + soul
  • 3-minute timeout
  • Iteration and tool call tracking
  • Results logged via slog

Authentication flow

StepDescription
Setup modeNo password configured — all requests allowed
RegisterPOST /api/auth/register creates super-admin
LoginPOST /api/auth/login returns 24h bearer token
CLI tokenGenerated at startup, stored in ~/.sorat/.cli_token
MiddlewareExempts /api/auth/* and non-API paths

Concurrency patterns

PatternUsage
Config managersync.RWMutex for concurrent reads, exclusive writes
Agent holdersync.RWMutex with lazy init and atomic swap on reinit
Ephemeral agentsGoroutine per cron job with 3-minute timeout
Cron service10-second ticker, jobs execute in goroutines
Atomic writesAll file persistence uses write-to-tmp + rename