Files
meeting-assistant/docs/meeting-workflow-engine.md
T
codex 7ff93b73b3
PR and Push Build/Test / build-and-test (push) Failing after 6m36s
Make agent file writes append by default
2026-05-28 12:36:23 +02:00

349 lines
10 KiB
Markdown

# 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`.
```json
{
"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.
The tray menu includes `Edit rules and identities`, which opens a small MewUI chat editor for this configured rules file and the local speaker identity database. The editor uses the summarizer agent configuration by default and can be overridden through `MeetingAssistant:WorkflowRulesEditor`.
```json
{
"MeetingAssistant": {
"WorkflowRulesEditor": {
"Endpoint": "",
"KeyEnv": "",
"Model": "",
"EnableThinking": null,
"ReasoningEffort": null,
"MaxOutputTokens": null,
"EnableCompaction": null,
"CompactionRemainingRatio": null,
"ResponsesCompactPath": "",
"InitialPrompt": ""
}
}
}
```
Blank editor values inherit from `MeetingAssistant:Agent`. This keeps the editor on the same LiteLLM Responses endpoint and model as the summarizer unless a value is explicitly configured. Each user-submitted editor turn sends its first model request with `X-Initiator: user`; follow-up model requests in the same turn, such as tool-call continuations, use `X-Initiator: agent`.
The editor agent prompt includes this document and is restricted to these tools:
- `read_rules`: read the configured rules file, optionally by inclusive 1-based line range.
- `write_rules`: append to the configured rules file by default after validating that the complete YAML document parses as a workflow rules document. Pass `replace_file: true` only when intentionally replacing the whole rules file.
- `search`: search only the configured rules file with ripgrep-style syntax.
`POST /diagnostics/workflow/reload` reloads application configuration without restarting the process. Use it after changing workflow automation configuration such as `MeetingAssistant:Automation:RulesPath`. The endpoint returns the currently bound rules path:
```json
{
"rulesPath": "meeting-rules.local.yaml"
}
```
The YAML rules file itself is read for every workflow event, so changing the contents of the same rules file does not require this endpoint. Use the endpoint when the application configuration changes, for example when pointing `RulesPath` to a different YAML file. A meeting run that already captured its options may continue with those options; future runs and future configuration reads use the reloaded configuration.
## 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.
```yaml
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.
```yaml
on:
- created: {}
```
### `state_transition`
Runs when the assistant context state transitions. `from` and `to` are optional filters; omitted filters match any value.
```yaml
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.
```yaml
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.
```yaml
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:
```yaml
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:
```yaml
steps:
- uses: add_context
value: "Known speaker identified: @Model.Speaker.Name"
```
The engine invokes Razor when the value contains an unescaped `@` that is not part of a
valid email address token. Literal email addresses such as `Support@contoso.com` are left
unchanged; if the same value also contains a Razor expression, the email `@` is escaped
before rendering so the address still appears normally in the rendered result.
## Steps
### `add_attendee`
Adds a normalized attendee display name to meeting note frontmatter if it is not already present case-insensitively.
```yaml
steps:
- uses: add_attendee
value: Manuel
```
### `remove_attendee`
Removes attendees whose normalized display name matches the rendered value.
```yaml
steps:
- uses: remove_attendee
value: "Guest"
```
### `set_property`
Sets a supported meeting property. The initial supported property is `title` or `meeting.title`.
```yaml
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.
```yaml
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.
```yaml
steps:
- uses: add_project
value: Meeting Assistant
```
## Examples
Add a default attendee only for empty meetings:
```yaml
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:
```yaml
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:
```yaml
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:
```yaml
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/Workflow/WorkflowRulesEditorChatPipeline.cs`
- `MeetingAssistant/Workflow/WorkflowRulesEditorTools.cs`
- `MeetingAssistant/Workflow/MewUiWorkflowRulesEditorWindowService.Windows.cs`
- `MeetingAssistant/Recording/MeetingRecordingCoordinator.cs`
- `MeetingAssistant.Tests/MeetingWorkflowEngineTests.cs`
- `MeetingAssistant.Tests/MeetingWorkflowDiagnosticEndpointTests.cs`
- `MeetingAssistant.Tests/WorkflowRulesEditorTests.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.