Skip to content

Instantly share code, notes, and snippets.

@mikehostetler
Created April 8, 2026 19:42
Show Gist options
  • Select an option

  • Save mikehostetler/43306ebc00f9cd2a17ba266dd5465397 to your computer and use it in GitHub Desktop.

Select an option

Save mikehostetler/43306ebc00f9cd2a17ba266dd5465397 to your computer and use it in GitHub Desktop.
Jido.Domain concept sketch

Jido.Domain

This is a concept sketch, not an implementation plan.

The core idea: Jido.Domain is the missing authoring layer above Jido.Pod.

Jido already has strong primitives:

  • Jido.Agent for behavior
  • Jido.Pod for durable topology
  • signals/directives for protocol and effects
  • Jido.AgentServer and InstanceManager for runtime lifecycle

What still feels incomplete is the application-level object a developer is actually building.

Problem

Today, a serious multi-agent app usually ends up inventing its own shape:

  • one module for the public API
  • one module for the pod
  • a few policy/helper modules
  • a few projection/artifact modules
  • a rationale for when to use signals directly vs when not to

That works, but it is all convention and no named abstraction.

So the developer experience still has a gap:

  • what is the root object?
  • where do I put startup logic?
  • where do I put app-facing commands and queries?
  • how should Phoenix or another host app interact with a pod-backed runtime?

Proposal

Introduce Jido.Domain as a higher-level authoring construct.

Not a new runtime. Not a replacement for Pod. Not a replacement for Agent.

Instead:

  • Domain is the public app-facing boundary
  • Pod is the durable runtime backing that domain
  • Agent remains the unit of behavior inside the pod
  • signals remain the internal protocol

Mental Model

The intended stack becomes:

Phoenix / Host App -> Domain -> Pod -> Agent

Or, in AgentOS terms:

Phoenix / Host App -> Domain -> AgentOS Kernel -> Pod -> Agent

That means:

  • the host app should call a domain module
  • the domain module should wrap pod operations in semantic commands/queries
  • the pod should remain the durable runtime unit underneath
  • signals should still matter, but they should not be the only public API

Why Not Just Use Signals?

Signals are still the right protocol for:

  • ingress events
  • inter-agent communication
  • child-to-parent communication
  • external dispatch and routing

But signals are not a great host-app API by themselves because they expose:

  • transport details
  • signal naming conventions
  • routing details
  • multi-step choreography

A Phoenix app generally wants to call:

  • IssueTriage.open_run/1
  • IssueTriage.ingest_issue/2
  • IssueTriage.status/1
  • RepoWorkspace.sync/1
  • SupportDesk.submit_ticket/2

So the rule should be:

  • signals are the internal protocol
  • domain commands and queries are the public API

What Domain Would Own

A Domain should own:

  • the public command/query API
  • orchestration across multiple pod/runtime calls
  • ingress translation from product events into pod protocol
  • policy logic or policy modules
  • workflow artifacts / projections / memory-thread shaping
  • lifecycle hooks such as fresh-run initialization and shutdown cleanup

A Domain should not replace:

  • pod topology
  • agent behavior
  • kernel boot and persistence

Example Shape

This is not intended to compile. It is a sketch of the authoring experience.

defmodule MyApp.IssueTriage do
  use Jido.Domain,
    name: "issue_triage",
    manager: :issue_triage_runs

  commands do
    command :open_run, params: [run_id: :string]
    command :ingest_issue,
      params: [
        issue_id: :string,
        repo: :string,
        title: :string,
        body: :string,
        labels: {:list, :string}
      ]

    command :publish_run
  end

  queries do
    query :status
    query :timeline
    query :artifacts
    query :topology
  end

  lifecycle do
    mount do
      {:ok, %{definition_id: "triage/v1"}}
    end

    shutdown do
      :ok
    end
  end

  pod do
    agent :triager, MyApp.IssueTriage.Agents.Triager,
      registry: :triager_agents,
      activation: :eager

    agent :researcher, MyApp.IssueTriage.Agents.Researcher,
      registry: :research_agents,
      activation: :lazy,
      depends_on: [:triager]

    agent :reviewer, MyApp.IssueTriage.Agents.Reviewer,
      registry: :review_agents,
      activation: :lazy,
      depends_on: [:triager, :researcher]

    # Owned nested pod
    pod :repo_analysis, MyApp.RepoAnalysis,
      registry: :repo_analysis_runs,
      activation: :lazy,
      key: &"repo:" <> &1.repo

    # External reference only
    ref :knowledge_base, MyApp.KnowledgeBase,
      registry: :knowledge_base_runs,
      key: & &1.repo
  end

  ingress do
    sensor MyApp.Sensors.GitHubIssueWebhook,
      as: :github_issue_webhook,
      config: %{source_path: "/webhooks/issues"}
  end

  policy do
    def classify(title, body, labels) do
      cond do
        "bug" in labels -> :bug
        String.contains?(String.downcase(body), "unknown root cause") -> :bug
        true -> :feature
      end
    end

    def requires_research?(body, labels) do
      "needs-research" in labels or
        String.contains?(String.downcase(body), "unknown root cause")
    end
  end

  artifacts do
    record :issue_ingested,
      memory: [world: :current_issue, events: {:append, %{kind: :issue_ingested}}],
      thread: {:append, %{kind: :issue_ingested}}

    record :triage_completed,
      memory: [world: :triage, events: {:append, %{kind: :triage_completed}}],
      thread: {:append, %{kind: :triage_completed}}
  end

  routes do
    on "issue.webhook.received" do
      project :issue_ingested
      send_to :triager, "issue.triage.requested"
    end

    on "triage.completed" do
      project :triage_completed

      if policy(:requires_research?) do
        ensure :researcher
        send_to :researcher, "issue.research.requested"
      else
        ensure :reviewer
        send_to :reviewer, "issue.review.requested"
      end
    end
  end
end

What This Would Buy Us

  • One obvious root object answers: "what am I building?"
  • Phoenix can call a stable domain API instead of raw pod mechanics.
  • Policy and artifact logic get real homes.
  • Signals remain important, but they become the internal wire protocol.
  • The Agent naming tension gets easier:
    • Agent still means runtime behavior primitive in Jido
    • Domain becomes the thing an app author is actually building

Nested Pods vs References

This distinction feels important:

pod

An owned nested pod.

  • this domain is responsible for acquiring it
  • this domain may reconcile it as part of its workflow
  • it participates in the domain's runtime composition

ref

A referenced external pod.

  • this domain can signal/query it
  • this domain does not own its lifecycle

That keeps composition and integration separate.

Scope Question

I think Domain is likely too opinionated to land in core Jido immediately.

The more natural incubation point may be one layer above core, especially in something like AgentOS, where the stack is already:

  • host app
  • kernel
  • pod
  • agents

In that world, Domain becomes the missing application authoring layer.

Open Questions

  • Should the name be Jido.Domain or something more specific like Jido.AgentOS.Domain?
  • Should v1 be a thin macro, or go straight to a real DSL?
  • Should policy and artifacts be DSL sections or just nested modules by convention?
  • Should nested pod and external ref be in v1 or come later?
  • Should the public API be generated commands/queries, or mostly hand-written functions with a little help underneath?
  • If this becomes a serious DSL, should it use Spark?

Summary

Jido.Domain is an attempt to formalize the thing developers are already building informally:

  • a public domain API
  • backed by a durable pod
  • composed of agents
  • integrated into a host app like Phoenix

That feels like the missing end-to-end story.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment