# Agent Builder Studio

One app for the agent design → run → package lifecycle — a visual canvas authors and runs a governed spec; a deterministic engine packages it. Local-first.

Status: active

> **In development (beta).** Mid-merge: `apps/agent-builder` (packaging workbench) and `apps/agent-studio` (runtime canvas) are being collapsed into a single canonical app, with phased compile + test gates between steps. `apps/agent-studio` is the primary, launchable app today (`npm run dev`, port 3030); `apps/agent-builder` is a retired-UI signpost that still hosts legacy tooling. The whole monorepo is plain JavaScript/`.mjs` — no TypeScript.

## The Problem

Designing an agent, running it against a real model, and packaging it for reuse are usually three disconnected tools — a whiteboard, a script, and a build step — with the truth scattered across all three. The graph you drew drifts from the code you ran, which drifts from the thing you shipped.

## Approach

The design bet: **the agent spec is the single source of truth**; canvas layout, run transcripts, and the generated package are all *derived, rebuildable projections* that can be regenerated from the spec. A derived layer never becomes the source of truth.

The corollary that shapes the packaging engine: there is no separate code path for "generate a skill" versus "generate a plugin" versus "generate an agent." Every export — whether from a two-node prototype or a nine-node governed graph — runs through the same deterministic template engine (`@tyroneross/agent-pack`) and produces the same artifact shape: a self-contained, copyable folder. What changes per-spec is *how much validation scaffolding* that folder carries and *which extra skill files* get bundled into it — both decided by rule-based signal detection over the spec text, not by a human picking an output type from a menu. Converting the folder's `skills/skill-bank.json` entries into an actual Claude Code skill, a subagent, or a Codex `AGENTS.md` entry is left to the operator at install time (`setup/host-deployment.md` documents the mapping); the generator does not emit `.claude-plugin/`, `commands/`, `hooks/`, or MCP-server manifests for the agents it builds. (The one plugin manifest in the repo, `apps/agent-builder/plugin/.claude-plugin/plugin.json`, packages Agent Builder Studio *itself* as a Claude/Codex skill for advising on harness design — that's the tool's own distribution, not something end-user specs route into.)

## Architecture

Two independent flows share one spec contract: **run** (Studio executes the graph live against a local model) and **package** (the same graph, or a hand-authored spec, is compiled into an installable folder). Tracing one graph from canvas to shippable package:

```mermaid
flowchart TD
    A["Canvas: author nodes + edges<br/>(role: agent/tool/subagent/guardrail/<br/>approval/verifier/orchestrator/executor/eval/memory)"] --> B["localStorage<br/>agent-studio:v7"]
    A --> C["Live run: agent-runtime.mjs<br/>topological DAG, 4-wide per-level parallelism"]
    C --> D["Ollama /api/chat, stream:true<br/>default gpt-oss:20b, per-node override"]
    D --> C
    C --> E["Run transcript + per-node cache<br/>(derived, replayable)"]

    B --> F["spec-export.mjs: projectToSpec()<br/>canvas -> agent-spec/v1"]
    F --> G{"Export depth"}
    G -->|"portable (10 files)"| H["agent.yaml, manifest.json,<br/>system-prompt.md, tools.json, evals/, memory/"]
    G -->|"full package (~30-40 files)"| I["agent-pack: buildAgentArtifacts()<br/>zero LLM calls, all deterministic templating"]

    I --> J["spec-profile.js: inferSpecProfile()<br/>regex/keyword signal detection over spec text<br/>-> skill / personal / team / enterprise"]
    J --> K["Profile sets required contracts:<br/>skill-contract only (skill) up to<br/>agent-registry+lifecycle+observability (enterprise)"]
    I --> L["emitted-capabilities/index.mjs<br/>conditional skill/tool bundler"]
    L -->|doc-heavy spec| M["doc-ingest tool + skill"]
    L -->|write/deploy/credential signal| N["threat-modeler skill"]
    L -->|doc-producing output| O["pyramid-principle skill"]
    K --> P["Package folder: manifest, agent.yaml,<br/>system-prompt.md, tools.json,<br/>skills/skill-bank.json, contracts/*,<br/>runtime adapters, evals/, memory/"]
    M --> P
    N --> P
    O --> P
    P --> Q["/api/artifacts: stage()<br/>.artifacts/package/<slug>/ under workingFolder"]
    Q --> R["promote()<br/>copy to <workingFolder>/promoted/<slug>/"]
    R --> S["Operator installs into a host:<br/>API-key runtime, Claude (skills/subagents),<br/>Codex (AGENTS.md), or hybrid multi-agent"]
```

1. **Author** — the Studio canvas builds a project as nodes (one of eleven canonical roles: `agent`, `tool`, `subagent`, `guardrail`, `approval`, `verifier`, `orchestrator`, `executor`, `eval`, `memory`, `state`) and edges, held in browser `localStorage` (`agent-studio:v7`); no server-side project database exists.
2. **Run** — `agent-runtime.mjs` topologically orders the graph from explicit edges plus declared `inputs`/`outputs` tag-matching, detects cycles before any call, and executes each level up to 4 nodes in parallel, streaming NDJSON from Ollama's `/api/chat` (`stream:true`, `format:"json"`, default model `gpt-oss:20b`, overridable per node or via `OLLAMA_MODEL`).
3. **Export to spec** — `spec-export.mjs#projectToSpec()` converts the canvas into `agent-spec/v1`: canonicalizes each node's role, drops studio-only fields (canvas x/y, mock outputs, snapshots, pan/zoom — logged in the README's "stripped fields" list), and fills runtime defaults (Ollama, `custom-loop` framework, `deny-by-default` permissions).
4. **Route to depth** — the studio offers two export sizes from the same spec: a lightweight 10-file "portable spec" (`exportProjectToSpec`) or the full ~30-40-file installable package (`exportProjectToFullPackage`, delegating to `@tyroneross/agent-pack#buildAgentArtifacts`). Both call the same `validateSpec()` gate first.
5. **Infer validation profile** — `spec-profile.js#inferSpecProfile()` decides how much governance scaffolding the package needs: an explicit `validationProfile` field wins outright; otherwise regex/keyword detection over the spec's description, tools, and I/O looks for enterprise signals (`production`, `regulated`, `hipaa`, `sox`, `iam`, `customer data`, etc.) → **enterprise**, then shared-workflow signals (hosted/hybrid runtime, human-checkpoint nodes, ≥4 agent-like nodes) → **team**, else → **personal**. A fourth profile, **skill**, exists only when explicitly declared and requires just a skill contract, not the full contract set.
6. **Bundle conditional capabilities** — `emitted-capabilities/index.mjs` runs three independent deterministic detectors over the spec text and, only when triggered, adds extra files: `detectDocIngest` (document-handling language) bundles a doc-ingest tool + skill and a `runtime/doc-ingest.mjs` shim; `detectRiskSurfaces` (write/deploy/credential/destructive language) bundles a `skills/threat-modeler.skill.md`; `detectDocProducer` (report/brief-style outputs) bundles a `skills/pyramid-principle.skill.md`. None of this is model-driven — every trigger is a word-boundary regex match against spec fields.
7. **Emit the package** — `buildAgentArtifacts()` assembles manifest, `agent.yaml`, `system-prompt.md`, `tools.json`, `skills/skill-bank.json` (one skill entry per graph node, keyed to that node's tools/inputs/outputs/permission), the profile-gated `contracts/*.yaml` files (system-boundary, tool-contracts, flow-topology, guardrails, human-checkpoints, agent-registry, observability, lifecycle — filtered to what the inferred profile requires), runtime adapter guides for seven target frameworks (custom-loop, OpenAI Agents SDK, Claude Code subagents, LangChain DeepAgents, LangGraph, Pydantic AI, NVIDIA NeMo Agent Toolkit), and `evals/golden-tasks.json` seeded from any node fixtures captured during a live run.
8. **Stage and promote** — `/api/artifacts` (backed by `@tyroneross/agent-artifacts`) writes the package to a git-ignored `.artifacts/package/<slug>/` under the project's working folder, then `promote` copies it to a standalone `<workingFolder>/promoted/<slug>/` outside the app tree — the operator's install unit.
9. **Install into a host** — `setup/host-deployment.md` (generated per package) is prose guidance, not code: it tells the operator when to convert a `skills/skill-bank.json` entry into a Claude Code skill or subagent ("only when isolated context is useful"), how to wire it into Codex via `AGENTS.md`, or how to run it as a plain API-key runtime — this final artifact-type decision is a human judgment call the generator surfaces but does not automate.

**Models — which, where, why:**

| Stage | Model / tier | Where it runs | Why there |
|---|---|---|---|
| Studio live run | Ollama, default `gpt-oss:20b` (env `OLLAMA_MODEL` or per-node `model` override) | `agent-runtime.mjs`, one call per graph node per level | Single local daemon, no key management, matches the studio's local-first default; per-node override lets one graph mix model sizes |
| Chief-of-Staff / shared cascade (`@tyroneross/local-llm`, used by `apps/chief-of-staff` and `apps/cos`) | Tiered: `parse` (MLX `Llama-3.2-3B-Instruct-4bit` / Ollama `llama3.2:3b`), `mid` (MLX `Qwen2.5-3B-Instruct-4bit` / Ollama `qwen3:8b-q4_K_M`), `synthesis` (MLX `Qwen2.5-3B-Instruct-4bit` / Ollama `gemma4:26b`) | `tiers.mjs#resolveCascade()`, per node-type routing table | MLX is the local-primary lane (Apple Silicon, `127.0.0.1:8080`, OpenAI-compatible), Ollama the local-fallback (`localhost:11434`); cascade order and cloud eligibility are config, not per-call choices |
| Cloud fallback (same cascade) | Groq (Llama 3.1/3.3), Anthropic (Claude Haiku/Sonnet family), OpenAI (GPT-5 family) | Only when local lanes fail or `allowCloud` policy is `on-failure`/`always`, and only when the matching API key env var is present | Key-gated and token-budget-capped (`createCloudBudget`) so a run degrades to local-only rather than silently spending cloud tokens |
| Package generation (`agent-pack#buildAgentArtifacts`) | **None** | n/a | 100% deterministic template/string assembly plus regex-based signal detection (spec-profile, emitted-capabilities) — no model call anywhere in the packaging pipeline |

**Tools & infra — which, why:**

- **Storage** — browser `localStorage` for project/canvas state (`agent-studio:v7`, versioned with migrations); the local filesystem (via Next.js route handlers, path-allowlisted to `/Users/`, `/tmp/`, `/var/folders/`) for staged/promoted package exports. No server database — the app is single-user, local-first by construction.
- **Runtime** — Next.js 16 App Router (route handlers under `app/api/`) + React 19, plain JavaScript/`.mjs` throughout (no TypeScript).
- **Model runtimes** — Ollama (`localhost:11434`) as the default local backend; MLX (`127.0.0.1:8080`, OpenAI-compatible `mlx_lm.server`) as the local-primary lane in the shared cascade package, Apple Silicon only.
- **Monorepo** — npm workspaces across six apps (`agent-builder`, `agent-studio`, `chief-of-staff`, `cos`, `meetings`, `investments`) and six packages: `packages/agent-spec` (schema/validation/YAML/role vocabulary), `packages/agent-pack` (the deterministic packaging engine described above), `packages/agent-artifacts` (stage/promote helpers), `packages/local-llm` (the MLX→Ollama→cloud cascade), `packages/tool-spec`, `packages/builder-tools`.
- **Doc parsing** — `@tyroneross/omniparse` (local SDK, sibling repo) is the doc-ingest tool bundled into packages when `detectDocIngest` fires.

## How it works

1. A node is added to the canvas with a role (`agent`, `tool`, `subagent`, `guardrail`, `approval`, `verifier`, `orchestrator`, `executor`, `eval`, `memory`, or `state`) and, optionally, declared input/output tags.
2. Running the graph computes a dependency map from explicit edges plus tag-matching (a node declaring `inputs:["scope"]` implicitly depends on any node declaring `outputs:["scope"]`), throws on cycles, then executes level-by-level, up to 4 nodes concurrently per level, streaming each node's Ollama response and accumulating NDJSON chunks into a parsed JSON output.
3. Exporting the project runs `projectToSpec()` to normalize the canvas into `agent-spec/v1`, then either the 10-file portable path or `agent-pack#buildAgentArtifacts()` for the full package — the same `validateSpec()` call gates both.
4. Inside `buildAgentArtifacts()`, `inferSpecProfile()` reads the spec once and returns a profile (`skill`/`personal`/`team`/`enterprise`) that determines which `contracts/*.yaml` files are required; `resolveEmittedCapabilities()` reads the same spec and conditionally attaches doc-ingest, threat-modeler, and/or pyramid-principle skill files.
5. The generator writes 20-plus core files unconditionally (manifest, `agent.yaml`, `system-prompt.md`, `tools.json`, `skills/skill-bank.json` with one entry per node, seven framework-adapter guides, `evals/golden-tasks.json`, `memory/` stubs) plus the profile- and capability-gated extras, landing at roughly 30-40 files for a governed package.
6. `/api/artifacts` stages the package under the project's working folder and, on promote, copies it to a standalone folder outside the app tree — the unit an operator hands to a host runtime.
7. The generated `setup/host-deployment.md` walks the operator through installing that folder as an API-key runtime, a Claude Code skill/subagent set, a Codex `AGENTS.md` entry, or a hybrid multi-agent setup — the artifact-type conversion is operator-driven prose guidance, not an automated branch in the generator.

## Tech stack

Next.js 16 App Router + React 19, plain JavaScript (`.js`/`.mjs`, no TypeScript) across a six-app, six-package npm-workspaces monorepo. Ollama is the default local model runtime for the Studio canvas; MLX (Apple Silicon, OpenAI-compatible local server) is the local-primary lane for the shared `local-llm` cascade package used by the Chief-of-Staff apps, with Ollama as fallback and Groq/Anthropic/OpenAI as key-gated cloud lanes. The packaging engine (`@tyroneross/agent-pack`) is a zero-dependency deterministic template compiler — no model calls, no external services. Project state lives in browser `localStorage`; generated packages land on the local filesystem via path-allowlisted Next.js route handlers. `@tyroneross/omniparse` (a sibling local SDK) handles document parsing when a spec triggers the doc-ingest capability.

## Status

Mid-merge — the two source apps are being collapsed into a single `apps/studio`, with phased compile + test gates between steps. It consolidates two earlier projects — **Agent Builder** (the evaluation/packaging workbench) and **Agent Studio** (the runtime canvas) — plus a standalone Chief-of-Staff product, merged via git subtree so the history is preserved.

## Links

- [HTML project page](https://rosslabs.ai/projects/agent-builder-studio/)
- [Source code](https://github.com/tyroneross/agent-builder-studio)
