Skip to content

Architecture contract ​

OpenFlow uses nested ports and adapters. UI calls desktop, desktop calls orchestration, and orchestration hosts the engine through its ports. Provider adapters implement the engine's model interface.

┌─────────────────────────────────────────────────┐
│  UI (crates/ui)                                 │
│  Presentation — visual state, interaction       │
└──────────────────┬──────────────────────────────┘
                   │
                   │ implements Desktop inbound port
                   │ (invokes Tauri commands)
                   ▼
┌──────────────────────────────────────────────────┐
│  Desktop (crates/desktop)                        │
│  Tauri Adapter — IPC transport, DTO mapping      │
└──────────────────┬───────────────────────────────┘
                   │
                   │ implements Orchestration inbound port
                   │ (calls AppBackend)
                   ▼
┌──────────────────────────────────────────────────┐
│  Orchestration (crates/orchestration)            │
│  Application — run lifecycle, coordination       │
└──────────────────┬───────────────────────────────┘
          ╱        │        ╲
    entity/    backend/   adapters/
    services   composition   drivers
         │        │            │
         └────────┼────────────┘
                  │
        ┌─────────┴──────────┐
        │ implements         │
        │ engine ports       │
        ▼                    ▼
    Engine (hex)        Providers (adapter)
    Execution engine    LLM transport

Crate roles ​

CrateQuestion it answersHolds
engineWhat is a valid workflow, and how does a run behave?Product rules: graph validation, execution semantics, tool approval policy
orchestrationHow does the desktop app store, load, wire, and host runs?App rules: persistence, catalog merge, run sessions, tool I/O
providersHow do we talk to OpenAI/Anthropic?The AiPort implementation
ui / desktopHow does the user interact?Inbound adapters only

Layer definitions ​

1. Engine (crates/engine) ​

  • Role: Hexagon: workflow execution engine
  • Scope: Workflow model, state machine, ports (AiPort, ToolPort). The self-driving InteractiveEngine::run() calls AiPort and ToolPort internally and surfaces only interaction pauses (NeedsInteraction) to orchestration.
  • Public interface: Traits in ports/ + model types
  • Dependencies: None upward; only serialization, async traits, tokio

2. Providers (crates/providers) ​

  • Role: Outbound adapter: implements engine::AiPort
  • Scope: LLM protocol/auth/transport
  • Public interface: create_provider() → Box<dyn AiPort>
  • Dependencies: engine types, HTTP client, serde

3. Orchestration (crates/orchestration) ​

  • Role: Inbound adapter for Engine; outbound provider for Desktop
  • Scope: Run lifecycle, session state, coordination, approval/input loops, event fanout; ToolPortImpl executes tools and subagents
  • Sub-roles:
    • backend/: composition root; wires services and adapters
    • agent/, workflow/, project/, settings/, tool/: flat entity folders with application/domain logic
    • run/: run coordination, execution host, persistence policy, and projected run state
    • adapters/storage/: concrete JSON/file persistence implementations
    • adapters/tool_impl/ and adapters/infrastructure/: concrete tool, git, LSP, and other I/O drivers
  • Public interface: AppBackend, the façade Desktop calls
  • Dependencies: engine + providers, no upward

4. Desktop (crates/desktop) ​

  • Role: Inbound adapter for UI; calls Orchestration
  • Scope: Tauri transport only: IPC wiring, command serialization, DTOs
  • Public interface: Tauri command handlers that map to Orchestration calls
  • Dependencies: orchestration::AppBackend, no upward

5. UI (crates/ui) ​

  • Role: Presentation layer; implements Desktop's command interface
  • Scope: Visual state, interaction, rendering
  • Public interface: SolidJS components, TypeScript types; the React Flow canvas is isolated behind the UI boundary
  • Dependencies: Tauri invoke client (to Desktop), no upward

State ownership ​

Orchestration ​

  • Active session/run state: approval queues, retry counters, execution trace
  • Runtime coordination: what step runs next, when to pause for approval
  • Not execution semantics (the engine owns those)

Engine ​

  • Model types and invariants (Workflow, Node, ToolCall, ToolResult)
  • Legal transitions and validation rules
  • Execution semantics: when the engine advances, what the error states are

Infrastructure / Persistence ​

  • Durable state (workflows.json, settings.json, agent definitions)
  • Credentials, cache
  • Not runtime session state (orchestration owns that)

Dependency rules ​

Allowed (dependencies point inward):

  • UI → Desktop (invoke Tauri commands)
  • Desktop → Orchestration (call AppBackend methods)
  • Orchestration → Engine (use model types, drive InteractiveEngine::run)
  • Orchestration → Providers (via Box<dyn AiPort>, no concrete types)
  • Providers → Engine (implements AiPort trait)

Forbidden:

  • UI → Engine or Providers (bypass orchestration)
  • UI → Orchestration directly (go through Desktop)
  • Desktop → Engine or Providers (go through Orchestration)
  • Desktop → Orchestration internals (run/execution/, entity folders, or adapters); only the AppBackend public façade
  • Orchestration → UI or Desktop (upward)
  • Engine → anything outward (no imports of provider, orchestration, UI, desktop)
  • Providers → UI or Desktop

CI enforcement ​

CI runs these checks through ./scripts/check-architecture.sh. The machine-readable rules live in ../../crates/workspace-checks/arch-check-rules.toml; this contract is the human-readable source of truth.

Tier 2 (Phase A): inter-crate ​

  1. Workspace dependency graph: path deps in each crate's Cargo.toml match the allowed inward graph.
  2. Forbidden cross-crate use: per-crate ban tables (e.g. desktop must not use engine::).
  3. Engine forbidden deps: engine must not depend on transport/GUI crates (reqwest, tauri, …). Pure validation crates are allowed, such as jsonschema with default features disabled so $ref resolution cannot perform I/O.
  4. Legacy crate aliases: domain and workflow_core are banned in all workspace use paths.

Tier 3 (Phase B): seams and layout ​

  1. orchestration → providers allowlist: orchestration/src may import only listed config/factory symbols. AiClient is banned; use create_provider.

  2. Engine invocation locality: only orchestration/src/run/execution/ may call InteractiveEngine::new.

  3. Orchestration domain folders: agent/, workflow/, project/, settings/, tool/ must not use crate::adapters::.

  4. UI Tauri seam: @tauri-apps/* imports only in api.ts, port.ts, and allowed test files. Frontend callers use the wrappers in api.ts.

  5. Orchestration domain store ban: agent/, workflow/, project/, settings/, tool/ must not use crate::{agent_store, flow_store, ...}. They depend on port traits; backend/ wires the File*Stores.

Deferred: tool/ → lsp narrowing; providers → engine submodule allowlist. The architecture rules file has the exact enforced scope.

Engine invocation rule ​

  • Only orchestration/run/execution/ may construct InteractiveEngine. run/execution/drive/mod.rs hosts engine.run() and coordinates state, interaction, and event delivery.
  • Interactive runs call InteractiveEngine::run(); orchestration handles only NeedsInteraction and terminal outcomes.
  • Tool and subagent execution goes through ToolPortImpl (engine::ToolPort).
  • UI, Desktop, and Providers never call the engine directly.

Port rule ​

The engine defines outbound ports (traits) for what it needs from external systems: AiPort and ToolPort.

Orchestration implements ToolPortImpl, calls AiPort via Box<dyn>, and resumes paused engines through InteractiveEngine::on_human_input / InteractiveEngine::on_tool_decision. Because orchestration depends on dyn AiPort, provider implementations can change without moving transport details into the engine.

Provider-specific branching stays in providers/. The engine does not know which LLM it is calling.

UI-to-Desktop calls go through typed wrappers in crates/ui/src/api.ts. Add a new port only when code is typed on that interface.

Testability rule ​

  • UI tests: Mock api.ts wrappers or Tauri invoke/event APIs at the boundary
  • Desktop tests: Mock AppBackend methods
  • Orchestration tests: Inline impl AiPort stubs; use acceptance fixtures for critical paths
  • Provider tests: Verify wire format mapping; test AiClient contract compliance
  • Engine tests: Colocated unit tests for state machine and model invariants

Development lanes ​

Pick source guidance and verification by the behavior you changed. The dependency rules above apply to every lane.

Touched areaLanePrimary source docsRequired local verification
crates/engine/**Engine semanticscrates/engine/AGENTS.md, this contract, docs/glossary.mdcargo nextest run -p engine; add workflow acceptance when run behavior, prompts, ports, tools, or telemetry change
crates/orchestration/src/run/**Run orchestrationcrates/orchestration/AGENTS.md, docs/architecture/threading-concurrency.md, docs/contributing/testing-workflows.mdcargo nextest run -p orchestration --lib; cargo nextest run -p orchestration --test workflow_acceptance --no-capture for execution behavior
crates/orchestration/src/agent.rs, crates/orchestration/src/{workflow,project,settings,tool}/**Application/domain servicecrates/orchestration/AGENTS.md, docs/contributing/coding-patterns.mdcargo nextest run -p orchestration --lib; add focused store/backend tests when persistence or IPC-visible behavior changes
crates/orchestration/src/adapters/**Concrete adapter/I/Ocrates/orchestration/AGENTS.md, this contractFocused adapter tests plus ./scripts/check-architecture.sh
crates/providers/**Provider adaptercrates/providers/AGENTS.mdcargo nextest run -p providers; live smoke only when intentionally checking a real provider
crates/desktop/**Desktop IPC adaptercrates/desktop/AGENTS.mdcargo nextest run -p desktop; update UI seam tests when payloads change
crates/ui/**UI/Desktop seam and presentationcrates/ui/AGENTS.mdnpm --prefix crates/ui run typecheck; focused Vitest for changed helpers/components
Cross-crate behaviorFull workflow sliceRoot AGENTS.md, this contract, docs/contributing/testing-workflows.md./scripts/test-fast.sh --execution; run ./scripts/verify.sh before handoff

Agent-facing skills may summarize this table but must treat these docs as authoritative. If a skill, rule file, or memory contradicts this contract, update the secondary artifact; don't copy stale architecture facts forward.

Change review checklist ​

  1. Does this change add a dependency that violates the allowed/forbidden rules?
  2. Does UI/Desktop bypass layers to call engine/provider code?
  3. Did provider-specific logic leak into Orchestration or Engine?
  4. Does any new runtime state live outside Orchestration without justification?
  5. Are new public interfaces declared at the correct seam (Desktop vs Orchestration vs Engine)?
  6. Does Engine remain free of filesystem/tool I/O (delegated to ToolPort)?
  7. Do Desktop/UI contain only transport logic, not orchestration or engine rules?

Released under the MIT License.