Workspace Settings
Overview
Section titled “Overview”A workspace setting is one administrative switch that changes how Logrite treats a whole
workspace: how AI coding sessions are recorded and checked, what happens to a log line between a
collector and the index, what a new collector collects, and what reaches the bell. There are 29
keys in five groups — coding.*, logging.*, ingest.*, collector.defaults.* and notify.*.
- The catalogue is code, not data. Every key, its type, default and bounds live in the static
WorkspaceSettingCatalog(backend,service/settings/). The ingest shim, the AI coding policy, the collector binding and the Settings hub read keys by name, so a key is a contract: adding one is a code change, and keys are never renamed (a rename would silently reset every workspace that changed the old one). - The table holds only what an admin changed.
workspace_setting(migration mirror2026-10-workspace-settings.sql) has one row per workspace per key:workspace_id,setting_key(VARCHAR(96)),value_json(the JSON of an already-validated value),updated_by,updated_at,UNIQUE (workspace_id, setting_key). No row = the catalogue default, so a new key never needs a data migration. - Reads are cached for 30 s per workspace. A read is the catalogue defaults overlaid with the stored values. A write evicts the cache immediately and again after commit. Consumers that cache on top (the shim’s 15 s binding cache, the coding policy’s 60 s cache) add their own delay — see How long a change takes to apply.
- A stored value the current catalogue refuses (a range that was later tightened, or a key a later build dropped) reads back as the default and logs a warning; it is never handed to a consumer.
REST API
Section titled “REST API”| Method | Path | Who | Body / answer |
|---|---|---|---|
| GET | /api/v1/workspaces/{workspaceId}/settings | Any workspace member — session JWT or a Logrite API key (lgp_live_…) | {workspaceId, settings, catalogue} |
| PATCH | /api/v1/workspaces/{workspaceId}/settings | Admin or Owner | Body: a flat partial map {"<key>": value, …}. Answer: same shape as GET, with the new values |
A non-member gets 404 workspace not found (or 403 where LOGRITE_CROSS_TENANT_NOT_FOUND=false).
A member below Admin gets 403 changing workspace settings requires workspace admin — the role is
checked before any key is looked at, so an outsider cannot probe which keys exist from the 400s.
Response. settings holds every key (stored value, else default). catalogue describes
every key as {key, type, default, min?, max?, values?, maxItems?, maxLength?}; type is one of
bool, int, enum, string, list. Read the catalogue before writing — it is what the Settings
hub uses for its bounds.
curl -s "https://leapfrogapi.deployd.io/api/v1/workspaces/$WORKSPACE_ID/settings" \ -H "Authorization: Bearer $LOGRITE_API_KEY"# {"workspaceId":3103,# "settings":{"coding.enabled":true,"coding.capture":"findings",…,"notify.coding.bell":true},# "catalogue":[{"key":"coding.enabled","type":"bool","default":true},# {"key":"coding.capture","type":"enum","default":"findings","values":["metadata","findings","extended"]},# {"key":"coding.retentionDays","type":"int","default":30,"min":7,"max":90}, …]}curl -s -X PATCH "https://leapfrogapi.deployd.io/api/v1/workspaces/$WORKSPACE_ID/settings" \ -H "Authorization: Bearer $LOGRITE_API_KEY" -H "Content-Type: application/json" \ -d '{"ingest.sampling.enabled": true, "ingest.sampling.percent": 25, "coding.capture": null}'PATCH semantics.
- All or nothing. Every key in the body is validated before any is written; one bad key refuses the whole request and nothing is stored.
nullresets a key to its default (the stored row is deleted). In the example abovecoding.capturegoes back tofindings.- Values are normalised before storage: strings and list items are trimmed; enum values and
list items from a fixed set match case-insensitively and are stored in the catalogue’s own
spelling (
"error"→"ERROR"); list items are de-duplicated; a whole number sent as25.0is accepted, a fraction is not. - An empty body is a no-op that answers the current values.
- Each successful PATCH writes one governance-audit row, action
settings.update, with the actor’s email and{"keys": […]}— the keys changed, not their values.
| Status | Body | When |
|---|---|---|
| 400 | {"error":"unknown_setting","key":"coding.foo"} | The key is not in the catalogue |
| 400 | {"error":"invalid_setting","key":"ingest.sampling.percent","rule":"must be a whole number between 1 and 100"} | The value breaks the key’s rule |
| 403 | — | Caller is a member but not Admin/Owner (PATCH only) |
| 404 | — | Caller is not a member of the workspace |
The rule strings the backend answers with:
must be true or falsemust be a whole number between <min> and <max>must be one of: …/every item must be one of: …must be a string/must be a list of strings/every item must be a non-empty stringat most <n> itemsmust be at most 64 characters/every item must be at most 64 charactersmust be a field name: a letter or _ followed by letters, digits, _ . or -(also with theevery itemprefix)
Every key
Section titled “Every key”The “Read by” column names the code that acts on the key. Defaults are what a workspace that never changed the key gets.
coding.* — AI coding sessions
Section titled “coding.* — AI coding sessions”Read by the Claude Code plugin through the AI coding policy (GET {mcpUrl}/coding/policy,
which carries the coding.* and logging.* values; the plugin caches it and uses built-in copies
of these defaults until a policy has been fetched), and by the backend where noted.
| Key | Type | Default | Allowed | What it does / read by |
|---|---|---|---|---|
coding.enabled | bool | true | — | Master switch for recording. MCP server coding ingest: when off, POST /coding/events answers 202 {"accepted":0,"disabled":true} and stores nothing. Plugin: does not send turn events or commit-scan events when off; it still checks code locally |
coding.capture | enum | findings | metadata, findings, extended | What is recorded per finding. MCP server coding ingest enforces it whatever the plugin sends: metadata drops each finding’s message, snippet and path (a pathHash is kept). extended currently records the same as findings (300-character messages, 200-character masked snippets). Shown on the AI Coding issue detail as captureLevel |
coding.retentionDays | int | 30 | 7–90 | How long AI coding session data is kept. Backend CodingRetentionJob (cron 0 30 3 * * *) deletes coding-events-<ws> events and stored evidence (masked snippets, paths) older than this; issue rows and their counts, mute and fixed state are kept |
coding.heal | bool | true | — | Plugin Stop hook: hand the agent the issues in the files it changed and ask it to fix them before it stops. Never blocks |
coding.healRounds | int | 1 | 0–2 | Plugin: how many times one turn can be sent back (0 = never). Never while the Stop hook is already active |
coding.healMinSeverity | enum | medium | low, medium, high, critical | Plugin: issues below this are recorded but not sent back to the agent |
coding.precommit | bool | true | — | Plugin pre-commit hook (installed by /logrite:init only when the developer agrees): when off, the hook does not check. It only warns — the commit always goes through |
coding.scan.compliance | bool | true | — | Backend coding policy includes the workspace’s compliance-framework rules only when on; the plugin applies them to lines the agent adds |
coding.scan.logging | bool | true | — | Policy includes the logging-standard rules (credential or PII in a log call, missing correlation field) only when on |
coding.scan.business | bool | true | — | Policy includes the workspace’s business rules on the AI coding plane only when on |
coding.notifyMinSeverity | enum | high | low, medium, high, critical | Backend CodingIssueNotifier: a new or returning AI coding issue below this severity is announced neither on Discord nor on the bell |
logging.* — logging standards for AI-written code
Section titled “logging.* — logging standards for AI-written code”| Key | Type | Default | Allowed | What it does / read by |
|---|---|---|---|---|
logging.correlationField | string | "" (not required) | field name, ≤ 64 chars | Backend coding policy builds the “log statement omits the required correlation field” rule from it (e.g. traceId). Empty = no such rule |
logging.piiFields | list | email, ssn, password, phone, dob, cardNumber | field names, ≤ 50 items, each ≤ 64 chars | Policy’s “log statement names a person” rule; the plugin also uses the list to mask those fields’ values in finding snippets. When an effective AI-coding policy of the workspace includes HIPAA, the policy appends fullName, full_name, birthDate, birth_date, dateOfBirth, date_of_birth, mrn (2026-10-09; the stored value is unchanged) |
ingest.* — ingest-time redaction and sampling
Section titled “ingest.* — ingest-time redaction and sampling”Read by the ingest shim (POST /ingest) on every line from a collector agent or the ingest
endpoint, via the agent resolve binding’s ingestPolicy. Not applied to OTLP/Data Prepper or the
Bulk API. Full behaviour:
Ingest-time redaction and sampling.
| Key | Type | Default | Allowed | What it does |
|---|---|---|---|---|
ingest.redaction.enabled | bool | false | — | Mask matching values before the line is stored; a changed line carries redacted: true |
ingest.redaction.kinds | list | every kind except longDigits | urlCredential, credentialPair, email, ssn, mrn, card, iban, phone, dob, secretKey, opaqueToken, longDigits (≤ 12 items) | Which value patterns are masked. longDigits (9+ digit numbers) is off by default because it also masks order and trace IDs. mrn, phone and dob were added 2026-10-09 (patterns in Relay compliance → Redaction kinds) |
ingest.redaction.fields | list | password, accessToken, refreshToken, otp, pin, last4, ssn, ccNumber, ccNbr, creditCardNumber, creditCardNbr | field names, ≤ 50 items, each ≤ 64 chars | Field names whose following value is masked (password=hunter2 → password=***) |
ingest.sampling.enabled | bool | false | — | Store only a share of lines; kept lines carry sampleRate |
ingest.sampling.percent | int | 100 | 1–100 | Share of lines kept at levels not in alwaysKeep |
ingest.sampling.alwaysKeep | list | FATAL, ERROR, WARN | FATAL, ERROR, WARN, INFO, DEBUG, TRACE (≤ 6 items) | Levels never sampled out |
warden.* and cortex.* — privacy switches (2026-10-09)
Section titled “warden.* and cortex.* — privacy switches (2026-10-09)”Admin-written. Every change to one of these six keys writes the settings.update
GovernanceAudit row with details.privacyChanges naming each key with its old and new value, so
turning masking off can be found later. Behaviour:
AI Warden → Redaction switches and
AI service → Cortex masking.
| Key | Type | Default | Allowed | What it does / read by |
|---|---|---|---|---|
warden.redaction.enabled | bool | true | — | Relay: false stores and shows Warden text as sent — previews, raw body, tool-call timeline, block message, compliance snippets, judge reasons. Detection and verdicts are unaffected |
warden.redaction.kinds | list | every kind except longDigits | same kinds as ingest.redaction.kinds | Kinds the relay masks. The raw body masks only the credential kinds (urlCredential, credentialPair, secretKey, opaqueToken) that are listed |
warden.redaction.fields | list | same as ingest.redaction.fields | field names, ≤ 50 items | Field names whose value the relay masks |
warden.sensitiveRawCapture | bool | false | — | “Keep the raw text of requests blocked for personal or health data. Turn on only when your model runs inside your own network.” When false, rows blocked or flagged for PHI/PII keep no raw text |
cortex.redaction | enum | auto | auto, on, off | Cortex: mask identifiers in answers. auto masks when HIPAA is enabled in the workspace (a policy that is not Off holds it, or a scan profile uses it) |
cortex.redaction.kinds | list | all | email, ssn, mrn, card, iban, phone, dob | Which identifiers Cortex masks when masking is on |
The relay reads the first four through the relay-key binding (redaction {enabled, kinds, fields, patterns} and sensitiveRawCapture); a binding without the block (older backend or cached binding) keeps the previous fixed masks.
collector.defaults.* — what a new collector collects
Section titled “collector.defaults.* — what a new collector collects”Read by the backend when a collector is created (POST /api/v1/agents): any capability or
interval the request leaves out (null) takes these values, and the New Collector Agent modal
pre-fills its fields from them (fields you already edited are left alone). Existing collectors are
not changed by editing a default — use Apply to existing collectors
(POST /api/v1/agents/apply-defaults, see
Collector agents → Live config at check-in).
| Key | Type | Default | Allowed | Capability |
|---|---|---|---|---|
collector.defaults.intervalMinutes | int | 15 | 1–1440 | Check-in / ship interval (minutes) |
collector.defaults.heartbeat | bool | true | — | Heartbeat each cycle |
collector.defaults.systemDetails | bool | true | — | System details (system_metrics host doc) |
collector.defaults.journald | bool | false | — | systemd journal |
collector.defaults.varlog | bool | false | — | /var/log files |
collector.defaults.docker | bool | false | — | Docker containers |
collector.defaults.k8s | bool | false | — | Kubernetes pods |
collector.defaults.syslog | bool | false | — | Syslog receiver |
notify.* — the bell
Section titled “notify.* — the bell”| Key | Type | Default | What it does / read by |
|---|---|---|---|
notify.warden.bell | bool | true | Frontend notification sync: when off, AI Warden blocked/flagged requests are not added to the bell. Evaluated in each browser; an unreadable store counts as on |
notify.coding.bell | bool | true | Backend CodingIssueNotifier: when off, new or returning AI coding issues are not posted to the bell (Discord is unaffected; coding.notifyMinSeverity gates both) |
gatekeeper.* — the pull-request gate (2026-10-15)
Section titled “gatekeeper.* — the pull-request gate (2026-10-15)”| Key | Type | Default | Allowed | What it does / read by |
|---|---|---|---|---|
gatekeeper.coverageThreshold | int | 80 | 0–100 | Backend GatekeeperThreshold: the log-coverage threshold a Gatekeeper check is measured against when the request sends none. Precedence: the request’s logCoverageThreshold → a stored workspace row (the workspace of the latest scan’s project; thresholdSource: "workspace") → the server property. Edited (Admin+) in Rules & Policies → Where rules apply → Code scans & log generation (“Pull-request gate”) |
How long a change takes to apply
Section titled “How long a change takes to apply”| Consumer | Delay after a PATCH |
|---|---|
Settings hub, workspace_settings get | Immediate (the writer’s cache is evicted) |
Ingest shim (ingest.*) | Up to the shim’s agent-binding cache, 15 s (SHIM_TOKEN_TTL_MS), plus up to 30 s on a backend instance that did not serve the PATCH |
Collectors (collector.defaults.*) | Only new collectors, or existing ones after Apply to existing collectors — each picks it up at its next check-in |
Claude Code plugin (coding.*, logging.*) | From the next session; the policy endpoint caches 60 s per workspace |
The 30 s settings cache is per backend instance; only the instance that served the PATCH evicts its copy.
The Settings hub
Section titled “The Settings hub”Four tabs in Settings read and write these keys
(/settings/aiCoding, /settings/logIngestion, /settings/privacy, /settings/notifications). Every member can open
them and see what is in effect; only an Admin or Owner can change a switch — others see “Only a
workspace admin can change these settings. You can see what is in effect.” Every switch is the
shared LogriteSwitch control, and each change is one PATCH.
AI coding (aiCoding)
| Card | Anchor | Keys |
|---|---|---|
| Recording | #setting-ai-coding-recording | coding.enabled (“Record AI coding sessions”), coding.capture (three side-by-side cards: Metadata only / Masked findings / Extended, each listing what is and is not recorded), coding.retentionDays (“Keep sessions for”) |
| Fixing in-session | #setting-ai-coding-heal | coding.heal, coding.healRounds (Never / Once / Twice), coding.healMinSeverity |
| Commit check | #setting-ai-coding-precommit | coding.precommit (“Warn at commit”) |
| What to check | #setting-ai-coding-checks | coding.scan.compliance, coding.scan.logging, coding.scan.business |
| Logging standards | #setting-ai-coding-logging | logging.correlationField, logging.piiFields |
| Privacy | #setting-ai-coding-privacy | Read-only: what is never recorded, and the per-repository opt-out ("recordSessions": false) |
| Recording tokens | #setting-ai-coding-tokens | Not a setting. Lists the workspace’s write-only lgsi_live_ tokens that /logrite:init mints — one per workspace, shared by every repository on a machine (2026-10-09; earlier plugin versions minted one per repository) — (label, masked token lgsi_live_… + last four, created by, created, last used, Active/Revoked) and revokes them; a workspace admin or the token’s creator may revoke. The card says a revoked token “stops working at once”; in practice the MCP server caches a valid token for up to 60 s, so the plugin stops at its first flush after that. Recorded sessions are kept — see AI Coding → the session-ingest token |
| Setup | #setting-ai-coding-setup | Read-only: the plugin install one-liner and /logrite:init |
Log ingestion (logIngestion)
| Card | Anchor | Keys |
|---|---|---|
| Redaction | #setting-ingest-redaction | ingest.redaction.enabled (“Redact before storing”), one switch per kind (ingest.redaction.kinds, with an example of what each kind masks), ingest.redaction.fields. Footnote: redaction and sampling apply to collectors and the ingest endpoint, not to OTLP exports |
| Sampling | #setting-ingest-sampling | ingest.sampling.enabled, ingest.sampling.percent (“Keep N% of lines at the other levels”), one switch per level (ingest.sampling.alwaysKeep) |
| Retention | #setting-ingest-retention | Not a workspace setting: the M-26-14 retention policy (hot/warm/cold tiers, searchable/retrievable windows), the same form as the M-26-14 page, saved through PUT /api/v1/retention/policy. Maintainer or above may save it |
| Collector defaults | #setting-ingest-collector-defaults | collector.defaults.intervalMinutes (“Check-in interval”) and one switch per capability, plus the Apply to existing collectors button (confirm dialog “Apply these defaults to every collector?”) |
Privacy (privacy, 2026-10-09)
| Card | Anchor | Keys |
|---|---|---|
| AI Warden redaction | #setting-privacy-warden-redaction | warden.redaction.enabled (“Mask what AI Warden stores”), one switch per kind (warden.redaction.kinds, now including MRN, phone and date of birth), warden.redaction.fields. Applies to requests captured after the change |
| Raw text of requests blocked for personal data | #setting-privacy-sensitive-raw | warden.sensitiveRawCapture (“Keep the raw text”) |
| Cortex answers | #setting-privacy-cortex | cortex.redaction (row “Mask identifiers”, Auto / On / Off — “Auto masks identifiers in workspaces whose policies use HIPAA.”), cortex.redaction.kinds. A line points to Log ingestion → Redaction for stored logs |
Notifications (notifications)
| Card | Anchor | What |
|---|---|---|
| In-app notifications | #setting-notifications-in-app | notify.warden.bell (“AI Warden”), notify.coding.bell (“AI coding”), coding.notifyMinSeverity (“AI coding: minimum severity — for the bell and Discord alike”) |
| Discord | #setting-notifications-discord | The Discord channel card, moved here from Settings → Integrations. Not a workspace setting; Maintainer or above may manage it |
| Email preferences | #setting-email-preferences | Moved here from Settings → General. Per-user, not per-workspace (saved through the user-settings API): Log analysis report, Critical issues alert, Code generation success, Welcome email. Security emails (verification, password reset, sign-in codes, new-device alerts) are always sent |
Settings → Connections (MCP / OAuth connections) lost its Workspace column (it always showed the current workspace) and merged Created and Last used into one Activity column (“Created …” over “Last used …” / “Never used”).
All of these cards are reachable from the settings search box.
MCP tool: workspace_settings
Section titled “MCP tool: workspace_settings”The MCP server exposes the store as one grouped tool in the Workspace family. The workspace is always the one the credential belongs to — there is no workspace argument, so a write cannot be aimed at a workspace the model merely knows the number of.
| Action | Scope | Arguments | Backend call |
|---|---|---|---|
get | workspace:read | none | GET /api/v1/workspaces/{ws}/settings — every value plus the catalogue |
set | settings:manage | settings: a flat map, e.g. {"coding.capture": "metadata", "ingest.sampling.percent": 25} | PATCH the same path; still needs the Admin or Owner role on the backend |
set with a missing or empty settings map is refused before any call. An unknown key or a bad
value refuses the whole change and names the key (the backend’s 400).
The OAuth consent text for settings:manage (“Change workspace settings”) now reads
“Change workspace settings: AI coding, log ingestion and notifications.” (it previously described
add-ons, integrations, observability connections and retention).