zuga-hivemind

n8n community node for Zuga Hivemind — tickets, docs, goals, messages and shared memory.

Documentation

n8n-nodes-zuga-hivemind

An n8n community node for Zuga Hivemind: file/list/update tickets, read/write docs,
read the goal tree, send messages, and search shared memory — plus a polling trigger
for new tickets and messages.

⚠️ Multi-tenant isolation — read before giving this to a client

Hivemind's tenant isolation exists in code but ships DISABLED.

src/hivemind/tenancy.py implements database-per-tenant: a request resolves to one
tenant's SQLite file, so another tenant's rows are not merely filtered out — they are
not present. It fails closed (raises rather than falling back to the default database).
That design is sound.

But it is gated behind HIVEMIND_TENANCY_ENABLED=1, and the module's own docstring
says "SHIPPED DISABLED". With the flag unset:

  • every valid API key can read and write every ticket, doc, goal and channel;
  • scopes on an account is documented in server.py as "NOT an access-control layer";
  • two clients pointed at the same server would see each other's data.

So the credential this node holds is a teammate key, not a per-client key. Before
this connector is handed to an external client:

  1. Turn on HIVEMIND_TENANCY_ENABLED=1 on the server the client will hit.
  2. Verify a second tenant's key cannot read the first tenant's tickets — by trying it,
    not by reading the code.

Until then this is an internal-team connector.

What it talks to

Every resource/operation maps to exactly one hivemind HTTP route that accepts a
teammate X-API-Key. The /bridge/* and /search/* routes are gated by hivemind's
separate service admin token and are deliberately out of scope.

Resource Operations Routes
Ticket file / list / update POST /report, GET /tickets, POST /ticket/update
Doc read / write / list GET /memory/doc, POST /memory/doc, GET /dashboard/api/docs
Goal getTree / addNote GET /dashboard/api/goals, POST /goal/note
Message send / readChannel / readInbox POST /dashboard/api/send, GET /dashboard/api/channel, GET /dashboard/api/inbox
Memory search POST /memory/retrieve

Auth

Reuses the existing per-teammate key minted server-side via POST /register
(admin-only — see the mint-hivemind-key skill). The key rides as the X-API-Key
header, the same header hivemind's dashboard and MCP clients use. This credential
holds a key; it does not mint or manage them.

The credential test calls GET /dashboard/api/me rather than /health, because
/health needs no key and would "pass" against any URL returning 200. /me proves
both that the key is valid and that the base URL is actually a hivemind server.

Trigger model — polling, by necessity

Hivemind emits no outbound webhooks. The only webhook it exposes
(POST /webhook/github, HMAC-signed) is an inbound listener, not an event bus.
So New Ticket / New Message poll and compare against the highest id seen on the
previous run, stored in the node's workflow static data.

On first activation the trigger records a baseline and emits nothing — otherwise
every ticket that already existed would fire the moment you switch it on.

Build

npm install
npm run build      # tsc -> dist/, then copies the icon

dist/ is gitignored: build output is not source.

Source recovery note (2026-08-16)

This package previously existed on disk as dist/ and node_modules/ only — no
package.json, no .ts, on either machine, and nothing in git history. A connector
that cannot be rebuilt cannot be shipped or maintained, so the source in src/ was
recovered from the compiled output.

The recovery was verified rather than assumed: the recovered source compiles clean
under strict: true, and its emitted JavaScript is byte-identical to the original
tested dist/ for all four files (modulo CRLF vs LF). What was lost at compile time
and had to be restored by hand: parameter/return type annotations, the this: IExecuteFunctions / this: IPollFunctions signatures, and interface declarations.

Discussion