Skip to content

cpu_pearl

cpu_pearl

CPU-based Pearl mining provider (decoupled from inference).

See spec docs/design/2026-05-05-apple-silicon-pearl-mining-design.md §13 for the full v1 design.

The provider runs Pearl's pure-Rust mine() function via py-pearl-mining and runs Pearl's pearl-gateway as a sibling subprocess. Engine-independent: this provider does not plug into the user's inference stack. The user keeps using whatever engine they want; mining runs alongside on the CPU.

Classes

CpuPearlProvider

CpuPearlProvider()

Bases: MiningProvider

v1 cpu-pearl: decoupled CPU mining via py-pearl-mining + pearl-gateway.

Source code in src/openjarvis/mining/cpu_pearl.py
def __init__(self) -> None:
    self._launcher: PearlSubprocessLauncher | None = None
    self._config: MiningConfig | None = None
    self._started_at: float | None = None
Functions
start async
start(config: MiningConfig) -> None

Spawn pearl-gateway and miner-loop subprocesses; write sidecar.

Source code in src/openjarvis/mining/cpu_pearl.py
async def start(self, config: MiningConfig) -> None:
    """Spawn pearl-gateway and miner-loop subprocesses; write sidecar."""
    extra = dict(config.extra or {})
    password_env = extra.get("pearld_rpc_password_env", "PEARLD_RPC_PASSWORD")
    password = os.environ.get(password_env, "")

    self._launcher = PearlSubprocessLauncher(
        gateway_host=extra.get("gateway_host", "127.0.0.1"),
        gateway_port=int(extra.get("gateway_port", DEFAULT_GATEWAY_RPC_PORT)),
        metrics_port=int(extra.get("metrics_port", DEFAULT_GATEWAY_METRICS_PORT)),
        pearld_rpc_url=extra.get("pearld_rpc_url", DEFAULT_PEARLD_RPC_URL),
        pearld_rpc_user=extra.get("pearld_rpc_user", "rpcuser"),
        pearld_rpc_password=password,
        wallet_address=config.wallet_address,
        log_dir=_log_dir(),
    )
    self._launcher.start(
        m=int(extra.get("m", CPU_PEARL_DEFAULT_M)),
        n=int(extra.get("n", CPU_PEARL_DEFAULT_N)),
        k=int(extra.get("k", CPU_PEARL_DEFAULT_K)),
        rank=int(extra.get("rank", CPU_PEARL_DEFAULT_RANK)),
    )
    self._config = config
    self._started_at = time.time()
    self._write_sidecar()
stop async
stop() -> None

SIGTERM both subprocesses; remove sidecar.

Source code in src/openjarvis/mining/cpu_pearl.py
async def stop(self) -> None:
    """SIGTERM both subprocesses; remove sidecar."""
    if self._launcher is not None:
        self._launcher.stop()
    self._launcher = None
    self._config = None
    self._started_at = None
    Sidecar.remove(_sidecar_path())

Functions

ensure_registered

ensure_registered() -> None

Idempotently register CpuPearlProvider in MinerRegistry.

Called once at import time from openjarvis.mining.__init__. Tests that rely on the autouse registry-clear fixture in tests/conftest.py must call this from a fixture or test body to re-register after the clear.

Source code in src/openjarvis/mining/cpu_pearl.py
def ensure_registered() -> None:
    """Idempotently register CpuPearlProvider in MinerRegistry.

    Called once at import time from ``openjarvis.mining.__init__``. Tests that
    rely on the autouse registry-clear fixture in ``tests/conftest.py`` must
    call this from a fixture or test body to re-register after the clear.
    """
    if MinerRegistry.contains("cpu-pearl"):
        return
    MinerRegistry.register_value("cpu-pearl", CpuPearlProvider)