name: sigit-code-cloud
description: "Reference for siGit Code Cloud, the hosted CHAT product for siGit Code (interactive, signed-in, no local model). Use when working on the cloud chat surface: the sigit login/logout/whoami account commands, the credential store, provider/backend selection (on-device vs siGit Code Cloud vs BYO endpoint), neutral quality tiers, the persisted Cloud Sessions API, or the onde-cloud API behind it. Covers the Copilot-Chat/Azure layering, the InferenceBackend engine, auth via sigit.si/api/v1, and what must never leak to the client. The autonomous task->PR product is siGit Code Cloud Agent; see the sigit-code-cloud-agent skill."
siGit Code Cloud
siGit Code Cloud is the hosted chat for siGit Code: interactive, signed-in, model in the cloud instead of on the device. The product framing is deliberate:
- siGit Code Cloud is the product, like Copilot Chat. The user signs in, picks a quality tier, and it works. They never see an API key, a base URL, or the name of any model provider.
- Onde Cloud is the infrastructure, like Azure. It is the OpenAI-compatible inference service siGit Code Cloud runs on, and it is an implementation detail.
- Onde Cloud, in turn, routes to upstream providers (today Anthropic) and
hides them too, see the onde-cloud
router/anthropicmodules.
This is the chat product, not the agent. The autonomous, sandboxed
task -> pull request product is siGit Code Cloud Agent (see the
sigit-code-cloud-agent skill). Chat's work unit is a session; the agent's is
a run. Keep them separate. Full product map:
docs/product/product-overview.md.
Keep the names straight (see the branding skill): the local agent is
siGit Code, the hosted chat is siGit Code Cloud, the autonomous one is
siGit Code Cloud Agent, the CLI is sigit, the company is smbCloud, the
inference infrastructure is Onde Cloud / Onde Inference, and the on-device
Rust crate is onde. sigit.si is Git hosting; code.sigit.si is the home of
siGit Code.
The layering (why it's built this way)
The original instinct was right: abstract the infrastructure away entirely. The engine underneath is intentionally generic so that "Onde Cloud = swappable infra" is literally true, but that generality is an internal seam, not the product.
┌─ Product surface (Copilot) ────────────────────────────────────────┐
│ sigit login → pick a tier (Fast / Balanced / Large) → just works │
│ No key, no URL, no provider names. Token is the only credential. │
└────────────────────────────────────────────────────────────────────┘
┌─ Engine (generic, swappable) ──────────────────────────────────────┐
│ InferenceBackend trait │
│ ├─ LocalBackend → on-device onde::ChatEngine │
│ └─ OpenAiBackend → any {base_url, api_key, model} over HTTP │
└────────────────────────────────────────────────────────────────────┘
┌─ Infrastructure (hidden) ──────────────────────────────────────────┐
│ Onde Cloud (OpenAI-compatible) → router → Anthropic (never named) │
└────────────────────────────────────────────────────────────────────┘
The generic engine is also what enables the BYO-endpoint escape hatch: an advanced user or enterprise can point siGit Code at their own OpenAI/Azure endpoint. That is a power-user override, not the front door.
Backend selection: identity and model choice are separate
Being signed in does not change which model runs. Login is auth; the model
is the user's explicit choice in /models. The startup default
(provider::active_provider()) is local-first, with one exception:
- Override (power user / BYO):
OPENAI_BASE_URL+OPENAI_API_KEY, or the active profile in~/.config/sigit/providers.toml. This is the only thing that changes the startup backend. - On-device by default: no override means local, signed in or not.
The user then picks a model in /models (see below), which swaps the running
backend in place. OPENAI_BASE_URL set without OPENAI_API_KEY logs a warning
and stays on-device, rather than silently looking like "the cloud didn't work."
/models: on-device models and cloud tiers together
/models always lists the on-device models and the siGit Code Cloud tiers
(Fast/Balanced/Large), so the cloud option is discoverable to everyone.
Access is gated at selection, not visibility:
- Pick a cloud tier while signed in → the backend hot-swaps to that tier
immediately (no restart).
provider::cloud_tier_provider(tier)builds the client from the stored token. - Pick a cloud tier while signed out → a prompt: sign in with
/login, or create an account at sigit.si. The backend does not change. - Pick an on-device model → loads and routes locally, as before.
The cloud tiers are real picker entries backed by a synthetic
GgufModelConfig whose id is sigit-cloud:<tier> (no /, so it never collides
with a HuggingFace id and on-device matching code stays inert). cloud_tier:
Option<String> on ModelPickerItem distinguishes them. The tier maps to a wire
model id via provider::tier_to_model (fast→onde-fast, etc.); the UI shows
siGit Code Cloud · Balanced, never the model id or "Anthropic".
Cloud tiers are a TUI-picker feature today. The ACP session-config paths use
models::local_picker_items() (cloud filtered out), because cloud inference over
ACP still needs the trait rewire (see Open items).
Account / auth (via sigit.si)
Account management is exposed as slash commands (/login <email> <password>,
/logout, /whoami) so it works inside an ACP session (Zed) and the terminal
TUI alike, since siGit Code has no separate place to run a CLI verb in those
contexts. Both surfaces parse /-prefixed input and call the I/O-free core
functions in sigit/src/account.rs (authenticate, end_session,
status_line). Slash input is not echoed to the transcript, so the password
does not persist there. /login only signs in; it does not change the active
model. To use the cloud after signing in, pick a tier in /models.
- Trust boundary is
sigit.si. It holds the confidential smbCloudapp_secretserver-side and returns the client only a bearer token. The client must never holdapp_secret(it is a public/shipped binary, see thesmbcloud-authskill's public-client rule). - Base URL:
$SIGIT_API_URL, elsehttps://sigit.si(dev:http://localhost:8088). - Endpoints (sigit.si
/api/v1, token-based):POST /api/v1/auth/sign_inreturns anAccountStatus("NotFound"|{"Ready":{"access_token":…}}|{"Incomplete":{"status":<u32>}});GET /api/v1/mereturns{id,email,created_at,updated_at};DELETE /api/v1/auth/sign_outreturns 204. Failures returnErrorResponse {error_code, message}. These shapes are a shared contract with the desktop client; keep them in sync (seesmbcloud-auth). The controllers live atapp/controllers/api/v1/{sessions,me}_controller.rb. - Credential store:
sigit/src/credentials.rswrites~/.config/sigit/credentials.toml($SIGIT_CONFIG_DIRoverride), mode0600, holdingaccess_token(+ email for display).
Cloud Sessions (persisted chat) and the web app
siGit Code Cloud chat conversations are saved as Cloud Sessions (short:
sessions), so they sync across signed-in surfaces and can be resumed. "Session"
means a chat conversation and belongs to this product only; the agent's unit is a
"run" (see sigit-code-cloud-agent).
- Models (sigit-si):
CloudSession(UUID primary key, since it appears in a shareable URL)has_many :cloud_messages;append_message!auto-titles from the first user turn and tracks activity.CloudMessageroles areuser/assistant/system. - API:
Api::V1::CloudSessionsController, token-scoped tocurrent_user, atpath: "sessions"(kept distinct from the authSessionsController):GET/POST /api/v1/sessions,GET/PATCH/DELETE /api/v1/sessions/:id, andPOST /api/v1/sessions/:id/messages. - Streaming is unchanged: clients still stream from
/api/v1/chat/completions; these endpoints only persist the transcript. The client appends the user message on send and the assistant message when the stream finishes. - Web app:
sigit-app/apps/code-cloud-webis the SvelteKit client at code.sigit.si./cloudis the chat;/cloud/sessionsand/cloud/sessions/<uuid>are the saved history. Same/api/v1surface, same-origin in production. (Root/is the on-device dashboard for signed-in users and the marketing landing for signed-out ones.)
Onde Cloud side (the API the cloud tier calls)
Repo: onde-cloud (Rust/axum, OpenAI-compatible).
- Endpoints:
POST /v1/chat/completions,GET /v1/models. - Routing:
router.rsresolves a requestmodelto a backend. Neutral public ids (onde-fast/balanced/large) map to upstream models; rawclaude-*ids are accepted as backward-compatible aliases but never advertised./v1/modelsreportsowned_by: "onde-inference": the upstream provider is never disclosed. - Tool calling:
anthropic.rsmaps OpenAI function-calling ↔ Anthropictool_use/tool_result(assistanttool_calls→tool_useblocks; coalescedtoolmessages → onetool_resultuser turn;parameters→input_schema;stop_reason→finish_reason). This is what makes the cloud tier a real agent backend, not just chat. - Error hygiene: client-facing errors are neutral ("Onde Cloud upstream error (NNN)"); full upstream detail is logged server-side only. Never echo provider names or upstream error bodies to the client.
- Auth: validates
app_id:app_secretagainst smbCloud (DEV_MODE=truebypasses for local dev and accepts any bearer).
Local dev / testing
# 1. Onde Cloud API (needs ANTHROPIC_API_KEY in onde-cloud/.env for the cloud tier)
cd ~/Repositories/onde-cloud && set -a && . ./.env && set +a && ./target/debug/onde-cloud
# → listens on $PORT (e.g. 8090); DEV_MODE=true accepts any bearer
# 2a. Product path: point siGit Code Cloud at the local API, start sigit
SIGIT_CLOUD_URL=http://localhost:8090/v1 SIGIT_API_URL=http://localhost:8088 sigit
# then inside the session:
# /login <email> <password> # sign in
# /models # pick a siGit Code Cloud tier (hot-swaps)
# 2b. BYO/override path (no login needed): explicit endpoint
OPENAI_BASE_URL=http://localhost:8090/v1 OPENAI_API_KEY=dev:dev SIGIT_TIER=fast sigit
# (or ~/.config/sigit/providers.toml with active = "<profile>")
SIGIT_CLOUD_URL overrides the baked-in cloud endpoint for dev. Watch routing in
the server log: chat: routing app_id=... to upstream provider. Use the fast tier
while testing to keep upstream spend low.
Key files
sigit/src/account.rs: account core (authenticate/end_session/status_line), sigit.si/api/v1client. Surfaced as/login,/logout,/whoami.sigit/src/credentials.rs: local token store.sigit/src/provider.rs: backend precedence, tiers, cloud default.sigit/src/backend.rs:InferenceBackendtrait,LocalBackend,OpenAiBackend.onde-cloud/src/{router,anthropic,chat,types}.rs: the hosted API + mapping.
Open items / known gaps
- Inference token ↔ Onde Cloud auth. Onde Cloud currently validates
app_id:app_secret; the product needs it to accept the sigit.si-issued user token (sigit.si fronts/mints inference auth, mirroring thegit_tokenexchange insigit-app). Until then, dev relies onDEV_MODE. sigit.si/api/v1auth routes exist (sessions/mecontrollers). The remaining gap is the inference-token link above, not the account endpoints.- Usage/billing:
onde-cloudreturns zeroedusage; wire Anthropic's returned token counts through for metering. - Tool-call id fingerprint: Onde Cloud passes Anthropic's
toolu_…tool-call ids straight through (OpenAI usescall_…). Reversibly rewrite before any external launch so the id format doesn't reveal the backend. - ACP path: the Zed/ACP server in
sigit/src/main.rsstill callsondedirectly; route it throughInferenceBackendtoo.
Billing — siGit's own, gating siGit Code Cloud
siGit Code is local-first and free; siGit Code Cloud (the Fast/Balanced/Large tiers) requires a paid plan. This is siGit's own Stripe billing, separate from Onde Inference's — siGit pays Onde as a customer; here siGit charges its end users.
- Gate:
Api::V1::ChatCompletionsController#enforce_cloud_entitlement!—CloudCatalog.cloud_tier?(model)(theonde-*/claude-*ids) requirescurrent_user.entitled_to_cloud?, else 402. On-device GGUF ids pass free. - Entitlement:
User#entitled_to_cloud?→Subscription#entitled_to_cloud?(pro/team&&active/trialing). No subscription = Free = local only. - Stripe:
StripeService(checkout/portal/construct_event/sync_subscription),Api::V1::BillingController(GET billing,POST billing/checkout|portal),StripeWebhooksController(POST /stripe/webhooks, signature-verified). Stripe is the source of truth; thesubscriptionstable caches plan + status. - Allowance:
enforce_cloud_allowance!caps cloud requests per billing period (Subscription::CLOUD_ALLOWANCE— pro 2000, team 6000; tunable).CloudUsagecounts per(user, billing_period_key), so it resets each cycle; over the cap → 429.GET /api/v1/billingreturnscloud_requests_used+cloud_allowance. - Plans: product
siGit Codein siGit's own Stripe account (acct_1TlaO9LwK8mGvn31, Splitfire AB); pricessigit_code_pro_monthly($20/mo) andsigit_code_team_monthly($40/mo), resolved by lookup key. - Env:
STRIPE_SECRET_KEY,STRIPE_WEBHOOK_SECRET(siGit account). Checkout is web-initiated (desktop/CLI open the returned URL) to avoid the App Store cut. - Remaining: a web/desktop billing UI; metering by tokens (the
usageOnde Cloud returns) instead of request count, if finer cost control is needed.
Common mistakes
- Making BYO-endpoint (env /
providers.toml) the default instead of thesigit loginproduct experience. - Exposing a model id, base URL, or provider name in product UI/errors.
- Treating the account access token as a model API key, or shipping smbCloud
app_secretin thesigitbinary (public-client violation). - Letting a persisted local model name show in the title while routing to the
cloud (guard with
InferenceBackend::is_remote()). - Mixing the names:
siGit Code(local agent),siGit Code Cloud(hosted chat),siGit Code Cloud Agent(autonomous, task -> PR),sigit(CLI),smbCloud(company),Onde Cloud(infrastructure). - Calling a chat conversation a "run", or the agent's work a "session". Chat has sessions; the agent has runs.