Python reference¶
Generated from the source. The prose explanation of how the pieces fit is in Architecture; this is the detail.
Only the modules worth calling from outside are listed. The engine's internals are documented in place — every module has a docstring saying what it is for — but a reference page for two hundred rule functions would be a worse way to read them than the source.
Paths and configuration¶
citar.paths ¶
Where CITAR keeps its files.
Every directory CITAR reads or writes is resolved here, once, so that the same code works whether it was started from a git checkout or installed from a wheel.
Two kinds of location¶
Package data ships inside the wheel and is read-only: the ruleset JSON, the browser client, the database migrations, the hardware-collector scripts. It always sits next to this file.
State is everything CITAR writes: saved games, the server registry, benchmark runs, lab results, reports, the usage ledger, the accounts database. Where that goes depends on how CITAR was started:
git checkout <checkout>/saves, <checkout>/config, <checkout>/benchmarks
installed a per-user state directory (see ``state_dir``)
The checkout case matters because it is the development workflow: a contributor's games, registry
and benchmark history sit beside the code where they can be inspected, diffed and deleted. The
installed case matters because site-packages is the wrong place to write a save file and is
often not writable at all — on Windows an installer puts it under Program Files, and on Linux a
system-wide install is owned by root.
Overrides¶
Environment variables win over both, and are how the systemd unit points a deployment at
/var/lib/citar:
====================== ====================================================================
CITAR_STATE_DIR Base for all state. Sets saves, config and benchmarks in one go.
CITAR_SAVE_DIR Saved games, and everything filed under them (maps, scenarios,
probes, lab, reports, usage, balance).
CITAR_CONFIG_DIR The server registry (servers.json) and its siblings.
CITAR_BENCH_DIR Benchmark suites and runs.
CITAR_DATA_DIR Private state: the accounts database and the generated secret key.
Never the checkout, even in development — see data_dir.
====================== ====================================================================
Nothing here creates a directory on import. Call ensure (or the *_dir helpers, which create
on demand) so that importing :mod:citar.paths stays free of side effects — tests and the --help
path both rely on that.
package_data ¶
web_dir ¶
migrations_dir ¶
collectors_dir ¶
in_source_checkout ¶
True when CITAR is running from a source tree that it may write to.
Both halves matter. A marker file alone is not enough — a checkout mounted read-only, or one owned by another user, would pass that test and then fail on the first save. So the directory is probed for writability as well, and anything unexpected counts as "not a checkout", which falls back to the per-user directory and always works.
Source code in citar/paths.py
state_dir ¶
The base directory for everything CITAR writes.
CITAR_STATE_DIR wins; then a writable source checkout; then the per-user directory that
:func:data_dir also uses. The result is not created here — the callers below do that for the
specific subdirectory they need.
Source code in citar/paths.py
ensure ¶
save_dir ¶
Saved games, and the folders filed beneath them (maps, scenarios, probes, lab, reports).
config_dir ¶
The server registry and other operator-edited configuration.
bench_dir ¶
Benchmark suites, runs and scoring settings.
CITAR_SAVE_DIR is honoured as a fallback so that a test or deployment which redirects saves
does not leave benchmark runs behind in a different tree; this was the existing behaviour and
tests depend on it.
Source code in citar/paths.py
data_dir ¶
Private state: the accounts database and the locally generated secret key.
This is deliberately not the checkout, even in development. The project folder may sync to OneDrive or Dropbox, and a sync client copying a SQLite file mid-write corrupts it; password hashes and session tokens do not belong in a synced folder either.
An explicit CITAR_STATE_DIR is honoured, because somebody who names one directory for all of
CITAR's state means this too — that is how the systemd unit points a deployment at
/var/lib/citar. What never happens is falling into a source checkout by accident.
Source code in citar/paths.py
sub ¶
A named subdirectory of :func:save_dir, created on demand.
sub("lab") is how the lab, probes, reports, usage ledger and balance reports find their
homes, so that redirecting CITAR_SAVE_DIR moves all of them together.
Source code in citar/paths.py
saves_path ¶
A path under the save directory, without creating anything.
config_path ¶
A path under the configuration directory, without creating anything.
Source code in citar/paths.py
bench_path ¶
A path under the benchmark directory, without creating anything.
Source code in citar/paths.py
describe ¶
Every resolved location, for citar doctor and bug reports.
Source code in citar/paths.py
citar.settings ¶
Runtime settings for the CITAR server, read from the environment once at import.
CITAR runs in one of two modes:
local (default) one operator on their own machine. The server binds to loopback, mints a
session for a single owner account automatically, and generates its own secret key.
This is the laptop workflow: no login screen, no TLS, no OAuth registration.
server the public deployment. Sessions are real, TLS is required, and the process refuses to
start without a secret key and a public origin.
Every authorization check runs the same way in both modes — local mode is a real account with a real session, not a bypass — so a permission bug cannot hide locally and surface in production.
Where things live¶
The project folder syncs to OneDrive, which is fine for code and game saves but not for a live SQLite database: the sync client copies files mid-write and a WAL database does not survive that. It is also the wrong place for password hashes and session tokens. So state that must be both durable and private goes to a per-user data directory outside the project:
Windows %LOCALAPPDATA%\CITAR
macOS ~/Library/Application Support/CITAR
Linux $XDG_DATA_HOME/citar (default ~/.local/share/citar)
Override with CITAR_DATA_DIR. On the VPS this is a normal directory under the service account, and it is the only thing that needs backing up besides saves/.
Secrets come from the environment, never from a file in the project folder. citar/env.example
lists every variable; on the VPS they are set in the systemd unit's EnvironmentFile with mode 0600.
SettingsError ¶
data_dir ¶
The private state directory (database, generated secret key). Created on demand.
Resolved by :func:citar.paths.data_dir, which every other directory also goes through, so
there is one answer to "where does CITAR put things" rather than one per module.
Source code in citar/settings.py
get ¶
reset ¶
Forget the cached settings, so the next read picks up a changed environment.
For tests, and for the setup wizard. Not something a running server does.
The command line¶
citar.cli ¶
The citar command.
One entry point in front of everything CITAR can do, so that an installed copy needs no knowledge of the module layout::
citar start the server and open the browser
citar serve the same, without opening a browser
citar setup interactive first-time configuration
citar doctor check this installation and print what it found
citar admin … operator commands (accounts, invitations, worker tokens)
citar worker … run the worker agent that serves local models to a remote CITAR
citar mcp … the MCP bridge, for Claude Code and other MCP clients
citar bench … command-line model benchmark
citar sim … one headless bot-vs-bot game in the console
citar balance … parallel bot games, for balancing the scripted bot
citar lab … the long-running bot experiment runner
citar where print every directory CITAR reads or writes
Sub-commands are thin: each one hands its remaining arguments to the module that already owned that
interface, so citar bench --all-models and python -m citar.bench --all-models are the same
program. python -m citar.server also still works, because the deployed systemd unit invokes it
that way and a packaging change should not require touching a running server.
main ¶
Dispatch a citar command line. Returns the process exit status.
Source code in citar/cli.py
citar.doctor ¶
citar doctor - check an installation and explain anything that is wrong.
The point of this command is to turn "it doesn't work" into a specific sentence. Every check prints one line: what was tested, what was found, and - when something is wrong - the one thing to do about it. Nothing here changes state, so it is safe to run against a live server, and its output is what a bug report should contain.
Checks, in the order a failure would stop you:
- Python and CITAR versions, and whether this is a checkout or an installed copy.
- Dependencies - every import CITAR needs, named individually rather than as one traceback.
- Directories - where state lives and whether it is writable.
- Configuration - the mode, and in server mode the settings that must be present.
- Database - that it opens and its schema is current.
- Ruleset - that the packaged data loads and how much of it there is.
- Model providers - every server in the registry that CITAR can reach right now.
- Port - whether the configured port is free, or already has a CITAR on it.
Report ¶
Collects check results and decides the exit status.
Exit status is what a health check or an installer looks at: 0 when everything that matters
works, 1 when something is actually broken. Warnings never fail the run - a missing optional
provider is a fact about the setup, not a fault in it.
Source code in citar/doctor.py
line ¶
Print one check's result, and count it.
Source code in citar/doctor.py
main ¶
Run every check and return 0 when nothing is broken.
Source code in citar/doctor.py
Setup¶
citar.wizard ¶
First-time setup: work out what this machine can do, then configure CITAR for it.
The wizard exists because CITAR spans two very different installations. Somebody who wants to play a few games against a model on their own PC needs a model endpoint and nothing else — no domain, no TLS, no accounts. Somebody standing up a public server needs all of that and will get a broken site if any one piece is missing. Asking every question to everybody guarantees that both audiences answer questions that do not apply to them, so the first question decides which of the three flows runs and the rest follow from it:
citar.wizard.local one person, this computer, models on localhost or an API key.
citar.wizard.server a public deployment: domain, TLS, sign-in, the first administrator.
citar.wizard.worker this machine's GPU serving models to a CITAR server somewhere else.
Two rules hold across all three:
Nothing is written until the end. Each flow collects a plan, shows it, and asks once. A wizard that edits as it goes leaves a half-configured machine behind when somebody presses Ctrl-C.
Every question has a non-interactive equivalent. The installers (install.sh, the Windows
installer, the Docker image) run the same code with --non-interactive and flags, so there is one
implementation of "set CITAR up", not one per packaging format.
citar.wizard.detect ¶
What can this machine do? — the questions the wizard answers before asking any.
Setup goes badly when the software makes the person describe their own computer. Everything here is something CITAR can find out for itself: which model servers are already running, what the GPU is, how much of a model will fit in it. A wizard that opens with "I found LM Studio with 6 models" is a different experience from one that opens with "enter your base URL".
Nothing in this module changes anything, and every probe has a short timeout: a model server that is not running should cost the wizard a fraction of a second, not a stall on a dead TCP port.
Endpoint
dataclass
¶
A model server found (or not found) on this machine.
Source code in citar/wizard/detect.py
port_open ¶
Is something listening? Checked first, because a closed port answers instantly.
An HTTP request to a closed port takes as long as the connect timeout, and the wizard probes half a dozen of them. Testing the socket first keeps detection under a second in the normal case, where none of them are running.
Source code in citar/wizard/detect.py
probe ¶
probe(
provider: str,
label: str,
base_url: str,
install_url: str = "",
timeout: float = 2.0,
) -> Endpoint
Ask one endpoint what models it has. Never raises.
Source code in citar/wizard/detect.py
find_endpoints ¶
Probe every well-known local model server.
Duplicates are collapsed by base URL: Ollama's OpenAI-compatible endpoint and LM Studio can be configured onto the same port, and offering the same URL twice under two names would be a confusing way to start.
Source code in citar/wizard/detect.py
hardware ¶
This machine's CPU, RAM and GPUs, via the same collector the Servers page uses.
usable_vram_gb ¶
The largest single GPU's VRAM, in GB.
The largest single card, not the total: a model has to fit in one of them unless the runtime is set up to split it, which is not something to assume during first-time setup. Integrated graphics are skipped — their "VRAM" is a slice of system RAM and produces a suggestion the machine cannot honour.
Source code in citar/wizard/detect.py
suggest_model ¶
A model to download, and why, for the VRAM this machine has.
Returns (name, reason). These are starting points rather than recommendations: the Models
page ranks what actually plays well once there are games to compare.
Source code in citar/wizard/detect.py
describe_hardware ¶
Human-readable lines about this machine, for the wizard's opening screen.
Source code in citar/wizard/detect.py
port_free ¶
Can CITAR bind here? Used to pick a default port that will actually start.
first_free_port ¶
The first free port at or after start, so a second CITAR does not collide with the first.
Source code in citar/wizard/detect.py
citar.wizard.prompts ¶
Terminal prompts for the setup wizard.
Deliberately small and dependency-free: CITAR is often set up over SSH on a box with nothing installed, and a setup wizard that needs a TUI library to ask "which port?" is a setup wizard that fails at the moment it is most needed.
Three things every prompt here gets right, which hand-rolled input() calls usually do not:
- A default you can accept with Return, shown in the prompt, so the whole wizard can be walked through by pressing Return when the defaults are right.
- Ctrl-C and EOF end the wizard cleanly, with a message saying nothing was changed, rather than
a traceback.
citar setuppiped from a script hits EOF on the first question; that must not look like a crash. - Non-interactive mode, where a question with no answer supplied is an error naming the flag that would have supplied it, instead of a hang waiting on a terminal nobody is watching.
Cancelled ¶
NeedsAnswer ¶
Bases: Exception
A non-interactive run reached a question it had no answer for.
Source code in citar/wizard/prompts.py
say ¶
heading ¶
note ¶
ask ¶
ask(
question: str,
default: str = "",
flag: str = "",
validate: Optional[
Callable[[str], Optional[str]]
] = None,
) -> str
Ask for a line of text.
validate returns an error message for a bad answer, or None when it is acceptable; the
question is asked again until it passes. That keeps validation next to the question rather than
in a second pass at the end, where the operator has forgotten what they typed.
Source code in citar/wizard/prompts.py
ask_yes_no ¶
Ask a yes/no question. Returns the default on a bare Return.
Source code in citar/wizard/prompts.py
ask_choice ¶
ask_choice(
question: str,
options: Sequence[tuple[str, str, str]],
default: str = "",
flag: str = "",
) -> str
Ask for one of several options.
Each option is (key, title, explanation). The explanation is printed under the title because
the whole reason a wizard beats a config file is that it can say what a choice means at the
moment the choice is made.
Source code in citar/wizard/prompts.py
ask_secret ¶
Ask for something that must not be echoed or stored in shell history.
Falls back to a visible prompt only when there is no terminal to hide the typing — and says so, because somebody pasting an API key deserves to know it is on screen.
Source code in citar/wizard/prompts.py
ask_port ¶
Ask for a TCP port, and reject the ones that will not work.
Source code in citar/wizard/prompts.py
confirm_plan ¶
Show everything that is about to happen, and ask once.
This is the only place the wizard asks for permission to change the machine, so it lists absolutely everything it will write — including files it will create outside the project.
Source code in citar/wizard/prompts.py
The game engine¶
citar.engine.rules ¶
Loads the rule set: UnCiv-derived data in citar/data/ruleset, CITAR additions in citar/data/custom and CITAR constants in citar/data/game.json. Every ruleset object is keyed by its display name (as UnCiv does, because uniques refer to objects by name) and also has a snake_case "id" that tools accept.
get_rules
cached
¶
citar.engine.tools ¶
The single registry of player tools (queries and actions).
Every interface — the browser UI, REST API, MCP server and LLM adapter — dispatches through execute(),
so rules are enforced identically for humans and AIs, and new tools automatically reach every client.
tool ¶
tool(
name: str,
description: str,
properties: Optional[dict] = None,
required=(),
kind="action",
any_time=False,
category="general",
)
Register a function as a player tool.
This decorator is the whole interface. Registering a function makes it callable from the browser, from MCP and from the LLM adapter at once, with the schema generated from properties - so there is no way to add an action to one interface and forget another, and no way for a model to have a power a human does not.
Source code in citar/engine/tools.py
Servers and costing¶
citar.servers ¶
The server registry (config/servers.json): every machine or online service that can run a model for CITAR, with everything needed to use it and to cost it.
A server has connection how to reach it: provider (lmstudio | ollama | openai_compatible | anthropic | dryrun | none), base URL, LM Link device (a model on another PC reached through this PC's LM Studio), API key backend (see keystore.py), how many games may use it at once, whether CITAR loads models itself hardware CPU, GPUs, RAM (or unified memory), disks... collected by hwinfo.py or typed in power idle watts and the extra watts at full CPU / full GPU load, used to estimate energy wherever it isn't sampled live components owned hardware with price, purchase date, lifespan and resale value (depreciated straight-line) costs effective-dated cost periods: electricity plan, fixed monthly costs (lease, maintenance), an hourly rate while in use (rented machines), per-token prices (APIs) restricted_hours windows (per weekday) when queued work must pause on it, optionally unloading its models models the model catalog: key, label, inference defaults and load profiles (context, GPU offload...)
Electricity plans are shared (both home PCs are on the same bill). One server is the CITAR host: the machine running this program, whose CPU runs the game engine and the scripted bots.
load ¶
The registry (cached until the file changes). Creates the default registry the first time.
Source code in citar/servers.py
save ¶
Write the registry atomically.
Atomic because the web page reads it while the lab writes it; a partial file would be read as a registry with no servers in it.
Source code in citar/servers.py
list_servers ¶
get ¶
One server by id, raising if it does not exist.
upsert ¶
Create or replace a server, validating it and its references first.
Source code in citar/servers.py
delete ¶
Remove a server.
resolve_llm ¶
Turn a seat's llm block into the agent config: provider, endpoint, key, model and inference settings. Seat blocks without a server_id (tests, scripts) are passed through unchanged.
Source code in citar/servers.py
seat_ref ¶
The small, saveable description of an LLM seat: which server, model and load profile, plus seat overrides.
Source code in citar/servers.py
restricted ¶
When the server is inside one of its restricted windows: the time the window ends. Otherwise None.
Source code in citar/servers.py
default_server ¶
A new server entry of a kind and provider, with sensible defaults throughout.
Source code in citar/servers.py
default_model ¶
A model entry with inference defaults suited to where it runs.
Local models default to low reasoning effort and automatic tool mode, because that is what makes them usable - about four times faster with no loss in play quality. Hosted models default to native tool calling, which they do well.
Source code in citar/servers.py
empty_registry ¶
A registry with no servers in it.
citar.usage ¶
Usage ledger: what every activity (game, benchmark game, probe run, lab game, report) used on which server, over time. Costs are not stored here: reports price the ledger with the server configs in force at the time, so fixing a wrong electricity rate later fixes every report.
Files (saves/usage/, JSON lines, one writer per file so processes never interleave):
server-YYYY-MM.jsonl written by the CITAR server (games, benchmarks, probes, reports)
lab-YYYY-MM.jsonl written by the lab runner (bot-vs-bot games)
power-
Row kinds
{"k": "act", "id", "kind", "name", "parent": {kind, id, name}, "ref": {...}, "t", ...} an activity; later rows for the same id update it {"k": "span", "act", "srv", "model", "t0", "t1", "held", "busy", "cpu", "in", "out", "rsn", "cr", "cw", "req"} one flush window (about a minute): seconds the activity held the server, seconds the model was generating, CPU seconds on the host, tokens (input, output, reasoning, cache read, cache write) and model requests {"k": "pw", "srv", "t0", "t1", "n", "gpu_w", "cpu_util"} one minute of power samples
Tracker ¶
Collects usage in the CITAR server process and writes it to the ledger about once a minute.
Source code in citar/usage.py
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 | |
activity ¶
activity(
act_id: str,
kind: str,
name: str = "",
parent: Optional[dict] = None,
ref: Optional[dict] = None,
**fields,
)
Begin recording one activity: a game, a benchmark, a probe run, a lab game.
Source code in citar/usage.py
update ¶
Add or change fields on an activity in progress.
Source code in citar/usage.py
register ¶
Count the servers holder() reports as held toward act_id (several holders may feed one activity, e.g.
the cases of a probe run: give each its own key).
Source code in citar/usage.py
unregister ¶
llm ¶
llm(
act_id: Optional[str],
server_id: Optional[str],
model: Optional[str],
seconds: float,
input_tokens: int = 0,
output_tokens: int = 0,
reasoning_tokens: int = 0,
cache_read: int = 0,
cache_write: int = 0,
requests: int = 1,
)
Record one model call: which server and model, how long, and how many tokens.
Source code in citar/usage.py
start ¶
stop ¶
flush ¶
Write pending ledger entries to disk.
Source code in citar/usage.py
PowerSampler ¶
Samples this machine's power draw every few seconds and writes one row a minute: GPU watts (nvidia-smi, all GPUs) and CPU utilisation. Only one process on the machine samples at a time (a heartbeat lock file), so the CITAR server and the lab runner can both start one.
Source code in citar/usage.py
324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 | |
start ¶
append ¶
Append rows to this writer's ledger file for the current month.
Source code in citar/usage.py
read ¶
All ledger rows between two timestamps: {"acts": {id: merged}, "spans": [...], "power": {srv: [...]}}.
Source code in citar/usage.py
tracker ¶
session_servers ¶
What a running game session holds right now: the host (its driver thread's CPU) and each LLM seat's server. Returns None when the session is finished (the tracker then drops it).
Source code in citar/usage.py
track_session ¶
track_session(
s,
kind: str = "game",
parent: Optional[dict] = None,
ref: Optional[dict] = None,
name: str = "",
)
Start (or continue, for a reloaded save) the usage record of a game session.
Source code in citar/usage.py
finish_session ¶
Close out a game session's ledger entry.
Source code in citar/usage.py
start_power_sampler ¶
Sample the host machine's power if the registry has a host with sampling on.
Source code in citar/usage.py
lab_game_rows ¶
lab_game_rows(
exp: str,
i: int,
res: dict,
host_id: Optional[str],
labels: Optional[list] = None,
) -> list[dict]
Ledger rows for one finished lab game (called by the lab runner).
Source code in citar/usage.py
citar.costing ¶
Prices the usage ledger (usage.py) with the server registry (servers.py).
Method, per server and per minute: * Fixed costs are charged on a calendar basis: a server's hourly fixed rate is its depreciation (straight-line per component over its lifespan, less resale) + fixed monthly costs (lease, maintenance, API subscription, a share of the electricity bill's fixed fee) spread over the hours of that month. An activity pays that rate for the time it held the server; activities holding it at the same time split the minute. Time nobody held is "unallocated". * Rented machines' hourly usage rate is charged the same way, but only for time in use. * Energy: the machine's power for the minute is measured (GPU watts sampled with nvidia-smi plus CPU utilisation times the server's CPU watts) or, where there are no samples, estimated from what CITAR had it doing (model generation seconds for the GPU, CPU seconds for the CPU). Idle power is split like fixed costs (by time held); the extra "dynamic" power above idle goes to the activities that did work in that minute, by their share of it. The energy price comes from the electricity plan in force (flat, time-of-use or tiered) plus, if chosen, the plan's fixed fee spread over the household's monthly kWh. * API servers: tokens x the model's per-million prices (input, output, cache reads, cache writes). Everything is computed with the configuration in force at the time of use (effective-dated cost periods).
hours_in_month ¶
depreciation_rate ¶
depreciation_rate(
server: dict,
when: datetime,
lifespan_override: Optional[float] = None,
) -> float
Currency per hour of calendar time for the server's owned components at when.
Source code in citar/costing.py
energy_price ¶
(price per kWh incl. any fixed-fee adder, fee per hour charged by share) for an electricity plan at when.
Source code in citar/costing.py
fixed_rates ¶
fixed_rates(
server: dict,
reg: dict,
when: datetime,
lifespan_override: Optional[float] = None,
) -> dict
Currency per hour: depreciation, other fixed (lease, maintenance, subscription, electricity fee share) and the rented-machine usage rate; plus the energy price per kWh.
Source code in citar/costing.py
total ¶
The total of a cost breakdown, on the chosen energy basis.
full counts all the energy the machine drew; marginal counts only the extra caused by the
work. Both are defensible and they differ by a lot, which is why it is a choice rather than a
constant.
Source code in citar/costing.py
compute ¶
compute(
since: Optional[float] = None,
until: Optional[float] = None,
reg: Optional[dict] = None,
ledger: Optional[dict] = None,
lifespan_override: Optional[float] = None,
) -> dict
Cost every activity in [since, until]. Returns { "acts": {act_id: {"info": act row, "servers": {srv: cost}, "models": {model: {...}}}}, "servers": {srv: {"allocated": cost, "calendar": {...}, "power": {...}}}, "days": {date: {srv: cost}}, "notes": [...]}
Source code in citar/costing.py
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 | |
hourly_profile ¶
hourly_profile(
server: dict,
reg: dict,
when: Optional[datetime] = None,
busy_fraction: float = 1.0,
) -> dict
What an hour on this server costs at when with the model busy busy_fraction of the time (for what-if
comparisons): fixed + depreciation + usage rate + estimated energy.
Source code in citar/costing.py
price_tokens ¶
What a number of tokens costs at a model's prices.
Source code in citar/costing.py
Hardware¶
citar.hwinfo ¶
CITAR hardware collector: what a machine has (CPU, GPUs, memory, disks, local model runtimes) and a first guess at its power draw, as JSON for the Servers page.
This file is deliberately standalone (standard library only; uses psutil when installed) so it can be copied to any Windows, Linux or macOS machine and run there:
python collect_hardware.py > my-machine.json # then import the file on the Servers page
On the machine that runs CITAR, the Servers page calls collect() directly.
collect ¶
Everything this machine is, as one dictionary.
Standard library only, so it can be downloaded as a single file and run on a machine that has nothing else installed - which is exactly the case it exists for.
Source code in citar/hwinfo.py
guess_power ¶
Rough wall-power figures to start from; replace them with measured numbers (a plug-in power meter) if you can.
Source code in citar/hwinfo.py
Keys¶
citar.keystore ¶
API key storage for servers. Keys never live in the project folder (it may sync to the cloud) and are never sent back to the browser: the web form posts a key once, and afterwards the UI only learns whether one is stored.
Backends (chosen per server):
keyring the operating system's credential store via the `keyring` package: Windows Credential Manager,
macOS Keychain, or the Linux Secret Service / KWallet. The default when available.
env an environment variable named in the server config (nothing is stored by CITAR).
file a file in the user's config directory (outside the project) encrypted with a passphrase
(scrypt + Fernet). For headless Linux boxes without a keyring. The passphrase comes from
CITAR_KEYS_PASSPHRASE or is entered in the web UI once per server start (kept in memory only).
backends ¶
Which backends work on this machine, for the Servers page.
Source code in citar/keystore.py
store ¶
Store a key for a server. The env backend can't store anything (set the variable yourself).
Source code in citar/keystore.py
get ¶
The API key for a server config (its connection.key block), or None.
Source code in citar/keystore.py
delete ¶
Remove a stored key.
Source code in citar/keystore.py
status ¶
What the UI may know: whether a key is present (never the key itself), plus a masked hint.
Source code in citar/keystore.py
unlock ¶
Remember the file passphrase for this server process (checked against the file when one exists).