Skip to content

Decorators Reference

Complete reference for @workflow and @data_provider decorators

Decorators are the foundation of Bifrost’s discovery system. They register workflows and data providers automatically when your modules are imported.

Registers an async function as a discoverable workflow. All parameters are optional - the decorator infers name and description from your function.

from bifrost import workflow
@workflow
async def send_greeting(name: str, count: int = 1):
"""Send a greeting message to the user."""
return {"message": f"Hello {name}!" * count}

This automatically:

  • Sets name to "send_greeting" (from function name)
  • Sets description to "Send a greeting message to the user." (from docstring)
  • Extracts parameters from the function signature
  • Generates a persistent id on first discovery
@workflow(
name: str | None = None, # Auto-derived from function name
description: str | None = None, # Auto-derived from docstring
category: str = "General",
tags: list[str] | None = None,
execution_mode: Literal["sync", "async"] | None = None, # Auto-selected
timeout_seconds: int = 1800,
retry_policy: dict[str, Any] | None = None,
schedule: str | None = None,
endpoint_enabled: bool = False,
allowed_methods: list[str] | None = None,
disable_global_key: bool = False,
public_endpoint: bool = False,
is_tool: bool = False,
tool_description: str | None = None,
time_saved: int = 0,
value: float = 0.0,
)
ParameterTypeDefaultDescription
namestrNoneWorkflow identifier. Auto-derived from function name if not provided
descriptionstrNoneUser-facing description. Auto-derived from docstring if not provided
categorystr”General”Category for grouping (e.g., “User Management”)
tagslistNoneTags for filtering and search
execution_modestrNone”sync” or “async”. Auto-selects based on endpoint_enabled
timeout_secondsint1800Maximum execution time (30 min default, 2 hour max)
retry_policydictNoneRetry configuration
schedulestrNoneCron schedule for scheduled execution
endpoint_enabledboolFalseExpose as HTTP endpoint at /api/endpoints/{name}
allowed_methodslistNoneHTTP methods allowed (GET, POST, etc.)
disable_global_keyboolFalseRequire workflow-specific API key
public_endpointboolFalseSkip authentication for webhooks
is_toolboolFalseMake callable by AI agents
tool_descriptionstrNoneDescription for AI agent tool use
time_savedint0Estimated minutes saved per execution (for ROI tracking)
valuefloat0.0Custom value metric per execution

Each workflow gets a persistent UUID generated by the discovery system. This ID is written back to your source file on first discovery:

@workflow(
id="550e8400-e29b-41d4-a716-446655440000", # Auto-generated
# ... other options
)

The ID ensures workflows maintain identity across renames and deployments.

If execution_mode is not specified:

  • endpoint_enabled=True → defaults to "sync" (webhooks need immediate response)
  • endpoint_enabled=False → defaults to "async" (background tasks scale better)
@workflow
async def create_user(email: str, name: str, department: str = "General"):
"""Create a new user in the system."""
user = await create_in_db(email, name, department)
return {"user_id": user.id}

Parameters are automatically extracted from your function signature. No @param decorators needed for basic use cases.

  1. Type hints determine field types:

    • str"string"
    • int"int"
    • float"float"
    • bool"bool"
    • list"list"
    • dict"json"
    • str | None"string" (optional)
  2. Default values determine optionality:

    • No default → required
    • Has default → optional with that default
    • | None union → optional
  3. Labels auto-generated from names:

    • user_name → “User Name”
    • firstName → “First Name”
@workflow
async def onboard_user(
first_name: str, # Required string, label: "First Name"
last_name: str, # Required string, label: "Last Name"
email: str, # Required string, label: "Email"
license: str = "E3", # Optional string, default: "E3"
active: bool = True, # Optional bool, default: True
tags: list | None = None # Optional list
):
"""Onboard a new user."""
return {"success": True}

Registers a function that provides dynamic options for form fields.

@data_provider(
name: str | None = None,
description: str | None = None
)
ParameterTypeDefaultDescription
namestrNoneUnique provider identifier (auto-derived from function name if not provided)
descriptionstrNoneDescription of what data it provides (auto-derived from docstring if not provided)
from bifrost import data_provider
@data_provider(
name="get_departments",
description="List available departments"
)
async def get_departments():
"""List available departments for selection."""
return [
{"label": "Engineering", "value": "eng"},
{"label": "Sales", "value": "sales"},
{"label": "Support", "value": "support"}
]

Workflows return any JSON-serializable value:

@workflow
async def example():
"""Example workflow."""
return {
"success": True,
"message": "Operation completed",
"data": [1, 2, 3]
}

✅ DO:

  • Use descriptive function names (they become the workflow name)
  • Write clear docstrings (they become the description)
  • Use type hints for all parameters
  • Set appropriate timeouts for long operations
  • Cache data provider results appropriately

❌ DON’T:

  • Use generic names like “workflow1”
  • Forget docstrings (empty description in UI)
  • Ignore type hints (parameters won’t be extracted)
  • Use sync mode for long-running tasks
  • Cache dynamic data too long
from bifrost import workflow, data_provider, context
import logging
logger = logging.getLogger(__name__)
# Data provider for departments
@data_provider(
name="get_departments",
description="Get available departments"
)
async def get_departments():
"""Fetch departments from system."""
org_id = context.org_id # Access context via proxy
return [
{"label": "Engineering", "value": "eng"},
{"label": "Sales", "value": "sales"},
{"label": "Support", "value": "support"}
]
# Workflow using inferred parameters
@workflow(category="User Management", tags=["onboarding"])
async def create_user(
email: str,
name: str,
department: str = "General",
send_welcome: bool = True
):
"""Create a new user in the system."""
logger.info(f"Creating user: {email}", extra={
"org_id": context.org_id,
"user_id": context.user_id
})
user_id = await create_user_in_system(email, name, department)
if send_welcome:
await send_welcome_email(email, name)
logger.info(f"User created: {user_id}")
return {"user_id": user_id, "email": email}