Skip to content

omadia Facilitator — agent that moderates group chats to a defined outcome (+ Conductor JIT/ephemeral workflows & Core group primitives) #330

Description

@iret77

Summary

Build an omadia Facilitator: a configurable agent plugin that is invited into a
group conversation, moderates the group to a predefined outcome like a professional
facilitator, keeps a protocol of how the result was reached and which insights
emerged along the way, and reports the result (or failure) back to the initiator.

It is a meta-feature: the Facilitator itself is a plugin, but a clean implementation
requires extending omadia Conductor (ephemeral / JIT workflows) and omadia Core
(group-conversation primitives). Step 1 targets virtual meetings as chat groups
(MS Teams group chat first; Telegram/Discord/etc. follow). Step 2 (audio / online /
hybrid meetings) is prepared and documented here but not built.

Depends on omadia Conductor (spec in #321, not yet merged). The Facilitator runs
on Conductor + the channel plugins + the Office plugin + the session transcript/KG.

Concrete scenario (the role-distribution case)

  • The owner tells the team to agree on a role distribution. A document lists all roles;
    by the end each role should carry exactly one name. The team sorts this out among
    themselves — the owner only wants the result.
  • It all happens in a Teams group chat.
  • The owner invites an omadia Facilitator into the chat to moderate the group to the
    result.
  • The Facilitator must know the goal and create a temporary workflow to run it.
    The system must not accumulate endless dead workflows: the agent generates a workflow
    with an expiry; after acceptance or abort it is removed again.
  • The Facilitator reports the result or failure to the owner per the workflow, and
    optionally interim status via an alternate channel (e.g. a Teams DM).
  • The Facilitator interacts the way professional moderators do — equipped out of the box
    with systemic coaching, NLP and other facilitation techniques.

Solution at a glance — three workstreams

The unifying idea: a facilitated meeting is an inherently free-form conversation
wrapped in a deterministic scaffold. Moderating is creative (the agent's job);
termination, goal-adherence, reminders, reporting, and cleanup are deterministic
(Conductor's job). This is exactly the Conductor harness theme, applied to a group of
humans plus a moderator — without it, a facilitation can run forever, drift off-goal, or
never report.

Workstream A — Conductor: Ephemeral / JIT workflows ("generative, governed, self-disposing")

The owner's explicit constraint: an agent that creates a workflow on the fly must not
clutter the system with snowflake workflows nobody needs.

  • origin: manual | ephemeral on a workflow. manual = user-authored business
    processes (the existing library). ephemeral = agent-generated, run-scoped, JIT.
  • Generated from a validated pattern, not arbitrary graphs. An ephemeral workflow is
    an instance of a known, engine-validated facilitation pattern; the agent fills its
    slots (goal, machine-checkable definition-of-done, rounds, escalation, report targets,
    deadline, interim cadence). It stays generative and validatable, and cannot produce
    garbage graphs.
  • Lifecycle bound to the run. On a terminal run state (completed | failed | aborted) or TTL expiry the ephemeral workflow definition is removed. The run's
    audit trace / protocol is retained — we discard the scaffold, never the minutes.
  • Programmatic seam: an agent capability conductor.createEphemeralRun(patternId, params) (permission-gated, deny-by-default) creates + starts in one call — no
    Designer, no entry in the main workflow library.
  • Storage & Admin-UI separation: ephemeral workflows are flagged (origin) and live
    in a separate namespace; the main workflow library shows only manual workflows. The
    Admin UI surfaces ephemeral ones in a separate, read-only "Live / ephemeral runs" view
    with TTL, owning agent, and run link. A reaper job (reuse the
    scratchPromotionReaper pattern) prunes expired/terminal ephemeral rows.
  • Guardrails against runaway generation: per-agent quota + rate-limit on ephemeral
    creation, a cap on concurrent ephemeral runs per agent, and a mandatory TTL with an
    upper bound.

Workstream B — Core: Group-conversation primitives (channel-SDK contract + Teams adapter)

Verified gaps today: the channel SDK carries per-speaker identity (IncomingTurn.userRef)
and a shared per-conversation session (scope = ${channelId}::${conversationId}), but
has no roster, no group-vs-1:1 signal, no bot-invited event, and no way to DM one
specific person separately from posting to the group
. Define these generically at the
SDK level; implement Teams first; Telegram/Discord follow.

  • B1 — Multi-party model: conversationType: 'group' | 'direct' and a participant
    roster accessor (resolve current members + the agent's own identity in the
    conversation).
  • B2 — Bot-lifecycle event: an "agent invited / members changed" event (incl. who
    added the bot), so the Facilitator can detect being invited and by whom.
  • B3 — Targeted send: post to a specific participant (DM) distinct from posting to
    the group — a per-user conversation reference plus a recipient target on the outbound
    path. Enables "report to me / interim status via Teams DM".
  • B4 (optional, fast-follow) — Addressing: @mention a participant in an outbound
    message.

These are reusable Core capabilities (any group-aware agent benefits), not
Facilitator-private.

Workstream C — Facilitator agent plugin

  • A configurable agent plugin; its skill (system prompt) covers systemic coaching, NLP,
    and classic facilitation techniques
    — round-robin, surfacing silent voices, reflective
    summarizing, conflict mediation, decision frameworks, timeboxing, neutrality.
  • Admin-configurable via manifest setup.fields + ctx.config: default models,
    default reminder/timebox cadence, default reporting behavior (result-only vs interim),
    max meeting duration / workflow TTL, moderation intensity/tone, language, and an
    enable_audio flag (Step 2).
  • Flow: invited to a group (B2) → reads the goal + a machine-checkable
    definition-of-done
    (e.g. "the target artifact has every role filled with exactly one
    participant, and the group explicitly confirmed") → calls conductor.createEphemeralRun( 'facilitation', { goal, definitionOfDone, participants, initiator, reportChannel, deadline, interimCadence }). Conductor then drives:
    • a moderation step (agentic) whose inner loop runs the conversational facilitation
      against the shared group session until the DoD postcondition is met,
    • reminder / timebox ticks (reuse scheduleWorker) to nudge a stalled group,
    • a deadline / abort fallback (in-graph transition),
    • a report step that sends result/failure to the initiator (B3 DM) and optional
      interim status,
    • a protocol step that renders minutes (Office create_docx) capturing how the
      result was reached and the insights, sourced from sessionLogger + the KG.
  • On terminal state the ephemeral workflow is auto-removed; the protocol is retained.
  • The evolving result artifact (e.g. roles→names) is maintained by the agent and can
    be rendered back into the chat; the DoD is checked against it.

Protocol & reporting

  • Protocol reuses the existing substrate: sessionLogger (Markdown transcript + KG
    Turn nodes with entity anchors) + sessionBriefing summaries. The protocol records
    the path to the result and the insights, and survives the workflow's disposal.
  • Reporting uses B3: final result/failure to the initiator; interim status optional
    per facilitation config (default: final + abort only).

Step 1 vs Step 2 — the MeetingSource abstraction (forward-compat)

Define the Facilitator to consume an abstract MeetingSource = an attributed-utterance
stream + a roster
, independent of origin.

  • Step 1 — ChatMeetingSource (this issue): group chat (Teams first).
  • Step 2 — AudioMeetingSource (future): online / real / hybrid meetings via audio.
    Transcription + speaker diarization produce the same attributed-utterance stream.
    The IncomingAttachment.kind === 'audio' seam already exists in the channel SDK; what is
    missing is a transcription/diarization service. Everything above the source layer (the
    facilitation loop, DoD, protocol, reporting, JIT workflow) is unchanged
    — Step 2 is a
    source plugin, not a rewrite. Documented here; not built.

What exists vs. what we extend/build (grounded)

Capability Status Where
Shared per-group session + speaker identity EXISTS channels/coreApi.ts (scope), harness-channel-sdk/src/incoming.ts (userRef)
Protocol substrate (transcript + KG + summaries) EXISTS harness-orchestrator/src/sessionLogger.ts, plugin-api/src/sessionBriefing.ts
Minutes/result artifact rendering EXISTS harness-plugin-office (create_docx/create_xlsx)
Admin config for a plugin EXISTS manifest setup.fields, ctx.config, agent_plugins.config
TTL / reaper patterns EXISTS (reuse) platform/flowState.ts, plugins/jobScheduler.ts, scratchPromotionReaper
Audio attachment seam EXISTS (kind only) harness-channel-sdk/src/incoming.ts (IncomingAttachment.kind: 'audio')
Conductor engine + programmatic start PLANNED #321 (specs/005-omadia-conductor)
Ephemeral/JIT workflows (origin, TTL, createEphemeralRun, UI separation, reaper, quotas) NEW — Conductor Workstream A
Group roster + conversationType NEW — Core (SDK + Teams) Workstream B1
Bot-invited / members-changed event NEW — Core (SDK + Teams) Workstream B2
Targeted DM to a participant NEW — Core (SDK + Teams) Workstream B3
@mention addressing NEW — optional Workstream B4
Facilitator plugin (skill prompt, config, orchestration) NEW Workstream C
Transcription + diarization FUTURE (Step 2) Workstream C / Step 2

Proposed design decisions (recommendations baked in; open to change)

  1. Generation model: ephemeral workflows are generated by filling slots in a
    validated facilitation pattern
    , not by emitting arbitrary graphs. Keeps "generative"
    while staying safe, validatable, and clutter-free. (Alternative: free-form graph
    generation — rejected: unvalidatable, clutter-prone.)
  2. Ephemeral storage: same workflow table + origin flag + separate namespace +
    reaper job, rather than a separate store. Minimal new schema; clean Admin-UI filter.
  3. Channel scope for Step 1: define the group primitives generically at the channel-SDK
    level, implement Teams first, with Telegram/Discord as fast-follow.

Acceptance criteria (Step 1)

  • An admin can install + configure the Facilitator plugin via its setup.fields.
  • A user can invite the Facilitator into a Teams group chat; the Facilitator detects the
    invitation and who invited it (B2), and reads the stated goal + definition-of-done.
  • The Facilitator creates exactly one ephemeral workflow run per facilitation via
    createEphemeralRun; it does not appear in the main workflow library and is
    visible in the Admin-UI ephemeral view with a TTL.
  • The Facilitator moderates the group (distinguishing speakers) until the
    definition-of-done postcondition is met, sends timebox reminders on stall, and fires the
    in-graph fallback on deadline/abort.
  • On success it reports the result to the initiator via a Teams DM (B3) and produces a
    minutes document; on failure it reports the failure.
  • After a terminal state (or TTL), the ephemeral workflow is removed while the run's
    protocol/trace is retained.
  • The same flow works on at least one additional channel (Telegram or Discord) with no
    Facilitator-side changes above the MeetingSource abstraction.

Dependencies & sequencing

  1. Conductor (feat(conductor): Spec 005 — Omadia Conductor (deterministic engine, designer, human-in-the-loop) — US1–US8 implemented + live-tested #321) merges and lands its engine + programmatic start seam.
  2. Conductor — Workstream A (ephemeral/JIT) on top of the engine.
  3. Core — Workstream B (group primitives; Teams adapter) — parallelizable with A.
  4. Facilitator — Workstream C consumes A + B.
  5. Step 2 (audio) — separate follow-up issue; only the AudioMeetingSource + a
    transcription/diarization service are new.

Out of scope

  • Building the transcription/diarization service or any Step-2 audio code.
  • Non-chat connectors themselves (calendar/online-meeting platforms) — Step 2.
  • The HR/role-movement policy (Conductor's role resolver, separate concern).

Drafted with Claude Code as the conception artifact for this feature; refine inline as needed.


Clarification — Principal (user | role) is the universal addressee, including report targets

Anywhere the Facilitator (or any Conductor workflow) addresses a person — not only
decision/approval steps but also report and interim-status targets
— the addressee is a
Principal: user:<id> or role:<key>. This reuses the Conductor role machinery
(resolver seam + late binding) already defined for human steps.

Examples:

  • A) "report to me" → user:<owner-id>.
  • B) "report to the management" → role:management, currently held by two people
    (owner + colleague).

Semantics differ by purpose:

Purpose A role with multiple holders resolves to…
Decision / approval (a response is required) its current holder(s); the step's quorum: any | all governs completion (existing behavior).
Notification / report / interim status (no response required) all current holders at send time, with the message fanned out to each (one delivery per holder via B3 targeted send — e.g. two Teams DMs for a two-person management role). No quorum — nothing is awaited; it is a broadcast to the current holders.

Resolution is late-bound for both: if the role's membership changes before the report
is sent, it goes to whoever holds the role at that moment.

Implications:

  • Workstream B3 (targeted send) MUST accept a Principal, not only a single user,
    and for a role MUST resolve to N recipients and deliver to each.
  • Workstream C: the Facilitator's report target and interim-status target are
    Principals, configurable as either a specific user or a role.
  • Conductor / Spec 005 (feat(conductor): Spec 005 — Omadia Conductor (deterministic engine, designer, human-in-the-loop) — US1–US8 implemented + live-tested #321): generalize Principal from "human-step addressee" to
    "any workflow addressee", and define role-as-notification = fan-out to all current
    holders (distinct from the decision quorum). A small extension to the existing role
    model, not a new concept.
  • Unreachable / empty role on a report follows the existing rule: a holder unreachable
    on the channel is logged/diagnosed; a role with no current holder surfaces a delivery
    diagnostic / escalation rather than a silent drop.

Acceptance addition (Step 1): reporting to a two-person role:management delivers the
result to both current holders; moving the role to a different person before the
report sends it to the new holder.


Invocation, mode lifecycle & transparency

Yes — the Facilitator is an explicit agent plugin with its own identity. It participates
under its own recognizable name/avatar (its own channel bot/handle), never as a generic
"omadia". A distinct, visible participant is the foundational anti-obfuscation property:
nobody is moderated by an invisible observer.

Scope: there is no global "Facilitator mode"

Facilitation mode is per-conversation / per-run, not a global instance toggle. A specific
group has an active facilitation while the rest of omadia runs normally. "Mode active" == the
ephemeral Conductor run for that conversation is in progress — so the mode state is the run
lifecycle
(auditable, announceable), not an opaque flag.

Lifecycle (how it's used)

  1. Admin installs + configures the Facilitator plugin (skill, defaults).
  2. A user invites the Facilitator into the group chat (the "agent invited" event,
    Workstream B2) — the explicit entry; nobody is moderated covertly.
  3. Opening handshake (visible in the chat): the Facilitator immediately posts its purpose —
    "I'm the omadia Facilitator. Goal: X. Definition-of-done: Y. I report to Z. Deadline: D.
    I'll moderate until we reach the result. /facilitator stop ends it."
    Mode start is an
    announced, visible transition.
  4. It creates the ephemeral Conductor run (JIT workflow) — the run is the active mode.
  5. On result / abort / deadline: a visible closing message + the report, and the JIT workflow
    is disposed. Mode end is announced too.

Transparency mechanisms (mode MUST NOT be obfuscated)

Mechanism Effect
Own agent identity Every moderation message is visibly "from the Facilitator", not generic.
Announced start/stop transitions Mode never begins or ends silently — always a handshake + closing summary in the conversation.
Persistent status Web-UI banner + (where the channel supports it) a pinned card: "Facilitation active: goal, definition-of-done, deadline, report target."
/facilitator status Any participant can ask at any time "are we being moderated, by whom, reported to whom?" and gets a truthful answer.
No silent mode switching An agent MUST NOT slip into facilitation mode without the handshake; entering/leaving is always explicit and logged.
Admin-UI ephemeral runs + protocol The active mode is visible outside the chat too (owner, TTL, run trace).

Disclosure of the report target (default)

Participants are being moderated and their result (and possibly insights) is reported to
someone. Default: the report target is disclosed to the group (e.g. "the result goes to the
management") — not merely courtesy but often a transparency / GDPR requirement. Disclosure is the
default; it is configurable only where a different legal basis applies. (Decision confirmed
2026-06-17.)

Skill vs. dedicated agent

The moderation capability could technically be a skill attached to any agent. For transparency the
recommended deployment is a dedicated Facilitator agent with its own identity — a recognizable
moderator beats a general-purpose agent that sometimes moderates covertly.

Acceptance additions

  • Inviting the Facilitator triggers a visible opening handshake stating goal, definition-of-done,
    deadline, and report target before any moderation begins.
  • At any time, /facilitator status returns who is moderating, toward what goal, and to whom the
    result is reported.
  • Entering and leaving facilitation mode are announced in-conversation and recorded in the run
    trace; there is no silent mode.
  • The report target is disclosed to participants by default.

Bot identity model & rationale — when a separate in-channel bot is justified

Recorded so the reasoning isn't lost or re-litigated. Refines the "Skill vs. dedicated agent"
note above: the Facilitator is the Convener agent in facilitate mode, not a bot-per-function.

Context / the tension. omadia's architecture is one user-facing orchestrator per channel
binding; everything else is sub-agents behind that front-door
. Introducing co-present
user-facing bots (a Facilitator joining a group chat) competes with that. This section resolves
the tension and keeps the reasoning.

Principle

Bot identities track accountabilities toward the people in the room, not internal agent
topology. Internally omadia stays orchestrator + sub-agents. Externally, a distinct in-channel
identity appears only when the bot's stance toward the humans is non-assistive — moderating,
recording, representing, or challenging them. Default: one front-door per conversation; a
second co-present identity is the deliberate, justified, ephemeral exception.

The litmus (sharpened)

Two identities only if they could be present in the same room at the same time with
contradicting accountabilities.
If two functions share the same accountability and can never
conflict, they are modes of one identity, not two bots.

Applying it — the taxonomy collapses

  • Convener / Session-Steward — one identity, multiple modes. facilitate (steer to an
    outcome), record (observe + minute), timekeep, … All share one accountability: "present on
    behalf of the session's outcome/record; the attendees are the subject; reports to a target."

    Recording is a mode / sub-feature-set of the Convener, NOT a separate bot — a silent Convener
    is the recorder.
    The active mode is announced for transparency (e.g. "I'm here only to take
    minutes; I won't steer."
    ).
  • Representative — genuinely separate. Partisan: speaks for a specific party in a
    multi-party / cross-org room. It cannot be the same entity as a neutral Convener (neutral vs.
    partisan), and both may be needed simultaneously → a distinct identity.
  • Challenger / Red-team — borderline. A separate identity only when a visibly neutral
    Convener and a deliberately adversarial voice are needed at the same time; otherwise it is
    a devil's-advocate mode of the Convener. (The litmus explains exactly why it's borderline.)

Net: not "a bot per function" — one Convener identity with modes, plus the Representative as the
one clearly separate second class.
Identity count stays tied to perceived accountability; bot
proliferation is prevented structurally.

Stays behind the front-door (sub-agent or mode, never a separate bot)

Domain specialists (HR, finance, Odoo, web-search), skills, tools, personas/tones, and a private
"notetaker just for me". Boundary test for the notetaker: it becomes a (Convener-mode) recorder
only when its presence changes what the other attendees must know ("now we're on the
record") — i.e. when it changes the social contract for the room, not merely my private
convenience.

Consequence for this issue

Model the plugin as a Convener agent with modes, not as "Facilitator + separate Recorder". The
Facilitator is the Convener in facilitate mode; minutes/recording is a Convener mode reusing the
same infrastructure (announced presence, floor arbitration, ephemeral Conductor run, protocol).

Prerequisite this implies (Core — Workstream B)

Any co-present multi-bot scenario (Convener + Representative, or a Convener alongside the general
front-door bot) requires conversation floor / turn arbitration — today omadia is
one-agent-per-binding. Rules:

  • Exactly one bot owns a given message: explicit @mention → that bot; an active Convener run
    holds the floor for its goal and the general front-door defers; otherwise the default
    front-door.
  • No bot replies to another bot unless explicitly addressed (no bot-to-bot loops).
  • Sub-agents never get a channel identity (Spec 001 separation stays hard; the Convener is an
    Agent, not a sub-agent).
  • Co-presence is explicit and ephemeral, with announced join/leave.

This is new Core work and a prerequisite for the Facilitator — without it, multi-bot = noise.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions