Skip to content

refactor(orchestrator): retire the /invoke-workflow text-scan router → one composable run-management primitive + per-adapter project binding #1886

@Wirasm

Description

@Wirasm

Context

Archon currently has three coexisting mechanisms for an AI chat to launch/manage workflow runs:

  1. Native manage_run tool — Claude/Pi, project-scoped. The agent manages runs through a real tool call (orchestrator-agent.ts:1076).
  2. CLI pointer over bash — Codex/OpenCode/Copilot (no native tools) get a system-prompt nudge to archon workflow …, project-scoped (orchestrator-agent.ts:1057).
  3. /invoke-workflow text-scan "router" — the legacy path: the orchestrator scans every chat response for a /invoke-workflow <name> line and dispatches it (handleStreamMode orchestrator-agent.ts:1218, handleBatchMode :1419handleWorkflowInvocationResult). Always-on, the lowest-common-denominator fallback.

Mechanisms 1 and 2 are the modern, intentional design ("the agent manages runs"). Mechanism 3 is a magic-string protocol the model has to emit into prose — implicit, brittle, and redundant wherever 1 or 2 apply. It survives only because it's the fallback for conversations that aren't scoped to a project (1 and 2 are both gated on conversation.codebase_id !== null).

This came out of the model-tier work (#1872): the "workflow router → small tier" criterion was dropped because there is no separate cheap "router" step to optimize — routing is fused into the single chat call, and for scoped chats it's already superseded by the tool/bash mechanisms.

Problem

  • Three ways to do one thing. Run-management has no single primitive; it's a tool or a bash pointer or a regex on prose, chosen implicitly by provider + scope. Hard to reason about, hard to test, hard to extend.
  • The router is implicit + brittle. /invoke-workflow is a string the model must emit verbatim into its reply; detection is regex over streamed text (with provider-specific quirks, e.g. Pi emitting **/invoke-workflow**). It's the opposite of a clean interface.
  • Project scoping is inconsistent across adapters, which is the only reason the router fallback still exists:
Surface Scopes a project today?
Web / new console ✅ explicit
Forge (GitHub/Gitea/GitLab) ✅ derived from the repo context
Chat (Slack/Telegram/Discord) ❌ conversations can be projectless

Goal

Retire the /invoke-workflow text-scan router and converge on one clean, composable primitive for run management, by first closing the project-scoping gap at the adapter layer. Keep it understandable and primitive — no new bolted-on paths.

Proposed primitives (direction — open to design)

Two seams, deliberately small:

  1. Project binding — one explicit seam every adapter satisfies: given a conversation + adapter context, resolve the project (codebase) it operates in. Forge adapters already do this from repo context; chat adapters need an explicit story (a default project per channel, a /project selection step, inherit-from-thread, etc.). This is the load-bearing prerequisite — once every conversation can be scoped, the gated mechanisms (1 & 2) are always available.
  2. Run-management capability — one abstraction for "this agent can see/launch/manage this project's runs," satisfied per-provider by its transport (native tool for tool-capable providers; bash/CLI for the rest). The orchestrator wires this, not three branches. The /invoke-workflow scan is deleted.

The end state: chat is a large manager agent that manages runs through a real capability; workflows carry their own tiers; there is no magic-string router. This aligns with the per-user model-selection direction (see .claude/PRPs/prds/user-scoped-ai-setup.prd.md) — chat=large, runs carry tiers — and removes a whole implicit protocol.

Scope

  • This is a refactor, not a feature. Behavior for already-scoped surfaces (console, forge) should be unchanged; the win is collapsing three mechanisms into one primitive and unblocking chat adapters.
  • Likely touches: packages/core/src/orchestrator/orchestrator-agent.ts (remove the scan, wire the capability), the run-management tool/CLI-pointer assembly, and each chat adapter's conversation-creation path (project binding).

Acceptance criteria

  • A single, documented run-management primitive; the /invoke-workflow text-scan and handleWorkflowInvocationResult are removed.
  • Every adapter binds a project to its conversations (or has an explicit, understandable fallback for the projectless case).
  • Console + forge behavior unchanged (regression-tested).
  • Chat adapters (Slack/Telegram/Discord) can launch/manage runs via the same capability once scoped.
  • No provider regresses: tool-capable providers use the native tool; others use the bash/CLI path; nothing relies on emitting a magic string into prose.

Non-goals

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions