cas-studio

cas-studio

Enables AI hosts to design and simulate complex adaptive systems, including managing agent types, running simulations, and analyzing emergent behavior, feedback loops, and sensitivities.

Category
Visit Server

README

CAS Studio

A studio for designing and simulating complex adaptive systems (CAS) as agent-based models. You define agent types (state + local rules), wire agents into a directed, signed interaction graph, give the system an open environment with sources and sinks, and run a deterministic, seeded, synchronous simulation engine against it. The seven canonical CAS properties are first-class, instrumented features — each has explicit model constructs and analysis endpoints, not just documentation.

Pure Python + numpy core. FastAPI + SQLAlchemy + Alembic for the REST API and persistence. Vanilla-JS canvas UI. No heavy deps, no network calls, no external data.

Quickstart

./run.sh                      # venv + deps (via uv), alembic upgrade, uvicorn
# HOST=127.0.0.1 PORT=8002 ./run.sh   to override

Then open http://localhost:8000/ — on first start against an empty database the seed demo is loaded (and audit-logged): innovation diffusion in a market — 40 agents on a small-world ring lattice with innovators, early-majority and laggard adopters, two skeptics (balancing loops), two adaptive-price vendors, and an information source in the environment. Two adjacent seeded adopters trigger an S-curve adoption cascade (mean adoption 0.12 → 0.95 over ~11 steps) with flagged emergence events at the takeoff; dropping to a single seed makes it fizzle.

Run the tests:

.venv/bin/python -m pytest tests/ -q

The seven CAS properties

1. Emergence

Macro patterns arise from micro interactions. Every step the engine records macro metrics: the mean field of every state var, population variance, active-cluster count (connected components of "active" agents — primary var ≥ 0.5 — over the undirected interaction graph), an order parameter |2·active_fraction − 1|, and a Moran's-I-like neighbor correlation. GET /api/runs/{id}/emergence returns the series plus flagged emergence events: steps where the z-score of a macro indicator's step-to-step delta exceeds 3 while exogenous inputs were constant (no injections). Honest caveat: a z-score over a short series is a blunt detector — treat events as flags for inspection, not proof.

2. Nonlinearity

POST /api/systems/{id}/sensitivity with {"param", "deltas": [...], "steps", "seed"} reruns the simulation with param perturbed by each delta and reports response ratios |Δoutcome/Δparam| (outcome = final mean field of the primary var). It flags nonlinear (ratios vary by >10× across magnitudes — superlinear regime), threshold (some perturbations produce a response, others none), and sign_flip. Parameter addressing:

param form meaning
env:<var> environment initial value
flow:<var>:inflow / flow:<var>:outflow_rate source/sink field
type:<type>:<var> initial state var of every agent of a type
seed_count:<type>:<var> how many agents of a type start with <var>=1 (a contiguous block anchored at the current first active agent)

The seed demo has a genuine threshold at seed_count:innovator:adopted: one seed fizzles (adoption 0.075), two adjacent seeds cascade (0.95).

3. Decentralization

Agents act only on local information: their own state, weighted means of their direct in-neighbors' state, and environment variables via flux. The rule DSL's condition vocabulary is closed — global state is unrepresentable by construction (there is no "all agents" aggregate, no global lookup). Self-organization is tracked by the Moran's-I-like neighbor-state correlation in every run's series.

4. Feedback loops

GET /api/systems/{id}/loops enumerates elementary cycles of the directed interaction graph (Johnson-style DFS, each cycle reported once from its smallest node; max_len and a 1000-cycle cap bound the enumeration and report truncated). Each loop is classified reinforcing (even count of negative couplings) or balancing (odd count) with loop gain = product of edge weights. Run summaries include per-loop activity: the edge flux that flowed through each loop's edges during the run, where edge flux per step = |Δ primary var of the source| × |weight| (a heuristic for "how much change propagated along this edge", not a physical quantity).

5. Adaptation

Agent types may declare "adaptation": {"target_var", "target_value", "rate"}. Each step the agent adds a bounded bias to target_var; after the step the bias is updated by gradient-free reinforcement: if the step moved the var closer to target_value, keep and amplify the bias (×1.25, capped at |1.0|), else reverse and damp it (×−0.5). Deterministic given the seed. The seed demo's vendors hill-climb price toward 0.7 this way.

6. Open boundaries

Every system has an environment: named float variables plus sources/sinks — {"var": "information", "inflow": 1.0, "outflow_rate": 0.05} applies v += inflow − outflow_rate·v per step. Agents exchange with the environment through conserving flux effects: {"flux": "information", "by": 0.02} moves 0.02 units from the environment into the agent (a state var of the same name); the environment loses exactly what agents gain (verified by test). POST /api/runs/{id}/inject with {"step", "var", "amount"} schedules an exogenous pulse: it re-runs the base run's system/seed/steps with the pulse added to the environment at that step, and returns the new run plus the outcome delta.

7. Nested hierarchy

A System may have a parent_id; subsystems are full systems with their own agents, rules and runs. GET /api/systems/{id}/rollup aggregates each child system's latest-run macro metrics into the parent's report (agent-count-weighted mean of the children's primary mean fields).

Rule DSL

An agent type has state (dict of float vars) and rules — a list of {"if": <condition>, "then": [<effect>, ...]}. The first matching rule fires; later rules are ignored that step (a rule with "then": [] is an absorbing-state guard).

Conditions:

"always"
{"var": "adopted", "op": ">=", "value": 0.5}                 // own state
{"neighbor_mean": {"var": "adopted"}, "op": ">=", "value": 0.35}  // in-neighbors

op> < >= <= ==. neighbor_mean is the weighted mean Σ wᵢxᵢ / Σ|wᵢ| over in-neighbors that carry the var — so negative-weight (skeptic) neighbors dilute the mean.

Effects:

{"set": "adopted", "value": 1.0}        // set own var
{"adjust": "energy", "by": -0.1}        // add to own var
{"flux": "information", "by": 0.02}     // conserve quantity with the environment

Individual agents may carry a state_override: floats, or {"uniform": [lo, hi]} which is sampled once per run from the run's seeded RNG (np.random.default_rng(seed)) — this is what makes the run seed meaningful. Same model + same seed → identical metric series (tested).

The engine steps synchronously: every agent computes its next state from the same snapshot — no update-order artifacts (tested with an oscillating two-agent model).

REST API

All mutations are audit-logged (GET /api/audit).

Endpoint Purpose
GET/POST /api/systems, GET/DELETE /api/systems/{id} system CRUD (parent_id for hierarchy)
POST /api/systems/{id}/agent-types, DELETE /api/agent-types/{id} agent types (DSL validated on write)
POST /api/systems/{id}/agents, DELETE /api/agents/{id} agents
POST /api/systems/{id}/interactions, DELETE /api/interactions/{id} directed signed edges
GET/PUT /api/systems/{id}/environment environment variables + flows
PATCH /api/agents/{id}/position {pos_x, pos_y} persist an agent's canvas position (saved by the UI on drag-end)
POST /api/systems/{id}/reset_positions clear all saved canvas positions in a system (UI "Reset layout")
PUT /api/agents/{id} {name?, state_override?} edit agent (validated, audit-logged)
PUT /api/interactions/{id} {weight} edit edge weight (audit-logged)
PUT /api/agent-types/{id} edit type state/rules/adaptation (DSL-validated)
GET /api/examples, POST /api/examples/{name} curated example systems, one-click copies
POST /api/agent/chat, POST /api/agent/generateGET /api/agent/jobs/{id} LLM jobs: submit (202) then poll for progress/result
POST /api/systems/{id}/runs {steps, seed} run the sim → {run_id}
GET /api/runs/{id} summary (final mean field, loop activity, final states)
GET /api/runs/{id}/series per-step macro metrics
GET /api/runs/{id}/emergence emergence events
POST /api/runs/{id}/inject {step, var, amount} exogenous pulse → new run
GET /api/systems/{id}/loops feedback loops, classified
POST /api/systems/{id}/sensitivity perturbation response analysis
GET /api/systems/{id}/rollup aggregate children's latest runs

MCP integration

CAS Studio ships an MCP (Model Context Protocol) stdio server so AI hosts (Cursor, Claude Desktop, …) can design and simulate systems directly. It fronts the REST API over HTTP — point it at any running instance with CAS_API_URL (default http://127.0.0.1:8000).

Register it in your host's MCP config (see mcp-config.example.json):

{
  "mcpServers": {
    "cas-studio": {
      "command": "/path/to/cas-studio/.venv/bin/python",
      "args": ["-m", "app.mcp_server"],
      "cwd": "/path/to/cas-studio",
      "env": {"CAS_API_URL": "http://127.0.0.1:8000"}
    }
  }
}

Tools exposed (each proxies the matching REST endpoint above): list_systems, get_system, list_agent_types, create_agent_type, create_agents (batch), add_interaction, set_environment_flow, run_simulation, get_run_series, get_emergence_events, list_feedback_loops, run_sensitivity, inject_pulse, get_rollup, and describe_cas_properties (the seven-property guide + rule DSL).

AI agent

The UI's AI Agent tab is a built-in chat agent that drives the studio through a local Ollama LLM — no API keys, nothing leaves the machine.

Requirements: Ollama running (ollama serve) and at least one model, e.g.:

ollama pull qwen3:8b   # the default; supports tool calling and thinking

The model dropdown lists whatever Ollama reports installed and defaults to qwen3:8b, then any gemma3*, then llama3.1:8b, then the first model (GET /api/agent/models). Point the agent at another Ollama host with OLLAMA_URL (default http://127.0.0.1:11434).

POST /api/agent/chat and POST /api/agent/generate run as background jobs (HTTP 202 {job_id}) so slow local LLMs never hang a request: poll GET /api/agent/jobs/{id} for {status: pending|running|done|error, progress: [...], result|error} — progress entries appear as the loop runs ("round 2: calling run_simulation…", "attempt 2: validation failed: …") and every failure ends in a specific human-readable error. LLM calls use Ollama's native /api/chat with think: false (qwen3's thinking mode is ~10× slower on CPU-bound Ollama and adds nothing to tool calls) and the default round cap is 5.

POST /api/agent/chat {messages, model?, system_id?, temperature?, max_rounds?} runs a tool-calling loop against Ollama's /api/chat: the model picks a tool, the server executes it (through the same tool registry the MCP server uses, app/tools.py), appends the result, and repeats — up to max_rounds — until the model writes a final answer. The job result is {reply, model, tool_trace, rounds}; tool_trace lists every call with its arguments and a trimmed result, and the UI renders it as a collapsible block under each reply. Chain-of-thought (<think> blocks from qwen3) is stripped from replies. If the selected model doesn't support tool calling, the agent falls back to a single-shot no-tools answer with a note; if Ollama is down, the models endpoint degrades gracefully and jobs end with a clear error.

POST /api/agent/generate {description, name?, model?, system_id?, temperature?, max_rounds?} turns plain English into a running CAS model. The LLM (Ollama JSON mode) drafts a system spec — agent types with rule-DSL behavior, agent counts, an interaction topology (ring_lattice / random / small_world), signed edge weights by source type, environment variables with sources/sinks, and optional nested child systems — guided by a compact DSL reference, a few-shot example, and systems-engineering design rules (typology decomposition, at least one reinforcing and one balancing loop, open-boundary flows for conserved quantities, nesting for systems-of-systems). The server validates the spec hard (schema, the full rule-DSL validator, topology materialization); on failure the validation errors are fed back to the model, up to max_rounds attempts (default 3), after which the job ends in error with the collected messages. On success everything is created through the repository (audit-logged as generate_system by ai-agent) and the result carries the new system id, a summary, and the raw spec. The same capability is exposed to MCP hosts as the generate_system_from_description tool (it submits the job and waits), and in the UI as the Generate system mode of the AI Agent tab (with live progress).

LLM settings

The gear button in the AI Agent tab opens settings: temperature (default 0.2) and max tool rounds (default 8) — sent per request as temperature / max_rounds to both /api/agent/chat and /api/agent/generate — plus the active Ollama URL and ollama pull hints. Model, temperature and round choices persist in localStorage.

Editing, examples, and guided UX

The canvas has an edit toolbar (Select / Add agent / Connect / Delete) for manual model building — clicking a node or edge opens an editor in the detail panel (name, state override, edge weight). The Structure tab edits agent types (state schema + rule DSL with inline validation errors) and the environment (variables + source/sink flows). New endpoints: PUT /api/agents/{id}, PUT /api/interactions/{id}, PUT /api/agent-types/{id} (all validated and audit-logged). The Examples tab loads curated systems in one click (GET /api/examples, POST /api/examples/{name}): the innovation-market cascade, a predator–prey meadow, and a two-tier supply network (nested hierarchy) — each with a what-to-watch blurb. A first-run help overlay, per-tab explainer bars, and empty-state hints guide new users; the emergence panel shows the strongest sub-threshold shifts (near_misses on GET /api/runs/{id}/emergence) when no events fire.

Graph canvas

The system graph is fully interactive: drag empty space to pan, mouse-wheel to zoom (anchored at the cursor, with a zoom indicator and a Fit button), drag nodes to rearrange them — positions persist server-side (pos_x/pos_y, saved on drag-end) and auto-layout only fills nodes without a saved position. Edges show direction arrows, green solid / red dashed for positive / negative coupling, and weight labels where they carry information beyond the sign. Nodes are labeled with name

  • primary state value after a run; hovering shows a tooltip with the full agent state; a type legend sits in the corner. The pure geometry/layout helpers live in app/static/graph.js and are unit-tested with node --test (see tests/js/).

Layout

app/
  main.py        FastAPI app, REST API, static UI
  tools.py       shared tool registry (names/schemas/execution) for MCP + agent
  mcp_server.py  MCP stdio server fronting the REST API (CAS_API_URL)
  agent.py       in-app AI agent: Ollama tool-calling loop (OLLAMA_URL)
  jobs.py        in-process background jobs for slow LLM work (submit/poll)
  generate.py    natural-language -> validated CAS system spec -> repository
  examples.py    curated one-click example systems
  db.py          engine + session factory (DATABASE_URL, default sqlite:///./cas_studio.db)
  repository.py  SQLAlchemy models + Repository (single DB access point)
  rules.py       rule DSL validation + evaluation (pure functions)
  engine.py      deterministic seeded synchronous simulation engine
  analysis.py    emergence events, loops, sensitivity, Moran's I
  seed.py        the innovation-diffusion demo model
  static/index.html  canvas UI (graph, run controls, chart, loops/sensitivity/inject panels, AI agent)
  static/graph.js    pure canvas helpers (view transform, force layout, edge geometry)
alembic/         schema migrations
tests/           pytest, one file per concern

License

MIT — © 2026 Vector Stream Systems LLC.

Recommended Servers

playwright-mcp

playwright-mcp

A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.

Official
Featured
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.

Official
Featured
Local
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

graphlit-mcp-server

The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.

Official
Featured
TypeScript
Kagi MCP Server

Kagi MCP Server

An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.

Official
Featured
Python
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
Exa Search

Exa Search

A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured
E2B

E2B

Using MCP to run code via e2b.

Official
Featured