Skip to content

Add dispatch trigger for workflow-to-workflow handoff#1

Draft
whme wants to merge 2 commits into
mainfrom
gerrit-otter-trigger
Draft

Add dispatch trigger for workflow-to-workflow handoff#1
whme wants to merge 2 commits into
mainfrom
gerrit-otter-trigger

Conversation

@whme

@whme whme commented Jun 23, 2026

Copy link
Copy Markdown
Member

What

Adds a new dispatch trigger type so one running workflow can start another triggered workflow with a payload and a pre-populated trigger-context/ directory. This is the data channel polling triggers already use, now available to programmatic handoff between workflows.

This is the otter-core half of the @otter <command> Gerrit entrypoint rework; the marketplace half (the otter-dispatch workflow that uses otter dispatch for kind = workflow commands) lives in a companion PR in checkmk-claude-marketplace.

Changes

  • TriggerEvent.inline_context(filename, contents) pairs materialized into trigger-context/ in run_once, beside the existing polling pending_context path. Plain file names only (path-traversal guarded).
  • TriggerDef::Dispatch + new triggers/dispatch.rs — a per-workflow inbox registry (DispatchRegistry); DispatchTrigger forwards inbox messages as trigger events. Queueing and one-run-at-a-time are reused from the existing run_triggered loop.
  • DaemonCommand::Dispatch + handler, threaded through WorkflowManager::dispatch().
  • CLI: otter dispatch <workflow> [--payload <str>] [--context-file <name=path>]… [--context-dir <dir>].
  • Docs/example: a Dispatch trigger section in USAGE.md and an examples/dispatch-handler package.

Notes

  • The target workflow must be running (its engine registers the inbox on start) and use trigger.type = "dispatch". Dispatching to a stopped or non-dispatch workflow returns a clean error.
  • Backward compatible: TriggerEvent and DaemonCommand additions use serde defaults; existing trigger constructors set inline_context: None.

Testing

  • triggers::dispatch unit test (inbox → trigger event with inline context).
  • engine integration test: run_once writes inline_context into trigger-context/, skips traversal entries.
  • workflow_manager integration test: start → dispatch → a run executes with the handed-over context.
  • Full otter-core suite green except pre-existing git ssh-signing env failures in workspace_pool/git tests (unrelated). cargo clippy clean.

🤖 Generated with Claude Code

Introduces a new `dispatch` trigger type so one running workflow can start
another triggered workflow with a payload and a pre-populated
`trigger-context/` directory — the data channel that polling triggers already
use, now available to programmatic handoff.

- `TriggerEvent.inline_context` is materialized into `trigger-context/` in
  `run_once` (path-traversal guarded).
- `TriggerDef::Dispatch` + `triggers/dispatch.rs`: a per-workflow inbox
  registry; `DispatchTrigger` forwards inbox messages as trigger events.
  Queueing / one-run-at-a-time is reused from the existing trigger loop.
- `DaemonCommand::Dispatch` and the `otter dispatch <wf> [--payload]
  [--context-file name=path] [--context-dir dir]` CLI.
- Tests: dispatch-trigger unit test, run_once inline-context integration test,
  and a manager-level start->dispatch->run test. Docs in USAGE.md plus an
  examples/dispatch-handler package.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@whme whme left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the data channel polling triggers already use, now available to programmatic handoff between workflows.

I think we should migrate the polling triggers to use the dispatch trigger if possible to showcase the new architecture with existing workflows.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new dispatch trigger to enable workflow-to-workflow (and CLI-to-workflow) handoff by sending a payload plus inline context files that are materialized into trigger-context/ before steps run.

Changes:

  • Introduces TriggerDef::Dispatch plus a new DispatchTrigger/registry for routing inbox messages into trigger events.
  • Extends TriggerEvent with inline_context and updates Engine::run_once() to write these files into trigger-context/ with path-traversal protection.
  • Adds otter dispatch CLI + daemon plumbing, plus documentation and an example workflow.

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
USAGE.md Documents the new dispatch trigger and CLI usage.
examples/dispatch-handler/workflow.toml Adds a minimal dispatch-triggered example workflow.
examples/dispatch-handler/README.md Adds usage instructions for the dispatch example.
crates/otter-tui/src/panels/right_panel.rs Displays dispatch as a trigger type in the TUI.
crates/otter-core/src/workflow_manager.rs Adds dispatch registry wiring and a dispatch() entrypoint.
crates/otter-core/src/workflow_manager_tests.rs Adds an integration-ish test ensuring dispatch runs write inline context.
crates/otter-core/src/types.rs Adds TriggerDef::Dispatch, DaemonCommand::Dispatch, and TriggerEvent.inline_context.
crates/otter-core/src/triggers/polling.rs Initializes inline_context to None for polling events.
crates/otter-core/src/triggers/oneshot.rs Initializes inline_context to None for oneshot events.
crates/otter-core/src/triggers/mod.rs Registers the new dispatch trigger module and build path.
crates/otter-core/src/triggers/manual.rs Initializes inline_context to None for manual events.
crates/otter-core/src/triggers/dispatch.rs Implements the dispatch inbox trigger + unit test.
crates/otter-core/src/engine.rs Plumbs dispatch registry into trigger construction; materializes inline_context into trigger-context/.
crates/otter-core/src/engine_tests.rs Adds coverage for inline context materialization + traversal skipping.
crates/otter-cli/src/main.rs Adds otter dispatch command and context-file collection logic.
crates/otter-cli/src/daemon.rs Handles DaemonCommand::Dispatch by calling WorkflowManager::dispatch().

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread crates/otter-core/src/engine_tests.rs Outdated
Comment thread crates/otter-core/src/triggers/dispatch.rs
Comment thread crates/otter-core/src/triggers/dispatch.rs
Comment thread crates/otter-core/src/workflow_manager.rs Outdated
Comment thread USAGE.md Outdated
- dispatch.rs: recover from poisoned registry lock instead of panicking
  the engine task; surface poisoned inbox lock as a TriggerError.
- workflow_manager: drop the stale registry entry and return the
  actionable "not accepting dispatches" error when the inbox is closed.
- engine_tests: assert the traversal entry would land in the run dir, not
  the scratch root, so the guard regression is actually caught.
- USAGE.md: document that context files must be valid UTF-8.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@whme

whme commented Jun 23, 2026

Copy link
Copy Markdown
Member Author

@whme on migrating polling triggers onto the dispatch trigger: agreed it'd be a nice showcase, but I'd keep it out of this PR. The two aren't quite substitutable — polling owns the seen-hash dedup, interval loop, and context_command execution, whereas dispatch is a passive inbox with the context handed in pre-built. A migration would mean factoring the polling loop into a thing that dispatches into the workflow's inbox, which is a worthwhile refactor but a separate change with its own tests. I've addressed the five inline review comments in a84e774. Want me to open a follow-up issue to track the polling→dispatch unification?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants