Cloud or local
Kaja is one agent with several front doors: the terminal, Telegram and a website widget. The two modes differ in where the agent loop runs, and so in which tools it can use.
| Front door | Loop runs on | Store | Your files, shell, own MCP servers and plugins |
|---|---|---|---|
kaja, local mode | your machine | SQLite | β |
kaja telegram | your machine | SQLite | β |
kaja, cloud mode | the API | Postgres | β (files are read locally) |
| cloud Telegram bot | the API | Postgres | β |
| Website widget | the API | Postgres | β |
Which mode a launch uses
---
config:
look: handDrawn
theme: neo-dark
---
flowchart TD
Start(["kaja"]) --> Flag{"--local or --cloud?"}
Flag -->|"--local"| Local
Flag -->|"--cloud"| Cloud
Flag -->|"neither"| Pref{"preferences.mode<br>in settings.toml?"}
Pref -->|"local"| Local
Pref -->|"cloud"| Cloud
Pref -->|"no config yet"| Wizard["setup wizard asks"]
Wizard --> Pref
Pref -->|"older config, no mode"| Guess{"usable chat model?"}
Guess -->|yes| Local
Guess -->|no| Cloud
Cloud(["βοΈ Cloud: device login,<br>loop on the API"])
Local(["π» Local: ~/.config/kaja,<br>loop in your process"])
The setup wizard saves your choice as preferences.mode, even before a provider works. A flag overrides it for one launch:
kaja --local # local agent loop, even with no config yet
kaja --cloud # cloud login, even if a local config exists
Cloud mode
The first time, Kaja does a device login: it shows a code, you approve it at kaja.io/device, and a token goes into your OS keychain. Nothing is written to disk. One account is signed in at a time, and kaja logout clears it.
Without a keychain, cloud mode stops and suggests
--local. There is no plaintext fallback.
The server picks the model and keeps your sessions, memory and dataset answers. The agent can use:
- the cloud built-in tools: memory, datasets,
ask_user, image generation and more; read_fileandlist_files, which run in your terminal, limited to the folder you started in and with no confirmation prompt;- every persona in the marketplace, each with the skills, HTTP tools and MCP servers it lists (Abilities in the cloud).
There is no shell, no code tools and no model switching.
Local mode
The whole agent runs in your process:
- config comes from
~/.config/kaja/(Configuration); - models come from your
models.toml. Without a chat model Kaja exits with an error; it never falls back to the cloud; - every tool works: files, shell, MCP servers, plugins, hosts on your own network;
- sessions, memory and dataset answers live in a SQLite file;
kaja -cresumes the last session,kaja -s <id>a specific one, andkaja sessionslists them.
How a turn runs
Both modes run the same agent core, Nasi:
---
config:
look: handDrawn
theme: neo-dark
---
flowchart TD
In["π¬ your message"] --> Prompt["compose system prompt<br>persona + memory + skills + language"]
Prompt --> CallLLM["β¨ call the model"]
CallLLM --> Decide{"what next?"}
Decide -->|"a tool"| RunTool["π οΈ run the tool"]
RunTool -->|"result"| CallLLM
Decide -->|"switch_persona"| Switch["π swap persona<br>(and its model)"]
Switch --> CallLLM
Decide -->|"ask_user"| Ask["β wait for your answer"]
Decide -->|"needs approval"| Approve["β
approve / decline"]
Ask -.->|"answer"| CallLLM
Approve -.->|"result"| CallLLM
Decide -->|"final answer"| Reply["π¬ reply"]
Reply --> DB[("SQLite (local)<br>Postgres (cloud)")]
Two things hand control back to you:
ask_user: the agent needs an answer. Your next message is that answer, not a new turn.- Approvals: a shell command, or an HTTP tool or MCP call that changes something, waits for your OK. You get a prompt above the input in the terminal, and buttons in Telegram.
switch_persona doesnβt stop anything. It swaps the persona and carries on.
Next: