pi-mono/packages/server
Armin Ronacher 8dce5da769 feat(coding-agent): coordinate replaceable servers and workers
Keep the stable socket in an opaque detached coordinator while server processes own detached worker discovery and lifecycle. Remove the public drain RPC in favor of server replacement through coordinator routing.
2026-08-15 11:49:53 +02:00
..
src feat(coding-agent): coordinate replaceable servers and workers 2026-08-15 11:49:53 +02:00
test feat(coding-agent): coordinate replaceable servers and workers 2026-08-15 11:49:53 +02:00
CHANGELOG.md Add [Unreleased] section for next cycle 2026-08-14 12:00:04 +02:00
package.json Merge remote-tracking branch 'origin/main' into dev 2026-08-14 10:25:49 +00:00
README.md feat(coding-agent): coordinate replaceable servers and workers 2026-08-15 11:49:53 +02:00
tsconfig.build.json chore: rename orchestrator to server (#6898) 2026-07-21 13:32:42 +02:00
tsconfig.test.json feat(protocol): add service-addressed session RPC 2026-08-13 18:24:27 +03:00
vitest.config.ts feat(protocol): add service-addressed session RPC 2026-08-13 18:24:27 +03:00

@earendil-works/pi-server

Experimental local server for the new durable Session and Agent Harness interfaces.

The current slice supports two client operations:

  • list calls SessionRepo.list() without opening sessions.
  • attach finds the requested metadata, passes it to the host, and retains the returned Harness handle in the server.

Concurrent attachments to one session reuse one hosted Harness. Attachment is a one-shot acquisition request, so losing the client connection does not close the Harness. Server shutdown closes every hosted Harness, releasing its Session writer ownership.

import { randomUUID } from "node:crypto";
import { MemorySessionRepo, type Session } from "@earendil-works/pi-agent-core";
import {
  type HostedHarnessHandle,
  type PiServerHost,
} from "@earendil-works/pi-server";
import { createUnixServer, getUnixSocketPath } from "@earendil-works/pi-server/unix";

async function startServer(
  createHarnessForSession: (session: Session) => Promise<HostedHarnessHandle>,
) {
  const sessions = new MemorySessionRepo();
  const host: PiServerHost = {
    sessions,
    async createHarness(metadata) {
      const session = await sessions.open(metadata);
      try {
        return await createHarnessForSession(session);
      } catch (error) {
        try {
          await session.close();
        } catch (cleanupError) {
          throw new AggregateError(
            [error, cleanupError],
            "Harness creation and Session cleanup failed",
          );
        }
        throw error;
      }
    },
  };

  const serverId = randomUUID();
  const server = createUnixServer(host, {
    serverId,
    path: getUnixSocketPath(serverId, "/run/user/1000/pi"),
  });
  await server.start();
  return server;
}

Applications supply a session catalog and a Harness factory. The server only calls list(); the host receives the repository's concrete metadata and owns opening the Session, creating the Harness, and cleaning up failed creation. This permits the host to perform those operations in a worker process without passing an open JavaScript Session across processes.

serverId is a logical identity supplied by the launcher, not a socket address. The Unix preset requires an explicit physical path; getUnixSocketPath() derives one from a caller-selected directory. Choose a short, private runtime directory rather than deriving the route from an unbounded home-directory path. A long-lived launcher can reuse the same ID and path when replacing a server process.

PiServer composes authenticated transports through PiServerListener. The Unix submodule provides createUnixListener() and createUnixServer(). Low-level CBOR framing and validation come from @earendil-works/pi-protocol.

Server and worker lifecycle is managed by the replaceable application server rather than the public Pi protocol. The experimental coordinator only supplies stable routing between processes.