Skip to Content
Docs

AGENTS.md

The app contract for coding agents. mixel new copies it into every app.

View as Markdown

This folder is one app: Python actions that Mixel runs on the person's computer. Use only the API on this page. Don't read the SDK source.

Loop

  1. Edit app.py. If what the code touches changes, update app.json › permissions.
  2. mixel check until it reports no errors. Each error ends with a fix: line; do that. --fix adds missing permissions and packages.
  3. mixel test runs tests/. mixel run ACTION --file song.wav --set NAME=VALUE runs one action on a real file.
  4. mixel dev opens the app in Mixel and reloads it on save. Never mixel publish, or change permissions, unless the person asks.

An action

import ox1audio as ox

@ox.action("Convert")
def convert(files: ox.Files("audio"), format: ox.Choice("wav", "flac", "mp3") = "wav"):
    made = []
    for i, path in enumerate(files):             # each path is a pathlib.Path
        ox.progress(i / len(files), path.name)   # Stop takes effect here
        out = ox.audio.convert(path, format)     # a new file, in a temporary folder
        made.append(ox.sound(out, title=path.stem))
    return made                                  # sounds are added to the Library
  • One decorated function per action. The parameters are the controls; Python defaults are their defaults. Mixel draws the form, checks the input, runs the action in the background, and shows progress and Stop.
  • Controls: ox.Files("audio" | "midi" | "any", multiple=True) gives list[Path] (one Path with multiple=False), ox.Folder() gives a Path, ox.Choice("a", "b") a str, ox.Text(placeholder=""), ox.Number(min, max, step), ox.Slider(min, max, step) (int if all three are ints), ox.Range(min, max, step) → (low, high), ox.Toggle. Number, Slider, Range take unit="dB". Plain str/int/float/bool work too. Every control takes label=. ox.Folder() | None = None makes one optional. Save beside an input with folder or ox.beside(path) (None for a read-only Open with file: copies go to the Library).
  • Return None, a str (shown to the person), a JSON-able dict or list (for a page), or ox.sound(path, title), ox.midi(...), ox.file(...), or a list of those. Metadata (bpm=120, key="A minor") follows the title.
  • ox.progress(fraction, label): fraction 0 to 1, or None when unknown. Call it often, also from library callbacks. When the person presses Stop it raises ox.Cancelled (a BaseException, so except Exception doesn't catch it): clean up in except ox.Cancelled: and re-raise. Every ox.sound/midi/file made so far is kept in the Library.
  • A problem the person can fix: raise ox.Problem("That file has no audio.", hint="Try a WAV or MP3."). Other exceptions show "The app ran into a problem"; details go to the log.
  • print goes to the log. Don't write a server, threads, or if __name__ == "__main__": Mixel runs the actions.

Testing

import unittest

import ox1audio as ox
import app

class ConvertTest(unittest.TestCase):
    def test_converts_to_wav(self):
        run = ox.test(app.convert, files=["tests/tone.wav"], format="wav")
        self.assertEqual(run["items"][0]["title"], "tone")  # what reached the Library

ox.test returns {"result", "items", "progress", "events"}: what a page gets, the Library items, each (fraction, label) and (name, body). stop_at=0.5 presses Stop halfway: it raises ox.Cancelled, whose .kept lists what was kept. ox.Problem is raised as is.

Helpers

NeedUse
File infoox.audio.probe(path) → .rate .channels .duration .codec
Another formatox.audio.convert(path, "wav" / "aiff" / "flac" / "mp3" / "aac" / "ogg" / "opus", folder=None, bits=None) → Path (keeps depth, tags, cover, loops; .note if it lowered an over)
Samples (numpy)ox.audio.read(path) → (samples, rate), float32 (frames, channels); ox.audio.write(samples, rate, "wav", folder=None, name="audio", bits=24) → Path. bits=32: float; 16 dithers. Over ±1 raises, with the fix.
A folder for new filesox.temp() → an empty folder, removed when the app closes
The app's own filesox.data.read / read_text / write / list / remove("presets/a.json") (needs data)

app.json

{
  "$schema": "https://0x1audio.com/schema/app/v1.json",
  "name": "my-converter",
  "displayName": "My Converter",
  "summary": "One file to many formats at once.",
  "version": "0.1.0",
  "sdk": "^0.3",
  "entry": "app.py",
  "license": "Apache-2.0",
  "permissions": {
    "files": "Open the songs you choose.",
    "library": "Save the converted copies to your Library."
  },
  "accent": "#20b4c4"
}
  • Permissions are binding: files (Files and Folder controls), library (returning sounds), library-read (a page lists it), data (ox.data), gpu (torch, onnxruntime), network as {"why": "…", "hosts": ["example.com"]}. Ask only for what the code uses. Each reason: one sentence (12–120 chars) saying why.
  • Optional: icon (a square .svg/.png in the app), window ({"width", "height", "minWidth", "minHeight"}), tags (up to 5), license (SPDX), page (below).
  • Raise version before each release. Never change name.

Dependencies

Python packages go in pyproject.toml › [project].dependencies, wheels only; Mixel installs only those (mixel check --fix adds what the code imports, then runs uv sync; commit uv.lock). numpy and scipy are fine. The SDK already has ffmpeg; don't add ffmpeg wrappers, pydub, or requests. Never commit models, songs or files over 5 MB.

A page (only when the drawn form is not enough)

mixel new --page adds one: Vite + React, built to dist/index.html. It uses @0x1audio/design-system and the same actions:

import { ox } from "@ox1audio/sdk/page"
import { ActionForm } from "@0x1audio/design-system"

export const App = () => <ActionForm action="convert" />   // the form Mixel draws; build around it
// your own controls: await ox.run("convert", { files, format }, { onProgress: (f, label) => {}, onEvent: (name, body) => {}, signal })

Page API: ox.run, ox.actions, ox.files.pick / fromDrop / read / reveal, ox.library.list / add / file / onChange, ox.data.*, ox.app.open. Errors are OxError with .code (cancelled after Stop, with .kept). ox.emit("partial", body) in Python reaches onEvent. Design-system components and tokens only, no hex colours. Mixel sets .dark on <html>: check both.

Words (what the person sees)

Say what it does for a musician: song, sound, file, folder, Library. Never: worker, manifest, SDK, ffmpeg. Sentence case. Buttons are verbs. No exclamation marks. Errors say what happened, then what to do.

Never

  • Touch files other than the ones the person chose, files next to them, ox.temp() and ox.data.
  • Use subprocess, os.system, eval/exec, raw sockets, or telemetry.
  • Edit AGENTS.md or dist/ by hand, or commit .venv/ or node_modules/.

On this page