Workflow hooks
A great app the tenant agent never invokes is invisible. Workflow hooks let your manifest declare, in plain language, when the orchestrator should reach for your tools — same mechanism the platform’s first-party apps (Office Assistant, Sachverständiger) use internally, now opened up to partner manifests.
This is the missing piece that turns “I shipped a tool with a good description” into “the agent reliably picks my tool when the user asks for X.”
How it works
Section titled “How it works”┌─ User: "Wie buche ich diese Rechnung?" ──────────────────────┐│ ││ 1. Orchestrator reads system prompt, which includes: ││ - Custom agent catalog (your app's specialist agent) ││ - Workflow routing reference (your app's workflows) ││ ││ 2. Orchestrator matches user's request against: ││ - intent: "User forwards an invoice and asks how to ││ book it" ││ - triggers: ["wie buche ich diese rechnung", ...] ││ ││ 3. Orchestrator delegates to your specialist agent. ││ ││ 4. Specialist agent reads its own system prompt, which now ││ includes the workflow's flow + specifics: ││ - flow: ["classify_invoice(...)", "email_send(...)"] ││ - specifics: "Always cite category in plain German..." ││ ││ 5. Agent executes the flow. │└──────────────────────────────────────────────────────────────┘Two separate audiences read your workflow:
- The orchestrator sees
intent,triggers,non_triggers— routing-only. - The specialist agent (your app’s executor) sees
flow,specifics— execution-only.
Schema
Section titled “Schema”A minimal workflow:
workflows: - id: classify_inbound_invoice name: Classify inbound invoice intent: User forwards or pastes an invoice and asks how to book it flow: - "classify_invoice(text=invoice_text) → category" - "email_send(to=user.email, body='Buchungs-Empfehlung: ...')"Add routing cues, tool requirements, and channel restrictions as your app matures:
workflows: - id: classify_inbound_invoice name: Classify inbound invoice intent: User forwards or pastes an invoice and asks how to book it triggers: - "Inbound email contains invoice attachment or invoice text" - "User asks 'wie buche ich diese rechnung'" non_triggers: - "Just a request for general tax advice with no document" flow: - "classify_invoice(text=invoice_text) → category" - "If category == 'unclear': ask follow-up" - "Else: email_send(to=user.email, body='Buchungs-Empfehlung: ' + category)" specifics: | Keep responses under 100 words. Always cite the chosen category in plain German. If the invoice has no clear date, ask for it before booking. tools_required: [classify_invoice, email_send] channels: ["*"]Full field reference: Manifest → workflows.
Tool gating with tools_required
Section titled “Tool gating with tools_required”The platform filters workflows out of the prompt if any declared
tools_required isn’t available to the tenant — scope not granted,
integration not connected, or the tool simply doesn’t exist on the
platform.
This avoids the failure mode where the agent confidently follows
your workflow, hits decision='scope_denied' partway through, and
leaves the user with a half-finished task. List every tool your
flow uses (platform tools and your own app.tool(...) declarations)
in tools_required and the orchestrator only sees workflows that’ll
actually work for the current tenant.
tools_required: - email_send # platform tool - calendar_create_event # platform tool - my_app_classify # tool declared in this manifest's tools[]Workflows without tools_required (or with an empty list) pass
through unchanged — first-party apps work this way today and continue
to.
Channel restrictions
Section titled “Channel restrictions”Some workflows only make sense on specific channels. A voice-driven flow doesn’t fit chat, a long-form drafting flow doesn’t fit WhatsApp’s character limits.
channels: ["whatsapp", "telegram"] # only thesechannels: ["*"] # all (default)Workflows whose channels excludes the current channel never reach
the agent’s prompt for that conversation. Default ["*"] means
“available everywhere.”
Tenant overrides
Section titled “Tenant overrides”Tenants can override individual workflow fields per-install via the
platform’s settings UI. The merge is keyed on id:
- Override matches a default
id→ deep-merge, override fields win. Common case: tenant rewritesspecificsto match their brand voice, or adds an extra step toflow. - Override
idhas no default match → it’s a tenant-defined workflow added on top of yours. - Defaults without an override → pass through unchanged.
You ship sensible defaults; the tenant tweaks what they need. Your upgrade doesn’t clobber their tweaks.
Versioning
Section titled “Versioning”Workflows are part of the manifest, so they version with your app. A tenant on v0.1.0 keeps loading v0.1.0’s workflows until they explicitly upgrade — same semantics as scopes, tools, and bundles.
If a workflow becomes critical to your app’s behaviour, document the version it was introduced in your README. The settings UI shows both default and override side-by-side so tenants can see what changed across upgrades.
Common patterns
Section titled “Common patterns””When the user asks X, do my flow"
Section titled “”When the user asks X, do my flow"”- id: send_recap name: Send recap email intent: User asks for a daily recap to be emailed triggers: ["mail me my open items", "send me a recap"] flow: - "list_open_items()" - "email_send(to=user.email, subject='Daily recap', body=summary)" tools_required: [email_send]"On schedule, do this even without a user request”
Section titled “"On schedule, do this even without a user request””Schedule workflows live in lifecycle.schedules[], not in
workflows[]. workflows[] is for orchestrator routing on
user-driven requests. See Lifecycle hooks for
schedule-driven flows.
”Tool X requires preconditions; check first”
Section titled “”Tool X requires preconditions; check first””- id: book_meeting name: Book meeting from request intent: User asks to schedule a meeting triggers: ["schedule a meeting", "book a call"] flow: - "calendar_list_events(from=today, to=today+7) → busy" - "Find first free 30-min slot" - "calendar_create_event(start=slot, end=slot+30min, attendees=[...])" specifics: | Default duration is 30 minutes. If the user doesn't specify attendees, ask before scheduling. Avoid 12:00–13:00 for German tenants (lunch). tools_required: [calendar_list_events, calendar_create_event]What workflow hooks do NOT do
Section titled “What workflow hooks do NOT do”- They don’t bypass scope checks. A workflow listing
tools_required: [email_send]still fails at runtime if the tenant didn’t grantmail.send. The filter just keeps the agent from seeing the workflow in the first place. - They don’t override platform safety gates.
risk_level: hightools still gate on the Security Gate regardless of what your workflow says. - They don’t replace tool
description. The agent still reads every tool’s description. Workflows are additional routing context, not a substitute.