1. Understand the application architecture
Start by separating the work your application owns from the work Skulk™ owns. Studio is a useful example because it is substantial enough to need persistence, a UI and several workflows, but it still calls ordinary public model APIs.
Follow one user action
An operator writes a shot description and clicks Plan. Studio reads the model catalog and live placement, resolves the shot and returns a plan. The operator then starts a render. Studio records a durable operation, submits a Skulk™ video job and follows it until the finished clip is downloaded into Studio's own storage.
The UI, application process and inference model need not run on the same machine.
Browser ── scoped HTTP ──┐
├── Studio child ── HTTP ── Skulk API ── model runner
Fabric caller ── host ──┘ │
├── SDK operation journal
└── application media library
The host supervises the child; the child tracks jobs; Skulk™ places and runs inference.
| Boundary | Input | Output | Owner |
|---|---|---|---|
| Browser → Studio | Application HTTP requests under the surface URL | Plans, operation records and media | Studio |
| Fabric caller → child | Negotiated capability call through Skulk™ and the managed host | One typed result | Skulk™ routing + Studio behavior |
| Child → Skulk™ API | Catalog/state reads, video jobs, chat completions | Model facts, job references and results | Skulk™ inference |
| Child → durable storage | Operation intent, logs, files and retention marks | Restartable application state | Studio + SDK persistence helpers |
The browser does not speak the managed child's socket protocol. The child does not import a model runner. These boundaries let you change the UI or switch the application workflow without rewriting Skulk™'s inference machinery.
The source map
The Python application lives under src/foxlight_video_studio/.
| File | Read it for |
|---|---|
contracts.py | Typed requests, results, settings, descriptors and the signed manifest. |
child.py | Startup, SDK wiring, dispatch, health, background workers and shutdown. |
planning.py | Resolving model cards, reference modes, geometry, engine settings and plan digests. |
skulk_api.py | Bounded HTTP requests and decoding Skulk™ replies. |
render.py | The SDK job adapter: submit, observe, collect and cancel. |
refine.py / guides.py | Prompt rewriting with a placed chat model and model-specific guidance. |
assets.py / media.py | Imports, content identity, media validation and reference lookup. |
takes.py / history.py | Retention marks and migration of earlier operation history. |
server.py / webapp.py | Application HTTP routes and packed browser assets. |
speed.py / thumbnails.py | Local timing observations and thumbnail backfill. |
packaging.py | Checking that the signed manifest matches the packaged code. |
The web application lives in web/. Its API client is separate from its state stores and components. schemas/ is generated from the application contracts; tools/ contains schema and packaging utilities; tests/ and integration/ exercise increasingly complete boundaries.
What Studio uses from the SDK
| SDK module | Studio's use | What you still implement |
|---|---|---|
contracts | Startup, Hello, Health, Invoke, Result, typed contracts and manifests | Your application schemas and dispatch behavior. |
wire | Read/write bounded messages to the managed host | The child loop and owned cleanup. |
storage | Protected directories, startup reads and atomic writes | Media validation, naming and retention. |
operations | Journals, services, runners, logs and operation descriptors | The backend adapter and meaningful plan. |
database | Used by SDK operation persistence | Application-specific stores and migrations where needed. |
serving | Host-selected addresses and caller admission helpers | HTTP routes, UI authentication and surface readiness. |
streams | Not used by Studio's six descriptors at this revision | Streaming handlers if your own workflow needs them. |
Studio uses unary calls to start and inspect durable work. A render's duration is not a reason to declare a streaming capability. Choose streaming when you actually exchange incremental output or caller media during a call. See choosing a contract.
Keep these identities separate
| Identity | Example / source | Purpose |
|---|---|---|
| Bundle id | foxlight.video-studio | Which installed application package this is. |
| Installed node id | Host-provided Startup.node_id | Which installation owns this child and its durable scope. |
| Capability id + version | video.render@1.0.0 | Which operation a caller requests. |
| Descriptor revision | Derived from the descriptor contract | Whether caller and provider agree on the exact contract. |
| Operation id | Caller-retained application id | Deduplicating and observing application work. |
| Backend reference | Skulk™ video job id | Following the external inference job. |
| Plan digest | Digest of resolved work | Detecting changes between review and execution. |
Do not substitute a job id for an operation id, or a plan digest for authorization. Each answers a different question.
Check your understanding
If the Studio tab closes during a render, the application operation can continue. If the Studio child restarts, its journal remains and the stored video job can be reconciled. If the model host fails, Studio cannot manufacture a successful model job. You should be able to point to the owner of each failure before writing recovery code.
Next: define the contracts.