Tier 2 · AI Core
Shared Capability

AI Companion

dcc/maya/ai_panel.py — a dockable natural-language-to-reviewable-Python-script panel (AIPanel, WINDOW_OBJECT_NAME = "CF_AICompanionWindow") any application can plug into. Launch it standalone from Utilities > AI Companion, or have another application open and bind it to its own curated API (see Embedding in another app — AnimHub's ClipsAPI integration, documented fully in ClipsAPI, is the worked example). This is a shared capability, not a separate autonomous agent - one panel, reused by every application that wants it, each one scoping it down to its own domain.

Its Maya-menu/QuickLaunch icon sits next to its launcher module, separate from its in-app window/button icon (also used by AnimHub's own "Ask AI" corner toolbar button).
Design goals
1
Problem

A general-purpose coding assistant with unrestricted access to a live production scene is a genuine risk - it can hallucinate a plausible-looking but wrong API, misuse a real Maya command in a way that silently corrupts data, or simply guess at functionality that doesn't exist. At the same time, forcing every request through hand-written code defeats the entire point of having an assistant.

2
Architectural decision

Split the problem in two. First, a hard process boundary: the model only ever proposes Python - a human reads it and clicks Run before anything executes, wrapped in one undo step. Second, a narrower, complementary boundary: each embedding application hands the model a small, tested, curated API scoped to its own domain (plus a short primer on how to use it) instead of raw cmds - so the common, expected actions go through a verified path, and the specific correctness traps a model can fall into by improvising are closed off before they happen.

3
Production benefit

Every application gets natural-language assistance for free, without writing a single line of AI integration code beyond its own curated API - and every time a new failure mode is found in real use, the fix extends the tested surface rather than hoping different prompt wording holds up next time. See ClipsAPI's documented history for concrete examples of exactly that pattern in practice.

Your request Model proposes Python only - nothing runs You review and click Run Curated API never raw cmds

The human review step before Run is the entire safety mechanism - not a sandbox. The curated API is a separate, complementary layer that keeps common actions on a tested path.

The safety model

The model never touches the Maya scene directly:

  1. You type a request.
  2. The model returns text only — an explanation plus a proposed Python script.
  3. You read the script.
  4. Clicking Run executes it, wrapped in a single Maya undo chunk (so one Ctrl+Z reverts everything it did).

That review step is the entire safety mechanism — there's no sandboxing or restricted cmds allow-list underneath it. See ClipsAPI's Safety model section for the full mechanics (core/ai/execute.py's run_generated_code) and why a curated API object matters even though it isn't a hard sandbox boundary.

UI layout

Header

Provider/model status (a small dot + text, same "connected/offline" language as the P4 status bar), and, when the panel has been bound by an embedding app, a green "· AnimHub connected"-style indicator. This exists specifically so you can tell at a glance whether this particular panel instance actually has the curated API bound. Split across two rows rather than one wide row, specifically so the panel can dock narrow without cropping.

💬

Conversation log

A scrolling transcript (You / AI / System messages), with a faint centered watermark that fades once a real conversation starts.

📄

Proposed Script card

Appears once the model responds with code; read-only code view plus Run (undoable) and Discard buttons. Hidden entirely when there's no pending proposal.

Input row

A multi-line text box (plain Enter sends, Shift/Ctrl+Enter inserts a newline) plus Send and a Settings gear (opens Settings straight to the AI page).

The window can be resized down to 280px wide / 580px tall without cropping — deliberately narrow-dockable, since a Maya dock column is often much narrower than a comfortable floating-window default.

Script History

Opened via the Script History button in the header. Records every script actually executed (Run clicked, or Re-run from this dialog) — not scripts that were only proposed and then Discarded, and not just successful ones either, since a failed attempt is often exactly what's worth studying later.

Persistent across Maya sessions, not session-scoped — reverted from an earlier in-memory-only design once it became clear that losing everything the moment the panel closed made it useless as a "what did I actually run last week" reference. Backed by core/ai/script_history_store.py: a plain append-only JSON Lines file in a local per-user data directory (the same convention the application log file uses). JSON Lines specifically because it needs only an O(1) file append for a new entry (no read-modify-write of a whole array), and a single malformed/truncated trailing line (e.g. Maya closing mid-write) doesn't lose every entry before it.

Each entry: timestamp, the original request text, the model's explanation, the code, whether it succeeded, and the failure message if not.

Per-entry actions:

Embedding in another app

AIPanel.__init__ accepts three optional hooks, and set_context() lets an embedding app rebind them after construction — necessary because show_dockable() reuses an already-open panel instead of reconstructing it, so a constructor-only kwarg would only ever take effect on the very first launch:

def _on_ask_ai(self):
    window = ai_panel.show()
    if window is None:
        window = dcc_ui.find_dockable_instance(ai_panel.AIPanel)
    if window is not None:
        window.set_context(
            context_provider=self._ai_context_summary,
            extra_namespace_provider=lambda: {"clips": ClipsAPI(self.clips)},
            on_run_complete=self._rebuild_rows)
AI-run code mutates the embedding app's live data model directly (the same object its own UI reads from), but nothing else tells that UI to redraw — a real reported bug was clips.create(...) genuinely succeeding (confirmed by re-querying clips.list()) while the newly created clip never appeared in AnimHub's row list, because on_run_complete had been accepted by the constructor but never actually wired through set_context() until this was fixed. If you embed the panel, always pass this, even if your data model looks like it "just updates automatically" — it doesn't, unless something explicitly asks the UI to rebuild.

All three default to None and are safe to omit — a plain, unbound AIPanel runs in "Full Scene" mode: just cmds, no context primer, no completion hook.

Provider / API key setup

Settings > AI (or the gear icon in the panel header) configures the provider (Anthropic / OpenAI / Gemini), model, and API key. Keys are stored in the OS keychain via core/ai/key_storage.py, never in a config file. If no key is set when you send a request, the panel prompts for one inline; keyring must be installed in Maya's Python for this to work at all (mayapy -m pip install keyring if the panel reports it's missing).