Skip to content
Deep diveNode 24+Runs in mock mode with no API keys

Everything in dev3d

dev3d is a company: a brief becomes an objective, specialists research and argue, a plan names the files, developers write them, QA tries to break the result, and the CEO reports back. Every one of those steps is a separate model call with its own role, its own skills and its own price band.

This page is the detailed version of the feature list. Each section says what the thing does and where the limit still is. The limits live in their own section rather than in a footnote.

Roles
13
across 8 departments, per floor
Pipelines
3
product-build · code-change · quick-answer
Stage modes
4
single · parallel · debate · review-loop
Tools
15
8 take paths and are confined
Skills
15
markdown files, loaded from disk at boot
Plugin surfaces
8
contributes keys the host publishes
At a glance

Twelve things that shape a run

The parts of the system you will meet first. The sections below take them one at a time.

Cost-aware routing

Every role carries a tier band with hard minimum and maximum. The router picks the cheapest capable model inside it, returning ordered fallbacks and the reason it rejected every other candidate.

Read the section

Four stage modes

single, parallel, debate and review-loop. Each shape is the one its stage’s work actually needs.

Read the section

Confinement

Eight of the fifteen tools resolve every path argument through one choke point bound to the workspace of the run being served. Two projects can be worked on at once and neither can read the other.

Read the section

A hierarchy

Thirteen named roles across eight departments. Someone owns the objective, someone owns the plan, and somebody else is paid to disagree with both.

Read the section

Skills are markdown files

Fifteen skill documents loaded from disk, selected per turn by what the task actually needs. A skill is a file you can read and diff like any other.

Read the section

A plugin host

Add a provider, model, skill, routing rule, tool, role template, pipeline or panel without editing the program. A bad plugin becomes a row with an error, never a dead office.

Read the section

Office space that grows

One generated floor per organisation, assembled from a block kit and extended when the roster outgrows the desks. The plan is deterministic: a reload never moves the building.

Read the section

SQLite persistence

The org chart, every run, turn, artifact and approval, plus an append-only event log, all in SQLite at DEV3D_DB through the node:sqlite built into Node 24.

Read the section

One typed wire protocol

33 client commands and 31 server events, typed end to end in the shared contracts package. The browser and the orchestrator compile against exactly the same definitions.

Read the section

Memory the office keeps

Facts written down by hand — conventions, decisions and pitfalls — with a supersession ledger that never overwrites and a correction that cannot widen a fact’s scope. Nothing is derived from a transcript.

Read the section

MCP servers as tools

Point the office at Model Context Protocol servers and their tools become tools your employees can hold, published as mcp__<server>__<tool>. Granted to nobody until you name the roles, and the first call asks a human.

Read the section

Vendors, docked not employed

Engage Codex, DeepSeek Harness, Hermes or anything you can name a command for as a read-only contractor. Each is published as agent__<id>__delegate and shown in the office as a rented terminal, not a person.

Read the section
Model routing

The cheapest model that can do the job

A one-line intake summary and an architecture review are not the same purchase. dev3d routes them to different models, inside a band the role declares.

A policy per role

Every role carries a ModelPolicy: a default tier, per-task-class overrides, a hard minimum and maximum, and an escalation threshold. A summarisation role cannot be talked into a frontier call.

A complexity estimate

Deterministic and observable: the stage kind, how much text the turn must digest, whether it touches files, how many revision passes have already failed to settle it, and whether the brief names a known-hard problem such as concurrency, migration, security, protocol or idempotency.

The transcript records the estimate it used.

A posture you can flip

Three postures — cheap, balanced, quality — switchable for the whole installation at runtime from the office itself. The hard bounds mean a cheap posture cannot silently downgrade the plan.

The tier bands

Five bands, cheapest first. A role declares a default and hard bounds; the router walks up from the cheapest model that satisfies both the band and the complexity estimate, returns ordered fallbacks, and records why it rejected each model it passed over.

Tier bands and what lands in themcheapest capable first
TierTypical workCapability this band coversBehaviour under a posture
nanoIntake and classificationReads a brief, sizes it, picks a pipeline. The cheapest call in the run, and the one that shapes every call after it.Cheap in every posture.
smallSummarise, extract, research turnsDigesting text and reporting what it says. Volume work where many cheap calls beat one expensive one.Stays cheap even under the quality posture unless the text names a known-hard problem.
standardImplementation turns and QAWrites and edits files, calls tools, runs a bounded tool loop. Has to be capable with tools, not necessarily the largest model.The middle of the walk. Balanced lands here; quality escalates past it on a failed revision pass.
strongFile-level planningDecides which files change, in what order, and what would falsify the approach. A wrong plan is the expensive mistake in the run.Escalates under quality. Bounded below by the role policy, so a cheap posture cannot talk it down.
frontierArchitecture review, contested rulingsThe calls where the difference between good and merely plausible is the whole product.Reachable only where a role declares it and the budget still allows it.

What the router decides

Providers with no key are catalogued so the UI can show them, but excluded from the routing pool in live mode.

  • The chosen model, plus ordered fallbacks for when it fails.
  • The reasoning for every model the router rejected, attached to the turn.
  • Provider adapters for OpenAI-compatible endpoints, Anthropic, and the scripted mock provider.
  • Plugin-contributed routing rules, which reorder candidates and can never override the band.
  • Budget awareness: the remaining run budget is an input to the decision, and exceeding it halts the run.
A role policy, in fulltyped
ModelPolicy {
  defaultTier: 'standard',        // where this role starts
  minTier:     'small',           // the router may not go below
  maxTier:     'strong',          // ...or above, whatever the prompt says
  escalateAfter: 1,               // failed revision passes before it climbs
  byTaskClass: {                  // per-class overrides
    intake:    'nano',
    summarize: 'small',
    plan:      'strong',
  },
}
A plugin can contribute routing rules, a model, or a provider — all validated data. A rule scoped to one task class does not touch any other stage, and the band still wins whenever the two disagree.

Correcting a price

Settings → Models overrides a model’s tier and its per-million prices for the whole installation. The registry reads the overrides on every use, so a correction changes routing and cost reporting on the next turn. An override is a patch, so the fields it does not mention keep the catalog’s values, and it is validated against the live catalog: a tier that is not a tier is refused, and an entry for a model that no longer exists is dropped and logged rather than left behind in the settings document.

127.0.0.1:8787
The Routing and cost page: the model catalog with providers and prices, and the spend attributed to each employee.
Models, prices, providers, and what each employee has spent. The router reads real prices from this catalog, including the ones a plugin contributes.
Composition

Four stage modes for four shapes of work

A pipeline is the shape of the conversation a brief travels through. Each stage inside it declares a mode, which decides how many calls happen, whether they overlap, and who is allowed to disagree with whom.

single1 call

One employee, one turn.

One employee, one turn. Intake uses it, and so does any stage whose question has one owner.

One model call. The router still prices the turn and writes its reasoning to the transcript.

parallelN calls

Everyone listed works at once, bounded by DEV3D_MAX_CONCURRENCY.

Each employee gets their own knowledge snapshot, and results merge in role order afterwards. Ordering is deterministic, so the same stage produces the same merged result regardless of which call finished first.

N concurrent calls, each routed independently. Research runs this way.

debateN calls

Positions, then rebuttals over N rounds, then the facilitator rules.

The facilitator records the decision and the position that lost. The stage emits one attributed speech event per speaker per round, which the office renders as that employee speaking in turn — the attribution is the point, and nobody is sent to the meeting room: a debate moves no one.

N × rounds calls plus the ruling.

review-loopN calls

Reviewers critique in parallel; the chair synthesises a verdict.

If the verdict objects, the employees who actually wrote the files revise. Looping is bounded by a hard iteration cap.

Reviewers are parallel, revisions are serial.

The three shipped pipelinesdeclarative data
PipelineStagesShipped for
product-build10End-to-end work: research, debate, plan, build, review.
code-change7A change to an existing codebase, where reading comes before writing.
quick-answer3A question with a bounded answer.
A pipeline is data, not code, so a plugin can contribute one and an operator can build a new one without touching the engine. Contribution is installation-wide: a plugin cannot know which floors exist.

What accumulates across a run

Knowledge is threaded forward: stage summaries, artifacts, the files that were written and who wrote them. A reviewer therefore sees the real files, and a report sees the decision the workshop actually reached.

What happens when a stage fails

A stage that produces nothing halts the run unless it is marked optional. An optional stage that fails is recorded and the run continues. Exceeding the budget always halts. A tool that fails returns an actionable message rather than throwing, so the employee can correct course instead of dying.

Approvals always resolve

Denied, answered, or timed out as refused. A run never waits forever on a human who has gone home. The approval callout floats top-centre in the office.

Tools and confinement

Fifteen built-in tools, and one path resolver

Every path argument a tool is given funnels through a single resolveInWorkspace choke point, and the root it resolves against is the workspace of the run being served. Eight of the fifteen take paths; run_shell and git are not confined and are approval-gated instead.

The fifteen built-in toolsworkspace-confined paths
ToolAccessWhat it does
thinkinternalA private scratchpad note. Writes nothing, moves nothing forward.
todo_writeinternalThe run’s working plan. It changes the run rather than the workspace, survives stage handover, and every call sends the whole list.
list_dirreadA directory as a small tree, confined to the workspace.
read_filereadRead a UTF-8 file with line numbers, paged, confined to the workspace.
search_filesreadOne-directory regex search, kept for compatibility with existing skills.
globreadFind files by path pattern, newest first.
grepreadSearch file contents by regex, with context lines and include/exclude filters.
write_filewriteCreate or overwrite a file in the workspace, making parent directories.
edit_filewriteReplace one exact literal string in a file.
apply_patchwriteSeveral exact-text edits across files, applied atomically.
run_shellapprovalRun a command. Not confined — cwd is a starting directory, not a boundary — so it is approval-gated, and its child environment is stripped of credentials.
gitapprovalInspect a repository without asking (status, diff, log, show, blame). add, commit, checkout -b, stash push, cherry-pick and tag go through a human. Destructive or hook-skipping commands are refused outright.
web_searchreadSearch the web for a fact the workspace does not hold.
web_fetchreadFetch one public http(s) URL and return it decoded as text.
recallreadSearch what the office has written down: conventions, decisions and the traps that already cost somebody time.

One path resolver

Every path argument funnels through resolveInWorkspace, which rejects .., absolute paths outside the root, Windows drive-relative tricks like C:foo, NTFS alternate data streams and reserved device names — and then resolves the real path and refuses any symlink, junction or mount point on the way out.

The root is the run’s workspace

Two projects can be worked on at once and neither can see the other’s files. The path is frozen onto the run record at submit time, so the record stays truthful after a floor is renamed.

run_shell and git are the exceptions

Neither is confined: a shell’s cwd is a starting directory rather than a boundary, and git runs against the repository. Both are approval-gated instead, split by what a command can do, and both spawn with a child environment scrubbed of credential-named variables.

Plugin and MCP tools are not confined

A plugin tool runs in the orchestrator’s own process, and an MCP tool’s arguments go to a separate server’s process verbatim. Neither consults the workspace resolver, and this page says so rather than implying a boundary that does not exist.

Grants are per employee

The org chart decides who may hold which tool. An employee reaching for a tool it was not granted is refused with a message it can act on. A plugin’s declared permissions are enforced for registering a tool and for subscribing to the event stream.

The network is address-guarded

web_fetch and web_search refuse loopback, private, link-local, CGNAT and cloud-metadata addresses on every redirect hop, and cap the body at 2 MB. DNS rebinding is a documented residual, not a closed hole.

Tool output is fenced, not trusted

Results are stripped of terminal control sequences, bidirectional overrides and zero-width characters, then wrapped in an untrusted-content marker before a model reads them. A hostile README is read as data, not as instructions.

A failed tool returns a message

Tools return ok: false with an actionable reason. The employee reads it and adjusts, so a bad guess costs a wasted turn instead of a dead run.

A boundary, with no operating-system sandbox

The eight path-taking tools cannot leave the run workspace. The workspace is a normal directory on the machine, readable by whoever can reach the UI and by anything else running as the same user. There is no operating-system sandbox around the orchestrator, which is why run_shell, git and any extension tool are gated or disclosed rather than contained. The local HTTP API is unauthenticated: cross-origin writes from a browser are refused, but any process on the machine that can reach the port can change settings.

The company

Thirteen roles, eight departments, three pipelines

The org chart is data, not code. Each floor owns its own company — its departments, roles, skills, pipelines and budget.

Departmentsper organisation
ExecutiveCEO, CTO

Owns the objective and the file-level plan.

ProductProduct manager, business analyst

Turns a vague request into something with edges.

ResearchResearcher, analyst

Reads the real files and reports what they say.

DesignDesigner, UX reviewer

Argues about shape before anyone writes code.

EngineeringDevelopers

Writes real files into the run workspace.

QualityQA engineer, security reviewer

Paid to falsify, not to agree.

OperationsDevOps engineer

Owns build, environment and release concerns.

CommunicationTechnical writer

Turns what was built into something readable.

A hierarchy

A run has a single owner of the objective (the CEO), a single owner of the plan (the CTO), and employees who are accountable for a bounded piece of it.

Every floor is fully staffed

A new organisation opens as a copy of the shipped company, every skill enabled and the installation’s default budget. Switching a skill off for a floor also trims it from every role holding it, so a role can never reference a skill its organisation has disabled.

Two layers

Installation — one per office
Providers, model catalog, concurrency, approval policy, where new floors are created.
Organisation — one per floor
Company, departments, roles, skills, pipelines, budget and directory.
Skills

Fifteen markdown files, selected per turn

A skill is a markdown document on disk. The loader reads the directory at boot, the index is searchable from the office, and each turn selects only the skills the task needs — so a research turn is not carrying the release checklist around in its context.

  • Loaded from disk at boot, so writing a skill is writing a file.
  • Selection is per turn: the task class and the stage decide which documents are attached.
  • A floor can enable or disable a skill like any other resource, and disabling one trims it from every role that held it.
  • A plugin can contribute a skill, merged into the office catalog exactly like a built-in one.
skills/example.mdYAML front matter + body
---
id: workspace-discipline
name: Workspace discipline
summary: Read before writing, and say which file you read.
tags: [engineering, process]
---

# Workspace discipline

Read the file you are about to change. A plan built from a
signature is a guess with better formatting.

## Rules

1. read_file before edit_file, every time.
2. Quote the line you are changing in your reasoning.
3. If two files disagree, say so instead of picking one.
4. Never widen the change to fix an unrelated problem —
   record it and move on.
The body is the prompt. The front matter is what the index and the per-turn selector read, so a skill can be reviewed, diffed and version-controlled like any other file in the installation.
Plugins

Eight kinds of contribution

A plugin is how the office gains a provider, a model, a skill, a routing rule, a tool, a role template, a pipeline, a settings form or a console panel without anyone editing the program. The host publishes eight contributes keys, and settings is a top-level manifest field rather than one of them.

What a manifest can contributeall live
providersA whole provider: adapter kind, base URL, and the name of the environment variable holding the key. https only, or keyless http on loopback.
modelsMerged into the provider registry catalog, so the router can pick it.
skillsMerged into the office skill catalog; a floor enables one like any other skill.
routingRulesApplied by the router to reorder candidates, scoped by task class.
toolNamesCode plugins only. Registered, namespaced, and grantable per role — each receives the same workspace root as every built-in tool.
roleTemplatesOffered in the hire form alongside the floor’s own roles.
pipelinesOffered to every floor.
settingsRendered as a form and coerced against the manifest on every write.
uiPanels / panelsA console surface declared as a closed set of widgets — either a fixed body, or a source URL the server fetches, validates and caches. No panel ships JavaScript.
eventsCode plugins only. A subscription to the run lifecycle.
Two kinds of plugin
DeclarativeCode
Shipsplugin.jsonplugin.json + entry
Can addModels, skills, role templates, pipelines, routing rules, panelsAll of that, plus tools and event subscriptions
Costs youNothing. It is data.It runs in the orchestrator’s process with its authority

The rules the host enforces

  • A bad plugin is contained. A manifest that will not validate, a module that throws on import, or one that fails halfway through activate() becomes a row with status: error and the message. It never stops the office booting or the other plugins loading.
  • A missing id or an incompatible apiVersion is refused. A typo in one model entry costs you that entry and is reported — not the whole plugin.
  • Contributions are recomputed, never patched. Disabling a plugin withdraws its models, its skill from every floor that had it enabled, its tools and its routing hints.
  • Tool names are namespaced, so dev3d.office-echo registering echo becomes dev3d_office_echo_echo. Two plugins cannot collide with each other or with the built-ins.
  • Routing rules are preferences, not overrides. A rule can reorder the candidates the router already considers; it can never make the router pick a model that cannot do the job.

What a plugin still cannot do

  • Reach the browser. No panel ships JavaScript, so a marketplace plugin cannot touch the page, the socket or the session.
  • Shadow a built-in provider. The first declaration wins and the DEV3D_* ones are always first, so .env stays authoritative over a manifest.
  • Ship a credential. A manifest names an environment variable; the server reads it, and a bundle never carries a secret.
  • Be routed to when it is unreachable. A model on a provider with no key is catalogued but excluded from the routing pool in live mode.
  • Be unloaded from memory. Disabling a code plugin withdraws its contributions and calls deactivate(), but Node cannot unload an ES module, so the module stays resident until restart.
127.0.0.1:8787
The Plugins page: the plugin host with its API and version, the installed plugins, and the contribution surfaces and permissions each one declares.
What extends the office: installed plugins, the surfaces each one contributes to, the permissions it asks for and its settings — all without a restart and without editing the program.

Installing is gated off by default

DEV3D_ALLOW_PLUGIN_INSTALL is false. The Install button explains that it is switched off rather than failing silently.

A bundle is treated as hostile input

The tar reader is written against POSIX ustar and refuses absolute paths, .., links, device nodes, GNU long names, base-256 sizes and truncated entries, with hard caps of 32 MB and 2048 files. Extraction is staged, then moved, so a failed install leaves nothing behind.

Updates are asked for, never pushed

The operator asks every registered marketplace what it is offering. An unreachable marketplace is reported, and an upgrade never replaces a plugin that ships with the office.

Generated office space

One floor per organisation, and it grows

The office is a GLB model in the browser. Employees are clones of a generated avatar figure seated at named desks, recoloured from the role, changing colour by status — and an idle one gets up and walks a route sampled from the floor geometry rather than an authored path. A debate is a speech event attributed to each employee, not a walk to the meeting room. Underneath, a floor is a core office plus a jigsaw of room modules that extends itself as the roster grows.

  • The loaded GLB is a template. Every organisation gets a clone raised to its own floor, 4.2 m per storey, with seat anchors indexed per floor group so employees are placed in world space against their own floor.
  • Employees are placed by looking up the GLB node named in role.seatId, never by hard-coded coordinates. An employee with an unknown or null seat is parked on a bench row and the office names them rather than silently dropping them.
  • Each avatar is a clone of a generated GLB figure, recoloured from the role — body colour, accent colour and a height clamped to 0.8–1.3 — with a procedural fallback if the asset is missing. They bob while idle, pulse while thinking, type while working, and turn toward the room while talking; an idle one gets up and walks a route sampled from the floor geometry. prefers-reduced-motion is honoured, and it stands the walking down.
  • Only the floor you are looking at shows walls. The others keep a thin coloured plate.
  • Space grows itself: after every hire, and again at boot, a floor is reconciled with its roster and builds until there are enough desks. A floor whose team grew while the office was down catches up on the next start.
  • Growth extends a floor without reshuffling the rooms that were already there, and removing one always removes the newest. The result is deterministic: the same floor with the same kit always grows the same way, so a reload never moves the building.
The block kit24 modules
ModuleSizeSeats
pod48 × 6 m4
office28 × 6 m2
meeting68 × 6 m6
lounge38 × 6 m3
junction6 × 6 m0
corridor8 × 3 m0
portal1.9 × 0.5 m0

The shipped kit is 24 modules across six categories. The seven above are the ones the core office needs to exist; the rest are the rooms a floor grows into — focus pods, a library, a workshop, a server room, a gallery and more.

A module attaches through a doorway port with an outward normal, and the module must have a doorway facing back at the wall it joins. A port that would collide is spent, not squeezed. Nothing overlaps, nothing ends up inside the core, and growth is deterministic.
Blender-scripted asset21 seats · 13 desks · 7 room anchors

The office asset is scripted in Blender: the shell with real doorway openings, the furniture and every anchor, then the block kit in a separate pass and a separate file. The furniture script checks the anchor contract: 21 seat empties, 13 desk empties, 7 room anchors, and no mesh carrying a reserved prefix.

The loader finds anchors by prefix, so a chair part named Seat_Meeting_01_Seat becomes somewhere to put an employee, and a desk whose parts were all suffixed with Desk_ is a desk that deskNameForSeat can never find. Both cases fail the build.

127.0.0.1:8787
The 3D office alone with no panels: a floor plate of rooms, glass partitions and desks.
The stage on its own. A floor is generated from a block kit and extends itself when it runs out of desks, 4.2 m to a storey.
Persistence and mock mode

A real database with zero dependencies, and a mode that costs nothing

Persistence is built on the SQLite that ships inside Node, and a scripted provider runs the entire pipeline with no API keys at all.

PersistenceDEV3D_DB

node:sqlite is built into Node 24, so dev3d has a real database with zero dependencies. What is stored: the org chart, every run, turn, artifact and approval, plus an append-only event log.

An event is persisted before it is broadcast, so a reconnecting client replaying from the log sees a superset of what it had, never a gap — and the loadRun command is enough to rebuild a transcript the browser never watched.

If the database cannot be opened the office still boots and runs. It forgets everything on exit, and it says so at boot. Losing history is bad; refusing to start is worse.

Where it lives is environment-only: the path has to be known before the database can be opened. Provider API keys are environment-only too: they are never exposed to the browser.
mock modeno keys required

With no provider configured the server boots in mock mode. The entire pipeline still runs: employees are scripted instead of billed, the office is fully demonstrable, and nothing reaches a paid endpoint.

/api/health reports the active mode and the UI badges it. DEV3D_LLM_MODE forces the answer: live requires real providers, mock uses the scripted ones even when keys are present.

What mock mode cannot tell you is how a real model behaves.
The wire protocol

One socket, typed at both ends

The console talks to the orchestrator over one WebSocket, using the same commands a script would, against types the orchestrator and the browser both compile from.

Client commands
33
26 of them served on the socket; the rest over HTTP
Server events
31
every one a typed variant \u2014 one is never emitted
  • On connect the server sends hello with the whole OfficeState, so a console never has to reconstruct the world from a series of deltas.
  • The store implements every ServerEvent, and the switch’s default branch asserts never.
  • Subscriptions are per-slice: a streaming turn delta re-renders only the streaming turn.
  • The socket reconnects with backoff and jitter and adopts the fresh hello wholesale.
  • A small read API sits alongside it: /api/health, /api/state, /api/runs, /api/runs/:id with persisted turns and artifacts, /api/skills, /api/models, /api/providers, and POST /api/submit, /api/chat and /api/plan.
Driving it without the UIhttp + ws
curl -s localhost:8787/api/health

curl -s -X POST localhost:8787/api/submit \
  -H 'content-type: application/json' \
  -d '{"brief":"Fix the null dereference in the
       session lookup and add a regression test"}'

curl -s localhost:8787/api/runs/<runId>

Two conversation-shaped commands

chat is a direct message to one employee, and it comes back broadcast as direct.message.

plan shapes a brief that has not been commissioned, so its answer is pushed to the asking socket only. A plan carries its own history and is otherwise stateless on the server: nothing is commissioned, so there is nothing to persist.

Nothing on this socket authenticates. Bind it to localhost.

What it does not do

The limits

Each of these is a known, written-down gap. Some are deliberate trades, some are unfinished work, and each one says which.

No authentication

Nothing in the orchestrator authenticates. Every route and the socket are open, so run it bound to localhost. Putting it on a network is your call and your risk.

The workspace root is the boundary

Anything reachable by whoever can reach the UI can read the workspaces root. Confinement keeps the agents inside the run workspace; it does not keep people out of it.

activate() is not sandboxed

A code plugin runs in the orchestrator process with the orchestrator’s authority. Its permission list is a consent record shown to the operator, not a runtime gate.

A code plugin cannot be unloaded from memory

Node cannot unload an ES module. Disabling a code plugin withdraws its contributions and calls deactivate(), but the module stays resident until restart.

A plugin cannot ship browser code

Panels are a closed set of widgets, and a live panel is fetched by the server so the browser never learns the plugin’s URL.

Direct messages carry no tools

A conversation outside a pipeline routes on the role’s default tier and answers from the model alone. It cannot read the workspace, and the reply says so.

Prices are routing estimates

The catalog prices exist so routing and display can be ordered plausibly. They are not billing truth, and Settings → Models is where you correct them.

Runs do not move between floors

workspaceId is frozen at submit time, so the run record stays truthful after a rename. Closing a floor does not migrate its history anywhere.

And the ones that are simply unfinished

  • The office has been looked at only on a synthetic camera path. Headless Chrome rendered it and found three real bugs, but lighting balance, storey height and fog are all first guesses rather than judged composition.
  • A run cannot be moved between floors. workspaceId is frozen at submit time, and closing a floor does not migrate its history anywhere.
  • At twenty-four modules the framing is wide enough that an individual desk is small, and there is no room-by-room navigation.
  • The planner builds in one style. Modules are tried least-used-first so a floor gets a pod, an office, a meeting room and a lounge rather than nine pods, but every chain is still axis-aligned and the kit has no corner or diagonal piece.
  • The regenerated office asset is a reconstruction. It asserts the anchor contract — 21 seats, 13 desks, 7 rooms — and every part of it is now a bevelled, smooth-shaded shape with a ribbon of windows along both long elevations, so it draws about 84,000 triangles from roughly a hundred shared meshes. It is still not the hand-authored original, which ships alongside it for comparison.
  • Updates are found, not pushed. Nothing polls a marketplace on its own, there is no downgrade, and there is no pinning to a version, so a bad release can only be answered by disabling the plugin.
  • The marketplace is a contract rather than an application. A catalog document, a bundle URL and a sha256 are the whole integration, and any static host that serves that JSON is a marketplace — this site is one implementation of it, not a required part of dev3d.

The fastest way to check any of this is to run it

Node 24, pnpm install, then the mock pipeline. No API keys, no spend, and you can point it at a real provider once you have seen the shape of the work.