# Video Review Os Plugins

> Write a custom provider for Video Review OS — a new transcription, diarization, copy, tagging, storyboard, or visual perception backend — and register it without forking the repo. Use when the user wants to plug in their own model, endpoint, or local runtime, asks how the provider registry works, wants to swap which AI serves a stage, or gets "unknown provider" behaviour.

- Skill: `cgallic/video-review-os-plugins` (Agent Skill)
- Install (CLI): `npx skillmds@latest add cgallic/video-review-os-plugins`
- Raw SKILL.md: https://api.skillmd.com/api/skills/cgallic/video-review-os-plugins/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- License: MIT
- Author: cgallic (https://skillmd.com/u/cgallic)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/cgallic/video-review-os-plugins

---


# Writing a provider

Every AI-ish stage is a *kind* with named *providers*. Which one runs is a string in
`config.toml`, resolved through the registry in `video_review_os/providers.py`. No stage
names a vendor, and neither should yours.

Kinds: `transcription`, `diarization`, `copy`, `tagging`, `storyboard`, `perception`.

```bash
video-review-os providers          # what is selectable right now
video-review-os providers --json   # the same, machine-readable
```

## The contract

Two rules, both non-negotiable:

1. **Never raise out of your public method.** Catch everything and return the kind's
   deterministic result instead, with the failure recorded in the artifact. A provider
   that throws takes down someone's overnight batch.
2. **Never widen the schema.** Sanitize your output to the known enums before returning
   it. The pipeline treats provider output as untrusted input, because it is.

## Registering

Write a module with a `register_providers()` function:

```python
from video_review_os import providers
from video_review_os.perception import PROVIDER_KIND, PerceptionProvider

class MyPerception(PerceptionProvider):
    name = "my-perception"

    def __init__(self, config):      # receives the [perception] config section
        self.config = config

    def perceive(self, clips, context, config):
        try:
            enriched = my_model(clips)
        except Exception:
            return {"provider": "fallback", "status": "fallback", "clips": clips,
                    "errors": ["my-perception failed; used the deterministic reading."]}
        return {"provider": self.name, "status": "ok", "clips": enriched}

def register_providers() -> None:
    providers.register(PROVIDER_KIND, "my-perception", MyPerception,
                       summary="What this does", source="plugin-module")
```

Then either list it in config:

```toml
[plugins]
modules = ["my_studio.perception"]
```

…or ship it as an installed package advertising a `video_review_os.providers` entry point
whose value is a zero-argument callable:

```toml
[project.entry-points."video_review_os.providers"]
my-studio = "my_studio.perception:register_providers"
```

Select it like any builtin:

```toml
[perception]
provider = "my-perception"
```

## Sanitizing perception output

Reuse the shipped sanitizers rather than writing your own — they are what keep an unknown
enum out of the artifact:

```python
from video_review_os.perception import sanitize_visual, sanitize_reframe, taxonomy
visual  = sanitize_visual(model_answer, clip["visual"], config.perception)
reframe = sanitize_reframe(model_answer.get("reframe"), clip["reframe"])
```

`taxonomy(config.perception)` gives the exact vocabulary a model is allowed to answer in —
send it in your prompt so the model has a chance of complying, and sanitize anyway.

## Debugging

- Plugin import failures are collected, not raised: `video-review-os providers` prints
  them at the bottom, and `providers.load_errors()` returns them.
- An unknown provider name silently resolves to the kind's fallback. If your provider
  "isn't running", check the spelling against `providers --json` first.
- Providers load builtins first, plugins second, so a plugin *can* deliberately replace a
  builtin name — check you have not done so by accident.

