Metadata-Version: 2.4
Name: flashnode
Version: 0.2.0
Summary: Open host agent for the FlashML fragmented-compute network: join, benchmark, execute sandboxed ML tasks, earn contribution credits.
License: Apache-2.0
Project-URL: Homepage, https://github.com/Zolli-Labs/flashnode
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: flashruntime<0.4,>=0.3
Requires-Dist: psutil>=5.9
Requires-Dist: websockets>=12
Requires-Dist: cryptography>=42
Provides-Extra: dev
Requires-Dist: pytest; extra == "dev"
Dynamic: license-file

# FlashNode

> **The open host agent of the FlashML system.** Install FlashNode on a
> machine you own, and it can safely execute distributed ML tasks for the
> FlashML network — earning contribution credits for verified useful work.

FlashNode is one of three components in the FlashML system by
[Zolli Labs](https://github.com/Zolli-Labs):

- **flashnode** (this repo) — open host agent installed by resource
  contributors. Because it runs on someone else's machine and executes
  third-party workloads, it must be inspectable, minimal, and explicit about
  permissions — which is why it is open source.
- **[flashruntime](https://github.com/Zolli-Labs/flashruntime)** — the open
  workload protocol and execution layer.
- **flashml-cloud** (private) — the managed control plane and dashboard.

Read [`docs/SYSTEM_OVERVIEW.md`](docs/SYSTEM_OVERVIEW.md) for the full
product architecture, and [`AGENTS.md`](AGENTS.md) if you are an AI coding
agent working in this repo.

## Status

**Pre-release; the device executor works today** (July 2026). A machine
with this agent can join a FlashRuntime coordinator over outbound HTTP,
pull leased tasks, execute them, relay training checkpoints, and commit
verified results. Two profiles:

- **Device profile** (`flashnode work`) — the pull-based executor for
  laptops/workstations. Implemented.
- **Kubernetes profile** (`flashnode agent`) — per-node telemetry reporter
  inside managed pools (DaemonSet); KubeRay owns workload pods there.
  Implemented.

## What it does today

```bash
pip install -e .                      # plus: pip install -e ../flashruntime
flashnode work --coordinator http://<coordinator>:8100
# optional hardening / pool config:
#   FLASHNODE_JOIN_CODE=...          join-code-gated pools
#   --runner docker + FLASHNODE_ALLOWED_IMAGES=img:tag,...   container tier
#   FLASHNODE_WORKDIR=$HOME/.cache/flashnode   (macOS + colima: VM-visible workdirs)
#   FLASHNODE_WORKDIR=C:\Users\<you>\.flashnode  (Windows: must be under a
#                                                 directory Docker Desktop shares)
```

If the coordinator enforces per-machine authentication
(`FLASHML_NODE_TOKENS` set server-side), save the bearer token you were
given before running `work`:

```bash
flashnode login --coordinator http://<coordinator>:8100 --token <token>
flashnode work --coordinator http://<coordinator>:8100   # reads the saved token automatically
flashnode logout --coordinator http://<coordinator>:8100 # forget it locally (does not revoke server-side)
```

`login`/`logout` write to a per-coordinator credential store at
`~/.flashnode/credentials.json` (override with `FLASHNODE_CREDENTIALS`),
keyed by coordinator URL so one machine can hold separate tokens for
separate pools. The file is written with mode `0600` on every save. A
missing or unparseable file is treated as "no saved token" rather than a
crash. `CoordinatorClient` sends the saved token as a bearer header on
every request to that coordinator once it's saved — there is nothing else
to configure. Token issuance is still manual and out-of-band today (the
coordinator operator hands you the token; there is no self-service signup
or browser device flow yet), and `flashnode logout` only removes the local
copy — the operator revokes access by removing your token from the
coordinator's configuration.

- Stable node identity; registers with capabilities (CPU, RAM, arch, GPU)
  and **re-registers automatically** if the coordinator restarts.
- **Outbound-only** HTTP — no inbound ports, no router configuration.
- Claims task leases, renews them with attempt heartbeats, and stops work
  the moment a lease is refused (the coordinator's idempotent commit
  rejects late duplicates regardless — defense in depth).
- Two execution tiers behind one interface: `SubprocessRunner`
  (allowlisted Python modules, wall-clock timeout, **scrubbed
  environment** — agent secrets never reach task code) and `DockerRunner`
  (allowlisted images, `--network none`, cpu/memory limits, read-only
  rootfs, uid mapping).
- Downloads shared input artifacts; uploads outputs with sha256 for the
  coordinator's commit-time validation.
- **Checkpoint courier**: tasks stay network-isolated, so the agent
  fetches the task's latest valid checkpoint before a run (resume) and
  ships each new checkpoint file during it — a task killed on this
  machine resumes from its checkpoint on another.

Still to come: Ed25519-signed identity, admission benchmarks (`benchmark/`),
richer telemetry (`telemetry/`), gVisor/Kata isolation tiers, and the
`join`/`status`/`leave` UX.

## Security contract

- Outbound-only control connection; no inbound SSH or public ports.
- Signed node identity; short-lived session credentials.
- Allowlisted or signed workload images only.
- Non-root execution; no host Docker socket, device passthrough, or
  privileged mode.
- The agent shows exactly which limits and permissions apply to a workload
  before executing it.
- Complete event logging of task assignment, image digest, permissions, and
  artifact commits.

Supported host class (initial): x86-64 Linux, macOS (Docker Desktop or
Colima), or Windows (Docker Desktop with the **WSL2 backend**), Python
3.10+, ≥4 CPU cores, ≥8 GB RAM, stable outbound internet.

**Windows note:** `flashnode work` used to crash immediately on Windows
(`os.getuid`/`os.getgid` don't exist there). It now omits `--user` on
Windows instead, relying on the curated images' own non-root `USER`
declaration for non-root execution — see
[`docs/guides/donate-a-machine.md`](https://github.com/Zolli-Labs/flashruntime/blob/main/docs/guides/donate-a-machine.md#platform-support)
in flashruntime for the full picture, including honest caveats: **Windows
support is constructed-argv-verified (tests fake the platform), not yet
execution-verified against a real Windows machine.**

## Package layout

Working today:

```
flashnode/
├── agent/       # CLI (`work`, `agent`), K8s-profile daemon, kube helper
├── identity/    # stable node ID (Ed25519 signing: planned); credentials.py
│                #   is the per-coordinator bearer-token store behind
│                #   `flashnode login`/`logout`
├── inventory/   # capability discovery (psutil + K8s allocatable)
└── executor/    # the device work cycle:
    ├── client.py         # stdlib outbound HTTP: leases, artifacts, checkpoints
    ├── runner.py         # Tier 1: allowlisted subprocess, scrubbed env
    ├── docker_runner.py  # Tier 2: allowlisted containers, network-none
    └── loop.py           # claim → run (heartbeating) → relay ckpts → commit
```

Scaffolds awaiting their vertical slice: `benchmark/` (admission probes),
`telemetry/` (rich metrics), `artifacts/` (local caching), `config/`
(host-owner policy).

## License

[Apache-2.0](LICENSE). Contributions via Developer Certificate of Origin
(`git commit -s`).
