Admin console settings
This table is generated from the vault's closed settings registry — the same enumeration the console enforces, so it is complete by construction. A console value overrides the environment baseline until cleared; an unknown key or out-of-contract value is refused, never stored.
Some settings are set in the deployment's env file, never here
The Where it is set column says which. The malware scanner is the whole of that class today — the engine, its signatures, the external command and its timeout — and the reason is that each of those is a lever that can leave the ingest scan gate detecting nothing. The vault refuses to start a production installation on a scanner that scans nothing, and that refusal reads the environment the container was started with, not the database: a check that needed the database would refuse a vault whose database was merely slow. So a stored value would be a downgrade no guard could see, and one console session is a narrower thing to trust than the file an operator edits on the host.
Set SKYKEEP_SCANNER in the env file this deployment was started with and restart. It ships empty, and empty is a configured absence rather than an oversight: with no engine named every upload quarantines. That is the gate working. Nothing anywhere turns the two ingest scan gates off — there is no such setting, and the console listing one would not make one.
Four of these settings — the two model roles, the embedding model and the entity-extraction fallback — have a page of their own, because choosing one is a decision with consequences elsewhere: see Choosing the models.
| Setting | Environment baseline | Where it is set | What it does | Example |
|---|---|---|---|---|
model_provider | SKYKEEP_MODEL_PROVIDER | admin console, over the environment baseline | Which model runtime serves every AI-driven step. 'ollama' serves embeddings and generation; 'openai' speaks the OpenAI-compatible chat API and generates only — its embeddings refuse rather than guess. An unrecognized provider refuses service; it never falls back. | ollama |
model_base_url | SKYKEEP_OLLAMA_BASE_URL | admin console, over the environment baseline | The model provider's endpoint (ADR-0002: connection details are configuration). Local-first: a non-local endpoint is refused unless the operator's explicit egress override is active. | http://models:11434 |
embed_model | SKYKEEP_EMBED_MODEL | admin console, over the environment baseline | Embedding model (ingestion step 7) — a different KIND of choice from the two chat roles, which is why ADR-0031 keeps it on its own control. Changing the answering model changes the next answer; changing this one invalidates every vector in the vault. A different-width model is refused by the engine; a same-width one is worse, because it succeeds and ranks new documents against old vectors from another space. In practice a kickoff-time decision (architecture doc, Model Configuration). | all-minilm |
think_model | SKYKEEP_THINK_MODEL | admin console, over the environment baseline | The ANSWERING model: it writes the answer to a question (ADR-0009 — safety is structural, so this choice changes answer quality, never what an answer may be written from). Settings → Models lists what this deployment's runtime actually has, with what each one is good and bad at. | granite4.1:3b |
ingest_model | SKYKEEP_INGEST_MODEL | admin console, over the environment baseline | The INGESTION model: it reads a document at upload to write its summary. Nobody waits on an upload, so a slower, more careful model is cheap here — but the cost is paid on every document. Empty means ingestion uses the answering model. | granite4.1:3b |
entity_fallback_model | SKYKEEP_ENTITY_FALLBACK_MODEL | admin console, over the environment baseline | Entity extraction's LLM fallback model (ingestion step 6). Empty disables the fallback: unresolved mentions honestly stay unresolved, never guessed (Gate IN.5). | granite4.1:3b |
model_context_tokens | SKYKEEP_MODEL_CONTEXT_TOKENS | admin console, over the environment baseline | How many TOKENS of context the vault asks the model runtime for. Ollama's own default is 2048 and it silently truncates a longer prompt to its tail — which for this vault means the highest-ranked passages are discarded and the answer is built from the leftovers. Raising this costs memory on the model host; lowering it costs evidence, quietly. | 8192 |
embed_batch_max_texts | SKYKEEP_EMBED_BATCH_MAX_TEXTS | admin console, over the environment baseline | Maximum number of texts to embed in a single call to the model provider (PT-30). Ingestion and search batch embeddings in sub-calls if they exceed this limit. A large value (e.g. 512) is safe on a high-memory runtime; a small value (e.g. 8) is safe on a constrained one. Empty or out of range = 64. | 64 |
passages_per_document | SKYKEEP_PASSAGES_PER_DOCUMENT | admin console, over the environment baseline | How many passages ONE document may contribute to a single retrieval. Empty means 3. Raising it lets a document that answers a question in several places actually answer it; lowering it spreads the same k results across more documents. 1 is the historical behaviour: exactly one passage per document, which is why a résumé could be stored, matched and still not yield the phone number printed at the top of it. The per-request k is unchanged and still bounds the total. | 3 |
summary_max_words | SKYKEEP_SUMMARY_MAX_WORDS | admin console, over the environment baseline | How long a document summary may be, in words. Empty means 200, the architecture's own bound. A document too long for the answering model's context window is read in PARTS and the part-summaries summarized together; the listing says when that happened, because a summary of summaries is a weaker claim than a summary and must not arrive looking like one. | 200 |
tags_max_count | SKYKEEP_TAGS_MAX_COUNT | admin console, over the environment baseline | How many TAGS a document may carry. Empty means 8. Tags are read from the vault's own summary by the classification pass and published in the OKF frontmatter an agent reads, so this bounds what leaves the vault about one document. A model offering more than this is TRUNCATED, not refused, and the ingest's audit entry records that it was. Changing this reaches later uploads only: tagging happens once, at ingest. | 8 |
tags_max_length | SKYKEEP_TAGS_MAX_LENGTH | admin console, over the environment baseline | How long ONE tag may be, in characters. Empty means 40. A longer candidate is DROPPED whole rather than trimmed to fit — trimming would manufacture a tag the model never offered — and this is a length bound only: a tag carrying a newline, a control character or a YAML `---` marker is dropped at any length, by a rule no setting can relax. | 40 |
chunk_window_words | SKYKEEP_CHUNK_WINDOW_WORDS | admin console, over the environment baseline | How many WORDS of a document go into one chunk. It must fit the embedding model's input limit: a chunk longer than the embedder reads is indexed by its opening only, and the rest is stored, decryptable and citable but unreachable by meaning. A value the configured embedder cannot read is REFUSED here, with both numbers named, rather than silently indexing a prefix. Empty means 190, which fits the shipped embedder. **This does not apply to documents already ingested.** Chunking happens once, at upload; changing this re-chunks nothing already stored and reaches only later uploads. | 190 |
document_date_floor_year | SKYKEEP_DOCUMENT_DATE_FLOOR_YEAR | admin console, over the environment baseline | The earliest year a document may claim to have been written. A document states its own creation date in bytes the uploader controls, so the vault records that claim separately from the moment it received the file — and refuses a claim that is in the future or older than this year, leaving the document's date UNKNOWN with the refusal recorded rather than letting an asserted date decide where the document sorts. Empty means 1900; there is no way to configure no floor at all. Changing it does not re-assess documents already ingested. | 1900 |
passage_expansion_max_words | SKYKEEP_PASSAGE_EXPANSION_MAX_WORDS | admin console, over the environment baseline | How many WORDS a passage may reach the answering model as. A chunk that wins the ranking is expanded to its enclosing heading section, up to this bound, so the model reads a whole thought instead of the window the embedder's input limit forced. Citations still name the chunk that MATCHED, never the wider span. Empty means 600; 0 turns widening off and every passage arrives exactly as it was chunked. A bound the configured context window cannot hold is REFUSED here, with both numbers named. **This applies to documents already stored** — it changes which offsets are read, not what was written. | 600 |
second_hop_max_passages | SKYKEEP_SECOND_HOP_MAX_PASSAGES | admin console, over the environment baseline | How many EXTRA passages a second retrieval hop may add. When a first search surfaces a reference code the question did not contain, the vault searches again for that code and the answering model gets both halves — the essay and the register row that points at it. The second search runs as the same person under the same compartment predicates, so it can never reach a document the asker could not already read. A question that surfaces no new code costs nothing. Empty means 2; 0 turns the hop off and the prompt is exactly what it was before. | 2 |
auth_provider | SKYKEEP_AUTH_PROVIDER | admin console, over the environment baseline | How this vault signs people in (ADR-0003). Two implementations ship. 'local' is the password+OTP provider whose credentials live in the vault's own database. 'directory' authenticates each sign-in as a simple bind against the configured LDAP/Active Directory server (SKYKEEP_DIRECTORY_*, ADR-0060) — the person must ALREADY hold an account here; this signs them in, it does not create anybody. The choice is exclusive and it is the whole vault's: under 'directory' a local-only account cannot sign in, INCLUDING the bootstrap administrator, and neither password rotation nor changing where sign-in codes go is offered, because SkyKeep only ever reads the directory and cannot write a new secret back into it. Unset, or any other value, refuses every sign-in (503) — there is no fallback to local. | local |
disable_window_seconds | SKYKEEP_DISABLE_TERMINATION_SECONDS | admin console, over the environment baseline | Disabling an account blocks new sessions immediately and ends an in-flight session within this window (Gate AU.4); ceiling-clamped to the human session ceiling. | 60 |
scanner | SKYKEEP_SCANNER | env file only — not in the console | ENVIRONMENT ONLY (PT-4c): set in the deployment's env file, never in the console. Malware scanner engine for the ingest gate. Empty — the shipped default — means no scanner is configured, and an unconfigured scanner quarantines every upload: fail-closed, never fail-open. 'pattern' is the byte-signature double for dev/CI; 'command' runs an external ClamAV-convention command (SKYKEEP_SCANNER_COMMAND) and is what a real engagement uses. Neither choice can turn the gate OFF: there is no such setting. | command |
scanner_signatures | SKYKEEP_SCANNER_SIGNATURES | env file only — not in the console | ENVIRONMENT ONLY (PT-4c). Comma-separated signature list for the pattern scanner. A scanner with no signatures could never detect, so it counts as unconfigured (uploads quarantine) — which is why emptying it is the same lever as naming no engine at all. | SKYKEEP-MALWARE-TEST-SIGNATURE |
scanner_command | SKYKEEP_SCANNER_COMMAND | env file only — not in the console | ENVIRONMENT ONLY (PT-4c): a console that could edit this line could point the malware gate at /bin/true, which exits 0 on anything. The command the 'command' scanner runs, shell-word-split. The document's path is appended as the final argument (clamscan's own convention) and the exit status is the verdict: 0 clean, 1 infected, anything else an error that quarantines. Empty while the engine is 'command' counts as unconfigured, so every upload quarantines. The shipped default points at containers/scanner/clamscan-stub.sh, which ANSWERS CLEAN WITHOUT SCANNING — replace it with a real clamscan/clamdscan and no code changes. | /opt/skykeep/scanner/clamscan-stub.sh --no-summary |
scanner_timeout_seconds | SKYKEEP_SCANNER_TIMEOUT_SECONDS | env file only — not in the console | ENVIRONMENT ONLY (PT-4c), for the same reason as the engine and the command: a timeout small enough that every scan gives up is the gate stopped by another name. How long the 'command' scanner may take on one document before the gate gives up. A timeout is a QUARANTINE, never a pass — a scanner that did not answer has not answered 'clean'. | 60 |
mail_relay | SKYKEEP_MAIL_RELAY | admin console, over the environment baseline | Outbound mail relay kind for one-time sign-in codes (Gate AU.6). Empty means no relay is configured, and code delivery refuses fail-closed — sign-in is honestly unavailable, never a silently dropped code. | smtp |
smtp_host | SKYKEEP_SMTP_HOST | admin console, over the environment baseline | The customer's SMTP relay host (ADR-0002: connection details are configuration). Required once mail_relay is 'smtp'. | relay.example.internal |
smtp_port | SKYKEEP_SMTP_PORT | admin console, over the environment baseline | The SMTP relay's TCP port (1-65535). Required once mail_relay is 'smtp'. | 587 |
smtp_from | SKYKEEP_SMTP_FROM | admin console, over the environment baseline | The From address on outbound one-time-code mail. Required once mail_relay is 'smtp'. | skykeep-vault@example.com |
smtp_username | SKYKEEP_SMTP_USERNAME | admin console, over the environment baseline | SMTP AUTH username. Empty (with an empty password) means an unauthenticated relay; setting one of username/password without the other refuses fail-closed. | skykeep-mailer |
smtp_password | SKYKEEP_SMTP_PASSWORD | admin console, over the environment baseline | SMTP AUTH password. A secret: it travels only in request bodies, is redacted in audit entries, and never appears in URLs or logs. | change-me-dev-only |
smtp_tls | SKYKEEP_SMTP_TLS | admin console, over the environment baseline | SMTP transport security: 'starttls' (upgrade after connect), 'tls' (implicit TLS from the first byte), or 'plain' (cleartext — trusted networks only). Empty defaults to 'starttls'. | starttls |
require_all_mfa | SKYKEEP_REQUIRE_ALL_MFA | admin console, over the environment baseline | Require the one-time sign-in code for EVERY account past its first login, whatever each person has chosen on their profile (ADR-0025). Empty or 'false' leaves the choice to each account. Turning this on overrides every stored preference and never overwrites one: turning it off again restores exactly what each person had chosen. | false |
purge_certificates | SKYKEEP_PURGE_CERTIFICATES | admin console, over the environment baseline | Issue purge certificates: a signed statement, with the audit chain entries that prove it, that a named version was destroyed in this vault (ADR-0087). Empty or 'true' (the shipped default) offers the capability. 'false' refuses every request for one, names the reason, and records the refusal in the audit trail — it does NOT invalidate a certificate already issued, which goes on verifying offline for as long as its engagement key slot is installed. | true |
email_domain_restriction | SKYKEEP_EMAIL_DOMAIN_RESTRICTION | admin console, over the environment baseline | Restrict invitations to the allowed e-mail domains below. Empty or 'false' (the shipped default) invites ANY domain, which is what this vault does today. 'true' refuses an address whose domain is not listed — and, if the list is empty, refuses every address rather than allowing every address, because a restriction with nothing to compare against is an unfinished intention, not permission. | false |
allowed_email_domains | SKYKEEP_ALLOWED_EMAIL_DOMAINS | admin console, over the environment baseline | The e-mail domains an account may be invited at, separated by commas, semicolons or spaces. Matching is EXACT and case-insensitive: listing 'example.com' does NOT admit 'mail.example.com' — a sub-domain may be delegated to anybody, so it is listed on its own or not at all. The Users page shows which existing accounts fall outside this list, whether or not the restriction above is on. | example.com, partner.example.org |
email_domain_exceptions | SKYKEEP_EMAIL_DOMAIN_EXCEPTIONS | admin console, over the environment baseline | Individual addresses admitted whatever the allowed-domain list says — the contractor, the auditor, the one person the rule was not meant for. WHOLE ADDRESSES only: a bare domain is refused here, because a domain belongs in the allowed list and two settings that meant the same thing would drift apart. | contractor@othercompany.example |
near_duplicate_max_distance | SKYKEEP_NEAR_DUPLICATE_MAX_DISTANCE | admin console, over the environment baseline | How different two documents may be and still be reported as nearly duplicate, in bits of a 64-bit fingerprint. Measured on ordinary prose: a re-flowed copy is 0, a word or a sentence changed is 3-8, one paragraph of seven rewritten is 13, an unrelated document of the same kind is 34. Empty or out of range = 16. Raising it finds more pairs and more false ones; 0 reports only documents whose fingerprints are identical. | 16 |
near_duplicate_min_shingles | SKYKEEP_NEAR_DUPLICATE_MIN_SHINGLES | admin console, over the environment baseline | How much text a document must have before the vault will fingerprint it at all, counted in overlapping five-word windows. Below this the document gets NO fingerprint and can never be reported as a near duplicate of anything — without that rule every file the vault could not read would pair with every other one. Empty or out of range = 8. | 8 |
near_duplicate_min_length_percent | SKYKEEP_NEAR_DUPLICATE_MIN_LENGTH_PERCENT | admin console, over the environment baseline | The shorter of two documents must be at least this percent of the longer one before they can be called near duplicates. The fingerprint is a fixed 64 bits however long a document is, so a page and a book that repeats it land close together; this is what stops the vault calling them the same file. Empty or out of range = 50. | 50 |
near_duplicate_max_candidates | SKYKEEP_NEAR_DUPLICATE_MAX_CANDIDATES | admin console, over the environment baseline | How many near-duplicate pairs one upload may record. A cap, because a corpus of near-identical forms would otherwise pair a new upload with hundreds of rows nobody will read. 0 turns detection off entirely — the same spelling SKYKEEP_SECOND_HOP_MAX_PASSAGES uses for the same meaning. Empty or out of range = 20. | 20 |
disk_space_threshold_bytes | SKYKEEP_DISK_SPACE_THRESHOLD_BYTES | admin console, over the environment baseline | Free disk space threshold in bytes below which uploads are refused (PT-7). The threshold applies only when a disk-space sample exists; a missing sample means 'unknown' and is not a refusal. A refused upload is given an honest reason naming both the current free space and the threshold, so an operator can tune either. Empty or out of range = 104857600 (100 MB). | 104857600 |
merge_confirmation_ttl_seconds | SKYKEEP_MERGE_CONFIRMATION_TTL_SECONDS | admin console, over the environment baseline | How long a compartment-merge confirmation stays spendable, in seconds. Merging is the vault's only clean-up verb and has no unmerge, so it is confirmed against a preview: the preview counts what moves, the merge re-counts and refuses if the numbers have changed, and this bounds the gap between the two. Lower is safer and more annoying; a long window is a confirmation of what the vault looked like some time ago. Empty = 300. | 300 |
hold_default_review_days | SKYKEEP_HOLD_DEFAULT_REVIEW_DAYS | admin console, over the environment baseline | How long after a legal hold is placed somebody should ask whether it still needs to stand, in days. THIS IS NOT AN EXPIRY: a hold past its review date is still a hold, it still refuses a purge, and no setting, timer or job anywhere in this vault lifts one — the only way out is an administrator typing the acknowledgment phrase and a reason. What the date does is put the hold on the holds-needing-review panel, answer the stale listing, and make a refused purge say in the trail that a stale hold is what refused it. Changing this moves the date stamped on holds placed from now on; it never moves the date on a hold already placed. Empty = 180. | 180 |
grant_expiry_warn_days | SKYKEEP_GRANT_EXPIRY_WARN_DAYS | admin console, over the environment baseline | How many days ahead the admin console's recertification list looks when it names the grants that are about to expire (PT-26). A grant with no expiry never appears in that list at whatever window — it is not going to lapse. This dial reports; it never grants, extends or revokes anything. Empty or out of range = 30. | 30 |
base_max_inflight | SKYKEEP_BASE_MAX_INFLIGHT | env file only — not in the console | ENVIRONMENT ONLY (ADR-0094): how many model calls this PROCESS may have open at once against the BASE endpoint — every chunk of every document, as embeddings, and every generation on a deployment that has not split one off. The largest cap of the six because an embed is milliseconds where a generation is seconds, so this is the seam a queue actually drains. A cap is a property of this process's own sockets, so N replicas are N caps; it is deployment configuration and a console that could raise it could raise it past what the runtime can serve. Past the cap a request waits SKYKEEP_ADMISSION_WAIT_SECONDS and is then refused 429 with Retry-After; an ingest call waits instead, because it has nobody to refuse to. 0 IS REFUSED AT START-UP by name — it is not 'no limit', it is a vault that can never think. Empty = 8. | 8 |
generation_max_inflight | SKYKEEP_GENERATION_MAX_INFLIGHT | env file only — not in the console | ENVIRONMENT ONLY (ADR-0094): how many model calls this PROCESS may have open at once against the GENERATION endpoint — ingest summaries, entity fallback, the auditor screen, translation. UNSET GENERATION SHARES THE BASE CAP: if no separate generation endpoint is configured, generation runs on the base runtime and is metered by SKYKEEP_BASE_MAX_INFLIGHT, because two numbers over one machine would mean neither of them was the real limit. A cap is a property of this process's own sockets, so N replicas are N caps; it is deployment configuration and a console that could raise it could raise it past what the runtime can serve. Past the cap a request waits SKYKEEP_ADMISSION_WAIT_SECONDS and is then refused 429 with Retry-After; an ingest call waits instead, because it has nobody to refuse to. 0 IS REFUSED AT START-UP by name — it is not 'no limit', it is a vault that can never think. Empty = 4. | 4 |
answer_max_inflight | SKYKEEP_ANSWER_MAX_INFLIGHT | env file only — not in the console | ENVIRONMENT ONLY (ADR-0094): how many model calls this PROCESS may have open at once against the ANSWER endpoint — the final synthesis call alone. Shares the GENERATION cap (and through it the BASE cap) unless SKYKEEP_ANSWER_MODEL_URL routes it somewhere of its own. A cap is a property of this process's own sockets, so N replicas are N caps; it is deployment configuration and a console that could raise it could raise it past what the runtime can serve. Past the cap a request waits SKYKEEP_ADMISSION_WAIT_SECONDS and is then refused 429 with Retry-After; an ingest call waits instead, because it has nobody to refuse to. 0 IS REFUSED AT START-UP by name — it is not 'no limit', it is a vault that can never think. Empty = 4. | 4 |
vision_max_inflight | SKYKEEP_VISION_MAX_INFLIGHT | env file only — not in the console | ENVIRONMENT ONLY (ADR-0094): how many model calls this PROCESS may have open at once against the VISION engine — image bytes. Small because a reading carries a whole file and its concurrency costs memory as well as runtime. Shares the BASE cap when SKYKEEP_VISION_BASE_URL is unset. A cap is a property of this process's own sockets, so N replicas are N caps; it is deployment configuration and a console that could raise it could raise it past what the runtime can serve. Past the cap a request waits SKYKEEP_ADMISSION_WAIT_SECONDS and is then refused 429 with Retry-After; an ingest call waits instead, because it has nobody to refuse to. 0 IS REFUSED AT START-UP by name — it is not 'no limit', it is a vault that can never think. Empty = 2. | 2 |
transcribe_max_inflight | SKYKEEP_TRANSCRIBE_MAX_INFLIGHT | env file only — not in the console | ENVIRONMENT ONLY (ADR-0094): how many model calls this PROCESS may have open at once against the TRANSCRIBE engine — audio bytes. Its own cap always: an unset transcription URL is a vault that does not transcribe, not one that transcribes somewhere else. A cap is a property of this process's own sockets, so N replicas are N caps; it is deployment configuration and a console that could raise it could raise it past what the runtime can serve. Past the cap a request waits SKYKEEP_ADMISSION_WAIT_SECONDS and is then refused 429 with Retry-After; an ingest call waits instead, because it has nobody to refuse to. 0 IS REFUSED AT START-UP by name — it is not 'no limit', it is a vault that can never think. Empty = 2. | 2 |
page_render_max_inflight | SKYKEEP_PAGE_RENDER_MAX_INFLIGHT | env file only — not in the console | ENVIRONMENT ONLY (ADR-0094): how many model calls this PROCESS may have open at once against the PAGE RENDER engine (ADR-0091) — PDF bytes drawn into pictures. Its own cap always, for the reason transcription's is its own. A cap is a property of this process's own sockets, so N replicas are N caps; it is deployment configuration and a console that could raise it could raise it past what the runtime can serve. Past the cap a request waits SKYKEEP_ADMISSION_WAIT_SECONDS and is then refused 429 with Retry-After; an ingest call waits instead, because it has nobody to refuse to. 0 IS REFUSED AT START-UP by name — it is not 'no limit', it is a vault that can never think. Empty = 4. | 4 |
admission_wait_seconds | SKYKEEP_ADMISSION_WAIT_SECONDS | env file only — not in the console | ENVIRONMENT ONLY (ADR-0094): how long a request-path model call waits for room on its seam before it is refused 429. SECONDS, and NOT the model timeout — a caller waiting here holds a request thread and a socket while producing nothing, so the wait exists to absorb a burst that is already draining, never to queue. The number is also what a refused caller is told to wait, because the seam was full for the whole of it. An ingest call ignores this and waits up to its seam's own model timeout instead. Empty = 5. | 5 |