Repository navigation
Configuration
Labello reads its server configuration from labello.server.toml in the
current working directory. Set LABELLO_CONFIG to use a different path. If the
selected file does not exist, the server creates its parent directories and
writes the default configuration before starting.
The tracked labello.server.example.toml
contains every supported setting. Copy it when you want to prepare a
configuration before the first start:
cp labello.server.example.toml labello.server.tomllabello.server.toml is ignored by Git because it can contain credentials.
Keep production secrets in the environment or another secret-management
system.
The complete example lists supported server
fields. Top-level settings and [developmentAuth] are required; unknown fields
are rejected. OAuth, import, and prelabel sections are optional, but require their complete
settings when present. Individual import, preview, and export limits use their
documented defaults when omitted. Environment overrides apply after file loading.
| Setting | Default | Description |
|---|---|---|
bind |
"127.0.0.1:8080" |
Socket address on which the API listens. It must parse as an IP address and port, such as 127.0.0.1:8080 or [::1]:8080. LABELLO_BIND overrides it. |
datasetsRoot |
"datasets" |
Filesystem directory containing all datasets and server authentication state. Relative paths are resolved from the server process working directory. LABELLO_DATASETS_ROOT overrides it. |
bootstrapAdmins |
["admin"] |
Internal user IDs allowed to create datasets. This does not replace per-dataset role checks. GitHub users have IDs such as github_123456. |
browserOrigins |
Local Trunk origins | Exact browser origins allowed to make credentialed cross-origin API requests. At least one origin is required. |
sessionCookieSecure |
false |
Whether session cookies receive the Secure attribute. Set this to true when the browser reaches the API through HTTPS. |
Each browserOrigins entry must be an http or https origin with a host and
optional port. Paths, credentials, queries, fragments, wildcards, and empty
lists are rejected. For example:
browserOrigins = ["https://label.example.com"]Use the exact hostname seen by the browser. localhost and 127.0.0.1 are
different origins and different cookie hosts.
Authenticated unsafe requests require the session-bound token returned as
csrfToken by the login and GET /me responses. Send it in
x-csrf-token. Browser mutations must also carry an Origin that exactly
matches browserOrigins; token-bearing native clients may omit Origin.
Local development login always requires a configured browser origin.
Credentialed CORS preflights allow content-type, x-csrf-token,
idempotency-key, upload-offset, upload-length, and digest for current
and planned mutation protocols.
bootstrapAdmins grants only the server-level ability to create a dataset.
Dataset access remains controlled by the annotator, reviewer, and
data-admin roles stored with each dataset. Keep at least one reachable account
in the list when dataset creation is required.
The [developmentAuth] section is required.
| Setting | Default | Description |
|---|---|---|
developmentAuth.localAdminLogin |
true |
Enables one-click session login as the first configured bootstrap administrator. It requires a loopback bind address and a valid bootstrap administrator. |
Local administrator login is intended only for a trusted local environment. Disable it for any internet-facing deployment:
[developmentAuth]
localAdminLogin = falseGitHub OAuth is disabled when [githubOauth] is absent. To configure it in the
file, uncomment the entire section and replace every placeholder:
[githubOauth]
clientId = "your-github-client-id"
clientSecret = "your-github-client-secret"
redirectUri = "https://api.example.com/auth/github/callback"| Setting | Default | Description |
|---|---|---|
githubOauth.clientId |
None | Client ID from the GitHub OAuth App. |
githubOauth.clientSecret |
None | Client secret from the GitHub OAuth App. Do not commit a real value. |
githubOauth.redirectUri |
None | API callback URI registered with GitHub, ending in /auth/github/callback. |
The browser application's public URL belongs in the GitHub OAuth App's homepage field. The callback must point to the API, not the browser client. Keep the browser and callback hostnames consistent throughout local cookie flows.
redirectUri must be an absolute HTTP(S) URL without user information, query,
or fragment. Its path must end exactly in /auth/github/callback. An optional
deployment prefix consists of nonempty slash-separated segments containing only
ASCII letters, digits, -, _, ., or ~. Dot segments . and .., repeated
slashes, backslashes, whitespace, cookie-attribute delimiters, and all percent
encoding are rejected at startup. Encoded paths are rejected rather than decoded
or normalized, so the configured public path is the cookie path's authority.
The OAuth flow cookie uses the callback's parent path for both creation and
expiry. For example, callbacks at /auth/github/callback,
/api/auth/github/callback, and /labello/api/auth/github/callback use
/auth/github, /api/auth/github, and /labello/api/auth/github, respectively.
The proxy may strip the deployment prefix before forwarding to the API; browsers
match cookies against the public path before that forwarding happens. Session
cookies retain Path=/. The flow cookie retains its ten-minute lifetime,
HttpOnly and configured Secure/SameSite attributes. See the
rollout instructions when replacing a
proxy cookie-path workaround.
The server first loads or creates the TOML file, then applies environment overrides.
| Variable | Effect |
|---|---|
LABELLO_CONFIG |
Selects the configuration file path. Defaults to labello.server.toml. |
LABELLO_DATASETS_ROOT |
Overrides datasetsRoot. |
LABELLO_BIND |
Overrides bind. |
GITHUB_CLIENT_ID |
Overrides githubOauth.clientId when all three GITHUB_* variables are present. |
GITHUB_CLIENT_SECRET |
Overrides githubOauth.clientSecret when all three GITHUB_* variables are present. |
GITHUB_REDIRECT_URI |
Overrides githubOauth.redirectUri when all three GITHUB_* variables are present. |
RUST_LOG |
Sets the tracing filter. This is not a TOML setting. |
LABELLO_LOG_FORMAT |
Selects text or json logs. Defaults to text and is not a TOML setting. |
All three GITHUB_* variables must be present for the environment to enable or
replace GitHub OAuth. A partial set is ignored. See
operations.md for logging and redaction requirements.
The Axum server and browser distribution are deployed separately. The WASM
application therefore cannot safely read labello.server.toml: it must know
the API address before making its first API request, and the server file may
contain OAuth credentials.
The tracked
labello.client.example.json
contains every supported browser field with its default value:
{
"apiBaseUrl": null
}Copy it to the public runtime file before setting a deployment-specific API URL:
cp apps/labello-wasm/labello.client.example.json \
apps/labello-wasm/labello.client.json"apiBaseUrl": null is equivalent to omitting the field and selects the
hostname-derived port 8080 fallback. Set it to an absolute URL to configure a
deployment:
{
"apiBaseUrl": "https://api.example.com"
}The runtime apps/labello-wasm/labello.client.json is ignored by Git. When it
exists, a Trunk post-build hook copies it into the distribution root. When it
is absent, the build still succeeds and the client uses the hostname-derived
fallback. Operators may replace the deployed copy without rebuilding the WASM
bundle. A browser reload is required after a deployed-file change; restart
trunk serve after changing the source runtime file.
The client resolves its API URL in this order:
- The
apiURL query parameter, when present. -
apiBaseUrlfromlabello.client.json, when present. -
http(s)://<browser-host>:8080.
apiBaseUrl must be an absolute HTTP or HTTPS URL with a host. It must not
contain credentials, a query, or a fragment. A non-root path prefix is
supported only when it ends in /, for example
https://example.com/labello-api/. Unknown fields, malformed JSON, files over
16 KiB, and invalid configured URLs stop browser startup with a bounded error
that does not repeat the URL. An HTTP 404 means the runtime file is absent and
uses the legacy fallback. Other fetch failures stop startup.
The HTML bootstrap starts the runtime-file fetch alongside WASM loading,
without browser caching on each page load. Rust validates that response before
starting API requests. It is a
public artifact and must never contain OAuth credentials, cookies, tokens, or
other secrets. Static hosting must return a real 404 for an absent
labello.client.json, rather than rewriting that path to index.html.
Dataset import is disabled when [import] is absent. Enabling it exposes the
four version-one YOLO detection, YOLO pose, COCO instances, and COCO keypoints
profiles only when the filesystem also provides Linux beneath-open and atomic
no-replace publication guarantees. Startup fails if configured server roots do
not exist, overlap the datasets root, overlap each other, or use duplicate or
unsafe IDs.
The tracked example shows enabled = true, retainRawSource = false,
failedRetentionHours = 24, and successfulMetadataRetentionDays = 30 when
import is configured. These are explicit settings, not defaults for a missing
section. It also declares one [[import.serverRoots]] entry. For a
browser-upload-only deployment, omit that array entry and set
serverRoots = [] inside [import].
| Setting | Description |
|---|---|
import.enabled |
Enables import capability probing and all supported profiles. |
import.serverRoots |
Optional list of server-side source roots. Browser folder import remains independent of this list. |
import.retainRawSource |
Retains copied/uploaded raw source after successful publication when true. |
import.failedRetentionHours |
Retention period for failed or cancelled job metadata. |
import.successfulMetadataRetentionDays |
Retention period for successful job metadata. |
import.serverRoots[].id |
Safe opaque ID advertised to clients; paths are never advertised. |
import.serverRoots[].path |
Existing source directory outside and non-overlapping with datasetsRoot. |
import.serverRoots[].allowedOwners |
Bootstrap administrator user IDs allowed to see and select this root. An empty list allows any bootstrap administrator. Invalid user IDs fail startup. |
The two retention values configure storage cleanup policy, but the production server does not currently schedule that cleanup. Startup recovery separately expires abandoned non-protected jobs; it does not provide periodic cleanup of retained failed, cancelled, or successful metadata. See Dataset Import operations.
The optional [import.limits] section controls every limit enforced by the
storage import service. Omit the section to retain all storage defaults, or set
only the fields that need to differ. Byte limits are literal bytes; the values
below correspond to the binary-size defaults used by storage.
| Setting | Default | Enforced limit |
|---|---|---|
import.limits.concurrentBuildJobs |
1 |
Concurrent preflight/build jobs for the server. |
import.limits.imageValidationWorkers |
8 |
Maximum concurrent YOLO image decoders; configurable up to 32. |
import.limits.decodedImageMemoryBytes |
5_368_709_120 (5 GiB) |
Aggregate image-validation memory reservation shared by concurrent preflights. Reservations include encoded bytes, worst-case decoded output, and an extra decoded canvas for GIF validation. |
import.limits.concurrentBrowserUploadJobs |
2 |
Concurrent browser upload jobs for the server. |
import.limits.activeReservationsPerOwner |
2 |
Active destination reservations per owner. |
import.limits.browserSourceFiles |
25_000 |
Files registered by one browser source. |
import.limits.browserSourceBytes |
21_474_836_480 (20 GiB) |
Total bytes registered by one browser source. |
import.limits.serverSourceFiles |
50_000 |
Files copied from one server-directory source. |
import.limits.totalSourceBytes |
107_374_182_400 (100 GiB) |
Total bytes in any source. |
import.limits.selectedImages |
10_000 |
Images selected by preflight. |
import.limits.singleSourceFileBytes |
4_294_967_296 (4 GiB) |
Bytes in one source file. |
import.limits.descriptorBytes |
16_777_216 (16 MiB) |
Bytes read from one dataset descriptor. |
import.limits.uploadChunkBytes |
8_388_608 (8 MiB) |
Bytes accepted in one browser upload chunk. |
import.limits.sourcePathBytes |
1_024 |
Bytes in one normalized relative source path. |
import.limits.sourcePathDepth |
32 |
Components in one relative source path. |
import.limits.sourceComponentBytes |
255 |
Bytes in one source path component. |
import.limits.selectedCategories |
100 |
Categories selected by preflight. |
import.limits.selectedTasks |
200 |
Tasks generated by an import plan. |
import.limits.coverageEntries |
2_000_000 |
Image-task coverage entries generated by an import plan. |
import.limits.annotationsTotal |
1_000_000 |
Annotations generated by an import. |
import.limits.annotationsPerImage |
10_000 |
Annotations generated for one image. |
import.limits.generatedFileBytesPerImage |
67_108_864 (64 MiB) |
Bytes in a generated event log or state file for one image. |
import.limits.keypointsPerSkeleton |
512 |
Keypoints in one skeleton category. |
import.limits.yoloLineBytes |
1_048_576 (1 MiB) |
Bytes in one YOLO annotation line. |
import.limits.yoloColumns |
4_096 |
Columns in one YOLO annotation line. |
import.limits.structuredDataNesting |
64 |
JSON or YAML nesting depth. |
import.limits.decodedImagePixels |
50_000_000 |
Decoded pixels in one image. |
import.limits.decodedImageBytes |
536_870_912 (512 MiB) |
Decoded image memory used by image validation. |
import.limits.stagedBytes |
268_435_456_000 (250 GiB) |
Source, spool, and generated output bytes staged by one import. |
import.limits.diagnosticExamplesPerCode |
100 |
Stored diagnostic examples for each diagnostic code. |
Every limit must be greater than zero and fit the server platform's numeric types. Limits advertised through the client capability contract must also fit that contract without truncation. Startup rejects contradictory ceilings: browser bytes or a single file cannot exceed total source bytes; descriptors and upload chunks cannot exceed a single file; path component and depth limits cannot exceed the path byte limit; per-image annotations cannot exceed total annotations; YOLO columns cannot exceed line bytes; generated per-image files cannot exceed staged bytes; and total source bytes cannot exceed staged bytes.
Image validation also requires:
decodedImageMemoryBytes >= singleSourceFileBytes + (2 * decodedImageBytes)
The reservation covers the encoded source file, its worst-case decoded output, and a second decoded canvas required by GIF validation. With the defaults this is exactly 4 GiB + (2 × 512 MiB) = 5 GiB. Startup rejects configurations that overflow while calculating the minimum or whose memory budget is below it with an error naming all three settings.
Server-root capability filtering is fail closed: only roots present in the loaded configuration and authorized for the current bootstrap administrator are advertised. The filesystem path is never returned by the API.
COCO keypoint imports may explicitly pair one instances descriptor with one
keypoints descriptor by assigning both the same release, split, and
pairingGroup. Descriptor kinds are retained in the committed import manifest.
Descriptors without a pairing group remain separate even when their numeric
image, category, or annotation IDs match.
Imports reserve and publish destinations under a process-local datasets-root
mutation lock shared with normal dataset creation. Run only one server process
per datasets root. Import staging under .labello-server/imports is private
server state and is never listed as a dataset.
The optional [previews] section has cacheRoot = ".labello-preview-cache",
relative to the process working directory. It must be separate from, and neither
an ancestor nor a descendant of, datasetsRoot after resolving existing path
components. The production service working directory is /var/lib/labello, so
the default cache is outside /var/lib/labello/data backups. The server creates
the private cache lazily; one process owns its filesystem lock.
[previews.limits] fields can be overridden independently:
| Field | Default | Accepted bound |
|---|---|---|
maxSourceBytes |
67,108,864 (64 MiB) | 1–536,870,912 |
maxPixels |
32,000,000 | 1–100,000,000 |
maxDecodedBytes |
268,435,456 (256 MiB) | 1–1,073,741,824 |
workers |
2 | 1–8 |
cacheBytes |
2,147,483,648 (2 GiB) | 1–1,099,511,627,776 |
cacheEntries |
4096 | 1–16,384 |
Source bytes, decoded pixel count, decoder allocation budget and decoded output
bytes are checked before generation. Resize/encoder work is additionally bounded
by source pixels and the fixed output profiles; maxDecodedBytes is a decoder
budget, not a total process-RSS cap. The same worker pool and source/decoder
limits cover encoded previews, original-detail reads and legacy RGBA fallback.
Thumbnail and foreground requests have separate bounded admission queues, each
allowing at most 16 * workers waiting or running requests. Admitted requests
wait for a worker; only a full admission queue fails promptly. With two or more
workers, thumbnails use at most workers - 1 slots so opening an image retains
foreground capacity. A single-worker configuration serializes both lanes.
Cancelled waiting requests release admission; already-started work retains its
permits until it finishes. Identical source/profile requests share one generation.
Encoded output is limited to 16 MiB. Cache accounting includes per-entry metadata
and reserves space before temporary publication; the zero-byte lock file is not
charged.
The service rejects zero/out-of-range limits and unknown settings. Changing limits requires restart. Smaller cache limits trigger eviction on first access. See operations for disposal and recovery.
Linux servers initialize dataset export when [export] is absent. Other
platforms advertise export as unavailable. The section uses camelCase fields
and rejects unknown fields.
Export size is unlimited by default. Omit maxImages, maxFiles,
maxSourceBytes, maxFileBytes, maxDecodedImageBytes, maxArchiveBytes, and
maxMetadataBytes to impose no application quota. JSON capabilities report
these omitted limits as null. Existing explicitly configured numeric limits
continue to apply; remove those entries to remove their quotas. Numeric limits
must be positive, and maxFiles, when present, must be at least three. There
are no fixed upper ceilings for these settings. Byte values are bytes.
Concurrency and retention remain independently configurable:
[export]
maxConcurrentJobs = 1
maxConcurrentDownloads = 2
maxRetainedJobs = 8
retentionSeconds = 86_400Accepted concurrency is one through four jobs and one through eight downloads. Retained-job capacity must accommodate configured workers and cannot exceed 64; retention is one second through seven days. These settings control simultaneous work and cleanup, not how many images one export can contain.
Unlimited size is not a disk reservation or a constant-memory guarantee. Image
validation decodes one image at a time; dataset metadata, annotation provenance,
and ZIP directory entries require memory proportional to the captured content.
See export operations for disk planning and
recovery. An optional maxMetadataBytes also bounds source configuration,
image-index and per-image event-log reads, as well as generated metadata.
Dataset administrators configure overlapping-box visibility in Administration > Automation > Overlapping boxes. The dataset setting defaults to 0.9 when absent:
[boundingBoxVisibility]
iouThreshold = 0.9The threshold must be finite and within [0, 1]. Boxes compete only within the
same image and class, across that image's workflows. A box is hidden when its
IoU with a retained box is strictly greater than the threshold. Set 1 to show
all boxes. Current exact-version approval wins over an unreviewed box, which
wins over a rejected box; stable annotation IDs break ties. Superseded decisions
and approvals of older versions do not rank the current box.
This filters annotation, review, dataset inspection, and migration guide views. It preserves stored annotations, versions, deletion flags, image membership, statistics, and export content. Moving or deleting a retained box or changing the threshold can reveal a previously hidden box. Reload other open clients to pick up a changed setting. Saving configuration updates the current client's views and releases its prepared assignments. Stale review submissions fail without partial writes and require a fresh assignment.
Pending migration targets with hidden guide boxes are skipped. Existing skeletons and their review targets remain. Changing visibility does not rewrite completed history or automatically reopen completed tasks. See workflow policy for the shared selection and replay contract.
Dataset administrators also configure preloadQueueSize in Administration >
Automation. It is a top-level integer in labello.dataset.toml, defaults to two
when absent, and accepts 1 through 200 upcoming assignments. The current image
is additional. The setting applies to both annotation and review; the old
browser queueSize URL parameter is ignored. Live clients reconcile the target
through their existing availability refresh, normally within 30 seconds.
Shrinking releases surplus prepared assignments and preserves current work.
Queue depth is a target, not a reservation of memory or balance capacity. Larger queues use more browser memory and bandwidth and hold work away from other workers. Each prepared assignment has a 30-minute lease; expired entries are discarded and released instead of being promoted. Decoded pixels alone can use 6.25 MiB per square 1280-pixel preview, or 625 MiB for 100 entries, before image state and transient allocations. Choose smaller queues on constrained devices. The 200-entry ceiling bounds pixels alone to about 1.22 GiB; it is not a recommended default. WebAssembly can retain its peak allocated memory for reuse after entries are removed. Workspace navigation releases unused reservations with at most eight release requests in flight. Closing the tab instead leaves server reservations to expire under the normal lease policy.
Assignment balance belongs to each versioned labello.dataset.toml, not the
server configuration above. Data administrators edit it through the
Administration Automation view or the dataset administration API.
This example enforces an absolute window of five completed images:
[imbalance]
enforce = true
maxDifference = 5maxDifference is a non-negative 64-bit integer; zero is valid. Ratio-based
configuration is not supported. Legacy ratio settings must be replaced before startup. For example, replace:
[imbalance]
maxRatio = 2.0
enforce = truewith an explicitly chosen maxDifference. Labello rejects maxRatio, tagged
policy objects, negative values, and values larger than an unsigned 64-bit
integer rather than guessing an absolute window. Omitting imbalance, or
setting enforce = false, disables assignment blocking. See
Assignment for count, denominator, peer,
zero-count, and exact-boundary semantics.
Administrators configure these settings in Automation. In labello.dataset.toml:
[workflowQueue]
historyDepth = 5
# maxPendingOverviews = 20historyDepth accepts 0 through 100. It bounds backward navigation and the retained
forward reservation window, in addition to the current item. maxPendingOverviews
is optional and must be positive when present. It counts distinct images awaiting
Overview, reserving capacity for active object work on new images. Already started
images can finish their prerequisite objects even at the limit. Omission disables
the limit. Objects and Overview share one task's image-based completion balance.
Dataset task review configuration retains the legacy allowReviewerCorrections
field for file and wire compatibility. Both false and true allow corrections
in approval review; the administration UI no longer offers the switch. Every
rejection requires substantive reviewer changes and creates a fresh review round.
Each current-round object and the final Overview require one approval, including
guided migration. Different reviewers may approve different items. Objects exclude
the final annotation author; Overview excludes anyone who contributed annotation
or review work to that image. Viewing or saving an empty proposal does not count
as a contribution. A recorded self-review exception is allowed only when the user
has no independent work available in any review queue in the dataset. Historical
requiredReviews values are normalized by the dataset review-policy upgrade.
Revisiting one's own independently approved Overview retains its original
eligibility when the history target and captured fingerprints remain valid.
Other available review work does not block that revisit. New reviews and history
originally admitted by fallback retain the dataset-wide exclusion checks.
Use TLS, secure session cookies, exact browser origins, and external secret injection. Disable local admin login. Run one server per datasets root and back up that root, including authentication state. Follow deployment for the supported guest layout and operations for backup and recovery.
Enable [prelabel] with an operator-managed modelsRoot to support server-managed detection and pose models. Omission disables model inspection, generation and management. Add the section to labello.server.toml or the file selected by
LABELLO_CONFIG, restart the server, and reload the web app. When omitted,
Automation and workflow settings show
"Prelabeling is disabled by server configuration." Annotators never have model
selection or generation controls. Existing dataset
model configurations and workflow bindings are preserved.
timeoutSeconds defaults to 120 and accepts 1 through 300.
[prelabel.limits] fields are optional and use the values in the
server example. Model supply, supported tensor
shapes, processing, Linux worker limits and retention are defined in
Model prelabels.
Optional [prelabel.runtime] settings onnxLibrary and webgpuLibrary select
operator-owned native ONNX Runtime and WebGPU plugin libraries. Server inference
tries CUDA, WebGPU, then CPU; missing native runtime libraries retain the included
Tract CPU fallback. Runtime/provider failures do not disable the whole feature.
The model check and inference share the configured worker concurrency limit.
See Model prelabels for dependencies,
resource bounds and the overall timeout budget.
Optional [prelabel.workers] controls retained inference processes:
| Field | Default | Range |
|---|---|---|
maxWorkers |
4 | 1–8 |
idleTimeoutSeconds |
120 | 1–3600 |
threadsPerWorker |
1 | 1–16 |
Workers are scoped to dataset/account or dataset generation run. Each caches two
compiled models. maxWorkers bounds live processes, including idle workers and
ones being reaped; limits.maxConcurrentInferences separately bounds active
inference and model checks. Native runtime thread counts use threadsPerWorker;
the included Tract backend is single-threaded. Idle processes expire within the
configured interval plus at most 30 seconds, or earlier under capacity pressure.
Interactive requests have priority between batch items. Up to 64 wait for admission
for at most 30 seconds; saturation returns busy; managed preparation reports its pending or failed state.
Edit documentation in the Labello repository. The wiki is generated from the current documentation; archived plans stay in the repository.
- Labello documentation
- Labello
- Getting started
- Product glossary
- Annotation and review
- Stylus input
- Dataset administration
- Model prelabels
- Dataset import
- Guided box-to-skeleton migration
- Dataset export
- Assignment
- Contribution scoring
- Current limitations