Version. 0.1
Date. 2026-06-29
Repo. loomworks-engine
Branch base. main @ d32c99b
Author. Claude.ai (scoping + CR). Operator: Marvin Percival.
Executor. Claude Code on DUNIN7-M4.
Room. Memory (Pipeline-walk Finding 1 of 3).
The Companion writes memory the system then cannot read back. The write side accepts an actor kind of companion; one read serializer does not, so any assertion the Companion authored crashes the Memory room with a 422. That is why E0060's Memory room shows "Couldn't load this room" — all five of its assertions are Companion-authored.
This CR makes read match write: it adds companion to the read-side actor-kind types so Companion-authored memory displays correctly. Introduced once as a shared ActorKind alias so the two sides cannot drift apart again.
The same inspection found a security-relevant hole the display fix would otherwise make more reachable: the commit and discard gates block non-humans by checking kind == "agent", but a companion kind is not "agent", so it slips through. The seed commits Loomworks to Operator-authority — "the machine surfaces and signals; the Operator approves." The Companion is the machine. This CR closes the gate so a companion actor cannot commit or discard, making that principle structural rather than dependent on indirection elsewhere holding forever.
Two changes, one contract: companion is a first-class actor kind we display honestly, and precisely because it is a distinct kind, we enforce that it is a non-committing one.
companion enforces this in code. Aligned — this CR strengthens the commitment.companion is a legitimate contributor kind the write path already records; the read path must represent it faithfully. Aligned — this CR repairs a read path that contradicted the write path.ActorKind alias. A single Literal["contributor", "agent", "person", "companion"] referenced everywhere the driver-kind is constrained, so the set cannot drift across duplicated inline declarations again.companion to the read-side serializers (the live crash plus its siblings of the same bug class):ActorRefResponse.kind (api/schemas.py:2525) — the live 422/500.DecidedBySchema.kind (api/schemas.py:3756).DecidedBySchema.actor_kind (api/schemas.py:3788).BoundaryRecordResponse.raised_by_actor_kind (api/schemas.py:3864).ActorRefSchema.kind (api/schemas.py:4292) — also nested in responses.memory/events.py:79 (currently the 3-value set, shadowed by live migration 0057) so a schema regen / create_all cannot silently re-narrow the DB.== "agent"" to "block any non-human kind," explicitly excluding companion, mirroring the canonical tier-mapping at credit/authorization.py:27 (_AGENT_KINDS = {"agent", "companion"}):engagement/assertions.py:204 (commit), :491 (discard).engagement/creation.py:748, :802 (engagement commit).api/deps.py:1044 (HTTP commit gate).compositions.py:73 — silently relabels a companion-triggered composition as contributor. Display-correctness on a path neither probe exercises. Note as residue; do not chase in this CR.
Before any change, CC reads each listed site at current HEAD and confirms the line still matches this CR's quoted state (d32c99b may have advanced). Two-pass discipline: inspect-and-report first, edit after confirmation. If any site differs from what is quoted here, halt and report the divergence rather than editing blind.
Specifically confirm:
credit/authorization.py:27 still defines _AGENT_KINDS = {"agent", "companion"} — this is the canonical pattern the gate fix mirrors. If it has changed, the gate-fix shape must be re-derived.== "agent" check.Stage by explicit path. Per-step commits. Push is a separate, explicitly-authorized instruction — do not push until the Operator says so.
Step 1 — Canonical alias.
Introduce ActorKind = Literal["contributor", "agent", "person", "companion"] in the appropriate shared location (co-locate with the domain ActorRef in memory/base.py, or a types module it can import without cycle — CC's judgment, report the chosen home). Reference it at the domain ActorRef.kind so the alias is anchored to the write authority.
Commit. Suite green.
Step 2 — Read-side serializers.
Repoint the five read-side kind/actor_kind Literals to include companion (via the alias where the field is the full driver-kind set; DecidedBySchema.kind additionally carries rule — preserve that, add companion to the actor portion). Sites: schemas.py:2525, 3756, 3788, 3864, 4292.
Commit. Suite green.
Step 3 — Stale ORM constraint.
Update memory/events.py:79 CheckConstraint to the 4-value set matching live migration 0057. No new migration needed (DB already correct); this aligns the ORM so a regen cannot re-narrow.
Commit. Suite green.
Step 4 — Commit/discard gate enforcement.
At each of assertions.py:204, assertions.py:491, creation.py:748, creation.py:802, deps.py:1044: change the non-human rejection from kind == "agent" to reject any kind in the non-human set ({"agent", "companion"}, referencing the credit/authorization.py pattern — ideally import or mirror _AGENT_KINDS so commit-gate and spend-tier share one definition of "non-human actor"). A companion actor must be rejected by these gates with the same error a human-only gate gives an agent.
Commit. Suite green.
Step 5 — Tests. Add/extend tests:
ActorRefResponse (and the four sibling serializers) without raising — regression lock on the primary bug.companion actor (enforcement lock — proves the gate hole is closed, not just that display works).Commit. Suite green.
HALT. Report terminal SHA and suite count. Await explicit push authorization.
After merge, restart the dev server (serializer + gate logic load in-process), then verify against both probes:
E0060 (repair):
kind: companion shown honestly./assertion-groups and /contributors/me for E0060 no longer 500.E0089 (clean loop + reproduction-then-fix):
companion-authored held assertion — the same shape that crashed E0060 from clean).Gate enforcement (the security fix actually holds at runtime, not just in code):
companion-attributed actor cannot commit or discard through the gate — attempt the commit path with a companion ref and confirm it is rejected. This proves the Operator-authority enforcement is structural.Pass condition: E0060 loads and renders; E0089 displays a freshly-added Companion note; companion ref is rejected at the commit/discard gate. Only then does Room 1 advance to Finding 2.
actor_from_person), so closing the gate should not change any working path — but it removes reliance on that indirection holding everywhere. The Step-5 enforcement test is what confirms no legitimate human-commit path is caught by the wider rejection.derived_by) are repaired by Step 2's Literal additions even though the probes don't exercise them — cheap forward-protection, same edit.