Oi (oi v0.1.0)

Copy Markdown

Oi means Orchid integration.

Core concepts

  • Workspace — pure data struct holding graph, cluster, interventions. No process, no GenServer. Compiled and dispatched via pure functions.

  • Compile + Dispatch — two-phase pipeline:

    1. Oi.compile/1 — topology → static bundles (reusable)
    2. Oi.dispatch/2 — bind interventions → plan → execute via pluggable executor
  • Executor — pluggable task execution strategy. Built-in: Sync (serial), TaskSup (Task.Supervisor), Pool (NimblePool).

  • Session — optional process tree per tenant, wrapping OrchidSymbiont.Runtime for multi-tenant isolation.

Quick start

Define a step

defmodule MyApp.Steps.Upcase do
  use Oi.Step, name: :upcase

  manifest(
    inputs: [:text],
    outputs: [result: :string]
  )

  routine text, _opts do
    text |> String.upcase() |> ok()
  end
end

Build and run

alias Oi.Topology.Graph
alias Oi.Topology.Graph.{Node, Edge}

graph =
  Graph.new()
  |> Graph.add_node(%Node{
    id: :up, container: MyApp.Steps.Upcase,
    inputs: [:text], outputs: [:result]
  })

ws = Oi.Workspace.new("demo", graph)
{:ok, ws} = Oi.compile(ws)
{:ok, ws} = Oi.dispatch(ws,
  interventions: %{{:port, :up, :text} => {:input, "hello"}}
)

ws.drafting.memory  # => %{"up|result" => "HELLO"}

Multi-tenant with Session

Oi.Session.start("tenant-1")
Oi.Session.start("tenant-2")

ws = Oi.Workspace.new("tenant-1", graph)
{:ok, ws} = Oi.compile(ws)
{:ok, ws} = Oi.dispatch(ws,
  executor: Oi.Executor.TaskSup,
  executor_opts: [sup: Oi.Session.tasks_tuple("tenant-1")]
)

Symbiont step

defmodule MyApp.Steps.Predict do
  use Oi.Step, name: :predict, symbiont?: true

  manifest(
    inputs: [:features],
    outputs: [prediction: :string],
    models: [:model]
  )

  routine features, models, _opts do
    {:ok, result} = OrchidSymbiont.call(models.model, {:predict, features})
    ok(result)
  end
end

Summary

Functions

Phase 1: Compile graph into static bundles + plan.

Phase 2: Dispatch plan with interventions, filling drafting.

Types

name()

@type name() :: String.t()

Functions

compile(ws)

@spec compile(Oi.Workspace.t()) :: {:ok, Oi.Workspace.t()} | {:error, :cycle_detected}

Phase 1: Compile graph into static bundles + plan.

Pure topology — no interventions. Result stored in workspace.static_bundles and workspace.plan. Reusable across different intervention sets.

dispatch(ws, opts \\ [])

@spec dispatch(
  Oi.Workspace.t(),
  keyword()
) :: {:ok, Oi.Workspace.t()} | {:error, term()}

Phase 2: Dispatch plan with interventions, filling drafting.

Interventions can be overridden via the :interventions option — useful for A/B testing with the same compiled plan.

Options

  • :interventions — overrides workspace.interventions (default)
  • :executorOi.Executor.Sync (default), Oi.Executor.TaskSup, or Oi.Executor.Pool
  • :executor_opts — passed to the executor (e.g. [sup: MyTaskSup])
  • :plugins — OrchidPlugin pipeline
  • :orchid_baggage — merged into Orchid run baggage

Examples

# Compile once
{:ok, ws} = Oi.compile(ws)

# Dispatch with default interventions
{:ok, ws} = Oi.dispatch(ws)

# Dispatch with swapped interventions (reuses compiled plan)
{:ok, ws_b} = Oi.dispatch(ws, interventions: other_interventions)

# With Task.Supervisor
{:ok, ws} = Oi.dispatch(ws,
  executor: Oi.Executor.TaskSup,
  executor_opts: [sup: Oi.Session.tasks_tuple("svs-1")]
)