agent-session-intercom is a local CLI and typed ESM API for communicating with agents in sessions owned
by an already-running Codex app-server. It connects through the app-server WebSocket Unix control socket;
it does not start Codex or create a parallel resumed CLI session.
- Node.js 24.18 or newer
- a running Codex app-server with its Unix control socket enabled
npm install --global agent-session-intercom@1.0.0
agent-session-intercom --version
agent-session-intercom --helpSource development uses pnpm 11.21 and an optional local link; see
docs/DEVELOPMENT.md. Registry installation does not require the project checkout or
its node_modules.
Session discovery checks the default control socket under CODEX_HOME, an optional
CODEX_APP_SERVER_SOCKET, and previously registered custom sockets. Pass --socket <path> for a
single-endpoint query. A successful explicit connection registers its canonical endpoint for later
discovery; an environment-only endpoint stays ephemeral.
agent-session-intercom sessions
agent-session-intercom sessions --search "report" --json
agent-session-intercom sessions --cwd "$PWD" --ndjsonThe listing probes candidate endpoints concurrently with bounded connection timeouts. One stale socket is
reported as unreachable without hiding sessions from healthy endpoints. Each session contains a stable
endpoint id and composite address such as ep_xxx:thread-id, plus thread id, title, status, timestamp, and
only the basename of the project directory. It does not return socket paths, transcript contents, or the
absolute working directory. The CLI and typed APIs accept integer limits from 1 through 500 and reject
invalid values before issuing a thread/list request.
Human output includes a safe endpoint-id/source line for every unreachable candidate, so a partial or
all-failed discovery is not presented as a complete empty result.
agent-session-intercom endpoints add /path/to/app-server.sock --label reports --json
agent-session-intercom endpoints list --json
agent-session-intercom endpoints remove <endpoint-id> --jsonendpoints add verifies the existing app-server before saving it. The private registry is stored under
CODEX_HOME with user-only permissions. Default output contains the stable endpoint id, optional label,
and reachability only; the socket mapping remains private. Custom filesystem socket paths cannot be
enumerated automatically, so register each one once or use it successfully through --socket. The
registry accepts up to 62 custom endpoints, reserving discovery capacity for the default and environment
sources. If that catalog is full, an explicitly selected live socket remains usable; JSON output reports
registration: "registry-full" (or endpointRegistration for send) and human output warns that the
endpoint was not persisted.
agent-session-intercom send <target-thread-id> "Please review the report." \
--from-project <sender-project> \
--message-id <uuid> \
--ttl-ms 300000 \
--jsonUse the composite endpointId:threadId returned by sessions to select an endpoint directly. A bare
thread id is resolved across reachable candidates when it has one match; duplicate matches fail without
delivery and require the composite address or an explicit --socket. When complete membership scanning has
no match, the configured single-socket route remains the compatibility fallback.
An exact contact alias created with contacts add takes precedence over thread-id and metadata lookup.
Bare-id resolution paginates each reachable endpoint to establish membership beyond the ordinary
500-session display limit. If pagination fails, cycles, or reaches its safety ceiling before absence can be
proved, the command rejects and requires a composite address or explicit socket instead of guessing.
Inside Codex, the CLI reads the sender thread from CODEX_THREAD_ID and proves that it exists on exactly
one reachable source endpoint. When the same thread id exists on multiple endpoints, select the source with
--from-endpoint <endpoint-id> or AGENT_SESSION_INTERCOM_SOURCE_ENDPOINT. A caller that supplies
--from-thread must also supply --from-endpoint; the target connection never silently becomes the source
address.
The reliable send path creates envelope version 2 and returns exactly delivered, rejected, or unknown.
delivered means the app-server acknowledged turn/start or turn/steer, not that the agent finished the
work. unknown means a turn request may have reached app-server but its acknowledgment was lost. Only
delivered exits zero. Supply a stable --message-id to deduplicate retries; the same id with changed text,
source, target, TTL, sender metadata, or correlation id is rejected. An unknown record is not sent again
unless the caller adds --retry-unknown. --retry-count, --retry-delay-ms, --retry-backoff, and
--retry-jitter bound automatic retry of overload and other failures known to occur before acceptance.
--ttl-ms is anchored to the first attempt and is not extended by duplicates or retries.
agent-session-intercom who --json
agent-session-intercom contacts add report-agent <endpoint-id>:<thread-id> --json
agent-session-intercom find report-agent --json
agent-session-intercom contacts rename report-agent reports --json
agent-session-intercom reply <original-message-id> "Received; I am checking it." --json
agent-session-intercom contacts remove reports --jsonAliases are trimmed, case-normalized, unique, and bound to one canonical endpoint-plus-thread address.
Changing an alias requires contacts rename; adding the same alias for another session or a second alias
for the same session is rejected. who combines bounded live discovery with known contacts and refreshes
only their project, display label, and last-seen metadata. It never reads transcript bodies. find gives an
exact durable alias precedence without requiring live discovery. Otherwise it searches safe
alias/project/title/thread metadata only after independently scanning every reachable endpoint to
completion. That scan is bounded to 100 pages per endpoint; a cursor cycle, protocol failure, or remaining
page at the bound is an explicit non-success rather than a false none or one. Complete searches return
none, one, or many; none and many exit nonzero instead of guessing.
reply looks up the original delivery receipt by message id, verifies that the current sender is the
original target using both endpoint id and thread id, and routes to the original sender through the private
endpoint catalog. Use --from-thread together with --from-endpoint as an explicit validated sender
override when CODEX_THREAD_ID is unavailable. The reply receives a new message id and sets its correlation
id to the original message id; it never copies the original message body or trusts a socket path from
message text.
The reliable CLI model-visible input begins with [agent-session-intercom/v2], followed by JSON with this
shape:
{
"protocol": "agent-session-intercom",
"version": 2,
"messageId": "16e66e8a-008c-4f09-86d7-dcaef30c7d36",
"sentAt": "2026-08-14T17:30:00.000Z",
"expiresAt": "2026-08-14T17:35:00.000Z",
"correlationId": "optional-caller-correlation",
"sender": {
"kind": "codex-agent",
"threadId": "sender-thread-id",
"address": {
"scheme": "codex-thread",
"endpointId": "ep_1234567890123456789012",
"threadId": "sender-thread-id"
},
"project": { "name": "source-project" },
"label": "optional-agent-label"
},
"replyTo": {
"scheme": "codex-thread",
"endpointId": "ep_1234567890123456789012",
"threadId": "sender-thread-id"
},
"target": {
"scheme": "codex-thread",
"endpointId": "ep_abcdefghijklmnopqrstuv",
"threadId": "target-thread-id"
},
"content": { "type": "text", "text": "Please review the report." }
}The runtime schema requires UUID and ISO anchors, canonical endpoint/thread addresses, a reply address that
exactly matches the sender address, a later expiry, and non-empty metadata/text. Socket paths are never part
of the envelope. The exported version-1 schema and low-level sendMessage API remain available for existing
callers; reliable delivery uses version 2.
import {
AppServerClient,
classifyAppServerDispatchError,
deliverReliably,
dispatchSerializedMessage,
loadEnvironment,
LocalStateStore,
resolveSource,
resolveTarget,
serializeEnvelopeV2,
} from 'agent-session-intercom';
const environment = loadEnvironment();
const store = new LocalStateStore(environment.statePath);
const targetInput = process.argv[2];
if (targetInput === undefined) {
throw new Error('Pass a contact alias, thread id, or endpointId:threadId as argv[2]');
}
if (environment.senderThreadId === undefined) {
throw new Error('Run inside Codex or set CODEX_THREAD_ID');
}
const target = await resolveTarget({
address: targetInput,
environment,
store,
});
const source = await resolveSource({
sourceThreadId: environment.senderThreadId,
...(environment.sourceEndpointId === undefined
? {}
: { sourceEndpointId: environment.sourceEndpointId }),
allowEndpointInference: true,
environment,
store,
});
const delivery = await deliverReliably({
store,
classifyDispatchError: classifyAppServerDispatchError,
sender: {
kind: 'codex-agent',
threadId: environment.senderThreadId,
address: source,
project: { name: environment.defaultProjectName },
},
target: {
scheme: 'codex-thread',
endpointId: target.endpoint.endpointId,
threadId: target.threadId,
},
text: 'Ping',
ttlMs: 300_000,
dispatch: async (envelope) => {
const targetClient = new AppServerClient({ socketPath: target.endpoint.socketPath });
await targetClient.connect();
try {
return await dispatchSerializedMessage(
targetClient,
target.threadId,
serializeEnvelopeV2(envelope),
);
} finally {
await targetClient.close();
}
},
});
console.log(delivery);
process.exitCode = delivery.status === 'delivered' ? 0 : 1;The low-level version-1 sendMessage API remains available for compatible callers that already selected a
connection. New callers should use deliverReliably; its state contains hashes, canonical endpoint/thread
addresses, timestamps, status, and safe acceptance metadata, never message text.
The exported contact schemas, complete-session scanner, peer-summary helpers, state methods, and
prepareReplyDelivery provide the same address-book, completeness, and endpoint-plus-thread authorization
contracts to typed callers. Durable schema v3 adds contacts without changing v1/v2 delivery behavior.
--json emits one versioned JSON object. --ndjson emits the same object as one JSON line. Diagnostics
and errors go to stderr, and a failed command exits nonzero. Raw WebSocket frames, credentials, transcript
bodies, and absolute home paths are not emitted by default.
| Exit code | Meaning |
|---|---|
0 |
The command succeeded. For send and reply, this requires delivery status delivered; for find, it requires exactly one match. Successful listing, who, contact mutation, and endpoint management also exit zero. |
1 |
The command did not succeed. This includes delivery status rejected or unknown, find with none or many, and validation, state, discovery, or connection errors. Structured results use stdout; thrown errors use stderr. |
Use Node.js 24.18 or newer. Confirm that the active npm global bin directory is in PATH, then reinstall
agent-session-intercom@1.0.0 and run agent-session-intercom --version.
Confirm that the Codex app-server is already running and its control socket exists. Set
CODEX_APP_SERVER_SOCKET, register it with endpoints add, or pass --socket <path> for a non-default
existing socket. The CLI will not start a replacement app-server.
This is an inconsistent app-server state. Re-list the session and retry only after its state changes; the CLI intentionally refuses to guess a turn id or silently start a second turn.
Remove title or working-directory filters and inspect sessions --json. The CLI requests all official
interactive, app-server, exec, and sub-agent source kinds. Use the exact returned thread id for delivery.
Filesystem access to the existing Unix socket is the local authorization boundary. Do not expose or proxy the socket to untrusted users. The package does not add authentication, remote transport, or broadcast.
See docs/DEVELOPMENT.md, docs/TESTING.md, and the authoritative docs/ARCHITECTURE.md. Agent-specific usage is intentionally short in docs/AGENT-USAGE.md.
MIT