Cat Factory
Home
Get Started
GitHub
Home
Get Started
GitHub
  • Start

    • Introduction
    • Core Concepts
    • Quick Start
    • Tutorial: Your First Task to a Merged Pull Request
  • Guides

    • Recipes

      • Cookbook
    • Plan the work

      • Design Your Board
      • Ask the Assistant to Do It
      • Clarify Requirements
      • Author a Document
      • Plan an Initiative
    • Run pipelines

      • Choose and Edit a Pipeline
      • Run a Pipeline
      • Schedule Recurring Work
      • Review and Merge Pull Requests
      • Control Spend with Budgets
    • Connect

      • Connect a Repository
      • Connect Issue & Document Sources
      • Feed Design Context to Agents
      • Preview and Test a Frontend
    • Models & prompts

      • Connect a Model Provider
      • Apply Standards with Prompt Fragments
      • Run a Claude Skill
      • Compare Prompts and Models in the Sandbox
    • Collaborate

      • Invite and Manage Your Team
      • Share Services Across Workspaces
      • Register Foundational Services
  • Deploy

    • Run Locally
    • Deploy to Node.js
    • Deploy to Cloudflare
    • Deploy on Kubernetes
    • Lay Out a Kubernetes Cluster
    • Set Up a Local Kubernetes Cluster on Windows
    • Register the GitHub App
    • Set Up Enterprise SSO
    • Set Up Your Deployment Repository
    • Configuration
  • Operate

    • Observability
    • Set Up Notifications
    • Run Jobs on Your Own Runners
    • Provision Ephemeral Environments
    • Debug a Run from Outside the Browser
    • Troubleshooting
    • Upgrades & Data Retention
  • Extend

    • Add a Custom Agent Kind
    • Add a Custom Gate or Judge
    • Run a Step on an Executor You Already Operate
    • Add a Custom Provider
    • Extend the App with Frontend Modules
    • Integration Manifests
    • Give Agents External Tools (MCP)
    • Package a Reusable Operation
    • Package an Inline Use Case
    • Register an Initiative Preset
    • Public API
    • Official SDKs
    • MCP Server
    • Cloudflare OS Gatekeeper
  • Reference

    • Architecture
    • Agent Isolation Model
    • Security Model & Hardening
    • Packages & Repository Layout
    • GitHub and GitLab Support Matrix
    • Environment Variables
    • API Endpoint Reference
    • Glossary

Package an Inline Use Case

For the deployment author whose users generate text from a tool of your own: a game content editor, a writing surface, an internal copy tool. The work is one model call rather than a pipeline. There is no repository to check out, no pull request to open, and nothing belongs on the board.

An inline use case packages that: a named unit of model work, the narrowed list of models it may run on, the small form it accepts, and the bounds a caller may steer within. Cat Factory publishes what you registered at GET /api/v1/use-cases and runs one at POST /api/v1/use-cases/{useCaseId}/invocations, so your editor generates through this deployment's own model credentials, workspace budget and call telemetry instead of holding its own vendor keys beside them.

Nothing organization-flavoured ships in Cat Factory: the catalog is empty until you register on it, through the same public registry seam custom agents and reusable operations use.

Operation or use case?

A reusable operation is the CONTAINER sibling: a form bundled with a pipeline that runs agents against a real repository checkout and ends in a pull request. Reach for an inline use case when the deliverable is text handed straight back to the caller, and for an operation when it is a change to a codebase.

The bundle

import { defaultInlineUseCaseRegistry } from '@cat-factory/node-server'

const inlineUseCaseRegistry = defaultInlineUseCaseRegistry()

inlineUseCaseRegistry.register({
  useCaseId: 'stefka:scene-prose', // ALWAYS namespaced (<ns>:<name>)
  label: 'Scene prose',
  description: 'Write a scene from its beat sheet, in the game’s voice.',
  category: 'Narrative', // optional grouping caption for your own picker

  // The NARROWING: nothing else is invocable through this use case, whatever else
  // the workspace's model catalog holds.
  models: [
    {
      id: 'magnum',
      label: 'Magnum v4',
      description: 'A prose fine-tune. Best register for in-world dialogue.',
      source: { kind: 'provider', ref: { provider: 'novelai', model: 'magnum-v4-72b' } },
      default: true,
    },
    {
      id: 'gemini-flash',
      label: 'Gemini Flash',
      description: 'Fast and cheap; use it for bulk barks.',
      source: { kind: 'catalog', modelId: 'gemini-flash' },
    },
  ],

  // The form, in the same descriptor vocabulary a reusable operation's brief uses.
  parameters: [
    { key: 'beats', label: 'Beat sheet', type: 'textarea', required: true },
    {
      key: 'tone',
      label: 'Tone',
      type: 'select',
      options: [
        { value: 'grim', label: 'Grim' },
        { value: 'wry', label: 'Wry' },
      ],
    },
    { key: 'wordTarget', label: 'Target length (words)', type: 'number' },
  ],

  // What a caller may steer, and how far.
  generation: {
    temperature: { default: 1.05, min: 0.6, max: 1.4 },
    maxOutputTokens: { default: 1_200, max: 4_000 },
  },

  systemPrompt: 'You write scene prose for a dark-fantasy RPG. Never narrate player choices.',
})

Pass the registry at boot, exactly as you would any other: start({ inlineUseCaseRegistry }), startLocal({ inlineUseCaseRegistry }), or the Worker's inlineUseCaseRegistry override. Register by reference, from your own composition root, and import the registry constructor from the facade package you boot through rather than from @cat-factory/kernel directly (Manifests explains why that matters).

Narrowing the models

models is the reason the feature exists. A creative-writing tool wants prose models for prose and a cheap general model for bulk work, and it wants that choice made once by the person who understands the models, not per request by whatever the client happens to send.

Each option names ONE of two sources:

SourceUse it for
{ kind: 'catalog', modelId }A model in Cat Factory's own model catalog. It routes through whichever provider this workspace has configured, so adding a direct key later upgrades the route with no edit here.
{ kind: 'provider', ref }Anything else: a prose fine-tune, a vendor whose whole product is fiction, an OpenAI-compatible endpoint you serve yourself. Register the resolver for its provider id the way custom providers describes.

Exactly one option carries default: true, which is what an invocation naming no model runs on. A single-model use case may omit the flag; several models with none flagged, or with more than one, fails boot rather than picking whichever you happened to list first.

A model outside the list is refused, never substituted. So is one this deployment cannot serve inline. That is the whole point of declaring a list: a caller that asked for Magnum and silently got a general-purpose model would store prose in the wrong register with nothing saying so. Discovery therefore publishes each model's availability, and an unavailable one says which of two things it is:

  • provider_unavailable — nothing here resolves it. You configure the provider.
  • container_only — it resolves only through a coding-subscription harness that runs inside a per-run container, which this surface has none of. No configuration changes that; the caller picks another model. (Local mode is the exception: an ambient claude or codex login can serve one inline, and says so.)

The parameter form

parameters draws on the same field vocabulary as a reusable operation's per-case form, so a client that renders one renders the other, and the platform runs one validator over both. The types available are text, textarea, number, select, checkbox, checkbox-group, each with the usual required, default, help, placeholder, section and showWhen attributes documented under the per-case form.

Two of the shared types are deliberately absent. password is excluded because a collected value is folded into the model prompt and captured in call telemetry, which is the wrong home for a secret. path is excluded because it means a repo-relative directory and there is no checkout in this call.

The answers reach the model as a labelled brief under your systemPrompt, with select values rendered as their captions and unanswered fields omitted. That default is what makes a use case declarable with no code at all. When the ordering or the phrasing of that brief IS the product, supply compose instead:

compose: ({ parameters }) => ({
  system: 'You write scene prose for a dark-fantasy RPG.',
  prompt: `Beats:\n${parameters.beats}\n\nWrite ${parameters.wordTarget ?? 400} words.`,
}),

Calling it

Discovery is read scope and invoking is write: an invocation spends model tokens and returns text, and it starts no run and merges nothing. See Use the Public API for keys and scopes, and the API Endpoint Reference for the field level.

curl -s "$BASE/api/v1/use-cases" -H "Authorization: Bearer $KEY"

curl -s -X POST "$BASE/api/v1/use-cases/stefka:scene-prose/invocations" \
  -H "Authorization: Bearer $KEY" -H 'content-type: application/json' \
  -d '{
        "model": "magnum",
        "parameters": { "beats": "They meet at dusk. She lies.", "tone": "grim" },
        "temperature": 1.2
      }'

The response carries the text, the model it actually ran on (never a substitute), why the model stopped, and what the call cost. finishReason: "length" arrives with truncated: true: the text is a prefix rather than an answer, so re-run with a larger maxOutputTokens rather than shipping it.

What a caller sees when something is wrong

Every refusal carries a machine-readable error.details.reason, so your editor can act on it rather than matching strings:

StatusreasonWhat to do
404use_case_not_foundRe-read the catalog; the deployment no longer registers this id.
422use_case_parameters_invaliddetails.problems names every problem at once. Fix the form.
422use_case_model_not_alloweddetails.allowed names what this use case does carry.
422use_case_generation_out_of_rangeThe knob is outside the published bounds. Nothing is clamped.
429budget_exhaustedThe workspace has spent its model budget. Nothing reached a vendor.
503use_case_model_unavailabledetails.cause is provider_unavailable or container_only.
503use_case_models_unconfiguredThis deployment has wired no model provider at all.
503use_case_empty_replyThe model answered with nothing usable. Retry, or offer another model.

Two of those are choices worth counting on. A model that cannot be honoured is refused rather than swapped, as above. And a 200 never carries an empty string: some reasoning models answer only into their private channel, and a content editor would otherwise save that silence as the model's answer.

What it costs, and where it shows up

An invocation answers to the workspace's spend budget, for the same reason a run does: it is a billable model call. When the budget is spent, the call is refused before anything reaches a vendor.

Every invocation files an LLM-call record tagged with the use case's id, so the spend and telemetry surfaces attribute an editor's usage per use case rather than as one undifferentiated bucket. Nothing else is stored: there is no board row, no run and no history of generated text. Your editor owns what it keeps.

Discovery is always available

GET /api/v1/use-cases never 404s and never 503s.

  • A deployment that registered nothing answers an EMPTY list, because "nobody registered a use case" and "this deployment has no such surface" are different facts and your client should be able to tell them apart.
  • A deployment with no model provider answers the full catalog with every model marked unavailable, so your picker shows what the use case offers, greyed out, rather than looking like a use case with no models.

Only the invocation refuses in that second state.


Next: Use the Public API for keys, scopes and the rest of the surface, or Package a Reusable Operation when the work is a change to a repository rather than a piece of text.

Edit this page on GitHub
Last Updated: 8/20/26, 4:54 PM
Prev
Package a Reusable Operation
Next
Register an Initiative Preset