Remote sessions
Save the URL and a scoped token from another personal AirCode Ø instance, then choose that connection in New session → Run on. The remote instance invokes its own connected Claude Code, Codex or another available native provider. Your current client displays the conversation and terminal as the remote agent works.
The project, files, provider credentials, permissions and usage accounting belong to the remote instance. This feature does not synchronize projects or make remote models operate tools on your current computer. Both instances need a version supporting remote sessions, and the remote address must be reachable from your current instance’s server.
Connect your two instances
- On the remote instance, connect your native provider account in AI Usage.
- Open Settings → AI gateway, or More → AI gateway on mobile, and enable the gateway. Under Application tokens, enable Allow remote sessions, choose the Authorized project, name the token, and select Create token.
- Copy the token from its one-time reveal and the displayed Base URL.
- On your current instance, open Settings → AI gateway → Remote instances. Enter a connection name, the remote instance URL or Base URL, and the token; select Save connection. You can also add it from New session → Run on → Add remote instance….
- In New session, choose the saved connection under Run on. Check the remote project, choose Engine → Model → Effort → Permissions, enter your prompt, and select Start session.
The token is sent once to your current server for encrypted storage. Web and native clients subsequently use a connection identifier. The website never receives the URL or token. Use HTTPS for Internet connections or your trusted private network. Gateway tokens are personal credentials, not share links.
Work with the remote agent
Chat shows new transcript events and current permission/question dialogs. Send follow-up messages, answer the dialog, or use Stop to interrupt the current response. Terminal attaches to the remote agent’s actual terminal, including input and resize. A dialog that cannot be represented in Chat has an Open Terminal to continue action.
The terminal transports live output over WebSocket. Chat uses a continuous SSE
response and publishes events as the provider writes its transcript; some
providers journal a completed message rather than every token. This does not
promise token-level Chat updates for every CLI. The separate text-only
/chat/completions API still uses buffered SSE.
A network reconnection reads from the last transcript cursor. It never starts another agent or resends a message automatically. After an uncertain launch, reopen New session, select the connection and look under Remote conversations. An explicit retry of the same request keeps its intention id. Do not change the id just to bypass an uncertain result.
Closing the view or removing its saved connection detaches the client and leaves the remote conversation in place. Close session stops its agent; its transcript remains on the remote instance and can be reopened for reading. Start a new session to run an agent again after closing it. Saved remote conversations are listed under their connection in New session.
Scope, revocation and limits
An ordinary application token grants text generation only. Allow remote sessions adds access to one project and only the conversations created with that token. It does not grant owner login, settings, other conversations, arbitrary owner file APIs or token management. The agent’s tools use the permissions selected at launch; choosing a project is not an OS sandbox.
Tokens expire after 90 days. Revoking the token or disabling the remote gateway removes its session authority and stops its agents. Expiry is checked on every operation and by a bounded background check, including after a server restart. Removing a saved connection does not revoke the original token: revoke it on the issuing instance if you want to withdraw authority.
There are at most 20 active gateway sessions per issuing instance and four live readers per token, with 20 readers across the gateway. Each opened Chat uses one reader; opening Terminal can add another. These are local resource limits, not subscription allowances. Provider quotas and compatibility requirements still apply; no provider CLI is installed or updated by connecting.
Session API for a personal backend or AI tool
The session OpenAPI schema describes this
protocol. Use Authorization: Bearer TOKEN on every call to the issuing
instance’s /api/ai-gateway/v1 Base URL. Refuse redirects; never use URL/query
credentials. Do not put the token in prompts, source code or browser storage.
| Endpoint | Meaning |
|---|---|
GET /session-catalog |
Protocol version, authorized project and live engine/model/effort/permission catalogue |
POST /sessions |
Start a native agent; required UUID intention, engine, model, permissionMode, initialPrompt; optional effort, name |
GET /sessions |
Up to 100 recent conversations created by this token |
GET /sessions/{id} |
Current summary of a conversation owned by this token |
POST /sessions/{id}/events |
Continuous SSE; JSON { "offset": null } initially, then the last returned offset on reconnect |
POST /sessions/{id}/transcript |
Read a named page; send offset for newer events or before for earlier messages |
POST /sessions/{id}/input |
Follow-up: UUID intention and non-empty text |
POST /sessions/{id}/interaction |
UUID intention, current dialog id, and the supported option/text/dismiss answer |
POST /sessions/{id}/command |
UUID intention and action: interrupt, close, model, effort or permission; selection changes require arg from the catalogue |
GET /sessions/{id}/terminal |
Authenticated WebSocket upgrade; raw terminal output, JSON input { "t": "i", "d": "text" }, resize { "t": "r", "c": 100, "r": 30 } |
An SSE update contains { id, project, session, transcript }.
transcript.events is the new event page, offset is its opaque integer cursor,
and interaction or terminalAttention describes current input needs. Keep
complete SSE frames across network chunks. Ignore comment keepalives. On an
error event, retain the conversation and reconnect for reading. Never infer
that an operation did not happen from a lost network response.
Permission modes, model ids and efforts come from /session-catalog, not the
text API’s composite model ids. Session creation and actions reject unsupported
fields. Initial prompts and follow-ups are bounded to 64 KiB. Launch intentions
are scoped to the token; action intentions are scoped to a session. Reusing an
intention with different content is rejected. Once runtime delivery was
attempted, an unknown result remains AI_GATEWAY_SESSION_UNCERTAIN and cannot
be replayed automatically.
For a quota wall, wait for the provider’s actual recovery; an unknown reset time
stays unknown. For AI_GATEWAY_SESSION_ATTENTION, answer the current dialog.
For revoked/expired access, create a fresh scoped token on the issuing instance.
For missing models, refresh the catalogue. Existing owner conversations cannot
be adopted with a new token.
Live session example
Node.js 22 or later. Set AIRCODE_REMOTE_BASE_URL and AIRCODE_REMOTE_TOKEN in private server configuration. The first run prints the catalogue until you also choose AIRCODE_REMOTE_ENGINE, AIRCODE_REMOTE_MODEL and AIRCODE_REMOTE_PERMISSION. AIRCODE_REMOTE_EFFORT is optional. The reader closes after 30 seconds; the remote agent remains running.
import { randomUUID } from 'node:crypto';
// Node.js 22+. Configure these privately on your application's server.
const base = process.env.AIRCODE_REMOTE_BASE_URL;
const token = process.env.AIRCODE_REMOTE_TOKEN;
if (!base || !token) throw new Error('Configure the remote Base URL and token');
const url = new URL(base);
if (!['https:', 'http:'].includes(url.protocol) || url.username || url.password
|| url.search || url.hash || url.pathname !== '/api/ai-gateway/v1') {
throw new Error('Use the instance Base URL without a trailing slash');
}
async function call(path, body, signal) {
const response = await fetch(base + path, {
method: body === undefined ? 'GET' : 'POST', redirect: 'error', signal,
headers: { authorization: 'Bearer ' + token, 'content-type': 'application/json' },
...(body === undefined ? {} : { body: JSON.stringify(body) }),
});
if (!response.ok) throw new Error('Remote request refused: HTTP ' + response.status);
return response;
}
const catalog = await (await call('/session-catalog')).json();
const engine = process.env.AIRCODE_REMOTE_ENGINE;
const model = process.env.AIRCODE_REMOTE_MODEL;
const permissionMode = process.env.AIRCODE_REMOTE_PERMISSION;
if (!engine || model === undefined || !permissionMode) {
console.log(JSON.stringify(catalog, null, 2));
throw new Error('Choose engine, model and permissions from this catalogue');
}
// Keep this intention for an explicit retry of this exact launch request.
const intention = process.env.AIRCODE_REMOTE_INTENTION || randomUUID();
const session = await (await call('/sessions', {
intention, engine, model, permissionMode,
effort: process.env.AIRCODE_REMOTE_EFFORT || '',
initialPrompt: 'Describe the project without changing its files.',
})).json();
console.log('Remote conversation:', session.id);
// Demonstrate a bounded live reader. Disconnecting leaves the agent running.
const signal = AbortSignal.timeout(30_000);
const stream = await call('/sessions/' + session.id + '/events', { offset: null }, signal);
const decoder = new TextDecoder();
try {
for await (const chunk of stream.body) process.stdout.write(decoder.decode(chunk, { stream: true }));
} catch (error) { if (!signal.aborted) throw error; }