Pairs with Dialectic.Backend@5cf4302 which removes the backend-driven
broadcast that was the only consumer of serviceEndpoint. With agent-
driven recruitment broadcasts via fabric-send-message, the
service-to-service URL distinction goes away (agents use the regular
guild endpoint).
Removed:
- GuildNode.serviceEndpoint column (TypeORM will drop on next sync)
- GET /api/auth/me/guilds + /api/nodes response fields
- NodeAdminService.setServiceEndpoint()
- cli 'node set-service-endpoint' subcommand
Kept:
- GuildNode.purpose (used by fabric-guild-list for intent-based
channel discovery — still wanted)
A guild's existing 'endpoint' field is its client-facing URL (browser,
remote openclaw plugin) — but in-deployment services on the same docker
network can't always reach it. Concretely, dialectic-backend on
compose_default network sees 'http://server.t3:7002' (from agent-supplied
URL) resolve to dind-network IP which it can't route to. Broadcasts
silently fail with 'connection refused'.
Adds a second URL per guild: serviceEndpoint. Used by other backends
in the same deployment (compose service name + internal port). Plumbed
through:
- GuildNode.serviceEndpoint (varchar 255 nullable; TypeORM auto-migrates)
- GET /api/auth/me/guilds returns serviceEndpoint per row
- GET /api/nodes (admin) returns serviceEndpoint
- new cli: 'node set-service-endpoint --node-id X --endpoint URL'
(admin-only via cli — same pattern as set-purpose)
Pairs with Fabric.OpenclawPlugin's fabric-guild-list returning the new
field + workflows teaching agents to use serviceEndpoint for
announce_guild_base_url (NOT the client-facing endpoint).
E2e verified: sim recruiter agent discovered sim-guild-1's
serviceEndpoint=http://fabric-backend-guild:7002, plumbed it into
dialectic_propose_topic, all 4 lifecycle broadcasts (signup_open,
signup_closed, debating, completed) landed in the announce channel.
Adds a free-form 'purpose' text field on GuildNode so admins can describe
what each guild is for (debate broadcasts / agent triage / dev sandbox /
etc.). Surfaced through:
- GET /api/auth/me/guilds (existing user-facing list)
- GET /api/nodes (existing admin-facing list)
- new cli: node set-purpose --node-id X --purpose 'text'
Admin-only writes via CLI (HangmanLab pattern) — never HTTP. Empty string
clears. TypeORM synchronize auto-adds the column on first startup.
Motivates: agents discover the right guild by intent (purpose substring
match) before picking a channel, so dialectic/announce workflows don't
need hardcoded guild ids.
package.json type=module, tsconfig module/moduleResolution=NodeNext,
target es2022, explicit .js on all relative imports. Center: jsonwebtoken
& bcryptjs switched to default imports (ESM/CJS interop). Verified:
builds, boots, full auth + plugin round-trip work under ESM.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>