gateway_main

Gateway service entry point — platform ingress and egress.

The Gateway is the only service that holds live platform connections (Discord, Matrix, WebChat, …). It runs GatewayService, a StargazerService that:

  • Creates a PlatformAdapter per enabled platform (via platforms.factory.create_platform()) and registers inbound callbacks for new messages, edits, deletes, and reactions.

  • On each inbound message, translates the IncomingMessage into a validated InboundEnvelopeModel (_make_inbound_envelope()) and publishes it to the sg:stream:inbound Redis stream via publish_inbound().

  • Runs one OutboundStreamConsumer per platform that reads sg:stream:outbound:{platform} and dispatches each response back through the real adapter (send / send_file / send_with_buttons / reactions).

  • Periodically publishes a platform inventory (servers/channels) to Redis for the web dashboard, throttled to at most every 12 hours.

Since Phase T3 (2026-06-02) the StranglerRouter has been removed: the monolith is gone and all inbound traffic routes unconditionally over the event bus to the Inference service.

Launched standalone (python gateway_main.py) by scripts/systemd/stargazer-gateway.service.

class gateway_main.GatewayService(config, redis_client, instance_id, use_health_server=True)[source]

Bases: StargazerService

Parameters:
__init__(config, redis_client, instance_id, use_health_server=True)[source]

Construct the Gateway service and initialise empty runtime state.

Wires the base StargazerService contract (service name "gateway", instance id, Redis client, mandatory Redis, optional health server) and stashes config on self.cfg. All heavy resources (event bus, media cache, adapters, outbound consumers, inventory task) are left as None/empty here and only created later in on_start(); self._stop_event is the asyncio.Event that run() blocks on and on_stop() sets to unblock shutdown.

Delegates base-class setup to StargazerService.__init__ (which builds the optional HealthServer). Called by main() when launching the service standalone, and directly by the gateway migration tests under tests/core/migration/ (e.g. test_gateway_service.py, test_strangler_removal.py).

Parameters:
  • config (Config) – Loaded Config holding platform definitions, media-cache settings, and tools_dir.

  • redis_client (Any) – Async Redis client (decode_responses=False) shared across the event bus, outbound consumers, and inventory cache.

  • instance_id (str) – Unique id for this process (e.g. "gateway-ab12cd34"), used as the consumer name for outbound stream reads and for service-registry registration.

  • use_health_server (bool) – When True (default) the base class starts an HTTP health server; tests pass False to skip it.

async on_start()[source]

Build and start every Gateway dependency: event bus, adapters, consumers.

This is the Gateway’s implementation of the base-class startup phase. It constructs the RedisEventBus and ensures the Redis streams exist, creates the MediaCache, then for each enabled platform in self.cfg.platforms builds a PlatformAdapter via platforms.factory.create_platform() (registering _handle_inbound_message() plus the edit/delete/reaction handlers). Crucially it starts every OutboundStreamConsumer before starting the adapters, so the response path on sg:stream:outbound:{platform} exists before any inbound message can arrive. Finally it launches the background _publish_platform_inventory_loop() as a named asyncio task.

Interactions: calls event_bus.ensure_streams, create_platform, consumer.start(), and each adapter.start(); populates self.event_bus, self.media_cache, self.adapters, self.outbound_consumers, and self._inventory_task. Adapter construction failures are logged and swallowed so one broken platform does not abort the whole service. Called by boot() during phase 3-7 (which main() invokes), and exercised directly by the adapter-lifecycle and strangler-removal migration tests.

Return type:

None

async run()[source]

Run the Gateway’s main loop by blocking until shutdown is requested.

The Gateway has no polling work of its own in the foreground: all activity happens in adapter callbacks and the outbound consumers started during on_start(). This coroutine therefore simply awaits self._stop_event, which on_stop() sets during graceful shutdown. Called by main() immediately after boot() completes.

Return type:

None

async on_stop()[source]

Gracefully tear down the Gateway: stop the loop, consumers, and adapters.

Sets self._stop_event (unblocking run()), cancels and awaits the background self._inventory_task (swallowing the resulting asyncio.CancelledError), stops every OutboundStreamConsumer so no further outbound responses are dispatched, and stops each platform adapter that exposes a stop method to close live connections. Called by shutdown() (which the SIGINT/ SIGTERM handler in main() triggers via service.shutdown()), and exercised by the gateway adapter-lifecycle migration tests.

Return type:

None

async gateway_main.main()[source]

Configure logging, build the Gateway service, and run it until signalled.

Process entry point for the standalone Gateway. It sets up basic logging, loads Config via Config.load(), mints a unique instance_id, builds a binary (decode_responses=False) async Redis client, and constructs GatewayService. It installs SIGINT/SIGTERM handlers that schedule service.shutdown(), then drives the lifecycle with service.boot() followed by service.run() (which blocks until shutdown). The finally block always closes the Redis client via aclose().

Invoked by the __main__ guard at the bottom of this module through asyncio.run(main()) when launched by scripts/systemd/stargazer-gateway.service. No internal Python callers.

Return type:

None