Rosterd

An operating system for agentic systems — isolated, resource-limited, killable for real, and auditable.

Inspiration

Most agent frameworks assume the agent is trustworthy once it's running: no real isolation, a cooperative shutdown at best, and no way to say "this tool call is outside policy" except by hoping the model behaves. At the same time, teams already have working LangGraph systems and don't want to hand-write a config file describing rules that mostly already live in their code — a typed field limit, a guard clause, an interrupt() call.

We wanted to see what happens if you treat an agent system the way an operating system treats a process: isolated, resource-limited, killable for real, and auditable, without asking the person who built the agent to describe it twice.

What it does

Rosterd takes an existing LangGraph agent system and a repo URL — nothing else — and turns it into something you can run safely, talk to in plain language, and scale automatically.

  • Discover — statically reads the repo's tool schemas, conditional edges, and interrupt() calls to infer a contract, no manual config file. A human reviews and confirms it before it governs anything live.
  • Enforce — every tool call an agent makes is checked against that confirmed contract by a kernel. A violation gets a real kill, container-level, not a cooperative shutdown the agent could ignore.
  • Ask — type what you need done in plain language, get back a proposed agent assignment and extracted criteria, confirm, dispatch.
  • Scale — agents are pods. Load on one agent grows its pool automatically, the same way Kubernetes scales replicas, and shrinks it back down after.
  • Federate — the same roster can run at multiple isolated sites, sharing only scores and failure patterns with a thin coordinator, never task content.
  • Observe — every dispatch is a distributed trace, every scaling decision is a live number, one click from a violation in the UI to its exact span in Jaeger.

How we built it

Four services, each in its own container, talking over plain REST, with SpacetimeDB as the one piece of shared live state:

  • Kernel — dispatch, the constraint evaluator, the kill switch, the autoscaler, the OTel tracing conventions. Owns the Docker SDK calls that actually start and stop agent containers.
  • Ingestion — reads a LangGraph repo's get_graph() output, tool argument schemas, and interrupt() calls, and turns them into a manifest with every rule tagged by source and confidence. Also handles /ask/parse, turning plain text into a proposed task — the same underlying move as ingestion, applied to a sentence instead of a codebase.
  • Coordinator, frontend, and SpacetimeDB — federation, the eight-screen UI (Ingest, Review, Ask, Roster, Contracts, Federation, Monitor, and the task detail view), and the SpacetimeDB tables everything subscribes to live: agent pool state, dashboard state, and scaling metrics.
  • Demo agent — a three-agent e-commerce fulfillment system (Order Intake, Fulfillment, Refund/Exception) written so its own tool schemas and one interrupt() call carry real, inferable constraints, no separate config to keep in sync.

We used the kernel/OS framing as an actual design tool, not just a metaphor: agents as processes, tool calls as syscalls through one mediated boundary, a scaler loop instead of ulimit, and OpenTelemetry as /proc — a live, inspectable trace of what every instance is actually doing.

Challenges we ran into

  • Dropping the manual config file was harder than adding one would have been. Inferring a spend limit from a Pydantic Field(le=100) is one thing; inferring "this node needs human approval" from a graph structure and an interrupt() call is a different kind of signal entirely, and we had to rank the two by confidence rather than treat them the same.
  • A real kill switch fights the runtime. An asyncio cancel doesn't stop a blocking call inside an agent. We ended up going container-level (Docker SDK) specifically so a kill could not be politely ignored.
  • Autoscaling and the kill switch turned out to share one mechanism. Scaling an agent's pool down under low load and killing it for a constraint violation are the same underlying action, triggered differently, which simplified the kernel more than we expected once we noticed it.
  • Four services, one shared contract. We wrote the Pydantic schemas before any of the actual services, and it still drifted — a field renamed in one person's doc without updating another's was a recurring source of confusion until we started treating the schema files as the single source of truth everyone diffed against.

Accomplishments that we're proud of

  • A repo URL is genuinely the only input. No constraints file, no annotations, no changes to the agent being wrapped — the contract comes out of the code the team already wrote, and discovery holds up on repos we didn't write, validated against five of LangChain's own public templates.
  • The kill is real. An over-limit tool call is stopped against a rule inferred from the repo's own type annotation, and the run is terminated at the container level rather than asked nicely to stop.
  • The federation loop closes. The same rule broken at two sites is detected as a shared pattern, tightened, and pushed back out — the tighter bound is enforced on the very next dispatch, with no task content ever crossing a site boundary.
  • Autoscaling works on live load, not a slider. A burst took one agent pool from 1 to 20 instances and back down to 1, with the scaling formula holding against the real numbers on every tick.
  • 313 passing tests across the four services, none of which need Docker, SpacetimeDB, a collector, or an LLM key to run.

What we learned

  • Treating agent infrastructure like an operating system (isolation, a mediated syscall boundary, hard resource limits, a real kill) surfaces gaps that most agent frameworks paper over with cooperative, best-effort mechanisms.
  • Federated learning's core move — share aggregates, not raw data — generalizes cleanly to agent behavior: a failure pattern caught at one site can update policy everywhere without any task content crossing a boundary.
  • SpacetimeDB is a genuinely different fit for autoscaling visualization than a polling dashboard. Once pool state is one row per running instance, every viewer watching a live pod count is just a subscription, not something we had to build sync logic for.

What's next for Rosterd

  • More inference sources beyond LangGraph (LangChain agents without the graph structure, raw function-calling loops)
  • A production-realistic scale-down cooldown instead of the shortened demo value
  • A broader library of adversarial scenarios beyond the one seeded misdirection case, to stress-test the kernel's constraint checking further

Built With

Share this project:

Updates

Submission history