core.service_base module

Service boot contract shared by every Stargazer microservice.

Defines StargazerService, the abstract base that runs a fixed boot sequence (Redis connect/ping, the subclass’s on_start, service-registry registration, and an HTTP health server via HealthServer) and a matching graceful shutdown. Every entry point (gateway / inference / agents / consolidation / web) subclasses it and implements on_start and run.

class core.service_base.StargazerService(service_name, instance_id, redis_client=None, redis_required=True, use_health_server=True)[source]

Bases: ABC

Abstract base class establishing the 9-phase boot contract.

Every Stargazer microservice (gateway / inference / agents / consolidation / web) subclasses this to inherit a uniform startup and shutdown lifecycle, so boot ordering – Redis ping gate, subclass init, service-registry registration, health server – is identical across the fleet and bugs in one tier cannot diverge from another. Subclasses fill in the three abstract hooks (on_start(), run(), on_stop()) while boot() and shutdown() orchestrate the fixed sequence around them.

Touches Redis (ping, registration via register_service(), deregistration via deregister_service()) and an HTTP health endpoint through HealthServer. It is never instantiated directly – each *_main.py entry point defines a concrete subclass (InferenceService, AgentsService, WebService, GatewayService, ConsolidationService) that calls super().__init__ and is launched from that module’s main under asyncio.run.

Parameters:
  • service_name (str)

  • instance_id (str)

  • redis_required (bool)

  • use_health_server (bool)

__init__(service_name, instance_id, redis_client=None, redis_required=True, use_health_server=True)[source]

Capture service identity and wire up the optional health server.

Performs only the cheap, synchronous Phase 1 (local init) portion of the boot contract; the network-touching phases run later in boot(). The supplied Redis client is stored for use by boot() / shutdown() (ping, service-registry registration, deregistration) and by subclasses. When use_health_server is true and a Redis client is present, a HealthServer is constructed (but not started here) bound to the port from the SG_HEALTH_PORT environment variable, defaulting to 9090; it is started later in boot()’s Phase 9. No callers construct StargazerService directly – it is abstract and each microservice entry point (gateway / inference / agents / consolidation / web) subclasses it and reaches this via super().__init__.

Parameters:
  • service_name (str) – Logical service name used in boot logs and in the service registry (e.g. "inference").

  • instance_id (str) – Unique identifier distinguishing this process from other replicas of the same service.

  • redis_client – An async Redis client, or None to run without Redis (in which case the registry, ping gate, and health server are all skipped).

  • redis_required (bool) – When true and the Redis ping in boot() fails, the process hard-fails via sys.exit(1); when false the failure is logged and boot continues.

  • use_health_server (bool) – When true (and Redis is present), build a HealthServer to expose HTTP health.

async boot()[source]

Run the full 9-phase startup sequence and bring the service live.

Drives the service from local init to a registered, health-serving process: it sleeps a random jitter (0-2s) to desynchronize replica starts, pings Redis and hard-fails the process if the ping fails and redis_required is set, runs the subclass’s on_start(), registers the instance in the service registry, and starts the health server. The jitter and ping gate exist so a thundering herd of restarts does not stampede Redis and so a service never claims work while its backing store is unreachable.

Touches Redis (ping plus a set of sg:registry:service:... with status "starting" via register_service()), may bind the HTTP health port, and on a required-Redis ping failure calls sys.exit(1). Called by each microservice’s main (in inference_main.py, agents_main.py, consolidation_main.py, web_main.py, gateway_main.py) immediately before service.run(), and exercised by tests/core/migration.

Raises:

SystemExit – Via sys.exit(1) when redis_required is true and the Redis ping fails.

async shutdown()[source]

Tear the service down gracefully in reverse boot order.

Counterpart to boot(): it stops the HTTP health server (so the instance immediately reports unready), removes the instance from the service registry, and then runs the subclass’s on_stop() cleanup. Ordering matters – deregistering and dropping health before on_stop() ensures operators and load balancers stop routing to a process that is mid-teardown.

Touches Redis by deleting the sg:registry:service:... key via deregister_service() and closes the health port. Invoked from the SIGINT/SIGTERM signal handlers installed in each microservice’s main (e.g. asyncio.create_task(service.shutdown()) in inference_main.py / gateway_main.py / web_main.py / consolidation_main.py / agents_main.py) and asserted by tests/core/migration/test_service_base.py.

abstractmethod async on_start()[source]

Subclass hook for tier-specific startup, run during boot.

Abstract extension point invoked by boot() (its phase 3-7) after the Redis ping succeeds but before registry registration and the health server start, so each tier can stand up its own machinery – loading tools, building platform adapters, wiring stream consumers, initializing observability – on a verified Redis connection. Has no behavior here; it must be overridden.

Called by boot(); the concrete implementations live in the *_main.py service classes (InferenceService.on_start, GatewayService.on_start, etc.) and are exercised directly by the tests/core/migration suite.

abstractmethod async run()[source]

Subclass hook for the main service loop, run after boot.

Abstract extension point that holds the service’s long-lived work – typically consuming a Redis stream or serving HTTP – and is expected to block until shutdown is signalled. Each tier’s main awaits this immediately after boot() returns, so it defines what the process actually does for its lifetime. Has no behavior here; it must be overridden.

Called by each microservice’s main via await service.run() (in inference_main.py, gateway_main.py, web_main.py, consolidation_main.py, agents_main.py), right after service.boot().

abstractmethod async on_stop()[source]

Subclass hook for tier-specific cleanup, run during shutdown.

Abstract extension point invoked by shutdown() after the health server is stopped and the instance is deregistered, giving each tier a place to release what on_start() and run() stood up (platform adapters, schedulers, consumers, sockets). Has no behavior here; it must be overridden.

Called by shutdown(); concrete implementations live in the *_main.py service classes (e.g. WebService.on_stop drives a graceful uvicorn exit, GatewayService.on_stop stops platform adapters) and are covered by tests/core/migration.