@crow-agent/server
v0.2.0
Published
Server host for Crow sessions: listeners, routing, and transports.
Readme
@crow-agent/server
Local server that hosts durable Sessions and exposes the Agent Harness over a byte transport.
A Server instance accepts connections, runs the hello handshake, and then
routes service calls at two levels: calls aimed at the server itself, and calls
aimed at one attached Session. Routing stays opaque — the server never decodes
application payloads. It only checks that the { serverId, sessionId,
attachmentId } route on each request is live, then forwards the envelope.
What the host provides
Everything session-shaped is application code. The server asks a ServerHost
for three things:
serverServices— the connection-scoped service endpoint. ItsattachClient()hands each connection a narrow attachment-management surface (RoutedServerServiceAttachment).resolveSession(sessionId, context)— map an id to metadata, or throwSessionNotFoundError/SessionAmbiguousError.openSession(metadata, context)— acquire the worker-local Session and wrap it in aRoutedSessionHandle. The handle'sattachClient()returns a presentation-scoped capability whoseinvokeService()forwards an opaque service/member envelope at the chosen Session endpoint.
Application-owned services (SessionDirectory, SessionManagement) project
the private session catalog into replicated, presentation-safe state and create,
remove, attach, and detach Sessions without leaking route ids into business
results. Subscription snapshots and updates stay scoped to the attachment that
requested them; attachment changes are published out of band once the router
installs or clears the live route.
Attachments
One Session can serve several presentation attachments at once. Repeating
attach from a single connection is idempotent. Each successful attachment
receives a server-generated attachmentId that only travels in routing
control data. Stale or mismatched routes are rejected. When a connection drops,
its pending responses fail, but its attachment is released only after admitted
service calls settle — the host decides when zero presentation demand plus
quiet worker-local Harness activity allow worker retirement. server.close()
shuts every routed Session handle down, relinquishing worker and Session-writer
ownership.
Example
import { randomUUID } from "node:crypto";
import { MemorySessionRepo, type Context } from "@crow-agent/agent-core";
import {
SessionAmbiguousError,
SessionNotFoundError,
type RoutedServerServiceHost,
type RoutedSessionHandle,
type ServerHost,
type Session,
} from "@crow-agent/server";
import { createUnixServer, getUnixSocketPath } from "@crow-agent/server/unix";
const catalog = new MemorySessionRepo();
async function lookupSession(sessionId: string, context: Context) {
const candidates = await catalog.list(undefined, context);
const match = candidates.filter((entry) => entry.id === sessionId);
if (match.length > 1) throw new SessionAmbiguousError();
const found = match[0];
if (found === undefined) throw new SessionNotFoundError(`Unknown session: ${sessionId}`);
return found;
}
async function launch(
serverServices: RoutedServerServiceHost,
openRoutedSession: (session: Session) => Promise<RoutedSessionHandle>,
) {
const host: ServerHost = {
serverServices,
resolveSession: lookupSession,
openSession: async (metadata, context) => {
const session = await catalog.open(metadata, context);
return openRoutedSession(session);
},
};
const serverId = randomUUID();
const socketPath = getUnixSocketPath(serverId, "/run/user/1000/crow");
const server = createUnixServer(host, { serverId, path: socketPath });
await server.start();
return server;
}The launcher picks serverId as a logical identity — it is not a socket
address. getUnixSocketPath() derives a filesystem path from a directory you
choose; prefer a short private runtime directory. A long-lived launcher may
reuse the same id and path when replacing the server process.
Server composes transports through the ServerListener interface; peer
authentication is application policy and is not provided by the Unix transport.
The Unix submodule offers createUnixListener() and createUnixServer().
Low-level envelope validation, CBOR, and framing come from @crow-agent/protocol;
Chord handles service-control parsing, error codes, snapshots and updates, and
each subscription's replicated-state encoder.
Server and worker lifecycle live outside the public Crow protocol. The replaceable application server turns connection attachments into private demand updates; the worker merges generation-tagged demand with authoritative Harness activity. The coordinator supplies stable routing and reports generic server-generation connection changes.
