Documentation/Portable Agent Packages
v2.0.0-beta.1Snapshot bebdcd1Markdown ↗

Portable Agent Packages

Portable Agent Packages

exportAgentPackage() captures the serializable state of an already-resolved native Agent: its unrendered instructions, portable runtime limits, context state, tool definitions and placement, plus optional Realtime metadata. It never serializes executable functions or connector credentials.

typescript
import {
  AgentPackageToolServer,
  createPortableToolImplementationFingerprint,
  exportAgentPackage,
  hydrateAgentPackage,
} from 'oneringai';

const localToolFingerprints = new Map([
  ['read_desktop_file', createPortableToolImplementationFingerprint(
    readDesktopFile.definition,
    'desktop:read_desktop_file:v3',
  )],
]);

// Trusted source server
const packageValue = exportAgentPackage(serverAgent, {
  expiresAt: new Date(Date.now() + 10 * 60_000),
  toolPlacement: (name) => name === 'read_desktop_file' ? 'local' : 'remote',
  toolImplementationFingerprint: (name) => localToolFingerprints.get(name),
  realtime: {
    provider: 'openai',
    connectorName: 'server-openai',
    model: 'gpt-realtime-2.1',
    voice: 'marin',
  },
});
const toolServer = new AgentPackageToolServer(serverAgent, packageValue);

// Trusted desktop main process
const desktopAgent = await hydrateAgentPackage(packageValue, {
  connector: 'desktop-openai',
  model: 'gpt-5.6-terra',
  userId: authenticatedUser.id,
  permissions: desktopPermissionPolicy,
  contextFactory: ({ package: pkg, model, userId, identities }) => ({
    model,
    agentId: pkg.agent.id,
    systemPrompt: pkg.agent.instructions,
    userId,
    identities,
    features: trustedContextFeatures,
    plugins: trustedContextPlugins,
  }),
  localToolResolver: (descriptor) => {
    const tool = resolveAuthorizedDesktopTool(descriptor);
    const implementationFingerprint = localToolFingerprints.get(
      descriptor.definition.function.name,
    );
    return tool && implementationFingerprint
      ? { tool, implementationFingerprint }
      : undefined;
  },
  remoteToolTransport: {
    execute: (request) => authenticatedRpc.executeRemoteTool(request),
  },
  agentConfig: trustedProviderPolicy,
});

Custom tools placed local must use a stable implementation ID that both trusted runtimes map through createPortableToolImplementationFingerprint(). The implementation ID is required and must change whenever executable behavior or one of its dependencies changes. Hydration requires localToolResolver to return a ResolvedLocalTool; returning a bare ToolFunction is rejected. Generated OneRingAI built-ins use their runtime-aware registry fingerprints automatically and can be wrapped with createResolvedLocalTool(tool) on the receiving host. A matching name or JSON schema alone is never treated as executable compatibility.

Fingerprint inputs follow JSON-wire semantics, so omitted and explicitly undefined optional fields do not diverge after serialization. A tool's dynamic function description is prompt presentation generated independently on each host and is excluded from its local executable contract; its callable schema, blocking mode, and timeout still must match. Remote tool definitions retain their full canonical definition fingerprint, which package validation recomputes before hydration or tool-server construction.

The RPC host must authenticate and authorize each request before forwarding it to toolServer.execute(request). Hydration also rejects a package tool whose name collides with a trusted context, identity, or host tool; package tools never overwrite host authority.

Packaged descriptor.permission values are informational and are never applied directly. If a portable tool needs a receiving-host override, provide toolPermissionResolver: (descriptor) => trustedPolicyFor(descriptor) to hydrateAgentPackage(). Returning undefined leaves the tool without a per-tool override; the Agent's required host-owned permission system still governs execution.

The package and remote-tool protocol require exact JSON objects. A package is limited to 10,000,000 UTF-8 bytes. Remote arguments and successful results are limited to 1,000,000 UTF-8 bytes each, and one tool-server session accepts at most 1,000 distinct request IDs. Unknown fields, non-JSON values, expired or incompatible packages, cross-package requests, and non-allowlisted tools fail closed. Exact request retries are idempotent for the bounded server-session lifetime.

Agent packages and remote-tool DTOs use exact protocol version 2. Version 1 peers reject version 2 objects, and version 2 peers reject version 1 objects. Deploy the exporting server and receiving host together, or keep the old pair active until all in-flight version 1 sessions have ended. Do not mix protocol versions within one tool-server session.

Call await toolServer.close() and desktopAgent.destroy() when the session ends. Concurrent close callers share the same shutdown operation, so every caller waits for in-flight tool execution to settle.

For a voice package, pass executionProfile: 'realtime' during hydration. The package's connector and model remain hints; an authorized connectorResolver may replace both. See Distributed Agent Execution for ownership, Electron/WebRTC flow, and security boundaries.

Documentation snapshot · 2026-11-02Suggest a correction ↗

Search documentation

Type to search the v2 documentation.