Write and publish a function
This guide takes a function from a notebook cell to a published package. You will:
- try the function in a notebook
- package it, with
pyproject.tomland abaler.tomlmanifest - check the manifest against the package
- run it in a container of your own, either a notebook or a scheduled worker
- tag a release that anyone can
pip install - list it in the function index
You need a running notebook first: see Notebooks.
Security. A function package is ordinary Python, and whoever installs it runs it with their own API key. Install packages in your own notebook or container only, never on the shared Baler host. Baler never runs function code; it only answers the Haystack API calls your code makes.
1. Try it in a notebook
Start in a notebook cell. A Baler function is a plain Python function that takes the client as its first argument:
Code
baler_fn.list() doesn't show daily_max yet, because it only lists functions from installed
packages. Packaging it fixes that.
2. Make it a package
A function package is a normal pip-installable project with two additions:
- entry points in the
baler.functionsgroup, inpyproject.toml - a
baler.tomlmanifest, which the docs site reads
The quickest start is to copy the template from a Baler checkout. It's the baler-sample
package that ships in the notebook image:
Code
Then rename baler-sample/baler_sample throughout. The rest of this section builds the same
shape from an empty directory, so you can see each piece.
Code
src/my_fns/__init__.py can be empty. src/my_fns/functions.py holds the function from step 1:
Code
pyproject.toml:
Code
Each entry point is either name = "module:function" for one function, or name = "module" for
every decorated function in that module.
baler_fn is deliberately not listed in dependencies. The Baler notebook image provides it,
the same way a SkySpark pod relies on the runtime it's installed into. To test outside the image,
install Baler itself: pip install --no-deps "baler @ git+https://github.com/jacobnash/Baler" && pip install requests pandas.
pip warns that Baler's server dependencies aren't installed. baler_fn doesn't need them, so you
can ignore that warning.
baler.toml:
Code
The docs site never imports your code. Everything the index shows comes from baler.toml, so the
[[functions]] entries must match the code. Step 3 checks that.
baler.toml reference
| key | required | rules |
|---|---|---|
manifest_version | yes | 1 |
package.name | yes | your pip distribution name, the same as [project] name. Letters, digits, ., _, -. |
package.version | yes | the same as [project] version, e.g. "0.1.0" |
package.description | yes | up to 500 characters |
package.author | yes | up to 200 characters |
package.repository | yes | an https:// git URL that pip can clone |
package.tags | no | up to 20, each made of lowercase letters, digits and - |
package.license | no | e.g. "MIT" |
package.docs | no | an https:// URL |
package.date | no | a TOML date, unquoted: 2026-11-07 |
package.ref | no | the git tag or branch the install line pins. Default v<version>. |
package.subdirectory | no | the package's path inside the repository, if it isn't the root |
entry-points."baler.functions" | yes | the same entries as pyproject.toml |
[[functions]] | yes, at least one | name (a Python identifier), signature (as inspect.signature prints it), summary (the docstring's first line), tags (optional) |
No text field may contain backticks or control characters. An unknown key is an error, which
catches typos. Every error names the file and the field, for example
baler.toml: package.repository: 'http://...' is not valid (an https:// git URL pip can clone).
The install line built from the manifest is
pip install "git+<repository>@<ref>#subdirectory=<subdirectory>".
3. Check the manifest
Wherever the package is installed (step 4's container is the easy place), run:
Code
check compares the manifest with the installed package: version, entry points, function names,
signatures, docstring summaries and tags. It exits with status 1 and lists every difference. Run it
again whenever you change a function.
4. Run it in its own container
Your project gets its own image, built from the Baler notebook image with your package installed. The same image can run a notebook or a scheduled worker.
Dockerfile:
Code
compose.my-fns.yml:
Code
Start it from the Baler checkout. Compose resolves relative paths (../my-fns) against the
first -f file's directory, so they're relative to the checkout, not to your project:
Code
Naming the service builds only your image, plus the base notebook image it starts from, and leaves
the rest of the stack alone. Your notebook mounts the same notebooks volume as the stock one, so
both see the same files. To keep them apart, give your service a volume of its own.
Open http://localhost:8889/lab?token=<your BALER_JUPYTER_TOKEN>. Your functions are listed there:
Code
Then check the manifest inside the container:
Code
After you change your code, run the same up -d --build my_fns_notebook command again.
A scheduled worker
To run a function on a timer instead of interactively, add a second service to your compose file
that uses the same image with a command:, under its own profile. The template's
compose.baler-sample.yml and src/baler_sample/worker.py show the whole pattern:
Code
my_fns/worker.py is a loop: client = baler_fn.connect(), call your function, time.sleep(...).
Start it with --profile my-fns-worker up -d --build my_fns_worker and read its output with
docker compose ... logs -f my_fns_worker. A worker that writes results back with
client.his_write needs an operator key.
5. Release it
Push the project to a git host, then tag the version that matches baler.toml:
Code
Anyone can now install it in their own notebook. In a notebook cell:
Code
Packages installed with %pip last until the container is recreated. Step 4's image is the way to
keep them.
6. List it on the docs site
Open a PR against Baler that adds an entry pointing to your manifest to docs-site/function-index.toml. See
Publishing a package. Once it's merged, the package and its functions appear in
the function index, with the pip install line from step 5.
The sample package
baler-sample (templates/function-project in the Baler repo) is installed in the notebook image
and listed in the index. Its functions make a good first test of a new setup:
Code
To run the sample's own containers, use its compose file. The worker needs
BALER_SAMPLE_POINT in .env:
Code

