Brokkr/docs/agent-access-cut.md
Meta Vens e94dcf2187 Record Brokkr adoption rulings F1-F6; mark the parked agent-access map as history
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 13:48:48 +02:00

29 KiB
Raw Blame History

History (2026-09-30). Superseded by docs/brokkr-adoption-cut.md. The operator unparked agent access, and the adoption map re-derives these cuts against the Codex donor. Read that map; this one is kept only for its rejected paths.

Brokkr Agent Access: Cut Map

Parked (operator, 2026-09-18): "screw it, I'll do any editor shenanigans we need for now." Nothing here is started. The immediate consumer (rigging a scene in Aetheria, F:\Projects\Aetheria\docs\shield-panel-cut.md Cut 4) is the operator's hands instead. Unpark this when editor work becomes repetitive enough to be worth six cuts in this repo, or when the CultMesh MCP bridge campaign (F:\Projects\CultLib\src\GameCult.Mesh\docs\mcp-bridge-campaign.md) takes it up. The probe results below keep their value either way: no serve command, a vendored-assembly collision with CultLib, the file-poke path being dead, and no admission at all.

Status: cut map, nothing landed. Ends: an agent process on this box sends a typed brokkr.unity.command_intent.v0 to a live Unity editor, the editor executes it, and the agent reads back brokkr.unity.command_receipt.v0 plus a refreshed brokkr.unity.host_snapshot.v0. Immediate consumer: rigging scenes in F:\Projects\Aetheria (Unity 6000.3.24f1, built-in pipeline).

This is the thin slice under the named CultMesh MCP bridge campaign (F:\Projects\CultLib\src\GameCult.Mesh\docs\mcp-bridge-campaign.md). It is built so the bridge subsumes it: the seam is a typed operation invoke + receipt read over an already-hosted CultNet endpoint, which is exactly what the campaign's "operation binding descriptor" lowers. Nothing here becomes a second description of what Brokkr offers.

Written by Imagination, 2026-09-18, against Brokkr 94b9334, CultLib fa327a0.

What probing established

Names in the repo do not match the machine. These are the measured facts.

The daemon builds and is green, and is not a daemon. cargo build --workspace and cargo test --workspace pass (20 tests, 0.27s), cargo fmt --check clean, against cultcache-rs pinned at CultLib c2a9a6e5 (brokkr-daemon/Cargo.toml:10), which resolves and compiles. brokkr-daemon/src/main.rs:127-141 dispatches exactly four subcommands: provider|smoke, sync-contract, sync-once, sync-loop. There is no serve. README.md:57 tells you to run cargo run -p brokkr-daemon -- serve under the heading "Unity Smoke"; that command exits 2. The process needs no config, no port, no CultMesh node, no credentials and no Odin, because it opens nothing but two files. sync-loop is a polling scheduler over two .ccmp paths, not a service. tools/check.ps1:112 and tools/vendor-cultmesh-unity.ps1:4 both point at E:\Projects\..., a drive this box does not have.

The daemon's write path is the file, not the mesh. MirrorStore::open (brokkr-daemon/src/main.rs:1400-1404) wraps SingleFileMessagePackBackingStore on the Unity .ccmp directly and pushes envelopes into it (:1421-1443). In cultcache-rs (F:\Projects\CultLib\packages\cultcache-rs\src\lib.rs:652-700) that store is whole-file read-modify-write under a sibling .lock held with fs2. So cross-process writes to the file are safe at the file level.

But a file write does not wake the editor. The Unity mirror subscribes to node.Database.Watch<BrokkrUnityCommand>() (surfaces/unity/Packages/com.gamecult.brokkr/Editor/BrokkrCultMeshMirror.cs:44-52), which observes in-process database mutations. Nothing in the package re-pulls the backing store, and Unity's next FlushAsync(soft: true) (:68, :78, :89) rewrites the whole file from memory. An external file poke is therefore both invisible and liable to be clobbered. This is the single most load-bearing finding: the .ccmp-poke option is not viable for live command delivery.

The live path already exists and is a network one. CultMesh.StartNodeAsync forces StartServer = true (F:\Projects\CultLib\src\GameCult.Mesh\CultMesh.cs:596-602) and CultNetLocal.CreateHostAsync starts the host (GameCult.Networking\CultNetLocal.cs:146-163), so the Unity editor is already hosting a CultNet server. A remote CultNetDocumentPutRawMessage is applied into the hosted database by CultNetDatabaseServer.HandlePutAsync (GameCult.Networking\CultNetDatabaseServer.cs:236), which is exactly the mutation the editor's Watch observes. The typed client side of that message exists in C# (CultNetDocumentRegistry.CreateRawDocumentPutMessage at GameCult.Networking\CultNetDocumentRegistry.cs:212; CultNetDatabaseSubscriptionClient.cs:237). cultnet-rs has RUDP, framing, schema discovery and snapshot query, but no evidenced typed document-put client — treat a Rust caller as unproven work, not a swap.

The port is a constant. GameCult.Networking\Server.cs:26: private const int ServerPort = 3075;. Not in CultNetHostOptions (CultNetLocal.cs:13-44), not in CultMeshNodeOptions (CultMesh.cs:20-50). One Unity editor per machine can host a Brokkr mirror. Security is ServerSecurityOptions.Development() by default (CultNetLocal.cs:149).

Command delivery depends on an open window and an off-by-default toggle. The mirror is owned by BrokkrWindow, an EditorWindow. OnEnable hooks EditorApplication.update (BrokkrWindow.cs:102); OnDisable unhooks it and disposes the mirror (:110-114). The drain is OnEditorUpdate (:775-788), gated on autoPollCommands, which defaults to false (BrokkrSettings.cs:23-27), throttled to one command per second, and it dequeues exactly one command per tick (:382-393). So today: no window open, no commands; window open but toggle off, no commands; window closed mid-run, mirror gone. EditorApplication.update also does not tick dependably while the editor is unfocused, which is the normal state when an agent is driving it.

There is no scene save. Every scene-mutating action ends at EditorSceneManager.MarkSceneDirty (BrokkrUnityCommandExecutor.cs:45, 64, 92, 96, 141, 160, 175, 195, 263). Only the asset commands call AssetDatabase.SaveAssets (:219, 240, 367). An agent's whole rig lives in the editor's memory until a human presses Ctrl+S.

Addressing is sound. Object ids are GlobalObjectId.GetGlobalObjectIdSlow(target).ToString() (BrokkrUnitySnapshotBuilder.cs:183-186), carried in the host snapshot and resolved back on execute. Read snapshot, get ids, issue commands: that loop is already coherent.

The Unity package cannot go into Aetheria as-is. It vendors GameCult.Caching.dll, GameCult.Caching.MessagePack.dll, GameCult.Mesh.dll, GameCult.Networking.dll, GameCult.Logging.dll, MessagePack.dll, R3.dll and 20 more into Plugins/CultMesh/. Aetheria already resolves org.gamecult.cultlib at cultlib-unity-v1.0.60 (F:\Projects\Aetheria\Packages\manifest.json:57), whose Runtime/Plugins/ ships the identical assembly names behind a GameCult.CultLib asmdef with overrideReferences: true. Duplicate assembly names are a Unity compile failure, not a warning. The vendored copies are also stale: GameCult.Mesh.dll is 75,776 bytes in Brokkr against 675,840 bytes in cultlib 1.0.60. package.json declares no dependencies at all and pins "unity": "2022.3".

There is no admission. None. No allowlist, no policy, no caller identity, no signature, no dry-run, anywhere in BrokkrUnityCommandExecutor.cs or BrokkrCultMeshMirror.cs. Anything that can reach the endpoint gets createScriptableObject, createPrefabVariant and arbitrary serialized property writes on a live project. The receipt (BrokkrHostSnapshot.cs:215-226) carries commandId, status, message, objectId, observedAt — the executor's own account of what it did. It is evidence, not proof: no requester identity, no before/after, nothing signed.

Ordered cuts

Cut 1. Cut the stale claims

  • Repo/branch: Brokkr, agent-access from 94b9334. No dependencies.
  • First: none.
  • Deletes first: README.md:49-64 (the serve invocation and the file:E:/Projects/... manifest line). tools/check.ps1:112-118 (the E:\Projects\CultLib-main-work Blender probe branch — it is dead on this box and silently skipped, which is worse than absent).
  • Keeps and moves: nothing.
  • Adds: a README line naming the four real subcommands and saying plainly that brokkr-daemon is a CLI over .ccmp files, not a listening service.
  • Per-file changes:
    • README.md:49-64: replace the Unity Smoke section with the real install path from Cut 2 and the real agent path from Cut 5.
    • tools/vendor-cultmesh-unity.ps1:4: $cultLibRoot from E:\Projects\CultLib to a -CultLibRoot parameter defaulting to F:\Projects\CultLib.
    • tools/check.ps1:112: same, parameterized; fail loudly when absent instead of skipping.
  • Authority map: Owner: README and tools/ describe the daemon's real CLI surface. Inputs: main.rs:127-141. Outputs: operator-followable commands. Derived state: none. Forbidden writers: docs may not invent subcommands. Shared paths: n/a. Deletion line: every E:\ literal in the repo.
  • Verification:
    • tests: tools/check.ps1 runs to completion on this box with no skipped branch.
    • negative: rg -n 'E:\\\\Projects' F:\Projects\Brokkr returns nothing.
    • negative: rg -n 'brokkr-daemon -- serve' returns nothing.

Cut 2. Make the Unity package installable beside CultLib

  • Repo/branch: Brokkr, same branch. Depends on nothing; blocks Cut 5's verification.
  • First: record Aetheria's resolved hashes from F:\Projects\Aetheria\Packages\packages-lock.json:245-266 so a regression is attributable.
  • Deletes first: the entire surfaces/unity/Packages/com.gamecult.brokkr/Plugins/CultMesh/ tree (27 DLLs plus .meta files) and Plugins.meta, Plugins/CultMesh.meta. These are the collision and they are stale.
  • Keeps and moves: tools/vendor-cultmesh-unity.ps1 is retired with them; the package stops carrying its own copy of CultLib.
  • Adds: a dependencies block in package.json naming org.gamecult.cultlib at the same git tag Aetheria pins, and "unity": "6000.3" only if 2022.3 support is actually abandoned (see Q7).
  • Per-file changes:
    • surfaces/unity/Packages/com.gamecult.brokkr/Editor/GameCult.Brokkr.Editor.asmdef:4-9: references ["GameCult.Brokkr", "GameCult.Caching", "GameCult.Mesh", "GameCult.Networking", "R3"] become ["GameCult.Brokkr", "GameCult.CultLib"].
    • surfaces/unity/Packages/com.gamecult.brokkr/Runtime/GameCult.Brokkr.asmdef:4-7: references ["GameCult.Caching", "MessagePack"] become ["GameCult.CultLib"].
    • surfaces/unity/Packages/com.gamecult.brokkr/package.json:5: add "dependencies": {"org.gamecult.cultlib": "https://github.com/GameCult/CultLib.git?path=/unity/org.gamecult.cultlib#cultlib-unity-v1.0.60"}.
    • tools/check.ps1:100-111: the required-DLL assertion inverts — assert the vendored tree is absent and the asmdefs reference GameCult.CultLib.
  • Authority map:
    • Owner: org.gamecult.cultlib owns the CultCache/CultNet/CultMesh assemblies in every Unity project. com.gamecult.brokkr owns only Brokkr's document types, editor service, executor and window.
    • Inputs: the host project's resolved CultLib package version.
    • Outputs: GameCult.Brokkr and GameCult.Brokkr.Editor assemblies.
    • Derived state: none.
    • Forbidden writers: the Brokkr package may no longer ship any assembly whose name CultLib also ships.
    • Shared paths: Aetheria and any future host project resolve one CultLib.
    • Deletion line: Plugins/CultMesh/ and the vendoring script, before any asmdef edit.
  • Verification:
    • builds: add "com.gamecult.brokkr": "file:F:/Projects/Brokkr/surfaces/unity/Packages/com.gamecult.brokkr" to F:\Projects\Aetheria\Packages\manifest.json, open Unity 6000.3.24f1, and confirm a clean compile with no duplicate-assembly error in Editor.log. This is also the first real test of whether the package's source compiles against cultlib 1.0.60's API at all — the vendored DLLs were an order of magnitude smaller, so drift is likely and StartNodeAsync, CultMeshNodeOptions.EnableDurableShardLogs, CultNetDatabaseOptions.RuntimeId and node.Database.Watch<T>() each need to still exist.
    • negative: packages-lock.json gains com.gamecult.brokkr and nothing else; no new transitive package appears.
    • operator: Unity's Package Manager shows Brokkr with no errors and GameCult > Brokkr opens.

Cut 3. Move command drain off the window

  • Repo/branch: Brokkr, same branch. Depends on Cut 2.
  • First: none.
  • Deletes first: BrokkrWindow.cs:113-114 (mirror disposal in OnDisable), :102/:110 (the EditorApplication.update hook/unhook), :775-788 (OnEditorUpdate), :377-400 region's ownership of dequeue-and-execute, and the private BrokkrCultMeshMirror mirror field at :18.
  • Keeps and moves: PollAndExecuteCommand's body moves into the new service unchanged in behaviour except for the batch rule below. The window keeps its buttons, which now call the service.
  • Adds: Editor/BrokkrEditorService.cs, [InitializeOnLoad], static, owning one BrokkrCultMeshMirror for the editor's lifetime, hooking EditorApplication.update once, draining all queued commands per tick rather than one per second, and surviving domain reload by re-opening on the static constructor. Gate on an EditorPrefs enable flag (renamed from AutoPollCommands, default still false) so an unattended project does not silently host a mutation endpoint.
  • Per-file changes:
    • BrokkrCultMeshMirror.cs:16: the Queue<BrokkrUnityCommand> stays, but the service owns dequeuing. No change to the watch.
    • BrokkrWindow.cs:65-114: OnEnable/OnDisable stop touching the mirror; they read service state for display only.
    • BrokkrSettings.cs:23-27: AutoPollCommands becomes AgentCommandsEnabled, same default false.
  • Authority map:
    • Owner: BrokkrEditorService owns mirror lifecycle and command drain.
    • Inputs: AgentCommandsEnabled, CultMeshCachePath, the watch queue.
    • Outputs: executed mutations, brokkr.unity.command_receipt.v0, refreshed brokkr.unity.host_snapshot.v0.
    • Derived state: BrokkrWindow's lastReceipt, lastSnapshot, lastSyncReceipt are display-only. The window is no longer an owner.
    • Forbidden writers: BrokkrWindow.OnDisable may not dispose the mirror; nothing but the service may call TryDequeueCommand.
    • Shared paths: window-button commands, agent-sent commands and daemon sync-once commands all land in the same queue and take the same BrokkrUnityCommandExecutor.Execute path.
    • Deletion line: the window's mirror field and update hook, deleted before the service is wired.
  • Verification:
    • builds: Aetheria compiles.
    • operator: enable the flag, close the Brokkr window, send a command, see it execute. That negative — the old path could not do this — is the point of the cut.
    • negative: rg -n 'EditorApplication.update' surfaces/unity matches only BrokkrEditorService.cs.

Cut 4. Make the editor reachable and awake

  • Repo/branch: Brokkr, same branch. Depends on Cut 3.
  • First: measure it. With the service running, unfocus Unity, send a command from a second process, and time the receipt. If it lands in under a second the unfocused-tick worry is theoretical on Unity 6000.3 and this cut shrinks to endpoint publication alone. Do not build the compensator before the measurement.
  • Deletes first: nothing.
  • Adds:
    • Runtime/BrokkrEndpoint.cs: brokkr.unity.endpoint.v0 — host, port, projectPath, unityVersion, startedAt, agentCommandsEnabled, process id. Published by the service on start into the mirror at unity/endpoint/current, and written as a plain sibling file <project>/.brokkr/endpoint.cc so an agent can find a live editor without already being connected to it. The file is a discovery pointer, not authority; the document in the mirror is the truth.
    • Only if the measurement demands it: a keep-awake that does not lie about ownership. EditorApplication.update stays the single drain; the addition is a background System.Threading.Timer that calls EditorApplication.QueuePlayerLoopUpdate()/delayCall to force ticks while agentCommandsEnabled is set. The timer schedules; it never executes a command off the main thread.
  • Per-file changes: BrokkrEditorService.cs publishes the endpoint on start and clears it on quitting.
  • Authority map: Owner: the service owns the endpoint document. Inputs: node.Server port, Application. Outputs: brokkr.unity.endpoint.v0. Derived state: .brokkr/endpoint.cc is a discovery cache; a stale one must be detectable (check pid, refuse on mismatch). Forbidden writers: the agent CLI may read it and must never write it. Deletion line: n/a.
  • Verification:
    • operator: with Unity unfocused and a browser in front, an agent command executes within a stated bound; record the measured latency in the doc.
    • negative: kill Unity, and the CLI reports "no live editor" from the pid check rather than hanging on a connect.

Cut 5. brokkr-ctl: the agent's hands

  • Repo/branch: Brokkr, same branch. Depends on Cuts 2-4.
  • First: none.
  • Deletes first: nothing. This is net-new surface and must justify itself: it buys the named capability (an agent process drives a live editor) that no existing owner provides, because brokkr-daemon writes files the editor cannot see and the Unity window is not callable from outside Unity.
  • Adds: tools/brokkr-ctl/ — a .NET console project referencing CultLib's GameCult.Mesh/GameCult.Networking projects and Brokkr's document types. Subcommands, all reading .brokkr/endpoint.cc for the target:
    • snapshot [--project PATH] — connect, read unity/host/current, print it.
    • send --action <a> [--target-object-id ID] [--name N] [--component-type T] [--property-path P] [--value V] [--asset-path A] [--parent-object-id P] [--local-position x,y,z] [...] — build a BrokkrUnityCommand, put it at unity/commands/{guid}, then block on the matching unity/receipts/{commandId} with a timeout, print the receipt, exit non-zero on status != accepted.
    • send --file plan.cc — a batch of intents applied in order, stopping at the first failure, printing every receipt. This is what makes scene rigging usable instead of a hundred process launches.
    • watch-receipts — tail receipts. The document types are shared with the Unity package rather than redeclared: the Runtime/ .cs files are compiled into brokkr-ctl by <Compile Include> so there is exactly one definition of brokkr.unity.command_intent.v0.
  • Authority map:
    • Owner: brokkr-ctl owns nothing. It is a caller. Unity owns the mutation; the receipt owns the outcome; the snapshot owns editor truth.
    • Inputs: CLI args, .brokkr/endpoint.cc.
    • Outputs: one brokkr.unity.command_intent.v0 per invocation, to the live node over CultNet.
    • Derived state: printed text is telemetry. Exit code is derived from the receipt status, never from "the put succeeded".
    • Forbidden writers: brokkr-ctl must never open the .ccmp file. That path is invisible to a running editor and clobbered by its next flush. It must never write receipts, snapshots or endpoint documents.
    • Shared paths: brokkr-ctl, the Brokkr window's buttons and brokkr-daemon sync-once all produce the same intent document and go through the same executor. No second commit primitive.
    • Deletion line: if brokkr-ctl grows its own policy, its own receipt ledger, or its own idea of editor state, cut it back to a caller.
  • Verification — this is the decisive one:
    1. Open F:\Projects\Aetheria in Unity 6000.3.24f1 with the Brokkr package installed. Enable agent commands. Note .brokkr/endpoint.cc.
    2. From a Claude Code session, run brokkr-ctl send --action createGameObject --name BrokkrProofOfLife. Assert: exit 0, receipt status=accepted, receipt objectId is a GlobalObjectId string.
    3. brokkr-ctl send --action attachComponent --target-object-id <that id> --component-type UnityEngine.BoxCollider → accepted.
    4. brokkr-ctl send --action setGameObjectTransform --target-object-id <that id> --local-position 1,2,3 → accepted.
    5. brokkr-ctl snapshot → the returned brokkr.unity.host_snapshot.v0 contains a BrokkrGameObjectSnapshot named BrokkrProofOfLife with localPosition (1.0, 2.0, 3.0) and a BoxCollider in components.
    6. The layer the operator sees: the operator looks at the Unity Hierarchy and the Inspector and confirms the object, the collider and the transform are there, and that the scene shows dirty. A receipt that says accepted while the Hierarchy is empty is the failure this step exists to catch — the executor writes its own receipt, so the receipt alone cannot prove the mutation.
    7. Undo (Ctrl+Z) removes it, proving the Undo.RegisterCreatedObjectUndo path at BrokkrUnityCommandExecutor.cs:43 is real and the operator retains a way out.
    8. Negative: with agent commands disabled, step 2 times out and exits non-zero without mutating anything.

Cut 6. Admission and the save question

  • Repo/branch: Brokkr, same branch. Depends on Cut 5.
  • First: verify what Server binds. Server.cs:26 fixes the port at 3075 but the bind address was not established in this pass. If it binds 0.0.0.0, every machine on the LAN can currently mutate an open Unity project, and this cut is urgent rather than tidy.
  • Deletes first: nothing, because there is nothing. Stated plainly: today admission is absent, not weak.
  • Adds:
    • Loopback-only binding for the Brokkr mirror node, or an explicit operator opt-in to bind wider.
    • A BrokkrCommandPolicy consulted by the service before BrokkrUnityCommandExecutor.Execute: an action allowlist and an asset-path prefix allowlist, defaulting to scene mutation permitted and asset-creating actions (createScriptableObject, createPrefabVariant) denied. Denials produce a status=denied receipt, so a refusal is as inspectable as an acceptance.
    • requestedBy on the intent and deniedReason on the receipt, so a receipt says who asked, not only what happened.
    • A saveScene action, gated by the same policy and denied by default (Q4).
  • Authority map: Owner: BrokkrCommandPolicy owns admission. Inputs: the intent, EditorPrefs policy. Outputs: permit/deny. Derived state: the receipt's status. Forbidden writers: the executor may not bypass the policy; the window's own buttons go through it too, so operator and agent share one admission path rather than two truths. Deletion line: n/a.
  • Verification:
    • negative: a createScriptableObject intent under default policy yields status=denied and creates no asset — verified by AssetDatabase absence, not by the receipt.
    • negative: rg -n 'Execute\(' surfaces/unity shows the policy call on every path into the executor.

Subtraction ledger (estimate)

Cut Removed Added Deps/targets
1 ~30 lines docs/scripts, 1 dead branch ~15 lines —
2 27 DLLs + 27 .meta (~6 MB), 1 script (~55 lines) ~6 lines manifest −0 packages in Brokkr, +1 UPM dep declared, +1 UPM dep in Aetheria
3 ~45 lines from BrokkrWindow.cs ~90 lines (BrokkrEditorService.cs) —
4 — ~70 lines + 1 document type +1 schema
5 — ~350 lines +1 build target (tools/brokkr-ctl, .NET console), +2 project references to CultLib
6 — ~120 lines —

Net: strongly negative in shipped bytes (the vendored DLL tree dwarfs everything added), net +1 executable target and +2 schemas. The one new target is the whole point of the campaign's gap and is the thing the MCP bridge later wraps rather than replaces. Build budget: cargo build/test --workspace already runs in 24s cold, and nothing here touches the Rust crate. The new .NET target builds GameCult.Mesh + GameCult.Networking from CultLib source — check that cost on first build; if it exceeds a few seconds, reference the packed assemblies instead. Build host is this Windows box and the artifacts run on it, so no cross-platform claim is being made.

Operator forks

Q1 — Where the agent-side tool lives, and in what language. Most blocking: everything after Cut 2 assumes an answer.

  • A: tools/brokkr-ctl, a .NET console in this repo, CultNet client over the Unity node's port.
  • B: Extend brokkr-daemon in Rust with a send/snapshot subcommand.
  • C: No tool; agents poke the .ccmp file.

Recommended: A. C is dead on the evidence — a file write is invisible to the running editor's Watch and gets clobbered by its next flush. B needs a typed document-put client in cultnet-rs that does not exist today; the RUDP and schema primitives are there, so it is buildable, but it is a campaign-sized detour to reach parity with a C# client that already works. A reuses CultNetDocumentRegistry.CreateRawDocumentPutMessage and the same document .cs files Unity compiles, so there is exactly one definition of the intent schema. When the MCP bridge lands in Rust, brokkr-ctl is what it replaces, and the intent/receipt contract it exercised is what the bridge lowers.

Q2 — Does the editor poll or subscribe?

  • A: Subscribe. The mirror already does (BrokkrCultMeshMirror.cs:44-52); the bug is that the drain of the subscription queue is window-owned and rate-limited to one command per second.
  • B: Add an explicit poll of the backing store.

Recommended: A. B would mean re-pulling the .ccmp in the editor and creating a second delivery path with different semantics — two truths about how a command arrives. Cut 3 fixes the real defect (ownership and batching) without inventing a lane.

Q3 — Unfocused ticking.

  • A: Measure first (Cut 4's "First"), and only add a keep-awake timer if the measurement shows a stall.
  • B: Add the timer now.
  • C: Require the editor focused; document it and move on.

Recommended: A. B is a compensator bought before the symptom is confirmed on Unity 6000.3. C is honest but makes the tool useless for the actual use — an agent drives Unity while the operator is elsewhere.

Q4 — Does scene save become a command?

  • A: New saveScene action, denied by default in policy, opt-in per project.
  • B: Save stays the operator's, always.

Recommended: A, shipped denied. B is safer today and is what Cut 5's verification assumes, but it means an agent rigging fifty objects leaves all of it in volatile editor memory, and one crash or one mis-clicked "Don't Save" takes the lot. A with a default denial keeps the operator's hand on the switch while making the capability exist and inspectable. If you would rather not have the action in the codebase at all, B is defensible; say so and Cut 6 drops it.

Q5 — MCP server now, or CLI the bridge wraps?

  • A: CLI now; the campaign's generic CultMesh MCP bridge wraps or replaces it.
  • B: A Brokkr-specific MCP server now.

Recommended: A. The campaign doc is explicit that Brokkr-specific tooling treats the symptom and that the missing organ is general (mcp-bridge-campaign.md, "Brokkr-specific tooling would treat the symptom"). A Brokkr MCP server is exactly the thing the campaign says not to build, and it would have to be deleted. A CLI is a caller — the bridge invokes the same typed operation over the same endpoint, and brokkr-ctl either becomes a thin shim or goes away without anything else moving. It also answers the campaign's own open question ("what a provider must publish before it is usable through the bridge, and whether Brokkr already publishes it") with a worked example: Brokkr publishes the commands and receipts but not the endpoint, which is Cut 4.

Q6 — The hard-coded port 3075 (Server.cs:26).

  • A: Accept it; one Brokkr-hosting Unity editor per machine, and the endpoint document says which project owns it.
  • B: Push a port option into CultNetHostOptions in CultLib.

Recommended: A for this slice, B as a CultLib follow-up. B is a change to shared substrate every runtime depends on, and it does not belong inside an unblock-the-work cut. A is honest as long as brokkr-ctl refuses clearly when .brokkr/endpoint.cc names a different project than the one asked for — which is worth stating as a requirement, because the silent failure (commands landing in the wrong Unity project) is exactly the class of damage this whole effort exists to stop.

Q7 — Does com.gamecult.brokkr stay self-contained?

  • A: Depend on org.gamecult.cultlib; drop the vendored DLLs (Cut 2 as written).
  • B: Keep vendoring, and make it installable only in projects without CultLib.

Recommended: A. B cannot serve the immediate consumer at all: Aetheria has CultLib and the assembly names collide. A also ends the staleness — the vendored GameCult.Mesh.dll is a tenth the size of the one Aetheria resolves, which means the package has been compiling against a CultMesh from a different era. Expect Cut 2 to surface real API drift; that is the cut doing its job, not a surprise.