generation · git:20261014.71d43e0 · 2026-10-14 · sha256 71927dec4854ec02

generation git:20261014.71d43e0A

Immutable. This exact content never changes and is served at /api/v1/blob/71927dec4854ec02.

---
name: generation
description: >
  Generate images and videos from text prompts using the elegant
  `astrid.generate` facade.  Image, audio, and video generation route through
  the same runtime-owned executor code path. Covers generate_image, generate_video, and
  generate_audio, generate_speech, and generate_image_openai (executor-only).
---

# Generation

The generation pack provides a first-class **library facade** for image, audio,
and video generation and also exposes the underlying executors for direct (SDK)
and automation use.

## Quick-start — runtime-backed generation

Use the runtime-backed SDK generation facade. Successful managed calls return
runtime artifact references (digest, media type, size, and name); the runtime
owns durable storage and task/run state.

### Image

```python
import astrid.sdk as sdk

# Simplest: text-to-image in the selected runtime project
img = sdk.invoke(
    "generation.generate_image",
    kind="executor",
    project="demo",
    inputs={
        "model": "flux-schnell",
        "mode": "t2i",
        "execution": "cloud",
        "prompt": "a serene mountain lake at dawn",
    },
    wait=True,
)
img.outputs["artifacts"]  # runtime-published image artifact references
img.ok                    # True on success
```

Mode (`t2i` / `i2i`) and execution (`cloud` / `local`) are inferred when
possible (SD-002).  Pass them explicitly to override:

```python
# Image-to-image (image_ref triggers i2i inference); input files must be
# runtime-owned references or an admitted materialization.
img = astrid.generate.image(
    model="flux-dev",
    image_ref="./sketch.png",
    prompt="turn this into a watercolor painting",
    strength=0.7,
)

# Explicit mode + execution
img = astrid.generate.image(
    model="flux-dev",
    mode="t2i",
    execution="cloud",
    prompt="cyberpunk city",
    seed=42,
)
```

`execution` is the sole facade backend parameter.  The retired `backend`
spelling is rejected before invocation admission; an unavailable pair (for
example `model="flux-schnell", execution="local"`) fails with the valid
backend list and creates no run.

> **Note:** `edit`, `inpaint`, `outpaint`, and `upscale` always require an
> explicit `mode` argument — they cannot be inferred from inputs (SD-002).

### Video

```python
# Text-to-video (cloud)
clip = sdk.invoke(
    "generation.generate_video",
    kind="executor",
    project="demo",
    inputs={
        "model": "wan-2.2",
        "mode": "t2v",
        "execution": "cloud",
        "prompt": "a wave crashing on rocks",
    },
    wait=True,
)
clip.outputs["artifacts"]  # runtime-published video artifact references
```

### Audio

```python
# Audio currently has a cloud-only `music` route in the model catalog.
sound = sdk.invoke(
    "generation.generate_audio",
    kind="executor",
    project="demo",
    inputs={
        "model": "stable-audio-3-medium",
        "mode": "music",
        "execution": "cloud",
        "prompt": "a two-second gentle water splash",
        "duration": 2.0,
    },
    wait=True,
)
sound.ok
```

### Speech

`generation.generate_speech` creates one WAV file from exact text using Edge
TTS. Invoke it through the SDK so the WAV and provenance manifest are stored as
runtime-managed artifacts (pass your connected `AstridClient` as `client`):

```python
speech = sdk.invoke(
    "generation.generate_speech",
    kind="executor",
    project="demo",
    inputs={
        "text": "The train will arrive in five minutes.",
        "provider": "edge-tts",
        "voice": "en-US-ChristopherNeural",
        "rate": "+0%",
        "volume": "+0%",
        "pitch": "+0Hz",
    },
    client=client, wait=True,
)
speech.outputs["artifacts"]  # managed WAV and speech_manifest provenance references
```

All three typed facades run the same read-only preflight before dry-run or
live admission.  It validates the model → mode → execution matrix and required
inputs.  In particular, `mode="flf"` requires both `image_ref` and
`image_end_ref`; a missing end frame is a typed `CapabilityMissingInputError`
and creates no run.  Local routes also require the VibeComfy/ComfyUI runtime;
the preflight reports an install command and never falls back to cloud.

```python
# Image-to-video
clip = astrid.generate.video(
    model="wan-2.2",
    mode="i2v",
    image_ref="./start_frame.png",
    prompt="continue this scene",
)
```

```python
# First-last-frame interpolation
clip = astrid.generate.video(
    model="wan-2.2",
    mode="flf",
    image_ref=["./first.png", "./last.png"],
)
```

## Append a generation variant

Image, video, and audio facades share the Runtime publication controls. Pass a
generation source as `variant_of={"generation_id": ..., "variant_id": ...}`
and choose `primary="preserve"` (the default) or `primary="promote"`.

```python
edited = astrid.generate.image(
    model="flux-dev", mode="i2i", execution="cloud",
    image_ref="./source.png", prompt="make the light warmer", project="demo",
    variant_of={"generation_id": "gen-123", "variant_id": "var-original"},
    primary="promote",
)
```

Runtime resolves the source generation variant, admits its managed object as
an immutable input, and settles through generation-version CAS. `preserve`
keeps the current primary; `promote` atomically demotes the old primary and
makes the settled output primary. An ordinary call with no `variant_of` still
publishes a new generation through the Runtime generation-publication effect
named `publish_v1`.

New generation capabilities opt in through a manifest declaration; the
executor does not implement lineage or promotion:

```yaml
metadata:
  generation_publication:
    modality: image
    variant_of: {type: object}
    primary: {type: string, enum: [preserve, promote], default: preserve}
```

This generation lineage is separate from `client.media.relate(kind="variant_of")`,
which links arbitrary managed objects; `references.set_primary`, which chooses
canonical media for a reusable reference; and timeline replacement, which is
an explicit timeline document edit with its own head CAS.

### LoRA and extra params

All keyword arguments beyond the explicit parameter list pass through to the
executor unchanged:

```python
img = astrid.generate.image(
    model="flux-dev",
    prompt="a weathered fisherman, golden hour",
    loras="z-realgen-v2@1.1",      # registry id @ scale
    steps=28,
    guidance_scale=3.5,
)
```

### Output routing

Managed generation has one output route: an admitted runtime project. The
generic host writes to an attempt-local staging directory, publishes each
declared file to the runtime content-addressed store, and records the digest
and project relation in the runtime database. Staging paths are temporary.

```python
import astrid.sdk as sdk

result = sdk.invoke(
    "generation.generate_image",
    kind="executor",
    project="my-project",
    inputs={
        "model": "flux-schnell",
        "mode": "t2i",
        "execution": "cloud",
        "prompt": "test",
    },
    wait=True,
)
result.outputs["artifacts"]  # digest, media type, size, and name
```

Do not pass `out` with `project`, and do not invoke a generation `run.py`
directly. A custom filesystem destination is an explicit export operation
after runtime publication; it is not an executor default and must not create a
second unregistered output cache.

### Self-describing outputs

Every generated file carries its own provenance, so an image stays identifiable
after it is materialized from its runtime artifact reference:

- **PNG outputs** get the generation metadata embedded as `astrid_*` tEXt chunks
  (`astrid_prompt`, `astrid_model`, `astrid_model_actual` = the actual endpoint,
  `astrid_seed`, `astrid_request_id`, `astrid_created`, plus `astrid_loras` when
  used). Local (vibecomfy/ComfyUI) outputs additionally keep ComfyUI's own
  `prompt`/`workflow` chunks — both are preserved.
- The universal manifest is a receipt produced inside host staging; durable
  results are runtime artifact references and task/run evidence. Do not depend
  on a local run-directory sidecar after settlement.

Read embedded fields after materializing the runtime artifact with
`PIL.Image.open(path).text` or any PNG tEXt reader.

## Scratchpad convention

For experiments, use a selected runtime project and the SDK. This keeps
generation and registration together even when the script is launched from
another working directory:

```python
# my_experiment.py
import astrid.sdk as sdk

result = sdk.invoke(
    "generation.generate_image",
    kind="executor",
    project="my-project",
    inputs={
        "model": "flux-schnell",
        "mode": "t2i",
        "execution": "cloud",
        "prompt": "a glass teapot on basalt",
    },
    wait=True,
)
print(result.outputs["artifacts"])
```

```bash
python my_experiment.py
```

The runtime selection may be used instead of `project="my-project"`; the
working directory never determines ownership or output location.

## Plugin verbs

The `astrid.generate` namespace is extensible.  Packs can register additional
verbs under `extensions.generation.verbs` in their manifest.  Registered verbs
appear as `astrid.generate.<name>` and are resolved lazily — `import astrid`
does NOT eagerly load plugin modules.

```python
# After a third-party pack registers "animate":
import astrid
astrid.generate.animate(model="...", prompt="...")
```

The built-in `image` and `video` methods always take priority over plugin
verbs.

## Executors (direct access)

The pack's three executors remain available for direct use through the SDK
(`astrid.sdk.invoke(...)`) and for subprocess/cron automation.

| Executor | What it does |
|---|---|
| `generation.generate_audio` | Generate audio from text prompts via local or cloud backends; the current mode is `music`. |
| `generation.generate_speech` | Synthesize exact text as WAV with configurable Edge TTS voice settings. |
| `generation.generate_image` | Generate images from text prompts via local (vibecomfy), cloud (fal), or Codex backends. v2: model→mode→backend taxonomy with a required `mode` input. Supports t2i, i2i, and edit modes. |
| `generation.generate_video` | Generate videos from text prompts via local or cloud backends. v2: model→mode→backend with t2v, i2v, and flf (first-last-frame) modes. |
| `generation.generate_image_openai` | Generate image files with OpenAI GPT Image models from a prompt file. Requires `OPENAI_API_KEY`. |

> **⚠️  `generate_image_openai` is executor-only for this sprint.**
> The `astrid.generate.image()` facade explicitly rejects
> `execution="openai"` with a diagnostic pointing to
> `generation.generate_image_openai`.  Direct executor access via the SDK is
> the supported path for OpenAI image generation.

For detailed image generation guidance — mode selection (t2i/i2i/edit),
model decision tree, drop-with-warning behavior, and escape hatches — see
the executor-level skill at
`astrid/packs/generation/docs/generate-image.md`.

### Quick-start (SDK)

Pass every declared input as a snake_case entry in `inputs` (each is forwarded
to the executor's `run.py` as a `--kebab-case` flag). Bind a runtime project;
the host supplies its private staging `out` value.

```python
import astrid.sdk as sdk

# Image from text (cloud, fast)
result = sdk.invoke("generation.generate_image", inputs={
    "model": "flux-schnell", "mode": "t2i", "execution": "cloud",
    "prompt": "a serene mountain lake at dawn",
}, project="demo", wait=True)

# Image from text (local, open model)
result = sdk.invoke("generation.generate_image", inputs={
    "model": "z-image", "mode": "t2i", "execution": "local",
    "prompt": "a serene mountain lake at dawn",
}, project="demo", wait=True)

# Video from text (cloud)
result = sdk.invoke("generation.generate_video", inputs={
    "model": "wan-2.2", "mode": "t2v", "execution": "cloud",
    "prompt": "a wave crashing on rocks",
}, project="demo", wait=True)

# OpenAI image generation from a prompt file
result = sdk.invoke("generation.generate_image_openai", inputs={
    "prompts_file": "./prompts.jsonl",
}, project="demo", wait=True)
```

## When to use

- Use `astrid.generate.image(...)` for standard image generation from text
  prompts (the recommended primary entry point).
- Use `astrid.generate.video(...)` for video generation from text or image
  prompts.
- Use `generation.generate_image_openai` via direct executor access (the
  SDK) when you specifically need OpenAI GPT Image models and have a prompt
  file ready.

For LoRAs, IP-adapter, controlnet, custom samplers, or graph composition,
use the `vibecomfy` skill instead (escape hatch).

## Credentials

| Env var | Used by |
|---|---|
| `FAL_KEY` | generate_image (cloud), generate_video (cloud) |
| `OPENAI_API_KEY` | generate_image_openai |

Set these in the process environment, or in a `.env` / `.env.local` file at the
repo root (both are searched; `.env.local` is gitignored and wins over `.env`).
The resolver also walks `~/.env` and a few workspace locations — see
`astrid.core.util.secrets.candidate_env_files`.