What problem are you trying to solve?
Eve’s custom-channel API works well for push-based integrations where an external platform calls an HTTP or WebSocket route. It does not currently provide a safe way to implement pull-based transports such as:
- Telegram
getUpdates long polling
- Queue consumers
- Message brokers
- IMAP polling
- Local sockets or IPC
- Database notification listeners
- Device and event-stream consumers
A custom channel receives Eve’s send function only inside an HTTP or WebSocket route handler:
POST("/message", async (request, { send }) => {
await send(...);
});
A pull-based connector needs to start a background consumer when the agent runtime becomes ready and dispatch messages without an inbound HTTP request. There is currently no supported lifecycle or ingress API for doing that.
Starting a loop at module scope is unsafe because channel modules may be imported during discovery, compilation, builds, and hot reloads. It can create duplicate consumers or perform external operations when no runtime is ready.
As a workaround, the connector must run as a separate process and forward every event into an Eve HTTP route on localhost. In a multi-agent application, each agent may run on a dynamic port, so the sidecar must also discover which server is the correct coordinator and reconnect whenever it changes.
This makes a logically self-contained custom connector require an additional process supervisor, internal HTTP transport, dynamic service discovery, and duplicate-consumer protection.
Proposed solution
Add lifecycle hooks and programmatic ingress to custom channels.
For example:
import { defineChannel } from "eve/channels";
export default defineChannel({
async start(context) {
const {
signal,
dispatch,
logger,
} = context;
let offset = 0;
while (!signal.aborted) {
const updates = await getUpdates({
offset,
timeout: 30,
signal,
});
for (const update of updates) {
await dispatch(update.message.text, {
auth: telegramAuth(update),
continuationToken: telegramContinuationToken(update),
state: telegramState(update),
});
offset = update.update_id + 1;
}
}
},
async stop() {
// Optional cleanup beyond aborting the lifecycle signal.
},
events: {
async "message.completed"(event, channel) {
await deliverToTelegram(event.message, channel.state);
},
},
});
The lifecycle context could provide:
interface ChannelLifecycleContext {
signal: AbortSignal;
dispatch: SendFn;
logger: Logger;
runtime: {
agentId: string;
environment: "development" | "production";
};
}
Desired behavior:
start runs only after the agent runtime and workflow handlers are ready.
dispatch has the same semantics as route-handler send.
signal aborts before shutdown or replacement.
- Eve prevents duplicate lifecycle instances for the same compiled channel.
- Hot reload stops the previous instance before starting the replacement.
- Startup failures are reported as channel health errors.
- Long-running tasks can use bounded restart and backoff behavior.
- Multi-agent applications start lifecycle hooks only for the owning agent.
- Builds, discovery, and static module imports never invoke lifecycle hooks.
- Unsupported environments fail clearly rather than silently starting duplicate consumers.
It may also be useful to expose channel health through /eve/v1/info:
{
"channels": [
{
"id": "telegram",
"status": "running",
"transport": "background",
"startedAt": "..."
}
]
}
For hosts that cannot support persistent background work, Eve could reject the configuration during startup or require an explicit deployment capability:
defineChannel({
runtime: "persistent",
start(context) {
// ...
},
});
Alternatives considered
External sidecar
Run the pull consumer as a separate process and forward events into a custom HTTP route. This works, but requires process supervision, internal HTTP authentication, runtime URL discovery, and coordination across restarts.
Module-scope background loop
Start polling when the channel module is imported. This can create duplicate consumers during discovery, builds, hot reloads, or multi-worker execution. It also has no reliable shutdown or readiness signal.
Self-calling HTTP route
Expose a custom route and have a background script call it over localhost. This preserves the existing channel API but turns an internal dispatch into an unnecessary network boundary and does not solve lifecycle ownership.
Platform webhooks
Use a public webhook rather than polling. This is appropriate for hosted deployments, but not for local machines, private networks, message brokers, local IPC, or integrations that only provide pull-based APIs.
Provider-specific polling support
Eve could add long polling directly to its Telegram channel. That solves Telegram but leaves the same limitation for queues, IMAP, brokers, and other pull-based integrations. A general custom-channel lifecycle API would support all of these use cases.
What problem are you trying to solve?
Eve’s custom-channel API works well for push-based integrations where an external platform calls an HTTP or WebSocket route. It does not currently provide a safe way to implement pull-based transports such as:
getUpdateslong pollingA custom channel receives Eve’s
sendfunction only inside an HTTP or WebSocket route handler:A pull-based connector needs to start a background consumer when the agent runtime becomes ready and dispatch messages without an inbound HTTP request. There is currently no supported lifecycle or ingress API for doing that.
Starting a loop at module scope is unsafe because channel modules may be imported during discovery, compilation, builds, and hot reloads. It can create duplicate consumers or perform external operations when no runtime is ready.
As a workaround, the connector must run as a separate process and forward every event into an Eve HTTP route on localhost. In a multi-agent application, each agent may run on a dynamic port, so the sidecar must also discover which server is the correct coordinator and reconnect whenever it changes.
This makes a logically self-contained custom connector require an additional process supervisor, internal HTTP transport, dynamic service discovery, and duplicate-consumer protection.
Proposed solution
Add lifecycle hooks and programmatic ingress to custom channels.
For example:
The lifecycle context could provide:
Desired behavior:
startruns only after the agent runtime and workflow handlers are ready.dispatchhas the same semantics as route-handlersend.signalaborts before shutdown or replacement.It may also be useful to expose channel health through
/eve/v1/info:{ "channels": [ { "id": "telegram", "status": "running", "transport": "background", "startedAt": "..." } ] }For hosts that cannot support persistent background work, Eve could reject the configuration during startup or require an explicit deployment capability:
Alternatives considered
External sidecar
Run the pull consumer as a separate process and forward events into a custom HTTP route. This works, but requires process supervision, internal HTTP authentication, runtime URL discovery, and coordination across restarts.
Module-scope background loop
Start polling when the channel module is imported. This can create duplicate consumers during discovery, builds, hot reloads, or multi-worker execution. It also has no reliable shutdown or readiness signal.
Self-calling HTTP route
Expose a custom route and have a background script call it over localhost. This preserves the existing channel API but turns an internal dispatch into an unnecessary network boundary and does not solve lifecycle ownership.
Platform webhooks
Use a public webhook rather than polling. This is appropriate for hosted deployments, but not for local machines, private networks, message brokers, local IPC, or integrations that only provide pull-based APIs.
Provider-specific polling support
Eve could add long polling directly to its Telegram channel. That solves Telegram but leaves the same limitation for queues, IMAP, brokers, and other pull-based integrations. A general custom-channel lifecycle API would support all of these use cases.