A workflow engine for VS Code developers — build, run, and automate workflows with Python nodes.
Project description
Choola
A workflow automation engine that bridges the determinism of Python with the power of agentic AI programming — built so developers who are just getting started can wire together LLM-powered pipelines without giving up predictability or control.
The idea is simple: you build workflows from self-contained Python nodes, connect them in a visual editor, and let AI agents (Claude, Gemini) do the heavy lifting inside those nodes — while the overall flow remains explicit, inspectable, and reproducible.
⚠️ Early-Stage Project — Not for Production
Choola is under active development. Core node classes, the payload contract, and internal APIs may change drastically between versions without backward compatibility. We do not recommend using Choola in production systems at this time. It is intended as an exploration platform and learning tool.
What It's For
Choola was built for beginner developers who want to:
- Build multi-step automations that include AI without writing orchestration from scratch
- See exactly what data flows through each step (no black boxes)
- Mix deterministic Python logic with LLM calls in a single workflow
- Use a visual editor to prototype, then inspect the underlying code to learn
It is not trying to be n8n or Airflow. It's trying to be the simplest possible on-ramp to agentic programming for someone who knows a bit of Python.
How It Works
A workflow is a folder containing:
topology.json— a DAG (directed acyclic graph) of nodes and the edges between themnodes/*.py— one Python file per node
Each node receives a payload dict, does one thing, and returns the (possibly modified) payload to the next node. The engine topologically sorts the nodes and executes them in order.
workflows/my_workflow/
├── topology.json
└── nodes/
├── __init__.py
├── fetch_data.py
├── summarize.py
└── send_email.py
Prerequisites
- Python 3.10+
- Node.js 18+ and npm
Installation
# Clone the repository
git clone https://github.com/igrosny/choola.git
cd choola
# Create a virtual environment and install the package
python3 -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install -e .
# Build the frontend
cd frontend
npm install
npm run build
cd ..
Quick Start
# Initialize a project (creates choola.db)
choola init
# Start the server
choola start
# → http://localhost:5000
Open http://localhost:5000 in your browser. You'll see the visual workflow editor where you can:
- Create new workflows
- Add nodes and configure them
- Connect nodes by dragging edges
- Run workflows and watch execution stream live
CLI usage
choola init # Set up a new project
choola start # Start the server (localhost:5000)
choola start --host 0.0.0.0 --port 8080 # Bind to all interfaces
choola create <workflow_name> # Scaffold a new workflow
choola list # List all workflows
choola run <workflow_name> --payload '{"key": "value"}' # Run headlessly
choola nodes # List available core node types
Writing a Node
Every node is a single .py file in workflows/<name>/nodes/. The @choola-node docstring makes it discoverable — do not remove it.
"""
@choola-node: MyNodeName
@category: processing
@description: Does one specific thing to the payload.
@input-payload:
- some_key (str): What this node expects
@output-payload:
- some_key (str): Same or transformed
- new_key (int): Something this node adds
@config-fields:
- threshold (int, default=10): Controls the threshold
@example-input: {"some_key": "hello"}
@example-output: {"some_key": "hello", "new_key": 42}
@side-effects: none
@errors: Raises ValueError if some_key is missing
"""
from typing import Any
from choola.core.base_node import BaseNode
class MyNodeName(BaseNode):
name = "My Node Name"
category = "processing"
description = "Does one specific thing to the payload."
fields = [
{"name": "threshold", "type": "number", "default": 10},
]
async def execute(self, payload: dict[str, Any], context: dict[str, Any]) -> dict[str, Any]:
# Your logic here
return payload
Node rules
- Self-contained — all logic in one file, no cross-node imports
- JSON in, JSON out — communicate only through the
payloaddict - Docstring required — the
@choola-nodemarkers make nodes grep-discoverable
Node categories
| Category | Use for |
|---|---|
input |
Entry points — webhooks, forms, file reads |
processing |
Transformation, enrichment, LLM calls |
routing |
Conditional branching, filtering |
output |
Sending results, notifications, writes |
validation |
Data checks, guards |
integration |
External API calls |
Persisting state across runs
value = await self.get_global("my_key")
await self.set_global("my_key", "new_value")
Core Nodes
Core nodes are built into the choola package. Never reference them directly in topology.json — instead, create a thin wrapper class in your workflow's nodes/ directory that extends the core node.
WebhookTrigger
Starts a workflow when an HTTP request hits a registered endpoint.
from choola.core.nodes.webhook_trigger import WebhookTrigger
class MyWebhook(WebhookTrigger):
pass
| Field | Type | Description |
|---|---|---|
path |
str | URL path, e.g. /hooks/my-endpoint |
method |
select | GET / POST / PUT / DELETE (default: POST) |
response_mode |
select | immediate (returns 202 at once) or after_workflow (waits for result) |
Output payload: { method, headers, query, body }
FormTrigger
Serves an HTML form at a URL; submission triggers the workflow.
from choola.core.nodes.form_trigger import FormTrigger
class MyForm(FormTrigger):
pass
| Field | Type | Description |
|---|---|---|
path |
str | URL path, e.g. /forms/contact |
form_title |
str | Heading above the form |
form_description |
str | Description below the title |
form_fields |
json | Array of field definitions (see below) |
response_mode |
select | after_workflow (returns JSON) or redirect (shows thank-you page) |
submit_label |
str | Button label (default: Submit) |
Each item in form_fields:
{
"label": "Email",
"field_name": "email",
"field_type": "email",
"required": true,
"placeholder": "you@example.com"
}
Supported field_type values: text, email, number, password, textarea, dropdown, date, checkbox
Output payload: { form_data: { field_name: value, ... }, submitted_at: "<ISO timestamp>" }
LLM
Sends a prompt to an LLM and returns the response. Supports Claude and Gemini.
from choola.core.nodes.llm import LLM
class Summarize(LLM):
pass
| Field | Type | Description |
|---|---|---|
credential_name |
str | Name of the stored credential to use |
provider |
select | claude or gemini |
model |
str | Model ID (defaults: claude-sonnet-4-20250514 / gemini-2.0-flash) |
prompt |
textarea | Prompt template; use {key} to interpolate payload values |
system_prompt |
textarea | Optional system prompt |
max_tokens |
number | Default: 1024 |
temperature |
number | Default: 1.0 |
Output payload: adds llm_response, llm_model, llm_provider to the existing payload
Credentials
API keys and OAuth tokens are stored encrypted in the SQLite database and never hardcoded.
Managing credentials
# Via the UI: Settings > Credentials
# Via API:
GET /api/credentials # List all (values masked)
POST /api/credentials # Create/update: { name, provider, value }
DELETE /api/credentials/<name> # Delete
Using credentials in a node
cred = await self.get_credential("my-anthropic-key")
api_key = cred["value"]
API Reference
| Method | Path | Description |
|---|---|---|
| GET | /api/nodes |
List all registered node types |
| GET | /api/workflows |
List all workflows |
| POST | /api/workflows |
Create a new workflow |
| GET | /api/workflows/<name>/topology |
Get workflow topology |
| PUT | /api/workflows/<name>/topology |
Update workflow topology |
| POST | /api/workflows/<name>/run |
Execute a workflow |
| GET | /api/workflows/<name>/stream/<run_id> |
SSE stream for live run status |
Project Structure
choola/ # The pip-installable package
├── cli.py # CLI entry point
├── server.py # Flask API + serves the React frontend
├── database.py # SQLite store (globals + run logs + credentials)
├── core/
│ ├── base_node.py # Abstract base class for all nodes
│ └── nodes/ # Built-in core nodes
│ ├── webhook_trigger.py
│ ├── form_trigger.py
│ └── llm.py
└── static/ # Built frontend (generated — not in source)
frontend/ # React + React Flow visual editor (Vite)
workflows/ # Your workflows live here (created by choola create)
└── <workflow_name>/
├── topology.json
└── nodes/
├── __init__.py
└── <node>.py
Using with Claude Code
If you use Claude Code, this project ships with slash commands that let you build workflows by describing what you want:
/choola— describe a workflow and Claude scaffolds the full thing (nodes + topology)/node— add a single node to an existing workflow
License
Apache 2.0 — see LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file choola-0.3.0.tar.gz.
File metadata
- Download URL: choola-0.3.0.tar.gz
- Upload date:
- Size: 593.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8933a43b5d2107d19fafb90adb1491f0b440f0ceb935825f358cec726a74826f
|
|
| MD5 |
a1f3bde0534b87037b5759968b9a3004
|
|
| BLAKE2b-256 |
bbb57eae572770367ed7ffafdc7a110e912db9893f56c05c7d0deb9c47b69786
|
File details
Details for the file choola-0.3.0-py3-none-any.whl.
File metadata
- Download URL: choola-0.3.0-py3-none-any.whl
- Upload date:
- Size: 605.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e46daaa2f911e1d1be6e0b1d7cf9455c32e726d84b0a312c0f23b9c4f151510c
|
|
| MD5 |
3ad6d4b0c31944eca2ca00e2197a44c5
|
|
| BLAKE2b-256 |
896473027354f9de8a31cd5174c84700838439b77179b8bb0042d9c9d6338a95
|