Skip to content

About

Domain-agnostic HTTP server that runs tools it never imports: one virtualenv and one JSON descriptor per tool

Topics

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

511 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

VISOR-serve

An HTTP server that runs tools it has never imported.

Each tool lives in its own directory with its own Python interpreter and its own lockfile. The server reads a JSON descriptor generated from the tool's run() signature, validates an incoming request against it, and executes the tool as a subprocess:

<TOOLS_DIR>/<tool>/.venv/bin/python <RUNNER_PATH> --job <job dir>/job.json

Nothing about a tool is compiled into the server. Two tools whose dependencies cannot coexist are therefore not a problem: one can run torch 2.8 while another runs torch 2.11, in the same deployment, while the server itself runs Python 3.13 and holds no deep-learning stack at all.

This repository is the server. The tools it was built for are in SADT-VISOR, and the 3D Slicer client is AutomatedDentalToolsRemote. The server carries no clinical knowledge, so a different catalogue can be served by pointing TOOLS_DIR somewhere else.

Three rules that explain most of the design

A tool is a directory with its own virtualenv. The server never imports one.

Discovery is declared, not inferred. A directory becomes a tool when its pyproject.toml carries [tool.sadt] tool = true, scanned two levels deep so a grouping folder can hold several. There is no central registry to keep in sync.

A tool calls another through the supervisor, never by importing it. The supervisor starts the callee in its own interpreter and passes a directory of files. The declared call graph is verified at every boot: the server refuses to start when a declared callee is not served.

Installing a server

One command takes a bare Ubuntu machine to a running server: the server image, the whole SADT-VISOR tool library as its prebuilt image, every model and test file, and Tailscale in front of it. Run it from the directory that should hold the deployment (it needs about 60 GB):

curl -fsSL https://raw.githubusercontent.com/DCBIA-OrthoLab/VISOR-serve/deploy/scripts/setup-server.sh \
  | sh -s -- --full --tailscale --auto-update apply \
      --tools-image ghcr.io/dcbia-ortholab/sadt-visor-tools:latest

What it does on its way, asking before each step that needs sudo:

  • NVIDIA driver. A card whose driver is missing or too old (below 525, or below 570 for an RTX 50xx) gets the driver Ubuntu recommends; reboot, then run the same command again. A card older than Turing (RTX 20xx) runs on CPU.
  • Docker and the NVIDIA Container Toolkit, when missing.
  • Tailscale. Before running the command, open the Tailscale admin console, Settings > Keys, and generate an auth key. Paste it when asked: the prompt hides it, and it never appears on a command line. The server is then reachable at https://<name>.<tailnet>.ts.net, from the tailnet only.

Run again, the same command updates the deployment and keeps its tokens. The two tokens a deployment needs are printed by:

python3 VISOR-serve/scripts/server_ctl.py token            # for the Slicer client
python3 VISOR-serve/scripts/server_ctl.py token --admin    # for /server-debug

Connecting a workstation

A workstation reaches the server through the same tailnet, and membership is what decides who can reach the server at all. With an auth key — the same kind the install above asks for — joining is one command on every platform:

Install Join
Ubuntu curl -fsSL https://tailscale.com/install.sh | sh sudo tailscale up --auth-key <key>
Windows the installer from https://tailscale.com/download tailscale up --auth-key <key> --unattended
macOS the standalone package from https://tailscale.com/download tailscale up --auth-key <key>

Generate the key in the Tailscale admin console under Settings > Keys; it looks like tskey-auth-…. A reusable key covers a whole set of workstations, so one key rolls out a lab. Treat it as a credential — anything holding a valid key can join the tailnet — and revoke it there once the rollout is done.

Without a key, tailscale up with no argument prints a link instead: open it and sign in with the lab's Tailscale account. That is the shorter path for a single machine with someone sitting at it, and the only one on a Mac installed from the App Store.

Two platform details, because the Ubuntu line does not carry over:

  • macOS — the App Store build is sandboxed and puts no tailscale on PATH, so the command above needs the standalone package; the App Store build signs in from the menu bar instead, and its command, if you want it, sits inside the bundle at /Applications/Tailscale.app/Contents/MacOS/Tailscale.
  • Windows — --unattended is what keeps a workstation on the tailnet while nobody is logged in, which is what a machine left running overnight needs. The flag is Windows-only and does not exist in the Linux build, so it cannot be copied the other way either.

Either way the machine then appears in the admin console, and removing it there is how access is withdrawn — nothing changes on the server or in Slicer.

Then, in 3D Slicer, open the Slicer Cloud module and set:

  • URL: https://<name>.<tailnet>.ts.net, as printed at the end of the server install (tailscale serve status on the server shows it again);
  • token: the API token above, never the admin one.

Running it by hand

cp .env.example .env          # API_TOKEN, TOOLS_DIR, DATA_DIR, DEVICE
docker compose up -d          # the deployment image

For a local server against a SADT-VISOR checkout, without Docker:

SADT_TOOLS=/path/to/SADT-VISOR ./run-local.sh    # serves on 127.0.0.1:8001

scripts/README.md documents the unattended installers for a fresh machine, the model-weight fetcher and the test-file fetcher.

Documentation

File What it covers
docs/ARCHITECTURE.md The shape of the system and the rules above, in full
docs/INTERNALS.md Registry, dispatch, transfer and the job lifecycle
docs/TOOL_GRAPH.md How one tool declares a call to another
ADDING_A_TOOL.md Adding a tool: one run() function, no base class
MIGRATING_A_TOOL.md Porting an existing Slicer module into a served tool
RUN_PROGRESS.md Reporting a run's progress, and cancelling it
SECURITY.md The threat model this server does and does not address
server/README.md The API surface, endpoint by endpoint
benchmarks/ The measurement harness, re-runnable

Citation

This server is the artifact described in:

Grivot Pélisson J, Barret R, Cevidanes L. VISOR: A Descriptor-Driven Architecture for Environment-Isolated and Composable Dental Imaging Tools. AMIA 2027 Amplify Informatics Summit. Submitted.

The benchmark harness under benchmarks/ reproduces every runtime number in that paper.

About

Domain-agnostic HTTP server that runs tools it never imports: one virtualenv and one JSON descriptor per tool

Topics

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages