Spaces:
Running on Zero
Running on Zero
GitHub Actions commited on
Commit ·
48ecfd2
1
Parent(s): f08047d
fix(node): replace 131 UTF-8 curly quotes with ASCII to fix SyntaxError
Browse files- hearthnet/node.py +23 -23
hearthnet/node.py
CHANGED
|
@@ -108,7 +108,7 @@ class HearthNode:
|
|
| 108 |
# remote peers over the network (e.g. the public HF Space). The
|
| 109 |
# in-process InMemoryNetwork still passes a shared InMemoryTransport.
|
| 110 |
# CompositeTransport is a drop-in superset of HttpBusTransport that also
|
| 111 |
-
# accepts pluggable delivery strategies (relay/WebRTC/tunnel)
|
| 112 |
# attached only on demand via join_relay(), keeping nodes local-first.
|
| 113 |
if transport is None:
|
| 114 |
from hearthnet.bus.transport import CompositeTransport
|
|
@@ -122,13 +122,13 @@ class HearthNode:
|
|
| 122 |
self.chat = ChatFacade(self.bus)
|
| 123 |
self.marketplace = MarketplaceFacade(self.bus)
|
| 124 |
|
| 125 |
-
# Manual peer bridging (discovery.peer.add / discovery.peers)
|
| 126 |
# cross-network peering where mDNS/UDP multicast cannot reach.
|
| 127 |
from hearthnet.discovery.service import DiscoveryService
|
| 128 |
|
| 129 |
self.bus.register_service(DiscoveryService(self.bus, self.peers))
|
| 130 |
|
| 131 |
-
# mesh.join
|
| 132 |
from hearthnet.transport.mesh_service import MeshService
|
| 133 |
|
| 134 |
self.bus.register_service(MeshService(self))
|
|
@@ -162,7 +162,7 @@ class HearthNode:
|
|
| 162 |
) -> dict[str, Any]:
|
| 163 |
"""Join a relay hub so this node meshes all-to-all with NAT-bound peers.
|
| 164 |
|
| 165 |
-
Opt-in only
|
| 166 |
redeemed invite or the ``mesh up`` launcher). Registers the relay roster's
|
| 167 |
capabilities locally and attaches a :class:`RelayStrategy` to the bus
|
| 168 |
transport so calls to those peers are delivered through the hub.
|
|
@@ -208,7 +208,7 @@ class HearthNode:
|
|
| 208 |
# ------------------------------------------------------------------
|
| 209 |
|
| 210 |
def install_demo_services(self, *, internet_llm: bool = False, corpus: str = "demo") -> None:
|
| 211 |
-
"""FOR TESTS ONLY â€
|
| 212 |
|
| 213 |
Production code should call install_services() which auto-discovers real backends.
|
| 214 |
"""
|
|
@@ -267,10 +267,10 @@ class HearthNode:
|
|
| 267 |
"""Install real services with auto-discovered LLM backends.
|
| 268 |
|
| 269 |
Backend discovery order (local-first, no internet unless explicitly enabled):
|
| 270 |
-
1. OllamaBackend â€
|
| 271 |
-
2. LlamaCppBackend â€
|
| 272 |
-
3. HfLocalBackend â€
|
| 273 |
-
4. _UnavailableBackend â€
|
| 274 |
|
| 275 |
Also installs ModelDistributionService so peers can pull model weights.
|
| 276 |
"""
|
|
@@ -311,7 +311,7 @@ class HearthNode:
|
|
| 311 |
# 4. NVIDIA Nemotron (cloud NIM or local; NVIDIA prize track)
|
| 312 |
if os.getenv("NVIDIA_API_KEY"):
|
| 313 |
nemotron = NemotronBackend(api_key_env="NVIDIA_API_KEY")
|
| 314 |
-
backends.append(nemotron) # cloud
|
| 315 |
_log.info("Nemotron backend registered (NVIDIA_API_KEY set)")
|
| 316 |
elif os.getenv("NEMOTRON_URL"):
|
| 317 |
nemotron_local = NemotronBackend(
|
|
@@ -388,7 +388,7 @@ class HearthNode:
|
|
| 388 |
"""Register the real auxiliary services beyond the core set.
|
| 389 |
|
| 390 |
Always (each degrades gracefully to an "unavailable" response when its
|
| 391 |
-
optional backend/model is missing
|
| 392 |
M11 EmbeddingService embed.text (real semantic vectors when
|
| 393 |
sentence-transformers present)
|
| 394 |
M24 RerankService rerank.text
|
|
@@ -501,7 +501,7 @@ class HearthNode:
|
|
| 501 |
data_dir: Path | str | None = None,
|
| 502 |
gossip_interval: int = _GOSSIP_INTERVAL_SECONDS,
|
| 503 |
) -> None:
|
| 504 |
-
"""Start the node â€
|
| 505 |
|
| 506 |
Steps:
|
| 507 |
1-2. Already done: node_id + bus created in __init__
|
|
@@ -530,22 +530,22 @@ class HearthNode:
|
|
| 530 |
|
| 531 |
# Step 9: Event log + replay engine
|
| 532 |
# If the caller already opened an EventLog and set node._event_log before
|
| 533 |
-
# calling start() (e.g. app.py for HF Space), reuse it
|
| 534 |
if self._event_log is None:
|
| 535 |
try:
|
| 536 |
from hearthnet.events import EventLog, ReplayEngine
|
| 537 |
|
| 538 |
self._event_log = EventLog(
|
| 539 |
-
data_dir_path /
|
| 540 |
)
|
| 541 |
self._replay_engine = ReplayEngine(self._event_log)
|
| 542 |
-
_log.debug(
|
| 543 |
except Exception as exc:
|
| 544 |
-
_log.warning(
|
| 545 |
else:
|
| 546 |
-
_log.debug(
|
| 547 |
|
| 548 |
-
# â
|
| 549 |
caps = [e.descriptor.name for e in self.bus.registry.all_local()]
|
| 550 |
try:
|
| 551 |
from hearthnet.discovery.mdns import MdnsAnnouncer, MdnsBrowser
|
|
@@ -572,13 +572,13 @@ class HearthNode:
|
|
| 572 |
except Exception as exc:
|
| 573 |
_log.warning("Discovery init failed (non-fatal): %s", exc)
|
| 574 |
|
| 575 |
-
# â
|
| 576 |
try:
|
| 577 |
await self.detector.start()
|
| 578 |
except Exception as exc:
|
| 579 |
_log.warning("Emergency detector start failed (non-fatal): %s", exc)
|
| 580 |
|
| 581 |
-
# â
|
| 582 |
try:
|
| 583 |
from hearthnet.events.sync import SyncServer
|
| 584 |
from hearthnet.transport.server import HttpServer
|
|
@@ -603,7 +603,7 @@ class HearthNode:
|
|
| 603 |
except Exception as exc:
|
| 604 |
_log.warning("HTTP server start failed (non-fatal): %s", exc)
|
| 605 |
|
| 606 |
-
# â
|
| 607 |
if self._event_log is not None:
|
| 608 |
self._gossip_task = asyncio.create_task(
|
| 609 |
self._gossip_loop(gossip_interval), name="gossip-sync"
|
|
@@ -827,7 +827,7 @@ class InMemoryNetwork:
|
|
| 827 |
|
| 828 |
|
| 829 |
# ---------------------------------------------------------------------------
|
| 830 |
-
# PeriodicTask
|
| 831 |
# ---------------------------------------------------------------------------
|
| 832 |
|
| 833 |
|
|
@@ -856,7 +856,7 @@ class PeriodicTask:
|
|
| 856 |
|
| 857 |
|
| 858 |
# ---------------------------------------------------------------------------
|
| 859 |
-
# ManifestPublisher
|
| 860 |
# ---------------------------------------------------------------------------
|
| 861 |
|
| 862 |
_MANIFEST_REPUBLISH_INTERVAL_SECONDS = 300 # 5 minutes default
|
|
|
|
| 108 |
# remote peers over the network (e.g. the public HF Space). The
|
| 109 |
# in-process InMemoryNetwork still passes a shared InMemoryTransport.
|
| 110 |
# CompositeTransport is a drop-in superset of HttpBusTransport that also
|
| 111 |
+
# accepts pluggable delivery strategies (relay/WebRTC/tunnel) -- relay is
|
| 112 |
# attached only on demand via join_relay(), keeping nodes local-first.
|
| 113 |
if transport is None:
|
| 114 |
from hearthnet.bus.transport import CompositeTransport
|
|
|
|
| 122 |
self.chat = ChatFacade(self.bus)
|
| 123 |
self.marketplace = MarketplaceFacade(self.bus)
|
| 124 |
|
| 125 |
+
# Manual peer bridging (discovery.peer.add / discovery.peers) -- enables
|
| 126 |
# cross-network peering where mDNS/UDP multicast cannot reach.
|
| 127 |
from hearthnet.discovery.service import DiscoveryService
|
| 128 |
|
| 129 |
self.bus.register_service(DiscoveryService(self.bus, self.peers))
|
| 130 |
|
| 131 |
+
# mesh.join -- redeem an invite/relay code into all-to-all relay membership.
|
| 132 |
from hearthnet.transport.mesh_service import MeshService
|
| 133 |
|
| 134 |
self.bus.register_service(MeshService(self))
|
|
|
|
| 162 |
) -> dict[str, Any]:
|
| 163 |
"""Join a relay hub so this node meshes all-to-all with NAT-bound peers.
|
| 164 |
|
| 165 |
+
Opt-in only -- a node stays purely local until this is called (e.g. from a
|
| 166 |
redeemed invite or the ``mesh up`` launcher). Registers the relay roster's
|
| 167 |
capabilities locally and attaches a :class:`RelayStrategy` to the bus
|
| 168 |
transport so calls to those peers are delivered through the hub.
|
|
|
|
| 208 |
# ------------------------------------------------------------------
|
| 209 |
|
| 210 |
def install_demo_services(self, *, internet_llm: bool = False, corpus: str = "demo") -> None:
|
| 211 |
+
"""FOR TESTS ONLY â€" install echo-LLM + in-memory services (no disk I/O, fast).
|
| 212 |
|
| 213 |
Production code should call install_services() which auto-discovers real backends.
|
| 214 |
"""
|
|
|
|
| 267 |
"""Install real services with auto-discovered LLM backends.
|
| 268 |
|
| 269 |
Backend discovery order (local-first, no internet unless explicitly enabled):
|
| 270 |
+
1. OllamaBackend â€" if ollama is running on localhost
|
| 271 |
+
2. LlamaCppBackend â€" if llama.cpp HTTP server is running on localhost
|
| 272 |
+
3. HfLocalBackend â€" if transformers is installed (loads on first call)
|
| 273 |
+
4. _UnavailableBackend â€" fallback: returns a clear error, not a silent echo
|
| 274 |
|
| 275 |
Also installs ModelDistributionService so peers can pull model weights.
|
| 276 |
"""
|
|
|
|
| 311 |
# 4. NVIDIA Nemotron (cloud NIM or local; NVIDIA prize track)
|
| 312 |
if os.getenv("NVIDIA_API_KEY"):
|
| 313 |
nemotron = NemotronBackend(api_key_env="NVIDIA_API_KEY")
|
| 314 |
+
backends.append(nemotron) # cloud -- no local check needed
|
| 315 |
_log.info("Nemotron backend registered (NVIDIA_API_KEY set)")
|
| 316 |
elif os.getenv("NEMOTRON_URL"):
|
| 317 |
nemotron_local = NemotronBackend(
|
|
|
|
| 388 |
"""Register the real auxiliary services beyond the core set.
|
| 389 |
|
| 390 |
Always (each degrades gracefully to an "unavailable" response when its
|
| 391 |
+
optional backend/model is missing -- never a mock):
|
| 392 |
M11 EmbeddingService embed.text (real semantic vectors when
|
| 393 |
sentence-transformers present)
|
| 394 |
M24 RerankService rerank.text
|
|
|
|
| 501 |
data_dir: Path | str | None = None,
|
| 502 |
gossip_interval: int = _GOSSIP_INTERVAL_SECONDS,
|
| 503 |
) -> None:
|
| 504 |
+
"""Start the node â€" wires all subsystems.
|
| 505 |
|
| 506 |
Steps:
|
| 507 |
1-2. Already done: node_id + bus created in __init__
|
|
|
|
| 530 |
|
| 531 |
# Step 9: Event log + replay engine
|
| 532 |
# If the caller already opened an EventLog and set node._event_log before
|
| 533 |
+
# calling start() (e.g. app.py for HF Space), reuse it -- don't open a second DB.
|
| 534 |
if self._event_log is None:
|
| 535 |
try:
|
| 536 |
from hearthnet.events import EventLog, ReplayEngine
|
| 537 |
|
| 538 |
self._event_log = EventLog(
|
| 539 |
+
data_dir_path / "events.db", self.community_id, self.node_id
|
| 540 |
)
|
| 541 |
self._replay_engine = ReplayEngine(self._event_log)
|
| 542 |
+
_log.debug("EventLog opened at %s", data_dir_path / "events.db")
|
| 543 |
except Exception as exc:
|
| 544 |
+
_log.warning("EventLog init failed (non-fatal): %s", exc)
|
| 545 |
else:
|
| 546 |
+
_log.debug("EventLog already set, reusing existing instance")
|
| 547 |
|
| 548 |
+
# â"€â"€ Step 3: Peer discovery (mDNS + UDP) â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€
|
| 549 |
caps = [e.descriptor.name for e in self.bus.registry.all_local()]
|
| 550 |
try:
|
| 551 |
from hearthnet.discovery.mdns import MdnsAnnouncer, MdnsBrowser
|
|
|
|
| 572 |
except Exception as exc:
|
| 573 |
_log.warning("Discovery init failed (non-fatal): %s", exc)
|
| 574 |
|
| 575 |
+
# â"€â"€ Step 8: Emergency detector â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€
|
| 576 |
try:
|
| 577 |
await self.detector.start()
|
| 578 |
except Exception as exc:
|
| 579 |
_log.warning("Emergency detector start failed (non-fatal): %s", exc)
|
| 580 |
|
| 581 |
+
# â"€â"€ Step 10: HTTP server (X01) + WebSocket pubsub (X06) â"€â"€â"€â"€â"€â"€â"€
|
| 582 |
try:
|
| 583 |
from hearthnet.events.sync import SyncServer
|
| 584 |
from hearthnet.transport.server import HttpServer
|
|
|
|
| 603 |
except Exception as exc:
|
| 604 |
_log.warning("HTTP server start failed (non-fatal): %s", exc)
|
| 605 |
|
| 606 |
+
# â"€â"€ Gossip sync loop (X02) â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€â"€
|
| 607 |
if self._event_log is not None:
|
| 608 |
self._gossip_task = asyncio.create_task(
|
| 609 |
self._gossip_loop(gossip_interval), name="gossip-sync"
|
|
|
|
| 827 |
|
| 828 |
|
| 829 |
# ---------------------------------------------------------------------------
|
| 830 |
+
# PeriodicTask -- generic async interval runner (M12 §5)
|
| 831 |
# ---------------------------------------------------------------------------
|
| 832 |
|
| 833 |
|
|
|
|
| 856 |
|
| 857 |
|
| 858 |
# ---------------------------------------------------------------------------
|
| 859 |
+
# ManifestPublisher -- republishes node manifest to mDNS + UDP (M12 §5)
|
| 860 |
# ---------------------------------------------------------------------------
|
| 861 |
|
| 862 |
_MANIFEST_REPUBLISH_INTERVAL_SECONDS = 300 # 5 minutes default
|