One agent server

Run a single long-lived process. Providers are interfaces into that process — not separate microservices per channel.
const app = await Skyline({
  projectId: process.env.SKYLINE_PROJECT_ID!,
  projectSecret: process.env.SKYLINE_PROJECT_SECRET!,
  providers: [imessage.config(), terminal.config()],
});

for await (const [space, message] of app.messages) {
  await handle(space, message);
}

Separate ingress from intelligence

  • Ingressapp.messages, webhooks, or both. Normalize to one handler shape.
  • Intelligence — your LLM, tools, memory. Provider-agnostic.
  • Egressspace.send(), reactions, typing. Branch only when a platform lacks a verb.

Human pacing

Use typing indicators and short delays before long replies. React with a tapback before sending text on iMessage.

Platform narrowing at the edges

Keep the core loop provider-agnostic. Call iMessage-only features (effects, app cards) only after checking message.platform or space.platform.