Files
meeting-assistant/docs/meeting-workflow-engine.md
T
2026-05-27 12:55:18 +02:00

7.5 KiB

Meeting Workflow Engine

Meeting Assistant has a small local workflow engine for meeting-specific automation. It is intended for rules that are too personal or environment-specific to hard-code, such as adding default attendees, cleaning meeting titles, binding projects, or adding context notes when known speakers are detected.

The workflow engine is deliberately narrow. It runs from a local YAML file, evaluates rules against the current meeting note read from disk, and applies a small set of meeting-safe mutations.

Configuration

The rules file path is configured through MeetingAssistant:Automation:RulesPath.

{
  "MeetingAssistant": {
    "Automation": {
      "RulesPath": "meeting-rules.local.yaml"
    }
  }
}

meeting-rules.local.yaml is the default local rules file and is ignored by git. The path may be absolute, relative to the process working directory, or use environment variables.

If the configured path is empty, missing, or points to a blank file, the workflow engine does nothing.

Execution Model

The engine runs when MeetingRecordingCoordinator emits a meeting workflow event:

  • created: after the meeting note and assistant context artifact are created.
  • state_transition: after the assistant context state moves forward.
  • speaker_identified: when live or final speaker identification reports a display name.

For every event, the engine:

  1. Loads the current rules file.
  2. Reads the latest meeting note from disk.
  3. Evaluates every matching rule in file order.
  4. Applies each matching rule's steps in order.
  5. Rebuilds the template model after every step, so later steps can see earlier mutations.
  6. Saves the meeting note once if any meeting-note step changed it.
  7. Updates assistant-context frontmatter links/title from the saved meeting note after note changes.

Rules are best-effort automation. Invalid expressions, unknown steps, or unsupported properties currently fail the workflow event and should be covered by tests before the engine is broadened.

YAML Shape

The top-level YAML document contains a rules array.

rules:
- name: add-default-attendee
  on:
    - created: {}
  if:
    - condition: meeting.attendees.count = 0
  steps:
    - uses: add_attendee
      value: Manuel

Each rule has:

  • name: log-friendly rule name.
  • on: one or more triggers. At least one must match.
  • if: optional list of conditions. All top-level conditions must pass.
  • steps: ordered actions to run when the rule matches.

Triggers

created

Runs after a meeting note is created.

on:
  - created: {}

state_transition

Runs when the assistant context state transitions. from and to are optional filters; omitted filters match any value.

on:
  - state_transition:
      from: collecting metadata
      to: transcribing

Supported state names are:

  • collecting metadata
  • transcribing
  • speaker recognition
  • summarizing
  • finished
  • error

speaker_identified

Runs when speaker identification reports a display name. name is optional; when supplied, it matches case-insensitively.

on:
  - speaker_identified:
      name: Ada

Conditions

Conditions use NCalc expressions. Dotted workflow variables may be written directly; the engine rewrites them into NCalc parameters internally.

if:
  - condition: meeting.attendees.count = 0

Available condition variables:

  • meeting.attendees.count
  • meeting.attendees
  • meeting.projects
  • meeting.title
  • meeting.state
  • event.type
  • state.from
  • state.to
  • speaker.name

Available helper functions:

  • contains(haystack, needle): case-insensitive string or collection contains.
  • starts_with(value, prefix): case-insensitive string prefix check.
  • ends_with(value, suffix): case-insensitive string suffix check.

Nested boolean groups are supported:

if:
  - and:
      - condition: contains(meeting.title, '[External]')
      - not:
          condition: contains(meeting.projects, 'Internal')

Top-level if entries are combined with and.

Step Templating

Step value fields can be plain strings or Razor templates. Razor templates receive this model:

  • Model.Meeting.Title
  • Model.Meeting.Attendees
  • Model.Meeting.Projects
  • Model.Meeting.State
  • Model.Event.Type
  • Model.Event.From
  • Model.Event.To
  • Model.Speaker.Name

Example:

steps:
  - uses: add_context
    value: "Known speaker identified: @Model.Speaker.Name"

The engine only invokes Razor when the value contains @.

Steps

add_attendee

Adds a normalized attendee display name to meeting note frontmatter if it is not already present case-insensitively.

steps:
  - uses: add_attendee
    value: Manuel

remove_attendee

Removes attendees whose normalized display name matches the rendered value.

steps:
  - uses: remove_attendee
    value: "Guest"

set_property

Sets a supported meeting property. The initial supported property is title or meeting.title.

steps:
  - uses: set_property
    property: title
    value: "@Model.Meeting.Title.Replace('[External] ', '')"

add_context

Appends rendered text to the assistant context artifact body. This step does not count as a meeting-note mutation and does not cause a meeting-note save by itself.

steps:
  - uses: add_context
    value: "Ada was identified; check project ownership notes."

add_project

Adds a project name to meeting note frontmatter if it is not already present case-insensitively.

steps:
  - uses: add_project
    value: Meeting Assistant

Examples

Add a default attendee only for empty meetings:

rules:
- name: add-me
  on:
    - created: {}
  if:
    - condition: meeting.attendees.count = 0
  steps:
    - uses: add_attendee
      value: Manuel

Remove a title marker after metadata collection:

rules:
- name: clean-external-marker
  on:
    - state_transition:
        from: collecting metadata
  if:
    - condition: starts_with(meeting.title, '[External] ')
  steps:
    - uses: set_property
      property: title
      value: "@Model.Meeting.Title.Replace(\"[External] \", \"\")"

Add context when a speaker is identified:

rules:
- name: note-ada
  on:
    - speaker_identified:
        name: Ada
  steps:
    - uses: add_context
      value: "Ada was identified. Check whether the architecture decision log needs an update."

Add context when metadata gathering ends with a specific attendee:

rules:
- name: note-review-owner
  on:
    - state_transition:
        from: collecting metadata
  if:
    - condition: contains(meeting.attendees, 'Manuel')
  steps:
    - uses: add_context
      value: "Manuel is attending; include implementation follow-ups in the summary."

Implementation Notes

Core files:

  • MeetingAssistant/Workflow/MeetingWorkflowEngine.cs
  • MeetingAssistant/Workflow/MeetingWorkflowModels.cs
  • MeetingAssistant/Workflow/MeetingWorkflowEvent.cs
  • MeetingAssistant/Workflow/FileMeetingWorkflowRulesProvider.cs
  • MeetingAssistant/Recording/MeetingRecordingCoordinator.cs
  • MeetingAssistant.Tests/MeetingWorkflowEngineTests.cs

When extending the workflow engine:

  1. Update OpenSpec requirements if behavior changes.
  2. Add behavior tests through MeetingWorkflowEngine or the recording coordinator event surface.
  3. Keep the supported trigger, condition, template model, and step lists in this document current.
  4. Keep README.md as the short operational overview and link back here for details.
  5. Keep the local rules file ignored by git; do not commit personal automation rules.