Developer docs Guides
Background services
Scripts that react to project-status changes: the on_change convention, trigger scoping, and how runs are recorded.
What a service is
A background service binds a script (Python or TypeScript) to a workspace. Instead of a schedule or a manual trigger, it reacts: whenever a reactive store in the project status changes, the platform runs the service’s on_change handler with what changed.
The on_change convention
Define on_change(mutation, state) in the service script. It runs in the workspace sandbox with the same injected module as task scripts — client, reporter, create_task, and friends — plus the change context:
def on_change(mutation, state) -> None:
# mutation: the change that fired this run (includes its events)
# state: snapshot of the store after the change
if state.get("stage") == "deploying":
reporter.set_phase("Notifying")
client.post("/projects/$PROJECT_ID/tasks", {
"identifier": "notifier",
"params": {"channel": state["channel"]},
})Trigger scoping
| Setting | Behaviour |
|---|---|
| Watched stores (empty = all) | Narrow which reactive stores fire this service |
| Enabled | Stop and resume reaction without deleting the definition |
| Workspace | Which workspace the handler runs against |
| Custom image | Optional environment for the handler |
How runs are recorded
Every trigger is a first-class run: the platform records a real task instance (kept off the kanban) with its phase, logs, and result — open the service’s run history to audit what fired and why. Inside the handler, BG_SERVICE_ID and ALLYBUILD_WORKSPACE_ID identify the service context.
Services in templates
Service definitions are one of the four configuration entities, so they ship with publish and fork like meta-tasks, agents, and crons — a forked team reacts out of the box.