2.18 · LuckyEditor-Agent
Editor-side agent functionality (LLM orchestration, tool dispatch, MCP bridges, chat panel) packaged as its own static library, with a plugin-shaped public surface so it can later be lifted out of the editor without re-architecture.
Overview
Editor-side agent functionality lives in its own static library, LuckyEditor-Agent, at LuckyEditor-Agent/. New code uses namespace LuckyEditorAgent for the public surface; legacy types still being moved sit in Hazel::. The lib links into LuckyEditor today, but is deliberately shaped so the same code can later be lifted into a plugin (SharedLib) with no further re-architecture. The public header <LuckyEditorAgent/EditorAgent.h> is already plugin-shaped — a single class with an opaque pimpl.
Public surface
| Header | Role |
|---|---|
<LuckyEditorAgent/EditorAgent.h> |
The editor instantiates one of these in Application::OnInit and drives the Construct → Attach → use → Detach lifecycle via Attach(host, config) / Detach(). Owns the MCP server, harness / tool bridges, and the AgentChatPanel registration via the host. |
<LuckyEditorAgent/IEditorAgentHost.h> |
Abstract dependency surface implemented by EditorLayer today (alternative hosts on the roadmap: headless app, plugin host, test fixture). 14 virtuals for scene / viewport / panel queries, scene + project mutations (NewEmptyScene, OpenSceneAtPath, … renamed from EditorLayer defaults to avoid override-vs-default-arg ambiguity), and editor-wide actions (ReloadCSharp, SetAutoDismissWelcome, HarnessEnterPlayMode / ExitPlayMode). |
EditorAgentConfig (in EditorAgent.h) |
Small POD: MCP TCP / HTTP ports and a welcome-dismiss flag. |
Internal layout
Under LuckyEditor-Agent/src/LuckyEditorAgent/:
| Path | Role |
|---|---|
EditorAgent.cpp | Façade impl. Pimpl owns HarnessBridge, ToolBridge, and the Hazel::Bridge::McpServer instances; binds bridges to the host. |
HazelAgentInterface.{h,cpp} | Front-of-house tool dispatch: ~30 Get* / Set* / *Property entry points the LLM can call. Holds a single IEditorAgentHost* for editor access. |
LLM/AgentClient.{h,cpp} | Conversation orchestrator. Sibling files: StreamingHttpClient (libcurl SSE), ToolRegistry, LocalClaudeRunner, LocalClaudeExecutable (optional local-Claude subprocess backend), AgentEvent.h. |
Get/, Set/, Set/PropertySetters/ | Actual tool implementations: entity CRUD, components, properties, prefabs, materials, screenshots. |
Panels/AgentChatPanel.{h,cpp} | EditorPanel subclass; the chat UI. Still in namespace Hazel (registration via EditorPanelIDs::Agent in EditorLayer.cpp). |
McpAdapters/HarnessBridge.{h,cpp} | Registers the harness/* surface (drive the in-editor agent end-to-end). Bound to IEditorAgentHost*. |
McpAdapters/ToolBridge.{h,cpp} | Registers tool/<name> (mirrors every ToolRegistry tool for direct testing). |
MainThreadDispatcher.{h,cpp} | Background-thread → main-thread marshaling, drained by EditorLayer::OnUpdate. Used by every MCP tool handler. |
AgentHelpers.{h,cpp} | UUID parsing / JSON formatting helpers. |
Build-system shape
LuckyEditor-Agent/premake5.lua is kind "StaticLib". Includes Net/Hazel-Bridge/Source plus the standard Hazel / imgui / mid / simdjson / SimdjsonBridge / base64 vendor directories.
A transitional includedir on LuckyEditor/src remains, used in exactly one place: Viewport/Viewport.h, because the editor's Viewport class lives there and IEditorAgentHost::GetMainViewport() returns a Hazel::Ref<Hazel::Viewport> that agent-side tools dereference. Future cleanup either moves Viewport into Hazel core or hides the viewport-using ops behind the host interface.
Dependency direction
LuckyEditor-Agent depends on Hazel-Bridge and Hazel. Hazel-Bridge depends only on Hazel. The arrow from LuckyEditor-Agent back to Hazel-Bridge in the diagram is the registration edge: bridges plug into the live McpServer.
Resources
LuckyEditor/Resources/AgentDocs/ holds the knowledge-retrieval markdown corpus the agent loads at runtime. It is resolved via current_path() / "Resources" / "AgentDocs", so it sits on the editor binary side rather than inside the static lib — the lib provides loader logic, the editor binary owns its resources. Same split that should make the eventual plugin migration painless.
Pitfalls
Engine code (Hazel, Hazel-Bridge) must not include editor or agent headers. The single transitional include into LuckyEditor/src is a known wart; do not add more. Any new editor-only type that agent code needs should go behind IEditorAgentHost.
Every MCP tool handler enters on a connection-worker thread (see § 2.17). Touching scene, NVRHI, or ImGui from there is a bug — go through MainThreadDispatcher, which EditorLayer::OnUpdate drains each tick.
Extending
- New LLM-callable tool — add a handler under
Get/orSet/, register it onToolRegistry;ToolBridgewill automatically mirror it undertool/<name>. - New harness operation — register in
HarnessBridge. UseMainThreadDispatcher::Executefor any engine touch. - New host (headless, plugin, test fixture) — implement
IEditorAgentHost's 14 virtuals, construct anEditorAgent, callAttach(host, config). No further hooks needed. - Move the lib into a plugin — flip
premake5.luatoSharedLib; the public surface is already designed to survive that.