
LasagnaStack¶
An AI pipeline that turns raw restaurant footage into an editable CapCut draft for a London foodie Instagram Reel account — seven stages, zero manual editing.
Stage-based pipeline¶
No agent framework — a shared state object flows through independently testable stages (ingest, analyse, direct, critique, enhance, render, post caption), similar in spirit to LangGraph. Adding, removing, or reordering a stage is a one-line change.
Skills¶
Customise tone and framing per-account with a Markdown skill file, or
generate and iteratively improve one automatically from a folder of
human-edited CapCut drafts via optimize-skill.
Chain-of-thought reasoning¶
Per-client control over reasoning token budget, qualitative effort level, and total output ceiling, supported identically across Gemini and OpenRouter models.
Human-in-the-loop¶
Every stage pauses for confirmation before handing off to the next, with
an --yes flag to skip prompts once you trust the output.
Prompt caching¶
Gemini responses are cached to disk by content hash, so re-runs after a tweak never re-bill or re-upload footage that hasn't changed.
CapCut Desktop, zero setup¶
Auto-detects CapCut on macOS, copies every clip (timeline and unused footage) into the draft folder, rewrites media paths, and registers the project so it's waiting on your home screen when you open the app.
For users...¶
Installation¶
Install via Homebrew:
Install via PyPI:
Authentication¶
You will need to provide your own API keys for the LLM APIs you use. The required API key depends on the value of LSNSTK_LLM_MODEL.
Gemini (e.g. gemini/gemini-2.5-flash)¶
Get a key at aistudio.google.com/apikey and set it as an environment variable:
OpenRouter (e.g. openrouter/deepseek/deepseek-v3.2)¶
Get a key at openrouter.ai/settings/keys and set these environment variables:
export LSNSTK_LLM_MODEL=openrouter/deepseek/deepseek-v3.2
export LSNSTK_LLM_OPENROUTER_API_KEY=your-key-here
NOTE: Stage 2 (analyse) uploads video to the Gemini Files API and always requires
LSNSTK_LLM_GEMINI_API_KEY, even when the other stages use an OpenRouter model.
How to add skills¶
Skills are used to customise the pipeline to your own social media account's styles and branding, or use pre-written skills from marketplaces to cater for different types of reel content.
A skill is a Markdown (.md) file injected into the direct, critique, and enhance stage prompts. Pass one to the make command via the --skill flag:
How to optimize a skill file¶
The optimize-skill command analyses a folder of human-edited CapCut drafts (your "trainset") and generates an improved skill file, or writes a new one from scratch if you do not have an existing skill yet.
To iteratively improve an existing skill file instead of starting from scratch:
trainset folder layout: each subdirectory inside TRAINSET_DIR must be a CapCut draft folder containing a draft_info.json file (i.e. the raw output from a previous lasagnastack make run, after you have hand-edited the draft in CapCut Desktop and exported the project). The pipeline reads each draft, reconstructs the cut list and style decisions, and uses them as training examples for the LLM.
The optimized skill file is written to OUTPUT_DIR/optimized_skill.md. Pass it to future make runs via --skill.
How to find skill files¶
Visit the Skills Marketplace to download and use pre-written skills for different types of reel content. Skills are organised in the following folder structure: [CATEGORY] - [AUTHOR_NAME SKILL_NAME (VERSION_TAG)]
How to use the CLI¶
lasagnastack make
Turn raw clips into a CapCut draft.
usage: lasagnastack make [-h] --out OUTPUT_DIR [--skill SKILL_FILE] [--yes]
[--critique-max-retries N] [--ingest-max-workers N]
[--analyse-max-workers N] INPUT_DIR
positional arguments:
INPUT_DIR Folder containing clips and brief .txt
options:
--out OUTPUT_DIR Destination for the CapCut draft and working files
--skill SKILL_FILE Path to Markdown skill file injected into the direct,
critique, and enhance prompt templates (optional)
--yes, -y Auto-confirm all stage prompts
--critique-max-retries N Maximum # of critique loop retries (default: 2)
--ingest-max-workers N Maximum # of parallel worker processes for `ingest` stage (default: 2)
--analyse-max-workers N Maximum # of concurrent LLM calls for `analyse` stage (default: 4)
lasagnastack optimize-skill
Generate or improve a skill file from human-edited drafts.
usage: lasagnastack optimize-skill [-h] --out OUTPUT_DIR [--skill SKILL_FILE]
[--yes] [--critique-max-retries N]
TRAINSET_DIR
positional arguments:
TRAINSET_DIR Folder of CapCut draft subfolders (each must contain draft_info.json)
options:
--out OUTPUT_DIR Destination for optimized_skill.md and working files
--skill SKILL_FILE Existing skill file to improve (omit to generate from scratch)
--yes, -y Auto-confirm all stage prompts
--critique-max-retries N Maximum # of critique loop retries (default: 2)
Opening the draft in CapCut Desktop (macOS)¶
If CapCut Desktop is installed, the pipeline automatically:
- Detects
~/Movies/CapCut/User Data/ - Copies all
.mp4/.movfiles from your input folder into the CapCut draft folder — including clips not used on the timeline — so they are immediately available in CapCut's import panel - Rewrites the timeline clip paths in
draft_info.jsonto point to the copied files - Registers the draft in
root_meta_info.jsonso it appears on the CapCut home screen straight away
Open CapCut Desktop after the pipeline finishes — the draft will appear on the home screen under your local projects with all media already linked. Drafts are named LasagnaStack - Reel Name and use that same string as the folder name so they are easy to identify among existing projects.
If CapCut is not installed, the draft is written to <output_dir>/draft/LasagnaStack - {reel_name}/ and you can copy it manually.
This has been tested with CapCut Desktop 8.5.0 on macOS Sequoia 15.6.1. There may be issues with older versions or other operating systems.
For developers...¶
Get started with development¶
- Clone the repository.
- Verify that you have a compatible Python version installed on your machine.
-
Install uv (used as the package manager for this project).
-
Install the development dependencies.
Set up environment¶
Copy .env.sample to .env and fill in your values (see Authentication above for the available variables):
.env is gitignored. Values set in the shell environment take precedence over .env.
Run the pipeline¶
Prepare an input folder containing your MP4/MOV clips and exactly one .txt brief file, then run:
The pipeline pauses for confirmation between each stage. To skip all prompts:
Configuration¶
| Parameter | How to set | Default |
|---|---|---|
| LLM model | LSNSTK_LLM_MODEL env. var. |
gemini/gemini-2.5-flash |
| Gemini API key | LSNSTK_LLM_GEMINI_API_KEY env. var. (required for gemini/ models and Stage 2) |
— |
| OpenRouter API key | LSNSTK_LLM_OPENROUTER_API_KEY env. var. (required for openrouter/ models) |
— |
| Path to skill file | --skill CLI flag |
— |
critique stage maximum # of retries |
--critique-max-retries CLI flag |
2 |
ingest stage maximum # of worker processes |
--ingest-max-workers CLI flag |
2 |
analyse stage maximum # of concurrent LLM calls |
--analyse-max-workers CLI flag |
4 |
| Reasoning token budget | reasoning_max_tokens arg on LLMClient / make_client() |
4000 |
| Reasoning effort level | reasoning_effort arg on LLMClient / make_client() (OpenRouter only) |
None |
| Total output token ceiling | total_max_tokens arg on LLMClient / make_client() (OpenRouter only) |
None |
| MLflow tracking server | MLFLOW_TRACKING_URI env. var. |
sqlite:///$HOME/.lasagnastack/mlflow.db |
| MLflow experiment name | MLFLOW_EXPERIMENT_NAME env. var. |
lasagnastack |
Architecture¶
Two concrete pipelines share the same Stage / Pipeline / PipelineState base classes in base.py:
ReelPipeline(reel_pipeline.py) — the seven-stage clips-to-draft pipeline described above. Its state isReelPipelineState.OptimizeSkillPipeline(optimize_skill_pipeline.py) — a three-stage pipeline that reads a trainset of human-edited drafts and writes an improved skill file. Its state isOptimizeSkillPipelineState.
Adding, removing, or reordering stages requires only editing the stages list on the relevant pipeline class. See ARCHITECTURE.md for annotated diagrams covering the pipeline data flow, the Stage 4 critique loop, the Stage 6 render + CapCut export, and the extensibility model.
Get started with Jupyter notebooks¶
- Once the above setup is complete, set up a Python kernel.
- Refer to the following common commands.
- Start the Jupyter server.
Track LLM costs with MLflow¶
Every pipeline run is automatically traced with MLflow. Each LLM API call is recorded as a span (prompt, response, token counts, latency, and USD cost). GeminiClient derives cost from a built-in pricing table; OpenRouterClient reads cost directly from the usage.cost field in the API response. Session-level totals are written to the run when the pipeline finishes.
Tracking works out of the box with no setup — runs are written to ~/.lasagnastack/mlflow.db automatically.
To browse past runs, start the MLflow server in a separate terminal:
mlflow server \
--backend-store-uri sqlite:///$HOME/.lasagnastack/mlflow.db \
--host 127.0.0.1 --port 5001
macOS note: port 5000 is reserved by AirPlay Receiver. Use 5001 or higher.
Open http://localhost:5001 in your browser. In Experiments -> lasagnastack -> Traces, each run has three span levels: the top-level pipeline span (ReelPipeline.run), a per-stage span (e.g. AnalyseStage.run), and individual LLM call spans (e.g. GeminiClient._call_api or OpenRouterClient._call_api) nested inside.
Runs are named lasagnastack-{brief_stem}-{4-char-id} and tagged with the model, reel name, and critique_max_retries.
To use a remote MLflow server instead, set MLFLOW_TRACKING_URI as an environment variable:
Note that runs already stored in ~/.lasagnastack/mlflow.db will not appear on a remote server — the two stores are independent.