Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ jobs:
- name: Ingest end-to-end example
run: uv run python examples/ingest_end_to_end.py

# The CLI half, and the only one that proves the *installed console script*
# works from a real shell — which CliRunner, running in-process, cannot.
# `uv run bash` puts the virtualenv's bin/ on PATH so `visionset` and
# `python3` are the same installation. Stills only, so no ffmpeg needed.
- name: CLI end-to-end example
run: uv run bash examples/cli_end_to_end.sh

frontend:
runs-on: ubuntu-latest
steps:
Expand Down
27 changes: 22 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,31 @@ plain `pip` package.
```bash
pip install visionset # coming soon

python -c "from visionset.kernel.services import WorkspaceService; WorkspaceService.init('.').close()"
visionset init # a workspace here
visionset ui # API at http://127.0.0.1:8000, app at /ui
```

Creating the workspace is a Python call for now — there is no `visionset init` yet. `visionset ui`
run outside a workspace refuses with one sentence and exit 1; it never creates one, because a
command that silently made a workspace out of whatever directory you were standing in is how data
ends up somewhere nobody chose. See [docs/cli.md](docs/cli.md).
`init` is the only command that creates a workspace, and it refuses a directory that already holds
something. `visionset ui` run outside one refuses with one sentence and exit 1; it never creates
one, because a command that silently made a workspace out of whatever directory you were standing
in is how data ends up somewhere nobody chose.

Or drive the whole cycle from the terminal, without a server:

```bash
visionset project create road-signs
visionset schema apply schema.json --project road-signs
BATCH=$(visionset ingest ./incoming --project road-signs)
visionset batch approve "$BATCH" --jobs-of 100 && visionset batch start "$BATCH"
# …annotate, then…
visionset batch complete "$BATCH" && visionset batch promote "$BATCH"
visionset release publish --tag v1.0 --project road-signs --split 0.7,0.15,0.15
visionset export --project road-signs --release v1.0 --format dummy --out ./out
```

Every command takes `--json` for scripting, and the shapes are the REST API's. See
[docs/cli.md](docs/cli.md), or [`examples/cli_end_to_end.sh`](examples/cli_end_to_end.sh) for that
walk with its assertions still in it.

Prefer to see the SDK first? [`examples/sdk_end_to_end.py`](examples/sdk_end_to_end.py) drives an
empty directory to a hash-verified release in one pass, generating its own images — no server,
Expand Down
2 changes: 1 addition & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@ contracts (kernel purity, headless annotator) are described there and enforced i
| [examples.md](examples.md) | The two runnable examples: the whole cycle in one pass, ingest on its own, and what each is built to demonstrate |
| [api.md](api.md) | The REST surface: the conventions every endpoint follows (paths, UUID ids, the list envelope, gates as query parameters), the one error body, why clients branch on `code` and not on the status, what decides 404 / 409 / 422, what a 5xx does and does not tell you, and which codes are worth retrying |
| [auth.md](auth.md) | Who may call it: per-workspace API tokens, why only a digest is stored, why every refusal is one identical 401, immediate revocation, the `visionset token` commands, and how a protected route is built |
| [cli.md](cli.md) | The command line: the three exit codes, why stdout is data and stderr is prose, why `--workspace` follows the subcommand, and what `visionset ui` starts — the resolved workspace it states, the bundle it serves, and what happens when nobody built one |
| [cli.md](cli.md) | The command line: the whole cycle as a script, the three exit codes (and why one of them also means "no"), why stdout is data and stderr is prose, what `--json` promises and how it stays the API's shape, why `--workspace` follows the subcommand, and what `visionset init` and `visionset ui` each do |
30 changes: 30 additions & 0 deletions docs/batches.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,36 @@ progress and the membership rows.
of work never deletes the work. Neither the assets nor any blob are touched either — see
[projects.md](projects.md) for why blobs are never deleted.

## At a terminal

```bash
visionset batch list --project road-signs
visionset batch approve "$BATCH" --jobs-of 100
visionset batch start "$BATCH"
visionset batch complete "$BATCH"
visionset batch promote "$BATCH"
```

Each is one service call, and the listing carries the progress counts because a batch's name and
state do not say whether anybody has started on it.

**`--jobs-of N` is `BySize`; with no flag the batch becomes one job.** There is no flag for
`BySegments`, and that is a decision rather than an omission: its own contract is that the caller
has already decided the split, and the only caller that ever holds an exact partition is a program —
which has the SDK and the API. It is also the one partition that can be *wrong*, with four distinct
refusals, and putting it behind a shell's quoting of tuples of UUIDs is a way to meet all of them.
If it is ever wanted it arrives as `--segments FILE.json`.

`--jobs-of` carries `min=1` at the Click layer, because `BySize.size` is `gt=0` and a pydantic error
is not a `VisionSetError` — it would print a traceback rather than a sentence.

**There is no `batch create`, and no membership editing**, for the reason there is none over HTTP: a
batch is born from an ingest. `BatchService` still has all four methods; this is a decision about
the surfaces.

`promote` is here rather than under a dataset group because `DatasetService.promote` takes a *batch*
id and derives the dataset from it — the same argument its route makes.

## Over HTTP

The [API](api.md) is this service with the curation half left off.
Expand Down
Loading
Loading