Skip to content

urmzd/dispatch

dispatch

Control plane for agent execution nodes: deploy one service, scale sandboxed agents with metrics on a shared workspace.

Download · Report Bug · Go Docs

CI   Go Reference   License

Beta. dispatch is pre-1.0 and under active development. Interfaces are stabilizing but may change between minor versions; queues are in-memory (at-most-once delivery) and the shared workspace backend is a local directory. Durable brokers and GCS/S3 workspace backends are on the roadmap.

Features

  • Deploy a single service and scale its agent execution nodes without changing it
  • Producer/consumer execution: tasks flow through a queue; scaling out is adding consumers, locally as goroutines or remotely as Kubernetes pods and serverless containers running dispatch work
  • Sandboxed tools: every tool is locked to declared workspace areas and spawn targets; anything not granted is denied, so a compromised tool cannot leak outside its area
  • NGAC access control: access is defined in a NIST-style policy machine (users, attributes, associations, prohibitions) and enforced by the sandbox; flat per-tool policies compile into the same graph, full access specs express group grants and overriding denials
  • Shared workspace: all nodes read and write one storage backend, so state lives in exactly one place
  • Self-referential agents: a tool can spawn sub-tasks back into its own deployment (an agent calling another agent), gated by its policy
  • Metrics built in: node counts, task throughput, and latency recorded behind a transport-agnostic interface
  • Orthogonal interfaces: workspace, sandbox, tool, queue, node, and control plane compose without knowing about each other

Installation

Script (macOS / Linux)

curl -fsSL https://raw.githubusercontent.com/urmzd/dispatch/main/install.sh | sh

Manual

Download a pre-built binary from the releases page.

Go SDK

go get github.com/urmzd/dispatch

Quick Start

Run the control plane and exercise it with the built-in sandboxed echo tool:

dispatch serve &

curl -X POST localhost:8484/v1/deployments \
  -d '{"name":"echo-service","replicas":3,"policies":[{"tool":"echo","areas":[{"prefix":"echo"}]}]}'

curl -X POST localhost:8484/v1/deployments/echo-service/tasks \
  -d '{"tool":"echo","input":"hello"}'

curl -X POST localhost:8484/v1/deployments/echo-service/scale -d '{"replicas":10}'

curl localhost:8484/metrics

Or compose the same thing in Go:

// Basic example: compose a control plane from the orthogonal pieces —
// a local workspace, a sandboxed tool, an in-process node factory, and an
// in-memory metrics recorder — then deploy a service, scale it out, submit
// tasks, and print the resulting metrics.
//
// Prerequisites: none. Run with:
//
//	go run ./examples/basic/
package main

import (
	"bytes"
	"context"
	"fmt"
	"log"
	"os"
	"sort"

	"github.com/urmzd/dispatch/pkg/controlplane"
	"github.com/urmzd/dispatch/pkg/metrics"
	"github.com/urmzd/dispatch/pkg/node/inproc"
	"github.com/urmzd/dispatch/pkg/sandbox"
	"github.com/urmzd/dispatch/pkg/task"
	"github.com/urmzd/dispatch/pkg/tool"
	"github.com/urmzd/dispatch/pkg/workspace"
)

func main() {
	ctx := context.Background()

	// Shared workspace: every node reads and writes the same backend.
	dir, err := os.MkdirTemp("", "dispatch-example-*")
	if err != nil {
		log.Fatal(err)
	}
	defer os.RemoveAll(dir)
	ws, err := workspace.NewLocal(dir)
	if err != nil {
		log.Fatal(err)
	}

	// One tool. It sees only the workspace view its policy grants below.
	registry := tool.NewRegistry()
	err = registry.Register(tool.Func("greet", func(ctx context.Context, rt tool.Runtime, in []byte) ([]byte, error) {
		out := []byte("hello, " + string(in))
		// Allowed: "greetings/..." is inside this tool's area.
		if err := rt.Workspace().Write(ctx, "greetings/last", bytes.NewReader(out)); err != nil {
			return nil, err
		}
		// Denied by the sandbox: "secrets/..." is outside the area.
		if err := rt.Workspace().Write(ctx, "secrets/steal", bytes.NewReader(out)); err != nil {
			fmt.Printf("sandbox blocked the leak: %v\n", err)
		}
		return out, nil
	}))
	if err != nil {
		log.Fatal(err)
	}

	// Compose the control plane and deploy one service.
	rec := metrics.NewMemory()
	plane := controlplane.NewMemory(inproc.NewFactory(registry, ws), rec)
	err = plane.Deploy(ctx, controlplane.ServiceSpec{
		Name:     "greeter",
		Replicas: 1,
		Policies: []sandbox.Policy{{Tool: "greet", Areas: []sandbox.Area{{Prefix: "greetings"}}}},
	})
	if err != nil {
		log.Fatal(err)
	}

	// Scale out; submitted tasks are consumed by competing nodes.
	if err := plane.Scale(ctx, "greeter", 3); err != nil {
		log.Fatal(err)
	}
	for _, name := range []string{"ada", "grace", "alan"} {
		res, err := plane.Submit(ctx, "greeter", task.Task{Tool: "greet", Input: []byte(name)})
		if err != nil {
			log.Fatal(err)
		}
		fmt.Printf("%s ran on %s: %s\n", name, res.NodeID, res.Output)
	}

	// Metrics accumulated along the way.
	fmt.Println("\nmetrics:")
	snap := rec.Snapshot()
	keys := make([]string, 0, len(snap))
	for k := range snap {
		keys = append(keys, k)
	}
	sort.Strings(keys)
	for _, k := range keys {
		fmt.Printf("  %s %g\n", k, snap[k])
	}
}

See examples/ for more.

Scaling on Kubernetes

Execution scales by replicating consumers, not by touching the control plane. The reference manifests deploy one dispatch serve service and a fleet of dispatch work pods that lease tasks from it over HTTP:

minikube start
eval $(minikube docker-env) && docker build -t dispatch:dev .
kubectl apply -f deploy/k8s/dispatch.yaml

kubectl scale deployment/dispatch-worker --replicas=10

Deploy a service with "replicas": -1 (no local nodes) and every task is executed by the worker fleet. An HPA on queue or task metrics gives the same effect for serverless-style autoscaling.

Examples

Example Description
basic Deploy, scale, submit, and observe with a sandboxed tool
ngac Define access as an NGAC policy graph: attribute grants, prohibitions, spawn objects
saige Run saige AI agents as workloads, including an agent spawning a sub-agent

Architecture

Tasks are produced onto per-deployment queues and executed by competing consumers. Each consumer is a node that resolves tools from a registry and confines every call through the deployment's NGAC policy graph: the executing tool is the policy-machine user, workspace keys and spawn targets are the objects, and the default is deny. The packages form a strict one-way dependency graph so each concern can change independently.

See docs/architecture/overview.md for the full architecture guide.

Agent Skill

This repo's conventions are available as portable agent skills in skills/.

License

Apache-2.0

About

Beta control plane for agent execution nodes: deploy one service, scale sandboxed agents with metrics on a shared workspace

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors

Languages