API reference

The public surface of tlc_plugin_sdk. Everything below is generated from the package’s own docstrings.

The contract

class tlc_plugin_sdk.ComputePlugin[source]

Bases: ABC

Behavior-only base class for a compute-service plugin (host or venv).

Subclass and implement at least get_ui_fragment(). The optional hooks below (compute, run_job, lifecycle, routes) ship as safe defaults, so the host can call any of them directly without probing for the method first.

Variables:

id (str) – Unique slug (e.g. run-insights). Identity only, hydrated onto the instance from the manifest by the host; the rest of the plugin’s metadata also comes from its manifest, never from the instance.

id: str
abstractmethod get_ui_fragment() str[source]

Return a self-contained HTML+JS+CSS fragment for the plugin UI.

compute(params: dict[str, Any]) dict[str, Any][source]

Execute the plugin’s synchronous GET /compute computation.

Override to expose a synchronous compute endpoint; the default returns an error dict so a plugin that only serves a UI and/or jobs need not implement it. Long-running work belongs in run_job(), not here.

Returns:

A JSON-serializable dict. The default is {"error": f"{id} does not implement compute()"}.

initialise_runtime() None[source]

Initialise the plugin’s runtime resources (runners, stores, models).

Called once after the shared GPU queue is ready. Default is a no-op.

shutdown_runtime() None[source]

Tear down the plugin’s runtime resources.

Must be safe to call on a plugin that was never initialised. Default is a no-op.

run_job(ctx: JobContext) None[source]

Run a long-running job against a host-provided context.

The plugin reports progress/metrics and polls cancellation via ctx; the code runs in the plugin’s worker and only ever touches ctx.

Raises:

NotImplementedError – The default — a plugin that streams jobs must override this.

get_route_handlers() list[Any][source]

Return the plugin’s custom routes as relative Litestar route handlers.

Each handler’s path is relative to the plugin’s mount point /api/plugins/{plugin_id}/ (e.g. a @get("/models") handler serves GET /api/plugins/{plugin_id}/models). The handlers are served by the plugin’s own Litestar app in its worker, reverse-proxied by the host (see tlc_plugin_sdk/asgi_app.py); Litestar runs def handlers in a threadpool, so a synchronous, blocking custom route does not block the event loop. The reserved routes (/run, /health, /ui, /compute, /jobs/*, and the host admin routes /provision /reload /venv /worker/stop) are host-owned — a plugin must not define them. Empty by default.

class tlc_plugin_sdk.JobContext(job_id: str, params: dict[str, Any], state_dir: Path, *, sink: Callable[[dict[str, Any]], None], cancel_event: threading.Event)[source]

Bases: object

Host-provided context a plugin uses to drive one job.

Parameters:
  • job_id – Unique id for this job.

  • params – Job parameters (parsed request body / query).

  • state_dir – Writable per-plugin scratch dir that survives a venv reinstall/reload (plugins must not write inside their package dir).

  • sink – Callable invoked with each emitted event dict.

  • cancel_event – Set by the host/worker to request cooperative cancellation.

property cancelled: bool

Whether cancellation has been requested (poll this at checkpoints).

progress(*, percent: float, label: str = '', timing: dict[str, Any] | None = None) None[source]

Report progress with an optional label and timing dict.

Parameters:
  • percent – Completion 0-100. Pass -1 for indeterminate — the generic panel then shows an activity indicator rather than a filled bar (use it when total work is unknown).

  • label – Short status line for the generic progress view.

  • timing – Optional {elapsed_s, eta_s, avg_step_s, step_label} dict.

metric(label: str, value: str | float) None[source]

Report a scalar metric as a key/value card.

log(message: str) None[source]

Emit a log line for the job.

result(url: str) None[source]

Record the job’s result link — the thing the Open button opens.

The host stores it on the generic job record so the Queue & Progress panel can render it as an “open result” link; safe to call multiple times (last write wins). Pass the one canonical artifact the job produced — a run or a table URL. Richer per-plugin output still goes through emit().

Parameters:

url – The URL the Open button opens (a run or a table URL).

fail(message: str) NoReturn[source]

Fail the job with a clean, user-facing message.

Raises JobFailed, which the worker reports as the terminal error event carrying message verbatim — no exception-type prefix, unlike an ordinary exception (reported as f"{type}: {exc}"). Use it for validation / precondition failures where the message is meant for the user; let ordinary exceptions propagate for genuine faults.

Parameters:

message – The failure message shown on the job’s generic error card.

Raises:

JobFailed – Always.

emit(name: str, payload: dict[str, Any] | None = None) None[source]

Emit a custom, plugin-defined event for the plugin’s OWN rich UI.

The host relays it verbatim on the plugin’s SocketIO namespace; the generic Queue & Progress panel ignores it. Use progress() / metric() / log() for the generic panel, and this for plugin-specific UI (e.g. a training plugin’s per-epoch loss curve) — so a plugin never opens its own SocketIO connection; the host owns the transport.

Parameters:
  • name – Event name the plugin’s UI listens for.

  • payload – JSON-serializable event body.

Raises:

ValueError – If name collides with a host-reserved event (job_update) used for the generic Queue & Progress channel.

request_cancel() None[source]

Request cooperative cancellation (host/worker side).

tlc_plugin_sdk.SDK_CONTRACT_VERSION = '0.3.2'

str(object=’’) -> str str(bytes_or_buffer[, encoding[, errors]]) -> str

Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.__str__() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to ‘strict’.

SDK_CONTRACT_VERSION is the one contract axis: this package’s own version — the dependency pin a plugin resolves against (3lc-compute-plugin-sdk>=X,<Y) and the version of both the Python surface (ComputePlugin / JobContext) and the browser surface (PLUGIN_API / PluginJobs / TlcData, declared in tlc_plugin_sdk/contract/plugin-api.d.ts). The host and frontend implement a range and compare compatibility on MAJOR.MINOR. See docs/plugin-guide.md → “Version & Compatibility”.

The .d.ts ships in the wheel at <site-packages>/tlc_plugin_sdk/contract/plugin-api.d.ts; a plain-JS ui.html references it with /// <reference types="3lc-compute-plugin-sdk/contract/plugin-api" />.

The browser contract

The browser surface a plugin’s ui.html programs against — PLUGIN_API, PluginJobs, and the TlcData helper — is declared in plugin-api.d.ts.

interface plugin-api.d.PluginApi

The single host -> fragment JS contract. The frontend injects this as window.PLUGIN_API when it mounts a plugin fragment; a fragment should reach for nothing else. Many plugins alias it: var API = window.PLUGIN_API.

exported from plugin-api.d

PluginApi.compute

type: TlcComputeService

Reference to TlcApi.computeService (currently only getHealth()).

PluginApi.container

type: HTMLElement

The DOM element the fragment was mounted into. Plugins scope their queries to it.

PluginApi.context

type: PluginContext

Launch context (resource type/urls + project).

PluginApi.contractVersion

type: string

The SDK contract version this host implements, as “MAJOR.MINOR” (e.g. “0.3”), so a fragment can feature-detect the bridge. The frontend declares which contract it implements (its IMPLEMENTED_SDK_CONTRACT) and surfaces it via <body data-contract-version>. ‘’ if the host predates it.

PluginApi.data

type: TlcData

Reference to the global TlcData helper (null if TlcData is undefined at mount).

PluginApi.libs

type: PluginLibs

Optional vendored third-party libraries (each null if the host didn’t load it).

PluginApi.location

type: TlcLocationApi

Reference to the global TlcLocation helper (null if undefined at mount).

PluginApi.objects

type: TlcObjectService

Reference to TlcApi.objectService. Plugins usually reach data via authFetch.

async PluginApi.authFetch(url, options)

Authenticated fetch. Injects Authorization (from TlcAuth) and a default Accept: application/json; sets Content-Type: application/json when the body is a string. Aborts after options.timeout ms (default 10000) unless the caller supplies a signal. Rejects non-ok responses with the parsed detail/message. The most-used bridge member.

Arguments:
Returns:

Promise<Response>

async PluginApi.computeFetch(path, options, requiresGpu)

authFetch against the compute-service base URL. path is joined to the compute-service root; when requiresGpu is given and a CPU/GPU counterpart service is configured, routes to the matching service. Most plugins instead build URLs from getConfig('compute_service_url') and call authFetch.

Arguments:
Returns:

Promise<Response>

PluginApi.getConfig(key)

Return a configured URL by key. dashboard_url has its trailing slash stripped; compute_service_url is the GPU/CPU-routed service for THIS plugin; object_service_url comes from TlcApi. These three keys are the only ones recognized — any other key returns ‘’.

Arguments:
  • key (“dashboard_url” | “compute_service_url” | “object_service_url”)

Returns:

string

PluginApi.getIcon(id)

Return an SVG icon string. With no id (or the current plugin’s id) returns the plugin manifest’s icon_svg when present; otherwise delegates to TlcIcons.get(id || pluginId), or ‘’ if TlcIcons is undefined.

Arguments:
  • id (string)

Returns:

string

PluginApi.navigate(path)

Navigate the host to a path (sets window.location.href = path).

Arguments:
  • path (string)

PluginApi.showToast(message, type)

Show a host toast notification; no-op fallback if the host’s showToast is unavailable.

Arguments:
  • message (string)

  • type (string)

interface plugin-api.d.PluginContext

Launch context: what the user launched the plugin against.

exported from plugin-api.d

PluginContext.projectName

type: string

Launch project name (’’ when none).

PluginContext.resourceType

type: string

Selected resource kind (‘run’,’table’,…) or null when launched bare.

PluginContext.resourceUrls

type: string[]

Selected 3LC object URLs (default []).

interface plugin-api.d.PluginFetchOptions

Custom non-standard fetch options layered on top of the standard RequestInit.

exported from plugin-api.d

Extends:
  • RequestInit

PluginFetchOptions.allowErrorStatus?

type: boolean

When true, resolve with the Response on a non-ok HTTP status instead of rejecting — the caller inspects response.ok / response.status itself. Custom, non-standard option — deleted before the underlying fetch() call.

PluginFetchOptions.timeout?

type: number

Abort the request after this many milliseconds (default 10000). Custom, non-standard option — deleted before the underlying fetch() call. Ignored when the caller supplies its own signal.

interface plugin-api.d.PluginJobHandlers

exported from plugin-api.d

PluginJobHandlers.onDone?(job)

Fires on terminal completed / cancelled.

Arguments:
PluginJobHandlers.onError?(job)

Fires on terminal failed.

Arguments:
PluginJobHandlers.onUpdate?(job)

Fires on every job_update.

Arguments:
interface plugin-api.d.PluginJobUpdate

The opaque generic job object delivered to PluginJobs handlers.

exported from plugin-api.d

PluginJobUpdate.error?

type: string

Failure message on a failed job (host >= 0.6); empty/absent otherwise.

PluginJobUpdate.id?

type: string

Job id.

PluginJobUpdate.metrics?

type: { label?: string; value?: any; }[]

PluginJobUpdate.progress?

type: { label?: string; percent?: number; timing?: any; }

PluginJobUpdate.run_url?

type: string

PluginJobUpdate.status?

type: string

‘queued’ | ‘running’ | ‘completed’ | ‘failed’ | ‘cancelled’.

PluginJobUpdate.subtitle?

type: string

Secondary status line (the current progress label); NOT the failure message.

PluginJobUpdate.title?

type: string

interface plugin-api.d.PluginJobsApi

The job-tracker client. SHIPS FROM 3lc-compute-plugin-sdk (tlc_plugin_sdk.shared.job_tracker, JOB_TRACKER_JS) — auto-injected into every fragment by the SDK’s /ui handler (build_plugin_app); a manual inject_scripts(raw, job_tracker_script()) is harmless but no longer needed. NOT part of the host PLUGIN_API bridge; it is layered on top of it.

exported from plugin-api.d

async PluginJobsApi.cancel(jobId)

POST {compute}/api/plugins/jobs/{jobId}/cancel with body ‘{}’.

Arguments:
  • jobId (string)

Returns:

Promise<{ cancelled?: boolean; }>

PluginJobsApi.connect(namespace)

Open (or reuse) the namespace socket now. track()/on() connect lazily on first use and SocketIO does not replay server→client events to a client that was not yet connected — call this on mount when the fragment wants custom events from the first second, or starts jobs by other means than run() (which connects for you). Returns false when PLUGIN_API.libs.io is unavailable.

Arguments:
  • namespace (string)

Returns:

boolean

async PluginJobsApi.list(pluginId)

GET {compute}/api/plugins/jobs → the host’s generic job records, resolved to PluginJobUpdate[] and optionally filtered (client-side) to one plugin by id. Use it to seed a freshly-mounted fragment from the durable job list — the fragment is torn down on navigation and job_update is live-only. See the guide’s “Job page is a launcher” section.

Arguments:
  • pluginId (string)

Returns:

Promise<PluginJobUpdate[]>

PluginJobsApi.on(namespace, event, handler)

Subscribe to a CUSTOM ctx.emit() event (not the generic job_update) on the plugin’s namespace; returns an unsubscribe function. For rich per-job detail the flat generic schema can’t carry (result payloads, loss curves). Subscribe before run() so the socket is connected when the event fires.

Arguments:
  • namespace (string)

  • event (string)

  • handler ((payload: any) => void)

Returns:

() => void

async PluginJobsApi.run(pluginId, params, handlers)

Start a job and track it on the generic job_update channel. Pre-subscribes on the default namespace '/' + pluginId (corrected to resp.namespace if it differs) and buffers events so a job completing before its id is known still delivers a terminal callback. Defaults params.project_name from PLUGIN_API.context.projectName. onDone fires on completed/cancelled, onError on failed. Most-used member.

Arguments:
Returns:

Promise<PluginRunResponse>

async PluginJobsApi.start(pluginId, params)

POST {compute}/api/plugins/{pluginId}/run with params as JSON. Defaults params.project_name from the launch context. Lower-level building block under run().

Arguments:
  • pluginId (string)

  • params (object)

Returns:

Promise<PluginRunResponse>

PluginJobsApi.track(namespace, jobId, handlers)

Subscribe to job_update for a single jobId on a known namespace; returns an unsubscribe function. Filters by job.id, fires onDone on completed/cancelled and onError on failed, then auto-unsubscribes.

Arguments:
Returns:

() => void

interface plugin-api.d.PluginLibs

Third-party libraries pulled from window if the host loaded them, else null per key.

Stability tiers (frozen contract):
  • io (socket.io client) — STABLE: the job-tracker channel rides it; the only libs member a plugin may depend on.

  • Chart, cytoscape, html2canvas, PptxGenJS — BEST-EFFORT: exposed for convenience, may be swapped/removed without a contract bump. A plugin that needs one should be prepared to vendor its own.

exported from plugin-api.d

PluginLibs.Chart

type: any

PluginLibs.PptxGenJS

type: any

PluginLibs.cytoscape

type: any

Best-effort — and always null on the plugin page: the host does not load

cytoscape into a plugin fragment, so a plugin that needs it must vendor its own.

PluginLibs.html2canvas

type: any

PluginLibs.io

type: any

socket.io client (STABLE).

interface plugin-api.d.PluginRunResponse

The parsed response of the generic POST /api/plugins/{id}/run route.

exported from plugin-api.d

PluginRunResponse.error?

type: string

PluginRunResponse.job_id?

type: string

PluginRunResponse.namespace?

type: string

PluginRunResponse.status?

type: string

interface plugin-api.d.TlcApi

TlcApi — the frontend API client (api-client.js). Plugins normally reach it through PLUGIN_API rather than directly, but it is an ambient global.

exported from plugin-api.d

TlcApi.computeService

type: TlcComputeService

TlcApi.objectService

type: TlcObjectService

TlcApi.objectServiceUrl

type: readonly string

Resolved Object Service base URL (trailing slash stripped).

async TlcApi.authFetch(url, options)
Arguments:
Returns:

Promise<Response>

async TlcApi.computeFetch(path, options, requiresGpu)
Arguments:
Returns:

Promise<Response>

async TlcApi.waitForMode()

Resolves once compute-mode detection (GET /health -> mode/version) completes.

Returns:

Promise<void>

interface plugin-api.d.TlcComputeService

Compute-service method bag, exposed as PLUGIN_API.compute. Currently only getHealth() (GET /health).

exported from plugin-api.d

async TlcComputeService.getHealth()
Returns:

Promise<object>

interface plugin-api.d.TlcData

The global TlcData helper (cached indexing tables). Referenced from PLUGIN_API.data. Implemented by the frontend (data-helpers.js).

exported from plugin-api.d

TlcData.allRunRows()

Raw rows from the cached RunIndexingTable response.

Returns:

object[]

TlcData.allTableRows()

Raw rows from the cached TableIndexingTable response.

Returns:

object[]

TlcData.getLocations?()

All locations relevant to this install: configured roots plus roots inferred from the loaded data. Absent on hosts predating SDK 0.2.

Returns:

TlcDataLocation[]

TlcData.getProjects()

Per-project rollup from both indexing tables, sorted by last_modified descending.

Returns:

TlcDataProject[]

TlcData.getRuns(projectName)

Runs (optionally filtered by project), with run_name derived from the URL and status mapped.

Arguments:
  • projectName (string)

Returns:

TlcDataRun[]

TlcData.getSummary()

Dashboard summary counts.

Returns:

TlcDataSummary

TlcData.getTables(projectName)

Tables (optionally filtered by project), with table_name derived from the URL.

Arguments:
  • projectName (string)

Returns:

TlcDataTable[]

TlcData.getTablesByDataset(projectName)

getTables() grouped by dataset_name (‘(ungrouped)’ when none).

Arguments:
  • projectName (string)

Returns:

{ [datasetName: string]: TlcDataTable[]; }

TlcData.invalidate()

Mark the cache stale so the next load() refetches; old data stays readable until replaced.

async TlcData.load()

Fetch and cache both indexing tables; dedupes concurrent callers; refetches when stale.

Returns:

Promise<void>

TlcData.resolveLocation?(url, projectName)

Resolve which root an object URL lives under (longest-prefix match against configured roots, falling back to inference from the URL structure). Absent on hosts predating SDK 0.2 — feature-detect before calling.

Arguments:
  • url (string)

  • projectName (string)

Returns:

TlcDataLocation

TlcData.runStatusName(statusCode)

Map a run status code to a name (‘completed’,’empty’,’running’,’collecting’, ‘post_processing’,’paused’,’cancelled’, else ‘unknown’).

Arguments:
  • statusCode (number)

Returns:

string

interface plugin-api.d.TlcDataLocation

The root a project lives under (the Object Service’s project root or one of its scan URLs). Resolved by the frontend from api://Configuration plus the object URLs themselves; hosts predating SDK 0.2 never set it, so treat every location member as optional.

exported from plugin-api.d

TlcDataLocation.is_alias

type: boolean

True when label came from a configured alias.

TlcDataLocation.is_default

type: boolean

True for the Object Service’s primary project root.

TlcDataLocation.key

type: string

Canonical prefix (file:// stripped, one trailing slash) — use for equality tests.

TlcDataLocation.label

type: string

Short display name: alias name > bucket/last path segment > ‘Local’.

TlcDataLocation.root

type: string

The root URL as configured or inferred (e.g. “s3://bucket/projects”).

TlcDataLocation.scheme

type: string

URL scheme: ‘file’, ‘s3’, ‘gs’, …

interface plugin-api.d.TlcDataProject

exported from plugin-api.d

TlcDataProject.dataset_count

type: number

TlcDataProject.last_modified

type: number

TlcDataProject.locations?

type: TlcDataProjectLocation[]

Locations this project’s contents resolve to. Absent on hosts predating SDK 0.2.

TlcDataProject.project_name

type: string

TlcDataProject.run_count

type: number

TlcDataProject.table_count

type: number

interface plugin-api.d.TlcDataProjectLocation

Per-location slice of a project’s contents (a project can span several roots).

exported from plugin-api.d

TlcDataProjectLocation.location

type: TlcDataLocation

TlcDataProjectLocation.run_count

type: number

TlcDataProjectLocation.table_count

type: number

interface plugin-api.d.TlcDataRun

exported from plugin-api.d

TlcDataRun.constants

type: object

TlcDataRun.created

type: string

TlcDataRun.description

type: string

TlcDataRun.is_url_writable

type: boolean

TlcDataRun.last_modified

type: string

TlcDataRun.location?

type: TlcDataLocation

Root this run lives under; null if unresolvable. Absent on hosts predating SDK 0.2.

TlcDataRun.metrics

type: any[]

TlcDataRun.project_name

type: string

TlcDataRun.run_name

type: string

TlcDataRun.status

type: string

TlcDataRun.status_code

type: number

TlcDataRun.url

type: string

interface plugin-api.d.TlcDataSummary

exported from plugin-api.d

TlcDataSummary.project_count

type: number

TlcDataSummary.run_count

type: number

TlcDataSummary.table_count

type: number

interface plugin-api.d.TlcDataTable

exported from plugin-api.d

TlcDataTable.created

type: string

TlcDataTable.dataset_name

type: string

TlcDataTable.description

type: string

TlcDataTable.input_table_urls

type: string[]

TlcDataTable.is_url_writable

type: boolean

TlcDataTable.location?

type: TlcDataLocation

Root this table lives under; null if unresolvable. Absent on hosts predating SDK 0.2.

TlcDataTable.project_name

type: string

TlcDataTable.row_count

type: number

TlcDataTable.table_name

type: string

TlcDataTable.type

type: string

TlcDataTable.url

type: string

interface plugin-api.d.TlcLocationApi

The global TlcLocation helper: shared renderers for project locations. Referenced from PLUGIN_API.location. Implemented by the frontend (location.js). Every renderer returns ‘’ when the install has a single root (isMultiRoot() === false), so output can be concatenated unconditionally.

exported from plugin-api.d

TlcLocationApi.chipHtml(loc)

Small chip (icon + label, root in tooltip) for one location; ‘’ when hidden.

Arguments:
Returns:

string

TlcLocationApi.iconSvg(scheme)

Inline SVG string: folder glyph for ‘file’, cylinder for bucket schemes.

Arguments:
  • scheme (string)

Returns:

string

TlcLocationApi.isMultiRoot()

True when the install has more than one known root.

Returns:

boolean

TlcLocationApi.pathLineHtml(project)

Muted mono path line for project cards; ‘’ when hidden.

Arguments:
Returns:

string

TlcLocationApi.projectChipHtml(project)

Chip for a project rollup: its location, or “N locations”; ‘’ when hidden.

Arguments:
Returns:

string

TlcLocationApi.rowsSpanLocations(rows)

True when the given rows (each optionally carrying .location) span more than one root — the per-row chip rule (spread within the shown list, so a chip is hidden when every visible row lives under the same root).

Arguments:
Returns:

boolean

TlcLocationApi.shortLabel(label)

Display form of a location label: left-ellipsized past 20 chars so the distinctive tail survives.

Arguments:
  • label (string)

Returns:

string

interface plugin-api.d.TlcObjectService

Object-service method bag, exposed as PLUGIN_API.objects. All methods go through authFetch against the object-service URL; object URLs are encoded via TlcApi.encodeObjectUrl.

exported from plugin-api.d

async TlcObjectService.deleteObject(objectUrl)
Arguments:
  • objectUrl (string)

Returns:

Promise<Response>

async TlcObjectService.getConfiguration()
Returns:

Promise<object>

async TlcObjectService.getObject(objectUrl)
Arguments:
  • objectUrl (string)

Returns:

Promise<object>

async TlcObjectService.getRunIndex()
Returns:

Promise<object>

async TlcObjectService.getStatus()
Returns:

Promise<object>

async TlcObjectService.getTableIndex()
Returns:

Promise<object>

async TlcObjectService.patchObject(objectUrl, patchData)
Arguments:
  • objectUrl (string)

  • patchData (object)

Returns:

Promise<object>

async TlcObjectService.reindex(force)
Arguments:
  • force (boolean)

Returns:

Promise<object>

The worker

Out-of-process plugin worker — the plugin’s Litestar app served on a Unix socket.

Run by the host’s worker supervisor inside the plugin’s own venv:

python -m tlc_plugin_sdk.worker --entry pkg:PluginClass --socket /run/.../id.sock

The bind transport is selectable: --socket (Unix domain socket, the default the supervisor uses) or --host/--port (TCP, e.g. --host 127.0.0.1 --port 9100) for a worker reachable over the network. Exactly one of the two must be given.

The worker serves the plugin’s Litestar app (tlc_plugin_sdk.asgi_app.build_plugin_app): the plugin’s own route handlers plus the generic reserved routes (/health, /ui, /compute). On top of that it adds the job channel the host supervisor drives:

  • POST /jobs/{job_id}/run → runs run_job(ctx) on a thread; the response

    streams NDJSON events (progress/metric/log) ending in a terminal done/error event.

  • POST /jobs/{job_id}/cancel → cooperative cancel (sets ctx.cancelled).

  • POST /reclaim → release cached GPU memory now (see

    release_gpu_memory()).

GPU memory is reclaimed automatically after every job, before the terminal event is emitted. /reclaim exists because only the host can see across workers: this process cannot know that another plugin’s worker needs the card. The host decides when; the worker is the only side that can act, because a CUDA allocator is per-process and the supervisor’s only in-process lever is killing the worker.

Because the worker runs a real Litestar app, a plugin’s custom routes get a real router, validation, multipart, and binary/streaming behavior — and Litestar runs synchronous def handlers in a threadpool, so CPU-bound routes don’t block the worker’s event loop. Litestar + uvicorn are base dependencies of this SDK; they are imported here, not by the import-light tlc_plugin_sdk package surface.

tlc_plugin_sdk.worker.release_gpu_memory() bool[source]

Return this process’s cached-but-unused GPU memory to the driver.

PyTorch’s caching allocator keeps freed blocks for reuse instead of handing them back, so a finished job’s peak allocation stays charged to this process until it exits. A worker is long-lived and serves many jobs, so without this a completed job keeps pinning VRAM that nothing is using, and the next GPU job — here or in another plugin’s worker — can fail to allocate against it. A job that died of OutOfMemoryError is the worst case: it has the largest cache to release, and the natural response to that error is an immediate retry.

torch is deliberately not imported. It is not an SDK dependency (only an ML plugin’s own venv brings it), and a worker whose plugin never loaded it has nothing to reclaim; the module is used only if the plugin already put it in sys.modules. This keeps the SDK’s import-light invariant intact.

Safe to call at any time, including while a job runs: releasing cached blocks never touches memory that is still referenced.

Returns:

True if a CUDA cache was released; False if this worker has no loaded torch, no usable CUDA device, or the release failed.

tlc_plugin_sdk.worker.serve(entry: str, plugin_id: str, *, socket_path: str | None = None, host: str | None = None, port: int | None = None, state_root: str | None = None) None[source]

Load the plugin and serve its Litestar app (blocking).

Bind to exactly one transport: socket_path (Unix domain socket, the supervisor’s default) or host + port (TCP). The plugin’s identity comes from plugin_id (passed by the supervisor from the manifest), not from a class attribute — venv plugins carry no metadata on the instance.

Raises:

ValueError – If not exactly one of socket_path or host/port is given.

tlc_plugin_sdk.worker.main(argv: list[str] | None = None) None[source]

Shared utilities

Plugin-facing helpers that depend only on tlc + the standard library — staged here so out-of-process (venv) plugin workers can import them without pulling in the service.

Shared URL alias utilities for plugins.

Two concerns:

  1. Registration — when creating a new table, register a persistent project alias so image paths use a portable <TOKEN> prefix.

  2. Override — when consuming an existing table, temporarily override an alias so <TOKEN> resolves to a fast local path (e.g. SSD) instead of the default (e.g. S3). Overrides are session-scoped and never persisted.

tlc_plugin_sdk.shared.aliases.default_alias_token(project_name: str) str[source]

Generate a default alias token from a project name.

Parameters:

project_name – Human-readable project name (e.g. “My COCO Dataset”).

Returns:

A valid alias token like MY_COCO_DATASET.

tlc_plugin_sdk.shared.aliases.register_alias(project_name: str, image_folder: str, alias_token: str | None = None) dict[str, Any][source]

Register a project URL alias for an image folder.

Parameters:
  • project_name – The 3LC project that owns the alias.

  • image_folder – Absolute path to the image root folder.

  • alias_token – Override token name. If None, one is derived from project_name via default_alias_token().

Returns:

Dict with token and path that were registered, or error on failure.

tlc_plugin_sdk.shared.aliases.get_table_aliases(table_url: str) list[dict[str, str]][source]

Discover which URL aliases a table uses.

Loads the table, reads image-path columns from the first row, and returns every alias token that appears together with its current resolved path.

Parameters:

table_url – 3LC table URL.

Returns:

List of {"token": "MY_DATA", "current_path": "/data/images", "is_local": true}.

tlc_plugin_sdk.shared.aliases.apply_alias_overrides(overrides: list[dict[str, str]]) list[dict[str, str]][source]

Temporarily override alias paths for the current session.

Uses tlc.url.register_url_alias (session-only, not persisted) so that <TOKEN> resolves to a different path during processing.

Parameters:

overrides – List of {"token": "TOKEN", "path": "/local/fast/path"}. Entries with empty path are skipped.

Returns:

List of {"token": "TOKEN", "original_path": "/original/path"} needed by restore_aliases() to undo the overrides.

tlc_plugin_sdk.shared.aliases.restore_aliases(originals: list[dict[str, str]]) None[source]

Restore aliases to their original paths after an override.

Parameters:

originals – List returned by apply_alias_overrides().

Generic on-disk store for a plugin’s saved job configs.

A “config” here is a reusable job parameterization a user names and re-runs from the plugin UI (the “New config” / config-bar feature driven by tlc_plugin_sdk.shared.config_ui.config_ui_script()) — NOT the service/host settings (those live in persistent_settings / settings.json).

Each plugin keeps its own @dataclass config schema and hands the type to PluginConfigStore, which owns the JSON-on-disk CRUD. The config dataclass must carry the common envelope fields the store manages:

  • id: str — assigned on first save

  • created: str — ISO timestamp, assigned on first save; list order key

  • last_run: str | None — bumped by update_last_run()

(name: str is conventional for the UI but not required by the store.)

Configs live under ~/.3lc-plugin-configs/<plugin-id>/. Pass legacy_dir to lazily migrate a pre-standardization location on first construction.

class tlc_plugin_sdk.shared.config_store.PluginConfigStore(config_cls: type[T], plugin_id: str, *, legacy_dir: Path | str | None = None)[source]

Bases: Generic[T]

Persist a plugin’s saved job configs as JSON files, one per config.

Parameters:
  • config_cls – The plugin’s config @dataclass (must have id / created / last_run fields). Instances are (de)serialized via dataclasses.asdict() and config_cls(**known_fields).

  • plugin_id – The plugin’s manifest id; configs live under ~/.3lc-plugin-configs/<plugin_id>/.

  • legacy_dir – Optional back-compat directory. If the standardized directory has no configs yet and legacy_dir holds some, they are moved on construction (one-time, idempotent).

list_configs() list[T][source]

Return all saved configs, newest first (by created).

get_config(config_id: str) T | None[source]

Load a config by id, or None if missing/unreadable.

save_config(config: T) T[source]

Save a config, assigning id and created on first save.

delete_config(config_id: str) bool[source]

Delete a config. Returns True if it existed.

update_last_run(config_id: str) None[source]

Stamp last_run with the current time, if the config exists.

Shared helper for translating training progress into the generic progress schema.

Used by training plugins inside run_job: they compute an epoch/batch progress dict and call epoch_progress() to render the generic {percent, label, timing} shape the frontend understands, which they then push through their own ctx.emit channel. (The host owns job listing and cancel; this module only shapes the progress payload.)

tlc_plugin_sdk.shared.generic_job.epoch_progress(progress: dict[str, Any], *, phase_key: str = 'phase', epoch_key: str = 'epoch', total_key: str = 'total_epochs', batch_frac_key: str = 'batch_frac', step_label: str = 'epoch') dict[str, Any] | None[source]

Build progress dict from epoch/batch-based training progress.

Common to epoch-based training plugins.

Shared helpers for discovering and reading image data from 3LC tables.

Centralizes the image-column discovery and image-reading pattern used across plugins. Raw paths are read from table.table_rows (the row view — paths as stored, no sample-view decoding) and resolved with tlc.Url.to_absolute against the table URL, mirroring what Table.__getitem__ does internally. This makes relative, aliased (<TOKEN>/...), and cloud (S3/GCS/Azure) paths all work — rather than being passed straight to PIL.Image.open, which only handles local filesystem paths.

Porting to tlc core

This functionality belongs in the tlc SDK; this module is shaped so the port is mechanical. Plugins import only from here, so the port touches exactly this file. Intended core mapping:

  • get_image_column(table, ...)Table.resolve_image_column(name) plus a Table.image_columns property (schema walk over STRING_ROLE_IMAGE_URL).

  • resolve_image_url(path, table_url) → already in core: Url(path).to_absolute(owner) (= Table.absolute_url_from_relative).

  • load_image(path, table_url)ImageHelper.open_image(url) (local file → PIL directly, else BytesIO(url.read_bytes()); faithful mode by default — the RGB conversion is this app’s policy and stays here).

  • read_image_from_table(table, idx, col)Table.read_image(idx, column).

  • get_image_paths(table, col)Table.get_image_urls(column).

  • list_image_urls(folder) → needs a public recursive listing API in core first: UrlAdapterRegistry.list_dir is not exported from tlc.url, and Url._list_dir is private, single-level, and drops the is_dir flag needed to recurse. Suggested core addition: Url.list_dir() / Url.walk() returning UrlAdapterDirEntry.

Once those land, each function body here becomes a one-line delegation and plugin code is untouched.

tlc_plugin_sdk.shared.images.get_image_column(table: Any, override: str | None = None) str[source]

Discover the name of the image column in a table.

Detection order:
  1. override if given (validated against the table’s columns).

  2. Schema string_role == STRING_ROLE_IMAGE_URL (the canonical SDK signal).

  3. Common column-name candidates (image, image_path, …).

Parameters:
  • table – A loaded tlc.Table.

  • override – An explicit column name to use, if provided.

Returns:

The image column name.

Raises:

ValueError – If no image column can be found, listing the available columns so the caller can diagnose the table.

tlc_plugin_sdk.shared.images.resolve_image_url(img_path: str, table_url: Any = None) tlc.Url[source]

Resolve an image path (possibly relative or aliased) against a table URL.

Uses tlc.Url.to_absolute — the same resolution Table.__getitem__ applies to URL columns — so absolute paths pass through unchanged, alias paths (<TOKEN>/...) are expanded, and relative paths are resolved against the table URL.

One deliberate deviation from core: aliases are expanded strictly. Core’s lenient to_absolute keeps an unregistered alias in the path and joins it onto the owner (.../tables/t/<TOKEN>/img.jpg), which turns “alias not registered” into a confusing FileNotFoundError — or silently-NaN metrics — far from the cause. Raising here fails jobs fast with the actual problem. (Whether core should do the same is tracked as a core ask in the deployment doc roadmap.)

Parameters:
  • img_path – The image path stored in the table.

  • table_url – URL of the table the image belongs to (tlc.Url or string; used as the owner for relative paths). May be omitted for absolute/alias paths.

Returns:

An absolute tlc.Url readable through 3LC’s URL adapters.

Raises:

ValueError – If img_path contains an alias that is not registered.

tlc_plugin_sdk.shared.images.load_image(img_path: str, table_url: Any = None) Image[source]

Load an image from a stored path as an RGB PIL image.

Resolves the path via resolve_image_url(), then opens local files directly with PIL and everything else through the URL adapters (BytesIO(url.read_bytes())).

Parameters:
  • img_path – The image path stored in the table.

  • table_url – URL of the table the image belongs to (base for relative paths). May be omitted for absolute/alias paths.

Returns:

The decoded image, converted to RGB.

tlc_plugin_sdk.shared.images.read_image_size(img_path: str, table_url: Any = None) tuple[int, int][source]

Return a stored image’s (width, height) without decoding its pixels.

Resolves the path the same way as load_image() (through the URL adapters, so any storage backend works), but reads only the image header rather than the full image — much cheaper when only the dimensions are needed (e.g. populating image_width/image_height for empty annotations on a table built from images alone).

Parameters:
  • img_path – The image path stored in the table.

  • table_url – URL of the table the image belongs to (base for relative paths). May be omitted for absolute/alias paths.

Returns:

The image (width, height) in pixels.

tlc_plugin_sdk.shared.images.read_image_from_table(table: Any, idx: int, image_column: str | None = None) Image[source]

Read a single image from a table row as an RGB PIL image.

Reads the raw path from table.table_rows (table[idx] would return a decoded image rather than the path) and opens it via load_image().

Parameters:
  • table – A loaded tlc.Table.

  • idx – Row index.

  • image_column – Image column name; discovered via get_image_column() if not provided.

Returns:

The decoded image, converted to RGB.

Raises:
tlc_plugin_sdk.shared.images.get_image_paths(table: Any, image_column: str | None = None) list[str][source]

Read all image paths from a table, resolved to absolute URLs.

Iterates table.table_rows (raw row view) and absolutizes each path against the table URL, so the result is safe to read from anywhere or to write into a new table at a different location. Rows without a path yield an empty string. This is the single bulk-read entry point — if it ever becomes a bottleneck, optimize here rather than at call sites.

Parameters:
  • table – A loaded tlc.Table.

  • image_column – Image column name; discovered via get_image_column() if not provided (an explicit name is validated the same way).

Returns:

One absolute URL string per row, in row order.

Raises:

ValueError – If the image column cannot be found.

tlc_plugin_sdk.shared.images.list_image_urls(folder: Any, max_count: int = 10000) list[str][source]

List image files under a folder, recursively, on any storage backend.

Resolves the folder through tlc.Url (so aliased <TOKEN>/... and relative paths work) and walks it via the URL adapter registry, so local, S3/GCS/Azure, and any custom-adapter folders all list correctly — unlike pathlib, which silently returns nothing for non-local paths.

The full tree is walked before sorting and capping, so the result is deterministic (the lexicographically first max_count paths).

Parameters:
  • folder – Folder path or URL (str or tlc.Url).

  • max_count – Maximum number of paths to return.

Returns:

Sorted list of image URLs/paths. Empty if the folder does not exist or exists but contains no images.

Raises:
  • ValueError – If folder contains an alias that is not registered.

  • OSError – If the folder exists but cannot be listed — e.g. a cloud auth, region, or permission misconfiguration. (A merely non-existent folder returns an empty list, not an error.)

Shared helpers for discovering and reading class labels from 3LC tables.

Centralizes the label-handling pattern used across plugins. The actual value map reading is core SDK API — table.get_value_map(path) and table.get_simple_value_map(path) (which canonicalize on MapElement.internal_name). What core does not provide, and this module centralizes, is discovering the label value path: the dot-path to the label value differs by modality and table convention:

  • classification: the label column itself (e.g. "label")

  • detection: "{column}.instances_additional_data.label" (tlc 3.x) or "{column}.bb_list.label" (legacy tables)

  • segmentation: "{column}.instance_properties.label"

Porting to tlc core

Like shared/images.py, this belongs in the SDK next to Table.get_value_map. Intended mapping: _find_label_pathTable.find_label_path(column=None); get_label_map / get_label_names → thin conveniences over Table.get_simple_value_map. Plugins import only the public helpers (get_label_map / get_label_names / get_class_name_lookup / get_display_value_map / find_label_column) from this module, so the port touches exactly this file.

tlc_plugin_sdk.shared.labels.get_label_map(table: Any, column: str | None = None, *, path: str | None = None) dict[int, str][source]

Read a table’s label map as {class index: internal name}.

Thin wrapper over table.get_simple_value_map with path discovery.

Parameters:
  • table – A loaded tlc.Table.

  • column – Optional column to restrict path discovery to.

  • path – Explicit value path; skips discovery when given.

Returns:

The label map, or {} if the table has none.

tlc_plugin_sdk.shared.labels.get_label_names(table: Any, column: str | None = None, *, path: str | None = None) list[str][source]

Read a table’s class names, ordered by class index.

Parameters:
  • table – A loaded tlc.Table.

  • column – Optional column to restrict path discovery to.

  • path – Explicit value path; skips discovery when given.

Returns:

Class names in index order, or [] if the table has no label map.

tlc_plugin_sdk.shared.labels.get_class_name_lookup(table: Any, column: str | None = None, *, path: str | None = None) dict[str, str][source]

Read a table’s label map as a string-keyed lookup table.

Keys include both the raw map key ("1.0") and its integer form ("1"), so callers can index with whichever flavor their data carries. This is the shape the insights statistics pipeline consumes.

Parameters:
  • table – A loaded tlc.Table.

  • column – Optional column to restrict path discovery to.

  • path – Explicit value path; skips discovery when given.

Returns:

{class key (str): internal name}, or {} if no label map.

tlc_plugin_sdk.shared.labels.get_display_value_map(table: Any, path: str) dict[float, str] | None[source]

Read a value map as {float key: display name} for UI purposes.

Unlike the label helpers above (which canonicalize on internal_name, matching core’s get_simple_value_map), this prefers display_name — the shape used when presenting categorical columns to users.

Parameters:
  • table – A loaded tlc.Table.

  • path – Value path (column name or dot-path).

Returns:

The display map, or None if the path has no value map.

tlc_plugin_sdk.shared.labels.find_label_column(table: Any) str | None[source]

Find a top-level categorical label column in a table, if any.

A column qualifies if it carries a value map directly (categorical), or as a name-based fallback, contains label in its name.

Parameters:

table – A loaded tlc.Table.

Returns:

The column name, or None.

Shared modality detection — single source of truth for all table/schema inspection.

Detects whether a table represents a detection, classification, segmentation, pose, or OBB task by walking the schema tree. Used by insights, training, and auto-labeling plugins.

class tlc_plugin_sdk.shared.modality.ModalityInfo(modality: str = 'unknown', gt_col: str | None = None, pred_col: str | None = None, class_names: dict[str, str]=<factory>, image_column: str | None = None, image_columns: list[str] = <factory>, label_column: str | None = None, num_classes: int = 0, classification_columns: list[str] = <factory>, detection_columns: list[str] = <factory>, segmentation_columns: list[str] = <factory>, all_columns: list[str] = <factory>)[source]

Bases: object

Result of modality detection.

modality: str = 'unknown'

detection, segmentation, classification, pose, obb, or unknown.

Type:

Detected modality

gt_col: str | None = None

Ground-truth column name (e.g. bbs, segmentations, label).

pred_col: str | None = None

Prediction column name (e.g. bbs_predicted, predicted).

class_names: dict[str, str]

Class index → display name mapping.

image_column: str | None = None

Image column name (if detected — first one found).

image_columns: list[str]

All image columns detected in the schema.

label_column: str | None = None

Label column name for classification tasks.

num_classes: int = 0

Number of classes detected from value maps.

classification_columns: list[str]

All columns identified as classification-type.

detection_columns: list[str]

All columns identified as detection-type.

segmentation_columns: list[str]

All columns identified as segmentation-type.

all_columns: list[str]

All column names in the schema.

tlc_plugin_sdk.shared.modality.detect_modality_from_table(table: Any) ModalityInfo[source]

Detect modality from a loaded table object.

Parameters:

table – A loaded tlc.Table object.

Returns:

ModalityInfo with modality, columns, and class names extracted.

Shared utility for saving and copying model checkpoints to Run folders.

Handles both local filesystem and cloud storage (S3, GCS, Azure) via the tlc.Url abstraction. The compute service is assumed to have write access to the Run folder location.

tlc_plugin_sdk.shared.model_storage.save_model_to_run(run_url: str, model_data: Any, filename: str = 'best_model.pt', source_file: str | Path | None = None, on_status: Any = None) str[source]

Save a model checkpoint to a Run’s model/ subdirectory.

Supports both local and cloud (S3/GCS/Azure) run folders. For cloud storage, the file is first written to a temp directory, then uploaded via tlc.Url.

Parameters:
  • run_url – The 3LC Run URL (local path or cloud URL).

  • model_data – PyTorch state_dict to save (ignored if source_file is set).

  • filename – Name for the model file in the run folder.

  • source_file – If set, copy this existing file instead of saving model_data.

  • on_status – Optional callback for status messages.

Returns:

The relative path to the saved model file (e.g. model/best.pt). This is relative to the run folder so it survives run renames.

Raises:

RuntimeError – If the model could not be saved.

tlc_plugin_sdk.shared.model_storage.store_model_info_in_run(run: Any, model_name: str, model_path: str, source_url: str = '', on_status: Any = None) None[source]

Store model metadata in a Run’s parameters.

Parameters:
  • run – The tlc.Run object.

  • model_name – Model architecture name (e.g. yolov8n.pt, resnet50).

  • model_path – Path/URL to the saved model checkpoint.

  • source_url – Original pretrained model URL (if fine-tuning).

  • on_status – Optional callback for status messages.

Random name generator for runs, jobs, and other entities.

Produces memorable names from data science and computer vision vocabulary.

tlc_plugin_sdk.shared.naming.generate_name() str[source]

Generate a memorable name like robust-gradient-42.

URL normalization utilities for 3LC object URLs.

Ensures file-path URLs are absolute before passing to the tlc SDK, preventing the CWD from being prepended to relative-looking paths.

tlc_plugin_sdk.shared.url_utils.normalize_url(url: str) str[source]

Normalize a 3LC URL for use with the tlc SDK.

  • If the URL is a protocol URL (e.g. api://, s3://, gs://), return as-is.

  • If the URL looks like a file path, expand ~ and ensure it’s absolute.

  • Handles URL-decoded paths that may have lost their leading slash.

tlc_plugin_sdk.shared.url_utils.normalize_local_path(path: str) str[source]

Normalize a user-typed local filesystem path.

Strips whitespace and expands ~/~user. Plugins run with the plugin venv as CWD, so a bare-relative path would silently resolve somewhere no user ever looks — reject it instead.

Parameters:

path – Raw path string as typed by the user.

Returns:

The expanded, absolute path.

Raises:

ValueError – If the path is empty or not absolute after expansion.

tlc_plugin_sdk.shared.url_utils.get_url_column_names(table: Any) list[str][source]

Return the names of a table’s URL/path-valued columns.

Reads the table’s _url_columns attribute, which is a private 3lc attribute that is not part of the typed public API and may be absent depending on the 3lc version, so reach it defensively. It can be [['image']] (nested) or ['image'] (flat).

Parameters:

table – A tlc.Table.

Returns:

Flat list of column names; empty if none could be determined.