Skip to content

fix(slack-bridge): make /reload load new Pinet code and keep the running mode - #1101

Merged
tmustier merged 2 commits into
mainfrom
fix/reload-restores-broker
Oct 17, 2026
Merged

tmustier merged 2 commits into
mainfrom
fix/reload-restores-broker

Conversation

@tmustier

Copy link
Copy Markdown
Collaborator

Problem

After pi update, /reload in a broker session:

  1. left Pinet off (the broker had to be started again with /pinet start), and
  2. still ran old code. A live Slack fix (fix(slack-bridge): route replies in agent-started DM threads #1100) did not take effect until Pi was quit and restarted.

Cause

  • Pi loads extensions with jiti (moduleCache: false), but jiti imports .js files in a "type": "module" package with Node's native import first. It only compiles a file itself when native import fails, which for Pinet happens to the few files that import Pi host packages (index.js, pinet-tools.js, tool-registration-runtime.js). Node evaluates a native module once per URL for the life of the process, so after /reload everything else — the broker Slack adapter, broker-core, pinet-core, transport-core — stayed old. Confirmed with JITI_DEBUG=1 and a minimal probe extension: /reload kept v1 code after the files changed to v2.
  • On reload, Pinet's session_shutdown stopped the runtime and session_start chose the mode from settings, so a broker started with /pinet start came back off.

Loading the TypeScript source instead would avoid native imports but undo the startup speed-up from #984.

Fix

  • module-reload.ts registers one module.registerHooks resolve hook on the first load. Each later load of the entry gives every Pinet module a new URL (?pinet-load=N), so Node evaluates the code on disk again. Third-party dependencies under node_modules and the hook module itself keep their single instance. Requires module.registerHooks (Node 22.15+; Pi requires 22.19+); Bun-compiled Pi binaries without it keep today's behaviour.
  • session_shutdown with reason: "reload" records the running mode on a process-wide symbol; session_start with reason: "reload" restarts that mode instead of the startup default.

Each reload keeps the previous module copies in memory; reloads are rare, so this is accepted.

Tests

  • module-reload.test.ts: a child Node process loads a mirrored install three times like jiti, editing a module between loads. Asserts fresh code each load, one hook-module evaluation, one third-party evaluation, and no double load on the first pass. Fails against: no hook, hook module reloaded, third-party reloaded, never bumping, tagging the first load.
  • token-free-follower.test.ts: against a real broker socket, a session that followed with /pinet follow rejoins after session_shutdown/session_start with reason: "reload" even though settings would leave it off. Fails without the carry-over.
  • slack-bridge: 1744 passed (run with a longer timeout; this Mac is heavily loaded). Lint, typecheck, agent-standards lint pass.

Real Pi check

Built this branch and ran pi --no-session --no-extensions -e slack-bridge/dist/index.js, /pinet follow to the live broker, then edited the built broker Slack adapter between two real /reloads. A temporary line in that file logged its module URL:

adapter v1 slack.js
adapter v2 slack.js?pinet-load=1
adapter v3 slack.js?pinet-load=2

After each reload the session rejoined the broker on its own as the same agent. The live broker's own reload follows after merge.

…ing mode

/reload after `pi update` kept running most of the old Pinet code and
left Pinet off, so a broker had to be restarted by hand and still ran
stale code until Pi itself restarted.

Pi loads extensions with jiti, which imports Pinet's built ES modules
with Node's native import whenever it can. Node evaluates each native
module once per URL for the life of the process, so only the few files
jiti compiles itself (those importing Pi host packages) were fresh after
/reload; everything else, including the broker Slack adapter and the
workspace packages, stayed old.

- module-reload.ts registers one Node resolve hook on the first load.
  Each later load of the entry gives every Pinet module (not third-party
  dependencies) a new URL, so Node evaluates the code on disk again.
- session_shutdown with reason reload records the running Pinet mode
  process-wide, and session_start with reason reload restarts it instead
  of the startup default.
…oad nested @PiNet packages

- Reload stops Pinet without releasing its identity, like the in-place
  broker reload, so the agent keeps its threads, inbox and supervised
  workers and rejoins as itself.
- The mode carried across reload includes off, so a session that left
  the broker stays off. A startup still in progress carries nothing.
- npm may nest @PiNet packages under another package's node_modules; the
  hook now reloads every @PiNet package and only skips third-party ones.
@tmustier

Copy link
Copy Markdown
Collaborator Author

Independent review (oracle, gpt-6.1-sol high): no critical findings; three warnings, all fixed in 827b679:

  1. Reload released the agent's identity (unregisterAgent: inbox deleted, thread ownership cleared, children orphaned). Reload now stops with releaseIdentity: false, like the existing in-place broker reload. Test: a follower that owns a thread rejoins after reload as the same agent and still owns it. Fails with releaseIdentity: true.
  2. An explicitly disconnected session reconnected on reload. The carried mode now includes off; a startup still in progress carries nothing, so that reload starts from defaults. Test: settings follower, /pinet unfollow, reload → stays off. Fails when off is not carried.
  3. Nested npm @pinet packages stayed stale. The hook now treats any @pinet/* package under node_modules (hoisted or nested) as Pinet, and other node_modules packages as third-party. The hook test adds a nested @pinet package that must reload. Fails with the old rule and with "all node_modules".

Not changed: the reload hand-off is one process-wide slot, not keyed per session. Interactive Pi runs one session per process; noted as a limit.

slack-bridge: 1745 passed; lint, typecheck, agent-standards pass.

@tmustier

Copy link
Copy Markdown
Collaborator Author

Follow-up review (oracle): no critical findings; the three fixes verified (joined workers still clear tokens and reread the join file on reload).

Two remaining continuity gaps are existing broker-start behaviour, shared with the in-place broker reload (/pinet start on a running broker), and are left out of this PR:

  • every broker start releases the broker's own thread claims (broker-runtime.ts releaseThreadClaims(brokerSelfId)), so threads the broker itself owned are re-routed after any reload or restart;
  • stopPinetRuntime stops subtree-broker children on reload, as the in-place reload already does.

With this PR, Pi /reload matches the in-place reload's semantics. Keeping broker-owned threads and subtree workers across a reload would be a separate change.

@tmustier
tmustier merged commit 99b98cc into main Oct 17, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant