<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Arturo</title>
    <description>The latest articles on Forem by Arturo (@arangel).</description>
    <link>https://forem.com/arangel</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3919055%2Ff56594b0-d976-4222-a942-ca91499f69d4.png</url>
      <title>Forem: Arturo</title>
      <link>https://forem.com/arangel</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/arangel"/>
    <language>en</language>
    <item>
      <title>BowerBot: Building an LLM Agent for OpenUSD</title>
      <dc:creator>Arturo</dc:creator>
      <pubDate>Fri, 08 May 2026 03:32:40 +0000</pubDate>
      <link>https://forem.com/arangel/bowerbot-building-an-llm-agent-for-openusd-46n6</link>
      <guid>https://forem.com/arangel/bowerbot-building-an-llm-agent-for-openusd-46n6</guid>
      <description>&lt;p&gt;I run &lt;a href="https://binarycore.us" rel="noopener noreferrer"&gt;Binary Core LLC&lt;/a&gt;, a small consultancy that builds OpenUSD and AI tooling for visualization, digital twin, and robotics teams. Over the past several months, we've been building &lt;a href="https://github.com/binary-core-llc/bowerbot" rel="noopener noreferrer"&gt;BowerBot&lt;/a&gt;, an open source LLM agent that assembles structured OpenUSD scenes from natural language.&lt;/p&gt;

&lt;p&gt;This post walks through the architectural decisions behind it: why we built it FastAPI-style, how the plugin system works, why validation is core to the agent loop, and the tradeoffs we made along the way.&lt;/p&gt;

&lt;h2&gt;
  
  
  What problem are we solving
&lt;/h2&gt;

&lt;p&gt;OpenUSD (Pixar's Universal Scene Description) is becoming the universal 3D scene format. Pixar uses it. Apple ships USDZ to Vision Pro. NVIDIA Omniverse is USD-native. Adobe, Autodesk, Siemens, and Trimble all back it through the &lt;a href="https://aousd.org" rel="noopener noreferrer"&gt;Alliance for OpenUSD&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But authoring USD scenes correctly is hard. You need to manage:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Asset folder structure (root + geo + materials separated)&lt;/li&gt;
&lt;li&gt;ASWF compliance (defaultPrim, metersPerUnit, upAxis)&lt;/li&gt;
&lt;li&gt;Material binding (MaterialX or existing materials)&lt;/li&gt;
&lt;li&gt;Native USD lighting (sun, dome, point, area)&lt;/li&gt;
&lt;li&gt;Reference resolution and validation&lt;/li&gt;
&lt;li&gt;Final packaging (USDZ for Vision Pro, USD for Omniverse, etc.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most of this is mechanical work that pipeline TDs end up scripting manually for every project. We thought: this is a great fit for an LLM agent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture: FastAPI-style separation
&lt;/h2&gt;

&lt;p&gt;The codebase is organized like a FastAPI service, even though BowerBot isn't a web service. Why? Because the same separation of concerns that makes web services maintainable also makes agent codebases maintainable.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;bowerbot/&lt;br&gt;
├── schemas/        # Pydantic models for tool inputs and outputs&lt;br&gt;
├── services/       # Core business logic (USD operations)&lt;br&gt;
├── tools/          # Agent-callable tool wrappers&lt;br&gt;
├── utils/          # Shared utilities&lt;br&gt;
└── agent/          # The agent loop itself&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The schemas layer is critical. Every tool input and output is a Pydantic model. This means:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The LLM gets structured tool definitions automatically (via Pydantic JSON schema)&lt;/li&gt;
&lt;li&gt;Tool outputs are typed and validated, making them easy to chain&lt;/li&gt;
&lt;li&gt;Tests are easier because we mock at the schema boundary&lt;/li&gt;
&lt;li&gt;Adding new tools doesn't require touching the agent core&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  The plugin system: Python entry points
&lt;/h2&gt;

&lt;p&gt;This was the most contested architectural decision. We considered three options for extensibility:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Monorepo with all skills built in&lt;/li&gt;
&lt;li&gt;Plugin loading from a known directory&lt;/li&gt;
&lt;li&gt;Python entry points (the setuptools mechanism)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We went with entry points. Here's why:&lt;/p&gt;

&lt;p&gt;When someone wants to extend BowerBot with a custom skill (say, a custom asset provider for their internal DAM, or a specialized DCC connector), they create a separate pip-installable package. Their &lt;code&gt;pyproject.toml&lt;/code&gt; declares an entry point:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[project.entry-points."bowerbot.skills"]&lt;/span&gt;
&lt;span class="py"&gt;my_custom_skill&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"my_package.skills:MyCustomSkill"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When BowerBot starts, it discovers all installed packages exposing the &lt;code&gt;bowerbot.skills&lt;/code&gt; entry point and registers them. No forking. No central registry. No PR queues.&lt;/p&gt;

&lt;p&gt;This matters because BowerBot's value compounds when third parties extend it. A studio with internal asset systems can write a skill, ship it as a private package, and BowerBot becomes their tool without any code changes to BowerBot itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Validation as a core agent capability
&lt;/h2&gt;

&lt;p&gt;The biggest insight from building this: validation isn't a separate step, it's part of the agent loop.&lt;/p&gt;

&lt;p&gt;Here's the pattern:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Agent calls a tool that modifies the USD stage&lt;/li&gt;
&lt;li&gt;Agent calls &lt;code&gt;validate_stage&lt;/code&gt; to check the result&lt;/li&gt;
&lt;li&gt;Validation returns structured &lt;code&gt;ValidationIssue&lt;/code&gt; objects (not just pass/fail)&lt;/li&gt;
&lt;li&gt;The agent can call fix tools targeting specific issues&lt;/li&gt;
&lt;li&gt;Re-validate, iterate&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is a tight feedback loop. The agent doesn't just hope its output is correct, it verifies and corrects. ASWF compliance, reference resolution, material bindings, unit consistency are all checked. Issues come back with enough context for the agent to know what to fix and how.&lt;/p&gt;

&lt;p&gt;The validation logic is shared with our packaging step. Before we package a USDZ, we validate the stage. If validation fails, packaging fails with a clear error. No surprise broken USDZ files reaching Vision Pro.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multi-LLM support via litellm
&lt;/h2&gt;

&lt;p&gt;We didn't want to lock BowerBot to one LLM provider. Studios have different policies (some can't send data to OpenAI, others have Anthropic enterprise contracts, some run local models). We wrapped LLM calls through &lt;a href="https://github.com/BerriAI/litellm" rel="noopener noreferrer"&gt;litellm&lt;/a&gt;, which gives us a common interface across OpenAI, Anthropic, Google, Cohere, local Ollama, and more.&lt;/p&gt;

&lt;p&gt;This was a small architectural choice with big practical impact. When a prospect says "we use Anthropic exclusively" or "we run on local Llama," BowerBot just works.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we got wrong (and learned)
&lt;/h2&gt;

&lt;p&gt;A few things didn't work the way we expected.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First: agent loops are expensive.&lt;/strong&gt; Each iteration is an LLM call. Naively, an agent that "just keeps iterating until done" can rack up surprising token costs. We added explicit step limits and structured the agent to do as much work as possible in each tool call.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Second: tool description quality is everything.&lt;/strong&gt; Early on, we wrote terse tool descriptions because the function names felt self-explanatory. The LLM struggled to chain tools correctly. Rewriting descriptions to be richer (with examples, edge cases, and explicit input/output behavior) dramatically improved success rates.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Third: the LLM lies about what it did.&lt;/strong&gt; Sometimes the agent would claim it placed an asset successfully when the underlying tool failed silently. The fix was making sure every tool returns explicit success/failure structures, and the agent's prompt encourages it to verify with validation tools rather than trust its own narration.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;The next major release adds &lt;a href="https://openusd.org/release/api/usd_physics_page_front.html" rel="noopener noreferrer"&gt;UsdPhysics&lt;/a&gt; support: mass, inertia, collision, and articulations. This makes BowerBot useful for simulation pipelines (Isaac Sim, Omniverse PhysX, MuJoCo-Warp) in addition to scene assembly.&lt;/p&gt;

&lt;p&gt;Once that ships, BowerBot can output SimReady scenes from natural language: "set up a warehouse with 200 pallets, articulated forklift, and robotic arms at picking stations, with realistic mass distributions." That's a meaningful capability for robotics, manufacturing, deconstruction simulation, and Physical AI training.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/binary-core-llc/bowerbot" rel="noopener noreferrer"&gt;BowerBot is Apache 2.0 on GitHub.&lt;/a&gt; Recently added to the &lt;a href="https://landscape.aswf.io/" rel="noopener noreferrer"&gt;ASWF Landscape&lt;/a&gt; in the OpenUSD section.&lt;/p&gt;

&lt;p&gt;Demo videos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://youtu.be/dXYdiJi2lXU" rel="noopener noreferrer"&gt;Part 1: Building the empty scene&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://youtu.be/hG9D0YEyhvo" rel="noopener noreferrer"&gt;Part 2: Furniture, materials, and lighting&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Always interested in hearing how others are approaching USD scene authoring at scale.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>agents</category>
      <category>python</category>
    </item>
  </channel>
</rss>
