Spaces:
Running on Zero
Running on Zero
Create app.py
#2
by deleeharris - opened
This view is limited to 50 files because it contains too many changes. See the raw diff here.
- .claude/settings.json +0 -10
- .dockerignore +0 -26
- .gitattributes +0 -2
- .github/workflows/release.yml +0 -193
- .github/workflows/test.yml +0 -63
- .gitignore +0 -0
- .live_test_node/events.db +0 -0
- .playwright-mcp/console-2026-06-12T13-03-23-327Z.log +0 -2
- .playwright-mcp/console-2026-06-12T13-12-18-364Z.log +0 -13
- .playwright-mcp/console-2026-06-12T13-16-09-405Z.log +0 -81
- .playwright-mcp/page-2026-06-10T14-22-38-882Z.yml +0 -3
- .playwright-mcp/page-2026-06-10T14-27-49-510Z.yml +0 -60
- .playwright-mcp/page-2026-06-10T14-28-44-543Z.yml +0 -67
- .playwright-mcp/page-2026-06-10T14-28-52-588Z.yml +0 -53
- .playwright-mcp/page-2026-06-10T14-29-00-723Z.yml +0 -110
- .playwright-mcp/page-2026-06-10T14-52-32-151Z.yml +0 -3
- .playwright-mcp/page-2026-06-10T14-52-36-986Z.yml +0 -98
- .playwright-mcp/page-2026-06-10T15-12-22-534Z.yml +0 -0
- .playwright-mcp/page-2026-06-10T15-16-39-829Z.yml +0 -3
- .playwright-mcp/page-2026-06-10T15-17-36-795Z.yml +0 -15
- .playwright-mcp/page-2026-06-10T15-18-10-485Z.yml +0 -15
- .playwright-mcp/page-2026-06-10T15-20-12-385Z.yml +0 -3
- .playwright-mcp/page-2026-06-10T23-26-07-697Z.yml +0 -3
- .playwright-mcp/page-2026-06-12T13-07-53-601Z.yml +0 -0
- .playwright-mcp/page-2026-06-12T13-16-10-970Z.yml +0 -25
- .playwright-mcp/page-2026-06-13T06-41-44-411Z.yml +0 -113
- .playwright-mcp/page-2026-06-13T06-52-18-615Z.yml +0 -113
- 6.0.0 +0 -18
- BLOG_COMPREHENSIVE.md +0 -616
- README.md +211 -446
- agents.md +0 -45
- app.py +0 -681
- app_nemotron.py +0 -558
- assets/initial_docs/README.md +0 -13
- build/android/HearthNetApp/config.xml +0 -32
- coverage_report.txt +0 -0
- data/hearthnet/hearthnet-space/corpora/test.md +0 -1
- data/hearthnet/test.md +0 -0
- docs/ARCHITECTURE.md +0 -441
- docs/CAPABILITY_CONTRACT.md +0 -5
- docs/ENV.md +0 -169
- docs/{modules/M01-identity.md β M01-identity.md} +0 -0
- docs/{modules/M02-discovery.md β M02-discovery.md} +0 -0
- docs/{modules/M03-bus.md β M03-bus.md} +0 -0
- docs/{modules/M04-llm.md β M04-llm.md} +0 -0
- docs/{modules/M05-rag.md β M05-rag.md} +0 -0
- docs/{modules/M06-marketplace.md β M06-marketplace.md} +0 -0
- docs/{modules/M07-file-blobs.md β M07-file-blobs.md} +0 -0
- docs/{modules/M08-ui.md β M08-ui.md} +0 -0
- docs/{modules/M09-emergency.md β M09-emergency.md} +0 -0
.claude/settings.json
DELETED
|
@@ -1,10 +0,0 @@
|
|
| 1 |
-
{
|
| 2 |
-
"permissions": {
|
| 3 |
-
"allow": [
|
| 4 |
-
"Bash(xargs wc -l)",
|
| 5 |
-
"Bash(git add *)",
|
| 6 |
-
"Bash(git commit -m ' *)",
|
| 7 |
-
"Bash(git push *)"
|
| 8 |
-
]
|
| 9 |
-
}
|
| 10 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.dockerignore
DELETED
|
@@ -1,26 +0,0 @@
|
|
| 1 |
-
.git
|
| 2 |
-
.gitignore
|
| 3 |
-
.github
|
| 4 |
-
.venv
|
| 5 |
-
venv
|
| 6 |
-
env
|
| 7 |
-
__pycache__
|
| 8 |
-
*.pyc
|
| 9 |
-
*.pyo
|
| 10 |
-
*.egg-info
|
| 11 |
-
.pytest_cache
|
| 12 |
-
.mypy_cache
|
| 13 |
-
.ruff_cache
|
| 14 |
-
dist
|
| 15 |
-
build
|
| 16 |
-
*.egg
|
| 17 |
-
.DS_Store
|
| 18 |
-
.env
|
| 19 |
-
.env.local
|
| 20 |
-
tests/
|
| 21 |
-
docs/screenshots/
|
| 22 |
-
*.md
|
| 23 |
-
!README.md
|
| 24 |
-
.vscode
|
| 25 |
-
.idea
|
| 26 |
-
Makefile
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.gitattributes
CHANGED
|
@@ -33,5 +33,3 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
-
*.png filter=lfs diff=lfs merge=lfs -text
|
| 37 |
-
hf_hackathon_screenrecording_v1.webm filter=lfs diff=lfs merge=lfs -text
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
.github/workflows/release.yml
DELETED
|
@@ -1,193 +0,0 @@
|
|
| 1 |
-
name: Release Build & Package
|
| 2 |
-
|
| 3 |
-
on:
|
| 4 |
-
push:
|
| 5 |
-
tags:
|
| 6 |
-
- 'v*'
|
| 7 |
-
workflow_dispatch:
|
| 8 |
-
inputs:
|
| 9 |
-
variant:
|
| 10 |
-
description: 'Build variant'
|
| 11 |
-
required: true
|
| 12 |
-
default: 'both'
|
| 13 |
-
type: choice
|
| 14 |
-
options:
|
| 15 |
-
- slim
|
| 16 |
-
- full
|
| 17 |
-
- both
|
| 18 |
-
|
| 19 |
-
concurrency:
|
| 20 |
-
group: release-${{ github.ref }}
|
| 21 |
-
cancel-in-progress: false
|
| 22 |
-
|
| 23 |
-
jobs:
|
| 24 |
-
build-matrix:
|
| 25 |
-
runs-on: ${{ matrix.os }}
|
| 26 |
-
strategy:
|
| 27 |
-
fail-fast: false
|
| 28 |
-
matrix:
|
| 29 |
-
include:
|
| 30 |
-
# Windows
|
| 31 |
-
- os: windows-latest
|
| 32 |
-
artifact-type: exe
|
| 33 |
-
platform: windows
|
| 34 |
-
# Linux
|
| 35 |
-
- os: ubuntu-latest
|
| 36 |
-
artifact-type: appimage
|
| 37 |
-
platform: linux
|
| 38 |
-
# macOS
|
| 39 |
-
- os: macos-latest
|
| 40 |
-
artifact-type: dmg
|
| 41 |
-
platform: macos
|
| 42 |
-
|
| 43 |
-
steps:
|
| 44 |
-
- name: Checkout code
|
| 45 |
-
uses: actions/checkout@v4
|
| 46 |
-
|
| 47 |
-
- name: Set up Python
|
| 48 |
-
uses: actions/setup-python@v4
|
| 49 |
-
with:
|
| 50 |
-
python-version: '3.12'
|
| 51 |
-
|
| 52 |
-
- name: Cache pip packages
|
| 53 |
-
uses: actions/cache@v3
|
| 54 |
-
with:
|
| 55 |
-
path: ~/.cache/pip
|
| 56 |
-
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
|
| 57 |
-
restore-keys: |
|
| 58 |
-
${{ runner.os }}-pip-
|
| 59 |
-
|
| 60 |
-
- name: Install dependencies
|
| 61 |
-
run: |
|
| 62 |
-
python -m pip install --upgrade pip setuptools wheel
|
| 63 |
-
pip install -r requirements.txt
|
| 64 |
-
pip install -r build/requirements-build.txt
|
| 65 |
-
|
| 66 |
-
- name: Build Windows EXE
|
| 67 |
-
if: matrix.platform == 'windows'
|
| 68 |
-
run: |
|
| 69 |
-
powershell -ExecutionPolicy Bypass -File build/windows/build.ps1 -Variant both -BuildInstaller $true
|
| 70 |
-
dir dist/ /s
|
| 71 |
-
|
| 72 |
-
- name: Build Linux packages
|
| 73 |
-
if: matrix.platform == 'linux'
|
| 74 |
-
run: |
|
| 75 |
-
chmod +x build/linux/build.sh
|
| 76 |
-
bash build/linux/build.sh both all
|
| 77 |
-
ls -lah dist/
|
| 78 |
-
|
| 79 |
-
- name: Build macOS app
|
| 80 |
-
if: matrix.platform == 'macos'
|
| 81 |
-
run: |
|
| 82 |
-
chmod +x build/macos/build.sh
|
| 83 |
-
bash build/macos/build.sh both
|
| 84 |
-
ls -lah dist/
|
| 85 |
-
|
| 86 |
-
- name: Upload artifacts
|
| 87 |
-
uses: actions/upload-artifact@v3
|
| 88 |
-
with:
|
| 89 |
-
name: hearthnet-${{ matrix.platform }}-${{ matrix.artifact-type }}
|
| 90 |
-
path: dist/
|
| 91 |
-
retention-days: 7
|
| 92 |
-
|
| 93 |
-
build-docker:
|
| 94 |
-
runs-on: ubuntu-latest
|
| 95 |
-
permissions:
|
| 96 |
-
contents: read
|
| 97 |
-
packages: write
|
| 98 |
-
|
| 99 |
-
steps:
|
| 100 |
-
- name: Checkout code
|
| 101 |
-
uses: actions/checkout@v4
|
| 102 |
-
|
| 103 |
-
- name: Set up Docker Buildx
|
| 104 |
-
uses: docker/setup-buildx-action@v2
|
| 105 |
-
|
| 106 |
-
- name: Log in to GitHub Container Registry
|
| 107 |
-
uses: docker/login-action@v2
|
| 108 |
-
with:
|
| 109 |
-
registry: ghcr.io
|
| 110 |
-
username: ${{ github.actor }}
|
| 111 |
-
password: ${{ secrets.GITHUB_TOKEN }}
|
| 112 |
-
|
| 113 |
-
- name: Extract version
|
| 114 |
-
id: version
|
| 115 |
-
run: |
|
| 116 |
-
VERSION=$(grep '^version' pyproject.toml | head -1 | cut -d'"' -f2)
|
| 117 |
-
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
| 118 |
-
|
| 119 |
-
- name: Build and push slim image
|
| 120 |
-
uses: docker/build-push-action@v4
|
| 121 |
-
with:
|
| 122 |
-
context: .
|
| 123 |
-
file: build/docker/Dockerfile.slim
|
| 124 |
-
push: true
|
| 125 |
-
tags: |
|
| 126 |
-
ghcr.io/${{ github.repository }}:${{ steps.version.outputs.version }}-slim
|
| 127 |
-
ghcr.io/${{ github.repository }}:latest-slim
|
| 128 |
-
labels: |
|
| 129 |
-
org.opencontainers.image.title=HearthNet (slim)
|
| 130 |
-
org.opencontainers.image.version=${{ steps.version.outputs.version }}
|
| 131 |
-
|
| 132 |
-
- name: Build and push full image
|
| 133 |
-
uses: docker/build-push-action@v4
|
| 134 |
-
with:
|
| 135 |
-
context: .
|
| 136 |
-
file: build/docker/Dockerfile.full
|
| 137 |
-
push: true
|
| 138 |
-
tags: |
|
| 139 |
-
ghcr.io/${{ github.repository }}:${{ steps.version.outputs.version }}-full
|
| 140 |
-
ghcr.io/${{ github.repository }}:latest-full
|
| 141 |
-
labels: |
|
| 142 |
-
org.opencontainers.image.title=HearthNet (full)
|
| 143 |
-
org.opencontainers.image.version=${{ steps.version.outputs.version }}
|
| 144 |
-
|
| 145 |
-
create-release:
|
| 146 |
-
needs: [build-matrix, build-docker]
|
| 147 |
-
runs-on: ubuntu-latest
|
| 148 |
-
if: startsWith(github.ref, 'refs/tags/')
|
| 149 |
-
|
| 150 |
-
permissions:
|
| 151 |
-
contents: write
|
| 152 |
-
|
| 153 |
-
steps:
|
| 154 |
-
- name: Checkout code
|
| 155 |
-
uses: actions/checkout@v4
|
| 156 |
-
|
| 157 |
-
- name: Download all artifacts
|
| 158 |
-
uses: actions/download-artifact@v3
|
| 159 |
-
with:
|
| 160 |
-
path: all-artifacts
|
| 161 |
-
|
| 162 |
-
- name: Generate checksums
|
| 163 |
-
run: |
|
| 164 |
-
cd all-artifacts
|
| 165 |
-
for file in */*; do
|
| 166 |
-
sha256sum "$file" >> SHA256SUMS.txt
|
| 167 |
-
done
|
| 168 |
-
cat SHA256SUMS.txt
|
| 169 |
-
|
| 170 |
-
- name: Create release
|
| 171 |
-
uses: softprops/action-gh-release@v1
|
| 172 |
-
with:
|
| 173 |
-
files: |
|
| 174 |
-
all-artifacts/**/*
|
| 175 |
-
all-artifacts/SHA256SUMS.txt
|
| 176 |
-
body: |
|
| 177 |
-
## HearthNet ${{ github.ref_name }} Release
|
| 178 |
-
|
| 179 |
-
### Download Options
|
| 180 |
-
- **Windows**: EXE (standalone) or MSI (installer)
|
| 181 |
-
- **Linux**: AppImage (portable) or native packages (snap/deb/rpm)
|
| 182 |
-
- **macOS**: DMG (drag-to-Applications)
|
| 183 |
-
- **Docker**: Pull from `ghcr.io/${{ github.repository }}`
|
| 184 |
-
|
| 185 |
-
### Installation
|
| 186 |
-
See [DEPLOYMENT.md](https://github.com/${{ github.repository }}/blob/main/docs/DEPLOYMENT.md) for detailed instructions.
|
| 187 |
-
|
| 188 |
-
### Checksums
|
| 189 |
-
Verify downloads with: `sha256sum -c SHA256SUMS.txt`
|
| 190 |
-
draft: false
|
| 191 |
-
prerelease: false
|
| 192 |
-
env:
|
| 193 |
-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.github/workflows/test.yml
DELETED
|
@@ -1,63 +0,0 @@
|
|
| 1 |
-
name: Test Suite & Coverage
|
| 2 |
-
on:
|
| 3 |
-
push:
|
| 4 |
-
branches: [ main, dev ]
|
| 5 |
-
pull_request:
|
| 6 |
-
branches: [ main, dev ]
|
| 7 |
-
|
| 8 |
-
jobs:
|
| 9 |
-
test:
|
| 10 |
-
runs-on: ubuntu-latest
|
| 11 |
-
strategy:
|
| 12 |
-
matrix:
|
| 13 |
-
python-version: ['3.10', '3.11', '3.12']
|
| 14 |
-
|
| 15 |
-
steps:
|
| 16 |
-
- uses: actions/checkout@v4
|
| 17 |
-
|
| 18 |
-
- name: Set up Python ${{ matrix.python-version }}
|
| 19 |
-
uses: actions/setup-python@v4
|
| 20 |
-
with:
|
| 21 |
-
python-version: ${{ matrix.python-version }}
|
| 22 |
-
cache: 'pip'
|
| 23 |
-
|
| 24 |
-
- name: Install dependencies
|
| 25 |
-
run: |
|
| 26 |
-
python -m pip install --upgrade pip
|
| 27 |
-
pip install -r requirements-dev.txt
|
| 28 |
-
pip install pytest pytest-cov pytest-asyncio pytest-benchmark
|
| 29 |
-
|
| 30 |
-
- name: Run tests
|
| 31 |
-
run: |
|
| 32 |
-
python -m pytest tests/ -v --tb=short --cov=hearthnet --cov-report=xml --cov-report=html
|
| 33 |
-
|
| 34 |
-
- name: Upload coverage to Codecov
|
| 35 |
-
uses: codecov/codecov-action@v3
|
| 36 |
-
with:
|
| 37 |
-
file: ./coverage.xml
|
| 38 |
-
flags: unittests
|
| 39 |
-
name: codecov-umbrella
|
| 40 |
-
|
| 41 |
-
- name: Generate coverage badge
|
| 42 |
-
run: |
|
| 43 |
-
python -c "
|
| 44 |
-
import xml.etree.ElementTree as ET
|
| 45 |
-
tree = ET.parse('coverage.xml')
|
| 46 |
-
root = tree.getroot()
|
| 47 |
-
coverage = float(root.attrib.get('line-rate', 0)) * 100
|
| 48 |
-
print(f'Coverage: {coverage:.1f}%')
|
| 49 |
-
"
|
| 50 |
-
|
| 51 |
-
- name: Comment PR with test results
|
| 52 |
-
if: github.event_name == 'pull_request'
|
| 53 |
-
uses: actions/github-script@v7
|
| 54 |
-
with:
|
| 55 |
-
script: |
|
| 56 |
-
const fs = require('fs');
|
| 57 |
-
const coverage = fs.readFileSync('./coverage.xml', 'utf8');
|
| 58 |
-
github.rest.issues.createComment({
|
| 59 |
-
issue_number: context.issue.number,
|
| 60 |
-
owner: context.repo.owner,
|
| 61 |
-
repo: context.repo.repo,
|
| 62 |
-
body: 'β
All tests passed!\n\nπ [View Coverage Report](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})'
|
| 63 |
-
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.gitignore
DELETED
|
Binary file (502 Bytes)
|
|
|
.live_test_node/events.db
DELETED
|
Binary file (28.7 kB)
|
|
|
.playwright-mcp/console-2026-06-12T13-03-23-327Z.log
DELETED
|
@@ -1,2 +0,0 @@
|
|
| 1 |
-
[ 756914ms] [ERROR] Access to script at 'http://127.0.0.1:8099/src/rag/rag.js' from origin 'http://127.0.0.1:7861' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. @ http://127.0.0.1:7861/:0
|
| 2 |
-
[ 756914ms] [ERROR] Failed to load resource: net::ERR_FAILED @ http://127.0.0.1:8099/src/rag/rag.js:0
|
|
|
|
|
|
|
|
|
.playwright-mcp/console-2026-06-12T13-12-18-364Z.log
DELETED
|
@@ -1,13 +0,0 @@
|
|
| 1 |
-
[ 1980ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:7860/theme.css?v=8ad6f9b14414574fe6c6d9b4362dcdd63dfdc66d8c34cbef0982888dfc44ff04:0
|
| 2 |
-
[ 1980ms] [ERROR] Unable to preload CSS for http://127.0.0.1:7860/theme.css?v=8ad6f9b14414574fe6c6d9b4362dcdd63dfdc66d8c34cbef0982888dfc44ff04 @ http://127.0.0.1:7860/assets/i18n-Cp0K4Pzb.js:12
|
| 3 |
-
[ 1995ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:7860/assets/Blocks-CXVCGwNW.css:0
|
| 4 |
-
[ 1995ms] Error: Unable to preload CSS for http://127.0.0.1:7860/assets/Blocks-CXVCGwNW.css
|
| 5 |
-
at HTMLLinkElement.<anonymous> (http://127.0.0.1:7860/assets/index-CTFdGFMX.js:2:1651)
|
| 6 |
-
[ 2059ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:7860/assets/de-BczWVYc-.js:0
|
| 7 |
-
[ 2059ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:7860/assets/Blocks-IWJfIfJH.js:0
|
| 8 |
-
[ 2059ms] Failed to fetch dynamically imported module: http://127.0.0.1:7860/assets/de-BczWVYc-.js
|
| 9 |
-
[ 2059ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:7860/assets/size-DEWon29f.js:0
|
| 10 |
-
[ 2059ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:7860/assets/state.svelte-C4lCeSea.js:0
|
| 11 |
-
[ 2059ms] Failed to fetch dynamically imported module: http://127.0.0.1:7860/assets/Blocks-IWJfIfJH.js
|
| 12 |
-
[ 3991ms] [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED @ http://127.0.0.1:7860/static/img/logo_nosize.svg:0
|
| 13 |
-
[ 3991ms] [WARNING] Error while trying to use the following icon from the Manifest: http://127.0.0.1:7860/static/img/logo_nosize.svg (Download error or resource isn't a valid image) @ http://127.0.0.1:7860/:0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.playwright-mcp/console-2026-06-12T13-16-09-405Z.log
DELETED
|
@@ -1,81 +0,0 @@
|
|
| 1 |
-
[ 1558ms] [ERROR] Failed to load resource: the server responded with a status of 404 (File not found) @ http://127.0.0.1:8099/favicon.ico:0
|
| 2 |
-
[ 12119ms] [ERROR] Access to fetch at 'https://api.allorigins.win/get?url=https%3A%2F%2Fnews.google.com%2Frss%2Fsearch%3Fq%3Dwhen%3A24h%2Ballinurl%3Areuters.com%26ceid%3DUS%3Aen%26hl%3Den-US%26gl%3DUS' from origin 'http://127.0.0.1:8099' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. @ http://127.0.0.1:8099/index.html:0
|
| 3 |
-
[ 12119ms] [ERROR] Failed to load resource: net::ERR_FAILED @ https://api.allorigins.win/get?url=https%3A%2F%2Fnews.google.com%2Frss%2Fsearch%3Fq%3Dwhen%3A24h%2Ballinurl%3Areuters.com%26ceid%3DUS%3Aen%26hl%3Den-US%26gl%3DUS:0
|
| 4 |
-
[ 12416ms] [ERROR] Failed to load resource: the server responded with a status of 451 () @ https://r.jina.ai/https://news.google.com/rss/search?q=when:24h+allinurl:reuters.com&ceid=US:en&hl=en-US&gl=US:0
|
| 5 |
-
[ 17544ms] [ERROR] Access to fetch at 'https://api.allorigins.win/get?url=https%3A%2F%2Fnews.google.com%2Frss%2Fsearch%3Fq%3Dwhen%3A24h%2Ballinurl%3Areuters.com%26ceid%3DUS%3Aen%26hl%3Den-US%26gl%3DUS' from origin 'http://127.0.0.1:8099' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. @ http://127.0.0.1:8099/index.html:0
|
| 6 |
-
[ 17544ms] [ERROR] Failed to load resource: net::ERR_FAILED @ https://api.allorigins.win/get?url=https%3A%2F%2Fnews.google.com%2Frss%2Fsearch%3Fq%3Dwhen%3A24h%2Ballinurl%3Areuters.com%26ceid%3DUS%3Aen%26hl%3Den-US%26gl%3DUS:0
|
| 7 |
-
[ 17758ms] [ERROR] Failed to load resource: the server responded with a status of 451 () @ https://r.jina.ai/https://news.google.com/rss/search?q=when:24h+allinurl:reuters.com&ceid=US:en&hl=en-US&gl=US:0
|
| 8 |
-
[ 30039ms] [ERROR] Access to fetch at 'https://api.allorigins.win/get?url=https%3A%2F%2Fnews.google.com%2Frss%2Fsearch%3Fq%3Dwhen%3A24h%2Ballinurl%3Aapnews.com%26ceid%3DUS%3Aen%26hl%3Den-US%26gl%3DUS' from origin 'http://127.0.0.1:8099' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. @ http://127.0.0.1:8099/index.html:0
|
| 9 |
-
[ 30039ms] [ERROR] Failed to load resource: net::ERR_FAILED @ https://api.allorigins.win/get?url=https%3A%2F%2Fnews.google.com%2Frss%2Fsearch%3Fq%3Dwhen%3A24h%2Ballinurl%3Aapnews.com%26ceid%3DUS%3Aen%26hl%3Den-US%26gl%3DUS:0
|
| 10 |
-
[ 30537ms] [ERROR] Failed to load resource: the server responded with a status of 451 () @ https://r.jina.ai/https://news.google.com/rss/search?q=when:24h+allinurl:apnews.com&ceid=US:en&hl=en-US&gl=US:0
|
| 11 |
-
[ 42224ms] [ERROR] Access to fetch at 'https://api.allorigins.win/get?url=https%3A%2F%2Ffeeds.bbci.co.uk%2Fnews%2Fworld%2Frss.xml' from origin 'http://127.0.0.1:8099' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. @ http://127.0.0.1:8099/index.html:0
|
| 12 |
-
[ 42224ms] [ERROR] Failed to load resource: net::ERR_FAILED @ https://api.allorigins.win/get?url=https%3A%2F%2Ffeeds.bbci.co.uk%2Fnews%2Fworld%2Frss.xml:0
|
| 13 |
-
[ 42225ms] [ERROR] Access to fetch at 'https://api.allorigins.win/get?url=https%3A%2F%2Ffeeds.bbci.co.uk%2Fnews%2Frss.xml' from origin 'http://127.0.0.1:8099' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. @ http://127.0.0.1:8099/index.html:0
|
| 14 |
-
[ 42225ms] [ERROR] Failed to load resource: net::ERR_FAILED @ https://api.allorigins.win/get?url=https%3A%2F%2Ffeeds.bbci.co.uk%2Fnews%2Frss.xml:0
|
| 15 |
-
[ 42751ms] [ERROR] Access to fetch at 'https://api.allorigins.win/get?url=https%3A%2F%2Frss.dw.com%2Frdf%2Frss-en-top' from origin 'http://127.0.0.1:8099' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. @ http://127.0.0.1:8099/index.html:0
|
| 16 |
-
[ 42751ms] [ERROR] Failed to load resource: net::ERR_FAILED @ https://api.allorigins.win/get?url=https%3A%2F%2Frss.dw.com%2Frdf%2Frss-en-top:0
|
| 17 |
-
[ 43250ms] [ERROR] Access to fetch at 'https://api.allorigins.win/get?url=https%3A%2F%2Fwww.france24.com%2Fen%2Frss' from origin 'http://127.0.0.1:8099' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. @ http://127.0.0.1:8099/index.html:0
|
| 18 |
-
[ 43250ms] [ERROR] Failed to load resource: net::ERR_FAILED @ https://api.allorigins.win/get?url=https%3A%2F%2Fwww.france24.com%2Fen%2Frss:0
|
| 19 |
-
[ 45505ms] [ERROR] Access to fetch at 'https://api.allorigins.win/get?url=https%3A%2F%2Fwww.aljazeera.com%2Fxml%2Frss%2Fall.xml' from origin 'http://127.0.0.1:8099' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. @ http://127.0.0.1:8099/index.html:0
|
| 20 |
-
[ 45505ms] [ERROR] Failed to load resource: net::ERR_FAILED @ https://api.allorigins.win/get?url=https%3A%2F%2Fwww.aljazeera.com%2Fxml%2Frss%2Fall.xml:0
|
| 21 |
-
[ 45717ms] [ERROR] Failed to load resource: the server responded with a status of 451 () @ https://r.jina.ai/https://www.aljazeera.com/xml/rss/all.xml:0
|
| 22 |
-
[ 49528ms] [ERROR] Access to fetch at 'https://api.allorigins.win/get?url=https%3A%2F%2Fnews.google.com%2Frss%2Fsearch%3Fq%3Dwhen%3A24h%2Ballinurl%3Aapnews.com%26ceid%3DUS%3Aen%26hl%3Den-US%26gl%3DUS' from origin 'http://127.0.0.1:8099' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. @ http://127.0.0.1:8099/index.html:0
|
| 23 |
-
[ 49528ms] [ERROR] Failed to load resource: net::ERR_FAILED @ https://api.allorigins.win/get?url=https%3A%2F%2Fnews.google.com%2Frss%2Fsearch%3Fq%3Dwhen%3A24h%2Ballinurl%3Aapnews.com%26ceid%3DUS%3Aen%26hl%3Den-US%26gl%3DUS:0
|
| 24 |
-
[ 49773ms] [ERROR] Failed to load resource: the server responded with a status of 451 () @ https://r.jina.ai/https://news.google.com/rss/search?q=when:24h+allinurl:apnews.com&ceid=US:en&hl=en-US&gl=US:0
|
| 25 |
-
[ 55238ms] [ERROR] Access to fetch at 'https://api.allorigins.win/get?url=https%3A%2F%2Ffeeds.bbci.co.uk%2Fnews%2Fworld%2Frss.xml' from origin 'http://127.0.0.1:8099' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. @ http://127.0.0.1:8099/index.html:0
|
| 26 |
-
[ 55238ms] [ERROR] Failed to load resource: net::ERR_FAILED @ https://api.allorigins.win/get?url=https%3A%2F%2Ffeeds.bbci.co.uk%2Fnews%2Fworld%2Frss.xml:0
|
| 27 |
-
[ 63318ms] [ERROR] Access to fetch at 'https://api.allorigins.win/get?url=https%3A%2F%2Fnews.ycombinator.com%2Frss' from origin 'http://127.0.0.1:8099' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. @ http://127.0.0.1:8099/index.html:0
|
| 28 |
-
[ 63318ms] [ERROR] Failed to load resource: net::ERR_FAILED @ https://api.allorigins.win/get?url=https%3A%2F%2Fnews.ycombinator.com%2Frss:0
|
| 29 |
-
[ 64862ms] [ERROR] Access to fetch at 'https://api.allorigins.win/get?url=https%3A%2F%2Fwww.aljazeera.com%2Fxml%2Frss%2Fall.xml' from origin 'http://127.0.0.1:8099' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. @ http://127.0.0.1:8099/index.html:0
|
| 30 |
-
[ 64862ms] [ERROR] Failed to load resource: net::ERR_FAILED @ https://api.allorigins.win/get?url=https%3A%2F%2Fwww.aljazeera.com%2Fxml%2Frss%2Fall.xml:0
|
| 31 |
-
[ 65075ms] [ERROR] Failed to load resource: the server responded with a status of 451 () @ https://r.jina.ai/https://www.aljazeera.com/xml/rss/all.xml:0
|
| 32 |
-
[ 65365ms] [ERROR] Access to fetch at 'https://api.allorigins.win/get?url=https%3A%2F%2Fthehackernews.com%2Frss.xml' from origin 'http://127.0.0.1:8099' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. @ http://127.0.0.1:8099/index.html:0
|
| 33 |
-
[ 65365ms] [ERROR] Failed to load resource: net::ERR_FAILED @ https://api.allorigins.win/get?url=https%3A%2F%2Fthehackernews.com%2Frss.xml:0
|
| 34 |
-
[ 69461ms] [ERROR] Access to fetch at 'https://api.allorigins.win/get?url=https%3A%2F%2Frss.dw.com%2Frdf%2Frss-en-top' from origin 'http://127.0.0.1:8099' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. @ http://127.0.0.1:8099/index.html:0
|
| 35 |
-
[ 69462ms] [ERROR] Failed to load resource: net::ERR_FAILED @ https://api.allorigins.win/get?url=https%3A%2F%2Frss.dw.com%2Frdf%2Frss-en-top:0
|
| 36 |
-
[ 71509ms] [ERROR] Access to fetch at 'https://api.allorigins.win/get?url=https%3A%2F%2Fkrebsonsecurity.com%2Ffeed%2F' from origin 'http://127.0.0.1:8099' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. @ http://127.0.0.1:8099/index.html:0
|
| 37 |
-
[ 71509ms] [ERROR] Failed to load resource: net::ERR_FAILED @ https://api.allorigins.win/get?url=https%3A%2F%2Fkrebsonsecurity.com%2Ffeed%2F:0
|
| 38 |
-
[ 73032ms] [ERROR] Failed to load resource: the server responded with a status of 400 () @ https://api.allorigins.win/get?url=https%3A%2F%2Falerts.weather.gov%2Fcap%2Fus.php%3Fx%3D0:0
|
| 39 |
-
[ 73200ms] [ERROR] Failed to load resource: the server responded with a status of 400 () @ https://r.jina.ai/https://alerts.weather.gov/cap/us.php?x=0:0
|
| 40 |
-
[ 74176ms] [ERROR] Access to fetch at 'https://api.allorigins.win/get?url=https%3A%2F%2Fwww.bleepingcomputer.com%2Ffeed%2F' from origin 'http://127.0.0.1:8099' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. @ http://127.0.0.1:8099/index.html:0
|
| 41 |
-
[ 74176ms] [ERROR] Failed to load resource: net::ERR_FAILED @ https://api.allorigins.win/get?url=https%3A%2F%2Fwww.bleepingcomputer.com%2Ffeed%2F:0
|
| 42 |
-
[ 74759ms] [ERROR] Failed to load resource: the server responded with a status of 451 () @ https://r.jina.ai/https://www.bleepingcomputer.com/feed/:0
|
| 43 |
-
[ 75095ms] [ERROR] Access to fetch at 'https://api.allorigins.win/get?url=https%3A%2F%2Fwww.france24.com%2Fen%2Frss' from origin 'http://127.0.0.1:8099' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. @ http://127.0.0.1:8099/index.html:0
|
| 44 |
-
[ 75095ms] [ERROR] Failed to load resource: net::ERR_FAILED @ https://api.allorigins.win/get?url=https%3A%2F%2Fwww.france24.com%2Fen%2Frss:0
|
| 45 |
-
[ 76810ms] [ERROR] Access to fetch at 'https://api.allorigins.win/get?url=https%3A%2F%2Fwww.nasa.gov%2Frss%2Fdyn%2Fbreaking_news.rss' from origin 'http://127.0.0.1:8099' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. @ http://127.0.0.1:8099/index.html:0
|
| 46 |
-
[ 76810ms] [ERROR] Failed to load resource: net::ERR_FAILED @ https://api.allorigins.win/get?url=https%3A%2F%2Fwww.nasa.gov%2Frss%2Fdyn%2Fbreaking_news.rss:0
|
| 47 |
-
[ 77217ms] [ERROR] Access to fetch at 'https://api.allorigins.win/get?url=https%3A%2F%2Fnews.google.com%2Frss%2Fsearch%3Fq%3Dwhen%3A24h%2Ballinurl%3Areuters.com%26ceid%3DUS%3Aen%26hl%3Den-US%26gl%3DUS' from origin 'http://127.0.0.1:8099' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. @ http://127.0.0.1:8099/index.html:0
|
| 48 |
-
[ 77217ms] [ERROR] Failed to load resource: net::ERR_FAILED @ https://api.allorigins.win/get?url=https%3A%2F%2Fnews.google.com%2Frss%2Fsearch%3Fq%3Dwhen%3A24h%2Ballinurl%3Areuters.com%26ceid%3DUS%3Aen%26hl%3Den-US%26gl%3DUS:0
|
| 49 |
-
[ 77217ms] [ERROR] Access to fetch at 'https://api.allorigins.win/get?url=https%3A%2F%2Ffeeds.bbci.co.uk%2Fnews%2Frss.xml' from origin 'http://127.0.0.1:8099' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. @ http://127.0.0.1:8099/index.html:0
|
| 50 |
-
[ 77217ms] [ERROR] Failed to load resource: net::ERR_FAILED @ https://api.allorigins.win/get?url=https%3A%2F%2Ffeeds.bbci.co.uk%2Fnews%2Frss.xml:0
|
| 51 |
-
[ 77538ms] [ERROR] Failed to load resource: the server responded with a status of 451 () @ https://r.jina.ai/https://news.google.com/rss/search?q=when:24h+allinurl:reuters.com&ceid=US:en&hl=en-US&gl=US:0
|
| 52 |
-
[ 78402ms] [ERROR] Access to fetch at 'https://api.allorigins.win/get?url=https%3A%2F%2Ffeeds.bbci.co.uk%2Fnews%2Fworld%2Frss.xml' from origin 'http://127.0.0.1:8099' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. @ http://127.0.0.1:8099/index.html:0
|
| 53 |
-
[ 78402ms] [ERROR] Failed to load resource: net::ERR_FAILED @ https://api.allorigins.win/get?url=https%3A%2F%2Ffeeds.bbci.co.uk%2Fnews%2Fworld%2Frss.xml:0
|
| 54 |
-
[ 83766ms] [ERROR] Failed to load resource: net::ERR_QUIC_PROTOCOL_ERROR @ https://api.allorigins.win/get?url=https%3A%2F%2Fkrebsonsecurity.com%2Ffeed%2F:0
|
| 55 |
-
[ 84831ms] [ERROR] Access to fetch at 'https://api.allorigins.win/get?url=https%3A%2F%2Fnews.ycombinator.com%2Frss' from origin 'http://127.0.0.1:8099' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. @ http://127.0.0.1:8099/index.html:0
|
| 56 |
-
[ 84831ms] [ERROR] Failed to load resource: net::ERR_FAILED @ https://api.allorigins.win/get?url=https%3A%2F%2Fnews.ycombinator.com%2Frss:0
|
| 57 |
-
[ 85281ms] [ERROR] Access to fetch at 'https://api.allorigins.win/get?url=https%3A%2F%2Fwww.france24.com%2Fen%2Frss' from origin 'http://127.0.0.1:8099' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. @ http://127.0.0.1:8099/index.html:0
|
| 58 |
-
[ 85282ms] [ERROR] Failed to load resource: net::ERR_FAILED @ https://api.allorigins.win/get?url=https%3A%2F%2Fwww.france24.com%2Fen%2Frss:0
|
| 59 |
-
[ 89471ms] [ERROR] Access to fetch at 'https://api.allorigins.win/get?url=https%3A%2F%2Fthehackernews.com%2Frss.xml' from origin 'http://127.0.0.1:8099' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. @ http://127.0.0.1:8099/index.html:0
|
| 60 |
-
[ 89471ms] [ERROR] Failed to load resource: net::ERR_FAILED @ https://api.allorigins.win/get?url=https%3A%2F%2Fthehackernews.com%2Frss.xml:0
|
| 61 |
-
[ 90583ms] [ERROR] Failed to load resource: the server responded with a status of 400 () @ https://api.allorigins.win/get?url=https%3A%2F%2Falerts.weather.gov%2Fcap%2Fus.php%3Fx%3D0:0
|
| 62 |
-
[ 90745ms] [ERROR] Failed to load resource: the server responded with a status of 400 () @ https://r.jina.ai/https://alerts.weather.gov/cap/us.php?x=0:0
|
| 63 |
-
[ 96191ms] [ERROR] Access to fetch at 'https://api.allorigins.win/get?url=https%3A%2F%2Fnews.ycombinator.com%2Frss' from origin 'http://127.0.0.1:8099' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. @ http://127.0.0.1:8099/index.html:0
|
| 64 |
-
[ 96191ms] [ERROR] Failed to load resource: net::ERR_FAILED @ https://api.allorigins.win/get?url=https%3A%2F%2Fnews.ycombinator.com%2Frss:0
|
| 65 |
-
[ 96704ms] [ERROR] Access to fetch at 'https://api.allorigins.win/get?url=https%3A%2F%2Fwww.nasa.gov%2Frss%2Fdyn%2Fbreaking_news.rss' from origin 'http://127.0.0.1:8099' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. @ http://127.0.0.1:8099/index.html:0
|
| 66 |
-
[ 96704ms] [ERROR] Failed to load resource: net::ERR_FAILED @ https://api.allorigins.win/get?url=https%3A%2F%2Fwww.nasa.gov%2Frss%2Fdyn%2Fbreaking_news.rss:0
|
| 67 |
-
[ 96878ms] [ERROR] Failed to load resource: the server responded with a status of 429 () @ https://r.jina.ai/https://www.nasa.gov/rss/dyn/breaking_news.rss:0
|
| 68 |
-
[ 97110ms] [ERROR] Access to fetch at 'https://api.allorigins.win/get?url=https%3A%2F%2Fwww.aljazeera.com%2Fxml%2Frss%2Fall.xml' from origin 'http://127.0.0.1:8099' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. @ http://127.0.0.1:8099/index.html:0
|
| 69 |
-
[ 97110ms] [ERROR] Failed to load resource: net::ERR_FAILED @ https://api.allorigins.win/get?url=https%3A%2F%2Fwww.aljazeera.com%2Fxml%2Frss%2Fall.xml:0
|
| 70 |
-
[ 97288ms] [ERROR] Failed to load resource: the server responded with a status of 429 () @ https://r.jina.ai/https://www.aljazeera.com/xml/rss/all.xml:0
|
| 71 |
-
[ 98185ms] [ERROR] Access to fetch at 'https://api.allorigins.win/get?url=https%3A%2F%2Frss.dw.com%2Frdf%2Frss-en-top' from origin 'http://127.0.0.1:8099' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. @ http://127.0.0.1:8099/index.html:0
|
| 72 |
-
[ 98186ms] [ERROR] Failed to load resource: net::ERR_FAILED @ https://api.allorigins.win/get?url=https%3A%2F%2Frss.dw.com%2Frdf%2Frss-en-top:0
|
| 73 |
-
[ 98483ms] [ERROR] Failed to load resource: the server responded with a status of 429 () @ https://r.jina.ai/https://rss.dw.com/rdf/rss-en-top:0
|
| 74 |
-
[ 116052ms] [ERROR] Access to fetch at 'https://api.allorigins.win/get?url=https%3A%2F%2Fthehackernews.com%2Frss.xml' from origin 'http://127.0.0.1:8099' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. @ http://127.0.0.1:8099/index.html:0
|
| 75 |
-
[ 116052ms] [ERROR] Failed to load resource: net::ERR_FAILED @ https://api.allorigins.win/get?url=https%3A%2F%2Fthehackernews.com%2Frss.xml:0
|
| 76 |
-
[ 116169ms] [ERROR] Access to fetch at 'https://api.allorigins.win/get?url=https%3A%2F%2Fwww.nasa.gov%2Frss%2Fdyn%2Fbreaking_news.rss' from origin 'http://127.0.0.1:8099' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. @ http://127.0.0.1:8099/index.html:0
|
| 77 |
-
[ 116169ms] [ERROR] Failed to load resource: net::ERR_FAILED @ https://api.allorigins.win/get?url=https%3A%2F%2Fwww.nasa.gov%2Frss%2Fdyn%2Fbreaking_news.rss:0
|
| 78 |
-
[ 116570ms] [ERROR] Access to fetch at 'https://api.allorigins.win/get?url=https%3A%2F%2Fkrebsonsecurity.com%2Ffeed%2F' from origin 'http://127.0.0.1:8099' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. @ http://127.0.0.1:8099/index.html:0
|
| 79 |
-
[ 116571ms] [ERROR] Failed to load resource: net::ERR_FAILED @ https://api.allorigins.win/get?url=https%3A%2F%2Fkrebsonsecurity.com%2Ffeed%2F:0
|
| 80 |
-
[ 116756ms] [ERROR] Failed to load resource: the server responded with a status of 400 () @ https://api.allorigins.win/get?url=https%3A%2F%2Falerts.weather.gov%2Fcap%2Fus.php%3Fx%3D0:0
|
| 81 |
-
[ 116915ms] [ERROR] Failed to load resource: the server responded with a status of 400 () @ https://r.jina.ai/https://alerts.weather.gov/cap/us.php?x=0:0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.playwright-mcp/page-2026-06-10T14-22-38-882Z.yml
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
- generic [ref=e5]:
|
| 2 |
-
- img [ref=e9]
|
| 3 |
-
- paragraph [ref=e20]: Laden...
|
|
|
|
|
|
|
|
|
|
|
|
.playwright-mcp/page-2026-06-10T14-27-49-510Z.yml
DELETED
|
@@ -1,60 +0,0 @@
|
|
| 1 |
-
- generic [ref=e21]:
|
| 2 |
-
- main [ref=e22]:
|
| 3 |
-
- generic [ref=e23]:
|
| 4 |
-
- heading "π₯ HearthNet β Community AI Mesh" [level=1] [ref=e28]
|
| 5 |
-
- generic [ref=e29]:
|
| 6 |
-
- generic [ref=e32]: β ONLINE
|
| 7 |
-
- paragraph [ref=e37]:
|
| 8 |
-
- text: "Node:"
|
| 9 |
-
- code [ref=e38]: unknown
|
| 10 |
-
- generic [ref=e39]:
|
| 11 |
-
- generic [ref=e40]:
|
| 12 |
-
- generic [ref=e41]:
|
| 13 |
-
- button [ref=e42] [cursor=pointer]: Ask
|
| 14 |
-
- button [ref=e43] [cursor=pointer]: Chat
|
| 15 |
-
- button [ref=e44] [cursor=pointer]: Marketplace
|
| 16 |
-
- button [ref=e45] [cursor=pointer]: Files
|
| 17 |
-
- button [ref=e46] [cursor=pointer]: Emergency
|
| 18 |
-
- button [ref=e47] [cursor=pointer]: Settings
|
| 19 |
-
- tablist [ref=e48]:
|
| 20 |
-
- tab "Ask" [ref=e49] [cursor=pointer]
|
| 21 |
-
- tab "Chat" [active] [selected] [ref=e50] [cursor=pointer]
|
| 22 |
-
- tab "Marketplace" [ref=e51] [cursor=pointer]
|
| 23 |
-
- tab "Files" [ref=e52] [cursor=pointer]
|
| 24 |
-
- tab "Emergency" [ref=e53] [cursor=pointer]
|
| 25 |
-
- tab "Settings" [ref=e54] [cursor=pointer]
|
| 26 |
-
- tabpanel [ref=e106]:
|
| 27 |
-
- generic [ref=e108]:
|
| 28 |
-
- heading "Direct Messages" [level=3] [ref=e113]
|
| 29 |
-
- generic [ref=e114]:
|
| 30 |
-
- generic [ref=e117]:
|
| 31 |
-
- generic [ref=e118]: Recipient Node ID
|
| 32 |
-
- textbox "Recipient Node ID" [ref=e120]:
|
| 33 |
-
- /placeholder: ed25519:...
|
| 34 |
-
- button "Load History" [ref=e121] [cursor=pointer]
|
| 35 |
-
- generic [ref=e123]:
|
| 36 |
-
- generic:
|
| 37 |
-
- generic:
|
| 38 |
-
- img
|
| 39 |
-
- text: Messages
|
| 40 |
-
- log "chatbot conversation" [ref=e124]:
|
| 41 |
-
- complementary [ref=e125]
|
| 42 |
-
- generic [ref=e126]:
|
| 43 |
-
- generic [ref=e129]:
|
| 44 |
-
- generic [ref=e130]: Message
|
| 45 |
-
- textbox "Message" [ref=e132]:
|
| 46 |
-
- /placeholder: ""
|
| 47 |
-
- button "Send" [ref=e133] [cursor=pointer]
|
| 48 |
-
- contentinfo "Gradio footer navigation" [ref=e97]:
|
| 49 |
-
- button "Γber API verwenden Logo" [ref=e98] [cursor=pointer]:
|
| 50 |
-
- text: Γber API verwenden
|
| 51 |
-
- img "Logo" [ref=e99]
|
| 52 |
-
- generic [ref=e100]: Β·
|
| 53 |
-
- link "Mit Gradio erstellt Logo" [ref=e101] [cursor=pointer]:
|
| 54 |
-
- /url: https://gradio.app
|
| 55 |
-
- text: Mit Gradio erstellt
|
| 56 |
-
- img "Logo" [ref=e102]
|
| 57 |
-
- generic [ref=e103]: Β·
|
| 58 |
-
- button "Einstellungen Einstellungen" [ref=e104] [cursor=pointer]:
|
| 59 |
-
- text: Einstellungen
|
| 60 |
-
- img "Einstellungen" [ref=e105]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.playwright-mcp/page-2026-06-10T14-28-44-543Z.yml
DELETED
|
@@ -1,67 +0,0 @@
|
|
| 1 |
-
- generic [ref=e21]:
|
| 2 |
-
- main [ref=e22]:
|
| 3 |
-
- generic [ref=e23]:
|
| 4 |
-
- heading "π₯ HearthNet β Community AI Mesh" [level=1] [ref=e28]
|
| 5 |
-
- generic [ref=e29]:
|
| 6 |
-
- generic [ref=e32]: β ONLINE
|
| 7 |
-
- paragraph [ref=e37]:
|
| 8 |
-
- text: "Node:"
|
| 9 |
-
- code [ref=e38]: unknown
|
| 10 |
-
- generic [ref=e39]:
|
| 11 |
-
- generic [ref=e40]:
|
| 12 |
-
- generic [ref=e41]:
|
| 13 |
-
- button [ref=e42] [cursor=pointer]: Ask
|
| 14 |
-
- button [ref=e43] [cursor=pointer]: Chat
|
| 15 |
-
- button [ref=e44] [cursor=pointer]: Marketplace
|
| 16 |
-
- button [ref=e45] [cursor=pointer]: Files
|
| 17 |
-
- button [ref=e46] [cursor=pointer]: Emergency
|
| 18 |
-
- button [ref=e47] [cursor=pointer]: Settings
|
| 19 |
-
- tablist [ref=e48]:
|
| 20 |
-
- tab "Ask" [ref=e49] [cursor=pointer]
|
| 21 |
-
- tab "Chat" [ref=e50] [cursor=pointer]
|
| 22 |
-
- tab "Marketplace" [active] [selected] [ref=e51] [cursor=pointer]
|
| 23 |
-
- tab "Files" [ref=e52] [cursor=pointer]
|
| 24 |
-
- tab "Emergency" [ref=e53] [cursor=pointer]
|
| 25 |
-
- tab "Settings" [ref=e54] [cursor=pointer]
|
| 26 |
-
- tabpanel [ref=e134]:
|
| 27 |
-
- generic [ref=e136]:
|
| 28 |
-
- heading "Community Marketplace" [level=3] [ref=e141]
|
| 29 |
-
- button "π Refresh" [ref=e142] [cursor=pointer]
|
| 30 |
-
- generic [ref=e143]:
|
| 31 |
-
- generic [ref=e144]:
|
| 32 |
-
- generic:
|
| 33 |
-
- generic:
|
| 34 |
-
- img
|
| 35 |
-
- text: Active Posts
|
| 36 |
-
- generic "Empty value" [ref=e146]:
|
| 37 |
-
- img [ref=e148]
|
| 38 |
-
- heading "Post Something" [level=4] [ref=e154]
|
| 39 |
-
- generic [ref=e156]:
|
| 40 |
-
- generic [ref=e158]:
|
| 41 |
-
- generic [ref=e159]: Title
|
| 42 |
-
- textbox "Title" [ref=e161]:
|
| 43 |
-
- /placeholder: ""
|
| 44 |
-
- generic [ref=e163]:
|
| 45 |
-
- generic [ref=e164]: Category
|
| 46 |
-
- generic [ref=e167]:
|
| 47 |
-
- listbox "Category" [ref=e168]: info
|
| 48 |
-
- generic:
|
| 49 |
-
- img
|
| 50 |
-
- generic [ref=e171]:
|
| 51 |
-
- generic [ref=e172]: Description
|
| 52 |
-
- textbox "Description" [ref=e174]:
|
| 53 |
-
- /placeholder: ""
|
| 54 |
-
- button "Post" [ref=e175] [cursor=pointer]
|
| 55 |
-
- contentinfo "Gradio footer navigation" [ref=e97]:
|
| 56 |
-
- button "Γber API verwenden Logo" [ref=e98] [cursor=pointer]:
|
| 57 |
-
- text: Γber API verwenden
|
| 58 |
-
- img "Logo" [ref=e99]
|
| 59 |
-
- generic [ref=e100]: Β·
|
| 60 |
-
- link "Mit Gradio erstellt Logo" [ref=e101] [cursor=pointer]:
|
| 61 |
-
- /url: https://gradio.app
|
| 62 |
-
- text: Mit Gradio erstellt
|
| 63 |
-
- img "Logo" [ref=e102]
|
| 64 |
-
- generic [ref=e103]: Β·
|
| 65 |
-
- button "Einstellungen Einstellungen" [ref=e104] [cursor=pointer]:
|
| 66 |
-
- text: Einstellungen
|
| 67 |
-
- img "Einstellungen" [ref=e105]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.playwright-mcp/page-2026-06-10T14-28-52-588Z.yml
DELETED
|
@@ -1,53 +0,0 @@
|
|
| 1 |
-
- generic [ref=e21]:
|
| 2 |
-
- main [ref=e22]:
|
| 3 |
-
- generic [ref=e23]:
|
| 4 |
-
- heading "π₯ HearthNet β Community AI Mesh" [level=1] [ref=e28]
|
| 5 |
-
- generic [ref=e29]:
|
| 6 |
-
- generic [ref=e32]: β ONLINE
|
| 7 |
-
- paragraph [ref=e37]:
|
| 8 |
-
- text: "Node:"
|
| 9 |
-
- code [ref=e38]: unknown
|
| 10 |
-
- generic [ref=e39]:
|
| 11 |
-
- generic [ref=e40]:
|
| 12 |
-
- generic [ref=e41]:
|
| 13 |
-
- button [ref=e42] [cursor=pointer]: Ask
|
| 14 |
-
- button [ref=e43] [cursor=pointer]: Chat
|
| 15 |
-
- button [ref=e44] [cursor=pointer]: Marketplace
|
| 16 |
-
- button [ref=e45] [cursor=pointer]: Files
|
| 17 |
-
- button [ref=e46] [cursor=pointer]: Emergency
|
| 18 |
-
- button [ref=e47] [cursor=pointer]: Settings
|
| 19 |
-
- tablist [ref=e48]:
|
| 20 |
-
- tab "Ask" [ref=e49] [cursor=pointer]
|
| 21 |
-
- tab "Chat" [ref=e50] [cursor=pointer]
|
| 22 |
-
- tab "Marketplace" [ref=e51] [cursor=pointer]
|
| 23 |
-
- tab "Files" [ref=e52] [cursor=pointer]
|
| 24 |
-
- tab "Emergency" [active] [selected] [ref=e53] [cursor=pointer]
|
| 25 |
-
- tab "Settings" [ref=e54] [cursor=pointer]
|
| 26 |
-
- tabpanel [ref=e176]:
|
| 27 |
-
- generic [ref=e178]:
|
| 28 |
-
- heading "π¨ Emergency Mode" [level=3] [ref=e183]
|
| 29 |
-
- generic [ref=e184]:
|
| 30 |
-
- generic [ref=e185]:
|
| 31 |
-
- generic:
|
| 32 |
-
- generic:
|
| 33 |
-
- img
|
| 34 |
-
- text: Current Mode
|
| 35 |
-
- generic "Empty value" [ref=e187]:
|
| 36 |
-
- img [ref=e189]
|
| 37 |
-
- button "Check Status" [ref=e191] [cursor=pointer]
|
| 38 |
-
- heading "Local Resources" [level=4] [ref=e196]
|
| 39 |
-
- paragraph [ref=e201]: In offline mode, all capabilities route to local nodes only.
|
| 40 |
-
- button "Run Connectivity Probe" [ref=e203] [cursor=pointer]
|
| 41 |
-
- contentinfo "Gradio footer navigation" [ref=e97]:
|
| 42 |
-
- button "Γber API verwenden Logo" [ref=e98] [cursor=pointer]:
|
| 43 |
-
- text: Γber API verwenden
|
| 44 |
-
- img "Logo" [ref=e99]
|
| 45 |
-
- generic [ref=e100]: Β·
|
| 46 |
-
- link "Mit Gradio erstellt Logo" [ref=e101] [cursor=pointer]:
|
| 47 |
-
- /url: https://gradio.app
|
| 48 |
-
- text: Mit Gradio erstellt
|
| 49 |
-
- img "Logo" [ref=e102]
|
| 50 |
-
- generic [ref=e103]: Β·
|
| 51 |
-
- button "Einstellungen Einstellungen" [ref=e104] [cursor=pointer]:
|
| 52 |
-
- text: Einstellungen
|
| 53 |
-
- img "Einstellungen" [ref=e105]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.playwright-mcp/page-2026-06-10T14-29-00-723Z.yml
DELETED
|
@@ -1,110 +0,0 @@
|
|
| 1 |
-
- generic [ref=e21]:
|
| 2 |
-
- main [ref=e22]:
|
| 3 |
-
- generic [ref=e23]:
|
| 4 |
-
- heading "π₯ HearthNet β Community AI Mesh" [level=1] [ref=e28]
|
| 5 |
-
- generic [ref=e29]:
|
| 6 |
-
- generic [ref=e32]: β ONLINE
|
| 7 |
-
- paragraph [ref=e37]:
|
| 8 |
-
- text: "Node:"
|
| 9 |
-
- code [ref=e38]: unknown
|
| 10 |
-
- generic [ref=e39]:
|
| 11 |
-
- generic [ref=e40]:
|
| 12 |
-
- generic [ref=e41]:
|
| 13 |
-
- button [ref=e42] [cursor=pointer]: Ask
|
| 14 |
-
- button [ref=e43] [cursor=pointer]: Chat
|
| 15 |
-
- button [ref=e44] [cursor=pointer]: Marketplace
|
| 16 |
-
- button [ref=e45] [cursor=pointer]: Files
|
| 17 |
-
- button [ref=e46] [cursor=pointer]: Emergency
|
| 18 |
-
- button [ref=e47] [cursor=pointer]: Settings
|
| 19 |
-
- tablist [ref=e48]:
|
| 20 |
-
- tab "Ask" [ref=e49] [cursor=pointer]
|
| 21 |
-
- tab "Chat" [ref=e50] [cursor=pointer]
|
| 22 |
-
- tab "Marketplace" [ref=e51] [cursor=pointer]
|
| 23 |
-
- tab "Files" [ref=e52] [cursor=pointer]
|
| 24 |
-
- tab "Emergency" [ref=e53] [cursor=pointer]
|
| 25 |
-
- tab "Settings" [active] [selected] [ref=e54] [cursor=pointer]
|
| 26 |
-
- tabpanel [ref=e204]:
|
| 27 |
-
- generic [ref=e206]:
|
| 28 |
-
- heading "Settings" [level=3] [ref=e211]
|
| 29 |
-
- heading "Node Identity" [level=4] [ref=e216]
|
| 30 |
-
- paragraph [ref=e221]:
|
| 31 |
-
- text: "Node ID:"
|
| 32 |
-
- code [ref=e222]: not initialized
|
| 33 |
-
- paragraph [ref=e227]:
|
| 34 |
-
- text: "Profile:"
|
| 35 |
-
- code [ref=e228]: hearth
|
| 36 |
-
- heading "Community" [level=4] [ref=e233]
|
| 37 |
-
- paragraph [ref=e238]:
|
| 38 |
-
- text: "Community:"
|
| 39 |
-
- code [ref=e239]: none
|
| 40 |
-
- heading "Phase Labels" [level=4] [ref=e244]
|
| 41 |
-
- table [ref=e249]:
|
| 42 |
-
- rowgroup [ref=e250]:
|
| 43 |
-
- row "Module Status" [ref=e251]:
|
| 44 |
-
- columnheader "Module" [ref=e252]
|
| 45 |
-
- columnheader "Status" [ref=e253]
|
| 46 |
-
- rowgroup [ref=e254]:
|
| 47 |
-
- row "M01 Identity β
Implemented" [ref=e255]:
|
| 48 |
-
- cell "M01 Identity" [ref=e256]
|
| 49 |
-
- cell "β
Implemented" [ref=e257]
|
| 50 |
-
- row "M02 Discovery β
Implemented (mDNS/UDP)" [ref=e258]:
|
| 51 |
-
- cell "M02 Discovery" [ref=e259]
|
| 52 |
-
- cell "β
Implemented (mDNS/UDP)" [ref=e260]
|
| 53 |
-
- row "M03 Bus β
Implemented" [ref=e261]:
|
| 54 |
-
- cell "M03 Bus" [ref=e262]
|
| 55 |
-
- cell "β
Implemented" [ref=e263]
|
| 56 |
-
- row "M04 LLM β
Implemented (Ollama/llama.cpp/HF)" [ref=e264]:
|
| 57 |
-
- cell "M04 LLM" [ref=e265]
|
| 58 |
-
- cell "β
Implemented (Ollama/llama.cpp/HF)" [ref=e266]
|
| 59 |
-
- row "M05 RAG β
Implemented" [ref=e267]:
|
| 60 |
-
- cell "M05 RAG" [ref=e268]
|
| 61 |
-
- cell "β
Implemented" [ref=e269]
|
| 62 |
-
- row "M06 Marketplace β
Implemented (event-sourced)" [ref=e270]:
|
| 63 |
-
- cell "M06 Marketplace" [ref=e271]
|
| 64 |
-
- cell "β
Implemented (event-sourced)" [ref=e272]
|
| 65 |
-
- row "M07 Blobs β
Implemented" [ref=e273]:
|
| 66 |
-
- cell "M07 Blobs" [ref=e274]
|
| 67 |
-
- cell "β
Implemented" [ref=e275]
|
| 68 |
-
- row "M08 UI β
This UI" [ref=e276]:
|
| 69 |
-
- cell "M08 UI" [ref=e277]
|
| 70 |
-
- cell "β
This UI" [ref=e278]
|
| 71 |
-
- row "M09 Emergency β
Implemented" [ref=e279]:
|
| 72 |
-
- cell "M09 Emergency" [ref=e280]
|
| 73 |
-
- cell "β
Implemented" [ref=e281]
|
| 74 |
-
- row "M10 Chat β
Implemented" [ref=e282]:
|
| 75 |
-
- cell "M10 Chat" [ref=e283]
|
| 76 |
-
- cell "β
Implemented" [ref=e284]
|
| 77 |
-
- row "M11 Embedding β
Implemented" [ref=e285]:
|
| 78 |
-
- cell "M11 Embedding" [ref=e286]
|
| 79 |
-
- cell "β
Implemented" [ref=e287]
|
| 80 |
-
- row "M12 CLI β
Implemented" [ref=e288]:
|
| 81 |
-
- cell "M12 CLI" [ref=e289]
|
| 82 |
-
- cell "β
Implemented" [ref=e290]
|
| 83 |
-
- row "M13 Onboarding β
Implemented" [ref=e291]:
|
| 84 |
-
- cell "M13 Onboarding" [ref=e292]
|
| 85 |
-
- cell "β
Implemented" [ref=e293]
|
| 86 |
-
- row "X01 Transport β
Implemented (FastAPI)" [ref=e294]:
|
| 87 |
-
- cell "X01 Transport" [ref=e295]
|
| 88 |
-
- cell "β
Implemented (FastAPI)" [ref=e296]
|
| 89 |
-
- row "X02 Events β
Implemented (SQLite)" [ref=e297]:
|
| 90 |
-
- cell "X02 Events" [ref=e298]
|
| 91 |
-
- cell "β
Implemented (SQLite)" [ref=e299]
|
| 92 |
-
- row "X03 Observability β
Implemented" [ref=e300]:
|
| 93 |
-
- cell "X03 Observability" [ref=e301]
|
| 94 |
-
- cell "β
Implemented" [ref=e302]
|
| 95 |
-
- row "X04 Config β
Implemented" [ref=e303]:
|
| 96 |
-
- cell "X04 Config" [ref=e304]
|
| 97 |
-
- cell "β
Implemented" [ref=e305]
|
| 98 |
-
- contentinfo "Gradio footer navigation" [ref=e97]:
|
| 99 |
-
- button "Γber API verwenden Logo" [ref=e98] [cursor=pointer]:
|
| 100 |
-
- text: Γber API verwenden
|
| 101 |
-
- img "Logo" [ref=e99]
|
| 102 |
-
- generic [ref=e100]: Β·
|
| 103 |
-
- link "Mit Gradio erstellt Logo" [ref=e101] [cursor=pointer]:
|
| 104 |
-
- /url: https://gradio.app
|
| 105 |
-
- text: Mit Gradio erstellt
|
| 106 |
-
- img "Logo" [ref=e102]
|
| 107 |
-
- generic [ref=e103]: Β·
|
| 108 |
-
- button "Einstellungen Einstellungen" [ref=e104] [cursor=pointer]:
|
| 109 |
-
- text: Einstellungen
|
| 110 |
-
- img "Einstellungen" [ref=e105]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.playwright-mcp/page-2026-06-10T14-52-32-151Z.yml
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
- generic [ref=e5]:
|
| 2 |
-
- img [ref=e9]
|
| 3 |
-
- paragraph [ref=e20]: Laden...
|
|
|
|
|
|
|
|
|
|
|
|
.playwright-mcp/page-2026-06-10T14-52-36-986Z.yml
DELETED
|
@@ -1,98 +0,0 @@
|
|
| 1 |
-
- generic [ref=e21]:
|
| 2 |
-
- main [ref=e22]:
|
| 3 |
-
- generic [ref=e23]:
|
| 4 |
-
- heading "π₯ HearthNet β Community AI Mesh" [level=1] [ref=e28]
|
| 5 |
-
- generic [ref=e29]:
|
| 6 |
-
- generic [ref=e32]: β ONLINE
|
| 7 |
-
- paragraph [ref=e37]:
|
| 8 |
-
- text: "Node:"
|
| 9 |
-
- code [ref=e38]: unknown
|
| 10 |
-
- generic [ref=e39]:
|
| 11 |
-
- generic [ref=e40]:
|
| 12 |
-
- generic [ref=e41]:
|
| 13 |
-
- button [ref=e42] [cursor=pointer]: Ask
|
| 14 |
-
- button [ref=e43] [cursor=pointer]: Chat
|
| 15 |
-
- button [ref=e44] [cursor=pointer]: Marketplace
|
| 16 |
-
- button [ref=e45] [cursor=pointer]: Files
|
| 17 |
-
- button [ref=e46] [cursor=pointer]: Emergency
|
| 18 |
-
- button [ref=e47] [cursor=pointer]: Settings
|
| 19 |
-
- tablist [ref=e48]:
|
| 20 |
-
- tab "Ask" [ref=e49] [cursor=pointer]
|
| 21 |
-
- tab "Chat" [ref=e50] [cursor=pointer]
|
| 22 |
-
- tab "Marketplace" [ref=e51] [cursor=pointer]
|
| 23 |
-
- tab "Files" [ref=e52] [cursor=pointer]
|
| 24 |
-
- tab "Emergency" [ref=e53] [cursor=pointer]
|
| 25 |
-
- tab "Settings" [active] [selected] [ref=e54] [cursor=pointer]
|
| 26 |
-
- tabpanel [ref=e55]:
|
| 27 |
-
- generic [ref=e57]:
|
| 28 |
-
- heading "βοΈ Node & Settings" [level=3] [ref=e62]
|
| 29 |
-
- generic [ref=e63]:
|
| 30 |
-
- button "πͺͺ Node Identity βΌ" [ref=e64] [cursor=pointer]:
|
| 31 |
-
- generic [ref=e65]: πͺͺ Node Identity
|
| 32 |
-
- generic [ref=e66]: βΌ
|
| 33 |
-
- table [ref=e73]:
|
| 34 |
-
- rowgroup [ref=e74]:
|
| 35 |
-
- row "Field Value" [ref=e75]:
|
| 36 |
-
- columnheader "Field" [ref=e76]
|
| 37 |
-
- columnheader "Value" [ref=e77]
|
| 38 |
-
- rowgroup [ref=e78]:
|
| 39 |
-
- row "Node ID not initialized" [ref=e79]:
|
| 40 |
-
- cell "Node ID" [ref=e80]
|
| 41 |
-
- cell "not initialized" [ref=e81]:
|
| 42 |
-
- code [ref=e82]: not initialized
|
| 43 |
-
- row "Profile hearth" [ref=e83]:
|
| 44 |
-
- cell "Profile" [ref=e84]
|
| 45 |
-
- cell "hearth" [ref=e85]:
|
| 46 |
-
- code [ref=e86]: hearth
|
| 47 |
-
- row "Community none" [ref=e87]:
|
| 48 |
-
- cell "Community" [ref=e88]
|
| 49 |
-
- cell "none" [ref=e89]:
|
| 50 |
-
- code [ref=e90]: none
|
| 51 |
-
- generic [ref=e91]:
|
| 52 |
-
- button "π Connected Peers & Capabilities βΌ" [ref=e92] [cursor=pointer]:
|
| 53 |
-
- generic [ref=e93]: π Connected Peers & Capabilities
|
| 54 |
-
- generic [ref=e94]: βΌ
|
| 55 |
-
- generic [ref=e96]:
|
| 56 |
-
- generic [ref=e97]:
|
| 57 |
-
- generic [ref=e98]:
|
| 58 |
-
- generic:
|
| 59 |
-
- generic:
|
| 60 |
-
- img
|
| 61 |
-
- text: Peers
|
| 62 |
-
- button "Copy" [ref=e100] [cursor=pointer]:
|
| 63 |
-
- img [ref=e102]
|
| 64 |
-
- generic [ref=e106]:
|
| 65 |
-
- generic [ref=e107]:
|
| 66 |
-
- generic "Line number 1" [ref=e108]: "1"
|
| 67 |
-
- generic [ref=e109]:
|
| 68 |
-
- button "Collapse" [ref=e110] [cursor=pointer]: βΌ
|
| 69 |
-
- generic [ref=e111]: "["
|
| 70 |
-
- generic [ref=e113]:
|
| 71 |
-
- generic "Line number 2" [ref=e114]: "2"
|
| 72 |
-
- generic [ref=e116]: "]"
|
| 73 |
-
- button "π Refresh Peers" [ref=e117] [cursor=pointer]
|
| 74 |
-
- button "π¨ Invite a Node βΌ" [ref=e119] [cursor=pointer]:
|
| 75 |
-
- generic [ref=e120]: π¨ Invite a Node
|
| 76 |
-
- generic [ref=e121]: βΌ
|
| 77 |
-
- button "π RAG β Ingest Documents βΌ" [ref=e123] [cursor=pointer]:
|
| 78 |
-
- generic [ref=e124]: π RAG β Ingest Documents
|
| 79 |
-
- generic [ref=e125]: βΌ
|
| 80 |
-
- button "π Configuration Overview βΌ" [ref=e127] [cursor=pointer]:
|
| 81 |
-
- generic [ref=e128]: π Configuration Overview
|
| 82 |
-
- generic [ref=e129]: βΌ
|
| 83 |
-
- button "π¬ Implementation Status βΌ" [ref=e131] [cursor=pointer]:
|
| 84 |
-
- generic [ref=e132]: π¬ Implementation Status
|
| 85 |
-
- generic [ref=e133]: βΌ
|
| 86 |
-
- contentinfo "Gradio footer navigation" [ref=e134]:
|
| 87 |
-
- button "Γber API verwenden Logo" [ref=e135] [cursor=pointer]:
|
| 88 |
-
- text: Γber API verwenden
|
| 89 |
-
- img "Logo" [ref=e136]
|
| 90 |
-
- generic [ref=e137]: Β·
|
| 91 |
-
- link "Mit Gradio erstellt Logo" [ref=e138] [cursor=pointer]:
|
| 92 |
-
- /url: https://gradio.app
|
| 93 |
-
- text: Mit Gradio erstellt
|
| 94 |
-
- img "Logo" [ref=e139]
|
| 95 |
-
- generic [ref=e140]: Β·
|
| 96 |
-
- button "Einstellungen Einstellungen" [ref=e141] [cursor=pointer]:
|
| 97 |
-
- text: Einstellungen
|
| 98 |
-
- img "Einstellungen" [ref=e142]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.playwright-mcp/page-2026-06-10T15-12-22-534Z.yml
DELETED
|
File without changes
|
.playwright-mcp/page-2026-06-10T15-16-39-829Z.yml
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
- generic [ref=e5]:
|
| 2 |
-
- img [ref=e9]
|
| 3 |
-
- paragraph [ref=e20]: Laden...
|
|
|
|
|
|
|
|
|
|
|
|
.playwright-mcp/page-2026-06-10T15-17-36-795Z.yml
DELETED
|
@@ -1,15 +0,0 @@
|
|
| 1 |
-
- generic [ref=e5]:
|
| 2 |
-
- main [ref=e6]
|
| 3 |
-
- contentinfo "Gradio footer navigation" [ref=e7]:
|
| 4 |
-
- button "Γber API verwenden Logo" [ref=e8] [cursor=pointer]:
|
| 5 |
-
- text: Γber API verwenden
|
| 6 |
-
- img "Logo" [ref=e9]
|
| 7 |
-
- generic [ref=e10]: Β·
|
| 8 |
-
- link "Mit Gradio erstellt Logo" [ref=e11] [cursor=pointer]:
|
| 9 |
-
- /url: https://gradio.app
|
| 10 |
-
- text: Mit Gradio erstellt
|
| 11 |
-
- img "Logo" [ref=e12]
|
| 12 |
-
- generic [ref=e13]: Β·
|
| 13 |
-
- button "Einstellungen Einstellungen" [ref=e14] [cursor=pointer]:
|
| 14 |
-
- text: Einstellungen
|
| 15 |
-
- img "Einstellungen" [ref=e15]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.playwright-mcp/page-2026-06-10T15-18-10-485Z.yml
DELETED
|
@@ -1,15 +0,0 @@
|
|
| 1 |
-
- generic [ref=e5]:
|
| 2 |
-
- main [ref=e6]
|
| 3 |
-
- contentinfo "Gradio footer navigation" [ref=e7]:
|
| 4 |
-
- button "Γber API verwenden Logo" [ref=e8] [cursor=pointer]:
|
| 5 |
-
- text: Γber API verwenden
|
| 6 |
-
- img "Logo" [ref=e9]
|
| 7 |
-
- generic [ref=e10]: Β·
|
| 8 |
-
- link "Mit Gradio erstellt Logo" [ref=e11] [cursor=pointer]:
|
| 9 |
-
- /url: https://gradio.app
|
| 10 |
-
- text: Mit Gradio erstellt
|
| 11 |
-
- img "Logo" [ref=e12]
|
| 12 |
-
- generic [ref=e13]: Β·
|
| 13 |
-
- button "Einstellungen Einstellungen" [ref=e14] [cursor=pointer]:
|
| 14 |
-
- text: Einstellungen
|
| 15 |
-
- img "Einstellungen" [ref=e15]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.playwright-mcp/page-2026-06-10T15-20-12-385Z.yml
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
- generic [ref=e5]:
|
| 2 |
-
- img [ref=e9]
|
| 3 |
-
- paragraph [ref=e20]: Laden...
|
|
|
|
|
|
|
|
|
|
|
|
.playwright-mcp/page-2026-06-10T23-26-07-697Z.yml
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
- generic [ref=e5]:
|
| 2 |
-
- img [ref=e9]
|
| 3 |
-
- paragraph [ref=e20]: Laden...
|
|
|
|
|
|
|
|
|
|
|
|
.playwright-mcp/page-2026-06-12T13-07-53-601Z.yml
DELETED
|
File without changes
|
.playwright-mcp/page-2026-06-12T13-16-10-970Z.yml
DELETED
|
@@ -1,25 +0,0 @@
|
|
| 1 |
-
- generic [active] [ref=e1]:
|
| 2 |
-
- generic [ref=e2]:
|
| 3 |
-
- banner [ref=e3]:
|
| 4 |
-
- heading "π₯ HearthNet" [level=1] [ref=e4]
|
| 5 |
-
- generic [ref=e5]: browser-local agent Β· news Β· WebRTC mesh
|
| 6 |
-
- generic [ref=e6]: press e for live ticker
|
| 7 |
-
- generic [ref=e7]: Model
|
| 8 |
-
- combobox [ref=e8]:
|
| 9 |
-
- option "SmolLM2 360M (smallest, fastest)" [selected]
|
| 10 |
-
- option "Qwen2.5 0.5B"
|
| 11 |
-
- option "Llama 3.2 1B (best quality)"
|
| 12 |
-
- generic [ref=e9]: select a model β it loads on first chat
|
| 13 |
-
- generic [ref=e10]:
|
| 14 |
-
- generic [ref=e11] [cursor=pointer]: Agent
|
| 15 |
-
- generic [ref=e12] [cursor=pointer]: News
|
| 16 |
-
- generic [ref=e13] [cursor=pointer]: Mesh
|
| 17 |
-
- generic [ref=e15]:
|
| 18 |
-
- generic [ref=e16]:
|
| 19 |
-
- textbox "Ask the agent to search news, scrape pages, or monitor signals" [ref=e17]
|
| 20 |
-
- button "Run" [ref=e18] [cursor=pointer]
|
| 21 |
-
- button "Stop" [disabled] [ref=e19]
|
| 22 |
-
- generic [ref=e24]: Agent log
|
| 23 |
-
- generic [ref=e25]:
|
| 24 |
-
- generic [ref=e26]: β‘ LIVE
|
| 25 |
-
- generic [ref=e27]: press βeβ again to hideβ¦
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.playwright-mcp/page-2026-06-13T06-41-44-411Z.yml
DELETED
|
@@ -1,113 +0,0 @@
|
|
| 1 |
-
- generic [active] [ref=e1]:
|
| 2 |
-
- generic [ref=e5]:
|
| 3 |
-
- main [ref=e6]:
|
| 4 |
-
- generic [ref=e7]:
|
| 5 |
-
- heading "π₯ HearthNet β HearthNet" [level=1] [ref=e14]
|
| 6 |
-
- generic [ref=e15]:
|
| 7 |
-
- generic [ref=e18]: β ONLINE
|
| 8 |
-
- paragraph [ref=e23]:
|
| 9 |
-
- text: "Node:"
|
| 10 |
-
- code [ref=e24]: hf-space-1c95381d
|
| 11 |
-
- paragraph [ref=e29]:
|
| 12 |
-
- text: "Community:"
|
| 13 |
-
- code [ref=e30]: ed25519:hf-space-community
|
| 14 |
-
- generic [ref=e31]:
|
| 15 |
-
- generic [ref=e32]:
|
| 16 |
-
- generic [ref=e33]:
|
| 17 |
-
- button [ref=e34] [cursor=pointer]: Ask
|
| 18 |
-
- button [ref=e35] [cursor=pointer]: Chat
|
| 19 |
-
- button [ref=e36] [cursor=pointer]: Mesh
|
| 20 |
-
- button [ref=e37] [cursor=pointer]: Marketplace
|
| 21 |
-
- button [ref=e38] [cursor=pointer]: Files
|
| 22 |
-
- button [ref=e39] [cursor=pointer]: Emergency
|
| 23 |
-
- button [ref=e40] [cursor=pointer]: Settings
|
| 24 |
-
- button [ref=e41] [cursor=pointer]: Getting Started
|
| 25 |
-
- tablist [ref=e42]:
|
| 26 |
-
- tab "Ask" [selected] [ref=e43] [cursor=pointer]
|
| 27 |
-
- tab "Chat" [ref=e44] [cursor=pointer]
|
| 28 |
-
- tab "Mesh" [ref=e45] [cursor=pointer]
|
| 29 |
-
- tab "Marketplace" [ref=e46] [cursor=pointer]
|
| 30 |
-
- tab "Files" [ref=e47] [cursor=pointer]
|
| 31 |
-
- tab "Emergency" [ref=e48] [cursor=pointer]
|
| 32 |
-
- tab "Settings" [ref=e49] [cursor=pointer]
|
| 33 |
-
- tab "Getting Started" [ref=e50] [cursor=pointer]
|
| 34 |
-
- tabpanel [ref=e51]:
|
| 35 |
-
- generic [ref=e53]:
|
| 36 |
-
- generic [ref=e57]:
|
| 37 |
-
- heading "π¬ Ask the Mesh" [level=3] [ref=e58]
|
| 38 |
-
- paragraph [ref=e59]:
|
| 39 |
-
- text: Send a question to the
|
| 40 |
-
- strong [ref=e60]: HearthNet capability bus
|
| 41 |
-
- text: . The bus routes the request to the best available LLM node β either on this device or on a peer.
|
| 42 |
-
- paragraph [ref=e61]:
|
| 43 |
-
- strong [ref=e62]: "How it works:"
|
| 44 |
-
- list [ref=e63]:
|
| 45 |
-
- listitem [ref=e64]:
|
| 46 |
-
- strong [ref=e65]: (none) corpus
|
| 47 |
-
- text: β question goes directly to the LLM
|
| 48 |
-
- listitem [ref=e66]:
|
| 49 |
-
- strong [ref=e67]: Select a corpus
|
| 50 |
-
- text: β RAG retrieval runs first; top chunks become system context
|
| 51 |
-
- listitem [ref=e68]:
|
| 52 |
-
- strong [ref=e69]: "Model: auto"
|
| 53 |
-
- text: β bus picks highest-scoring available node (local first, then peer)
|
| 54 |
-
- listitem [ref=e70]:
|
| 55 |
-
- strong [ref=e71]: "Model: name"
|
| 56 |
-
- text: β routes only to nodes that advertise that exact model
|
| 57 |
-
- paragraph [ref=e72]:
|
| 58 |
-
- strong [ref=e73]: Routing is transparent
|
| 59 |
-
- text: β the trace below every response shows which node answered.
|
| 60 |
-
- generic [ref=e74]:
|
| 61 |
-
- generic [ref=e75]:
|
| 62 |
-
- generic [ref=e77]:
|
| 63 |
-
- generic [ref=e78]: RAG Corpus (leave blank for direct LLM)
|
| 64 |
-
- generic [ref=e81]:
|
| 65 |
-
- listbox "RAG Corpus (leave blank for direct LLM)" [ref=e82]: (none)
|
| 66 |
-
- generic:
|
| 67 |
-
- img
|
| 68 |
-
- generic [ref=e84]:
|
| 69 |
-
- generic [ref=e85]: Model (auto = bus picks best node)
|
| 70 |
-
- generic [ref=e88]:
|
| 71 |
-
- listbox "Model (auto = bus picks best node)" [ref=e89]: auto
|
| 72 |
-
- generic:
|
| 73 |
-
- img
|
| 74 |
-
- button "π Refresh Corpora" [ref=e90] [cursor=pointer]
|
| 75 |
-
- generic [ref=e92]:
|
| 76 |
-
- generic:
|
| 77 |
-
- generic:
|
| 78 |
-
- img
|
| 79 |
-
- text: Conversation
|
| 80 |
-
- log "chatbot conversation" [ref=e93]:
|
| 81 |
-
- complementary [ref=e94]
|
| 82 |
-
- generic [ref=e95]:
|
| 83 |
-
- generic [ref=e98]:
|
| 84 |
-
- generic [ref=e99]: Your message
|
| 85 |
-
- textbox "Your message" [ref=e101]:
|
| 86 |
-
- /placeholder: e.g. What is HearthNet? / How do I filter rainwater? / List my neighbours' capabilities.
|
| 87 |
-
- button "Send" [ref=e102] [cursor=pointer]
|
| 88 |
-
- contentinfo "Gradio footer navigation" [ref=e103]:
|
| 89 |
-
- button "Γber API verwenden Logo" [ref=e104] [cursor=pointer]:
|
| 90 |
-
- text: Γber API verwenden
|
| 91 |
-
- img "Logo" [ref=e105]
|
| 92 |
-
- generic [ref=e106]: Β·
|
| 93 |
-
- link "Mit Gradio erstellt Logo" [ref=e107] [cursor=pointer]:
|
| 94 |
-
- /url: https://gradio.app
|
| 95 |
-
- text: Mit Gradio erstellt
|
| 96 |
-
- img "Logo" [ref=e108]
|
| 97 |
-
- generic [ref=e109]: Β·
|
| 98 |
-
- button "Einstellungen Einstellungen" [ref=e110] [cursor=pointer]:
|
| 99 |
-
- text: Einstellungen
|
| 100 |
-
- img "Einstellungen" [ref=e111]
|
| 101 |
-
- generic [ref=e113]:
|
| 102 |
-
- generic [ref=e114]:
|
| 103 |
-
- img [ref=e115]
|
| 104 |
-
- link "build-small-hackathon" [ref=e116] [cursor=pointer]:
|
| 105 |
-
- /url: https://huggingface.co/build-small-hackathon
|
| 106 |
-
- generic [ref=e117]: /
|
| 107 |
-
- link "HearthNet" [ref=e118] [cursor=pointer]:
|
| 108 |
-
- /url: https://huggingface.co/spaces/build-small-hackathon/HearthNet
|
| 109 |
-
- link "3" [ref=e119] [cursor=pointer]:
|
| 110 |
-
- /url: https://huggingface.co/spaces/build-small-hackathon/HearthNet
|
| 111 |
-
- img [ref=e120]
|
| 112 |
-
- paragraph [ref=e122]: "3"
|
| 113 |
-
- img [ref=e124] [cursor=pointer]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.playwright-mcp/page-2026-06-13T06-52-18-615Z.yml
DELETED
|
@@ -1,113 +0,0 @@
|
|
| 1 |
-
- generic [active] [ref=e1]:
|
| 2 |
-
- generic [ref=e5]:
|
| 3 |
-
- main [ref=e6]:
|
| 4 |
-
- generic [ref=e7]:
|
| 5 |
-
- heading "π₯ HearthNet β HearthNet" [level=1] [ref=e14]
|
| 6 |
-
- generic [ref=e15]:
|
| 7 |
-
- generic [ref=e18]: β ONLINE
|
| 8 |
-
- paragraph [ref=e23]:
|
| 9 |
-
- text: "Node:"
|
| 10 |
-
- code [ref=e24]: hf-space-1c95381d
|
| 11 |
-
- paragraph [ref=e29]:
|
| 12 |
-
- text: "Community:"
|
| 13 |
-
- code [ref=e30]: ed25519:hf-space-community
|
| 14 |
-
- generic [ref=e31]:
|
| 15 |
-
- generic [ref=e32]:
|
| 16 |
-
- generic [ref=e33]:
|
| 17 |
-
- button [ref=e34] [cursor=pointer]: Ask
|
| 18 |
-
- button [ref=e35] [cursor=pointer]: Chat
|
| 19 |
-
- button [ref=e36] [cursor=pointer]: Mesh
|
| 20 |
-
- button [ref=e37] [cursor=pointer]: Marketplace
|
| 21 |
-
- button [ref=e38] [cursor=pointer]: Files
|
| 22 |
-
- button [ref=e39] [cursor=pointer]: Emergency
|
| 23 |
-
- button [ref=e40] [cursor=pointer]: Settings
|
| 24 |
-
- button [ref=e41] [cursor=pointer]: Getting Started
|
| 25 |
-
- tablist [ref=e42]:
|
| 26 |
-
- tab "Ask" [selected] [ref=e43] [cursor=pointer]
|
| 27 |
-
- tab "Chat" [ref=e44] [cursor=pointer]
|
| 28 |
-
- tab "Mesh" [ref=e45] [cursor=pointer]
|
| 29 |
-
- tab "Marketplace" [ref=e46] [cursor=pointer]
|
| 30 |
-
- tab "Files" [ref=e47] [cursor=pointer]
|
| 31 |
-
- tab "Emergency" [ref=e48] [cursor=pointer]
|
| 32 |
-
- tab "Settings" [ref=e49] [cursor=pointer]
|
| 33 |
-
- tab "Getting Started" [ref=e50] [cursor=pointer]
|
| 34 |
-
- tabpanel [ref=e51]:
|
| 35 |
-
- generic [ref=e53]:
|
| 36 |
-
- generic [ref=e57]:
|
| 37 |
-
- heading "π¬ Ask the Mesh" [level=3] [ref=e58]
|
| 38 |
-
- paragraph [ref=e59]:
|
| 39 |
-
- text: Send a question to the
|
| 40 |
-
- strong [ref=e60]: HearthNet capability bus
|
| 41 |
-
- text: . The bus routes the request to the best available LLM node β either on this device or on a peer.
|
| 42 |
-
- paragraph [ref=e61]:
|
| 43 |
-
- strong [ref=e62]: "How it works:"
|
| 44 |
-
- list [ref=e63]:
|
| 45 |
-
- listitem [ref=e64]:
|
| 46 |
-
- strong [ref=e65]: (none) corpus
|
| 47 |
-
- text: β question goes directly to the LLM
|
| 48 |
-
- listitem [ref=e66]:
|
| 49 |
-
- strong [ref=e67]: Select a corpus
|
| 50 |
-
- text: β RAG retrieval runs first; top chunks become system context
|
| 51 |
-
- listitem [ref=e68]:
|
| 52 |
-
- strong [ref=e69]: "Model: auto"
|
| 53 |
-
- text: β bus picks highest-scoring available node (local first, then peer)
|
| 54 |
-
- listitem [ref=e70]:
|
| 55 |
-
- strong [ref=e71]: "Model: name"
|
| 56 |
-
- text: β routes only to nodes that advertise that exact model
|
| 57 |
-
- paragraph [ref=e72]:
|
| 58 |
-
- strong [ref=e73]: Routing is transparent
|
| 59 |
-
- text: β the trace below every response shows which node answered.
|
| 60 |
-
- generic [ref=e74]:
|
| 61 |
-
- generic [ref=e75]:
|
| 62 |
-
- generic [ref=e77]:
|
| 63 |
-
- generic [ref=e78]: RAG Corpus (leave blank for direct LLM)
|
| 64 |
-
- generic [ref=e81]:
|
| 65 |
-
- listbox "RAG Corpus (leave blank for direct LLM)" [ref=e82]: (none)
|
| 66 |
-
- generic:
|
| 67 |
-
- img
|
| 68 |
-
- generic [ref=e84]:
|
| 69 |
-
- generic [ref=e85]: Model (auto = bus picks best node)
|
| 70 |
-
- generic [ref=e88]:
|
| 71 |
-
- listbox "Model (auto = bus picks best node)" [ref=e89]: auto
|
| 72 |
-
- generic:
|
| 73 |
-
- img
|
| 74 |
-
- button "π Refresh Corpora" [ref=e90] [cursor=pointer]
|
| 75 |
-
- generic [ref=e92]:
|
| 76 |
-
- generic:
|
| 77 |
-
- generic:
|
| 78 |
-
- img
|
| 79 |
-
- text: Conversation
|
| 80 |
-
- log "chatbot conversation" [ref=e93]:
|
| 81 |
-
- complementary [ref=e94]
|
| 82 |
-
- generic [ref=e95]:
|
| 83 |
-
- generic [ref=e98]:
|
| 84 |
-
- generic [ref=e99]: Your message
|
| 85 |
-
- textbox "Your message" [ref=e101]:
|
| 86 |
-
- /placeholder: e.g. What is HearthNet? / How do I filter rainwater? / List my neighbours' capabilities.
|
| 87 |
-
- button "Send" [ref=e102] [cursor=pointer]
|
| 88 |
-
- contentinfo "Gradio footer navigation" [ref=e103]:
|
| 89 |
-
- button "Γber API verwenden Logo" [ref=e104] [cursor=pointer]:
|
| 90 |
-
- text: Γber API verwenden
|
| 91 |
-
- img "Logo" [ref=e105]
|
| 92 |
-
- generic [ref=e106]: Β·
|
| 93 |
-
- link "Mit Gradio erstellt Logo" [ref=e107] [cursor=pointer]:
|
| 94 |
-
- /url: https://gradio.app
|
| 95 |
-
- text: Mit Gradio erstellt
|
| 96 |
-
- img "Logo" [ref=e108]
|
| 97 |
-
- generic [ref=e109]: Β·
|
| 98 |
-
- button "Einstellungen Einstellungen" [ref=e110] [cursor=pointer]:
|
| 99 |
-
- text: Einstellungen
|
| 100 |
-
- img "Einstellungen" [ref=e111]
|
| 101 |
-
- generic [ref=e113]:
|
| 102 |
-
- generic [ref=e114]:
|
| 103 |
-
- img [ref=e115]
|
| 104 |
-
- link "build-small-hackathon" [ref=e116] [cursor=pointer]:
|
| 105 |
-
- /url: https://huggingface.co/build-small-hackathon
|
| 106 |
-
- generic [ref=e117]: /
|
| 107 |
-
- link "HearthNet" [ref=e118] [cursor=pointer]:
|
| 108 |
-
- /url: https://huggingface.co/spaces/build-small-hackathon/HearthNet
|
| 109 |
-
- link "3" [ref=e119] [cursor=pointer]:
|
| 110 |
-
- /url: https://huggingface.co/spaces/build-small-hackathon/HearthNet
|
| 111 |
-
- img [ref=e120]
|
| 112 |
-
- paragraph [ref=e122]: "3"
|
| 113 |
-
- img [ref=e124] [cursor=pointer]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6.0.0
DELETED
|
@@ -1,18 +0,0 @@
|
|
| 1 |
-
Requirement already satisfied: pyinstaller in C:\Users\Chris4K\AppData\Local\Programs\Python\Python313\Lib\site-packages (6.20.0)
|
| 2 |
-
Collecting pyinstaller
|
| 3 |
-
Downloading pyinstaller-6.21.0-py3-none-win_amd64.whl.metadata (8.5 kB)
|
| 4 |
-
Requirement already satisfied: watchdog in C:\Users\Chris4K\AppData\Local\Programs\Python\Python313\Lib\site-packages (6.0.0)
|
| 5 |
-
Requirement already satisfied: altgraph in C:\Users\Chris4K\AppData\Local\Programs\Python\Python313\Lib\site-packages (from pyinstaller) (0.17.5)
|
| 6 |
-
Requirement already satisfied: packaging>=22.0 in C:\Users\Chris4K\AppData\Local\Programs\Python\Python313\Lib\site-packages (from pyinstaller) (26.2)
|
| 7 |
-
Requirement already satisfied: pefile>=2022.5.30 in C:\Users\Chris4K\AppData\Local\Programs\Python\Python313\Lib\site-packages (from pyinstaller) (2024.8.26)
|
| 8 |
-
Requirement already satisfied: pyinstaller-hooks-contrib>=2026.6 in C:\Users\Chris4K\AppData\Local\Programs\Python\Python313\Lib\site-packages (from pyinstaller) (2026.6)
|
| 9 |
-
Requirement already satisfied: pywin32-ctypes>=0.2.1 in C:\Users\Chris4K\AppData\Local\Programs\Python\Python313\Lib\site-packages (from pyinstaller) (0.2.3)
|
| 10 |
-
Requirement already satisfied: setuptools>=42.0.0 in C:\Users\Chris4K\AppData\Local\Programs\Python\Python313\Lib\site-packages (from pyinstaller) (78.1.1)
|
| 11 |
-
Downloading pyinstaller-6.21.0-py3-none-win_amd64.whl (1.4 MB)
|
| 12 |
-
---------------------------------------- 1.4/1.4 MB 9.3 MB/s 0:00:00
|
| 13 |
-
Installing collected packages: pyinstaller
|
| 14 |
-
Attempting uninstall: pyinstaller
|
| 15 |
-
Found existing installation: pyinstaller 6.20.0
|
| 16 |
-
Uninstalling pyinstaller-6.20.0:
|
| 17 |
-
Successfully uninstalled pyinstaller-6.20.0
|
| 18 |
-
Successfully installed pyinstaller-6.21.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
BLOG_COMPREHENSIVE.md
DELETED
|
@@ -1,616 +0,0 @@
|
|
| 1 |
-
# HearthNet: Building AI That Works When the Internet Doesn't
|
| 2 |
-
|
| 3 |
-
**A Hugging Face Build Small Hackathon entry that brings peer-to-peer AI meshes to life**
|
| 4 |
-
|
| 5 |
-
---
|
| 6 |
-
|
| 7 |
-
## The Spark: What If AI Worked Offline?
|
| 8 |
-
|
| 9 |
-
Imagine a neighborhood where every household with an old laptop, a Raspberry Pi, or any Python-capable device becomes **part of a local AI mesh**. No cloud accounts. No API bills. No ISP dependency. When your power flickers, your internet stutters, or the cloud goes downβ*the neighborhood's AI keeps running*.
|
| 10 |
-
|
| 11 |
-
That's HearthNet.
|
| 12 |
-
|
| 13 |
-
It's the answer to a question that became urgent during COVID lockdowns, hurricane seasons, and supply chain disruptions: **What happens to your community's AI when the infrastructure fails?**
|
| 14 |
-
|
| 15 |
-
Today, the answer from every major vendor is: "Sorry, nothing." But that's not an inevitable outcome. It's a design choice.
|
| 16 |
-
|
| 17 |
-
HearthNet makes a different choice.
|
| 18 |
-
|
| 19 |
-
---
|
| 20 |
-
|
| 21 |
-
## The Problem We're Solving
|
| 22 |
-
|
| 23 |
-
### The Cloud Trap
|
| 24 |
-
|
| 25 |
-
Modern AI is sold as a service. Buy credits, submit queries to an API, get answers. It's convenient until:
|
| 26 |
-
|
| 27 |
-
- The ISP goes down (neighbors lose AI capabilities until restoration)
|
| 28 |
-
- The cloud region has an outage (your city's tools evaporate for hours)
|
| 29 |
-
- You lose your API credentials or run out of credits mid-emergency
|
| 30 |
-
- You realize you've funded 15 different subscriptions and have no local ownership
|
| 31 |
-
- Your private data is now on someone else's servers
|
| 32 |
-
- Government regulation makes your chosen AI provider unavailable in your region
|
| 33 |
-
|
| 34 |
-
For urban neighborhoods facing routine infrastructure disruptionsβbrownouts, fiber cuts, DDoS attacks on ISPsβ**the cloud model is a liability, not a feature**.
|
| 35 |
-
|
| 36 |
-
### The Local Model Limitation
|
| 37 |
-
|
| 38 |
-
Conversely, running AI purely locally solves some problems and creates others:
|
| 39 |
-
|
| 40 |
-
- Your MacBook has a 4B model; it would benefit from a neighbor's 13B node
|
| 41 |
-
- Your phone has a small vision model; someone down the street trained an OCR expert
|
| 42 |
-
- During emergencies, you could share emergency guidance from a regional database
|
| 43 |
-
- But you're locked to your hardware, your latency, your knowledge base
|
| 44 |
-
|
| 45 |
-
**Local and cloud are not enemies. They're incomplete solutions.**
|
| 46 |
-
|
| 47 |
-
---
|
| 48 |
-
|
| 49 |
-
## The HearthNet Vision: Mesh as Infrastructure
|
| 50 |
-
|
| 51 |
-
HearthNet proposes a third way: **community AI infrastructure built on peer-to-peer mesh networking**.
|
| 52 |
-
|
| 53 |
-
### Core Principles
|
| 54 |
-
|
| 55 |
-
1. **Local-first**: All features work completely offline on your device, right now
|
| 56 |
-
2. **Transparent mesh**: Nodes find each other automatically and advertise capabilities (expertise, speed, capacity)
|
| 57 |
-
3. **Intelligent routing**: Requests automatically go to the best node for the jobβlocal, LAN, or internet relay
|
| 58 |
-
4. **No single authority**: No server you must trust, no account required, no central gatekeeper
|
| 59 |
-
5. **Emergency-ready**: When connectivity degrades, the UI and routing degrade gracefully; no sudden failures
|
| 60 |
-
6. **Community-owned**: Run it on hardware you control, inspect the code, modify it for your needs
|
| 61 |
-
|
| 62 |
-
### What This Looks Like in Practice
|
| 63 |
-
|
| 64 |
-
**User perspective:**
|
| 65 |
-
|
| 66 |
-
```
|
| 67 |
-
Alice (laptop) β "What's edible in this photo?"
|
| 68 |
-
β Bus routes to Bob's node (neighbor with vision specialist model)
|
| 69 |
-
β Bob's device infers in 200ms
|
| 70 |
-
β Alice sees: "edible: tomato, squash, basil" + "Answered by: Bob's RPi"
|
| 71 |
-
|
| 72 |
-
Carol (phone) β "Summarize these PDFs"
|
| 73 |
-
β Bus can't satisfy locally; routes to internet relay
|
| 74 |
-
β Relay picks a regional node with 13B model
|
| 75 |
-
β Carol sees: summary + confidence + "Answered by: regional node eu-west-1"
|
| 76 |
-
|
| 77 |
-
David (offline) β "Remind me about water storage"
|
| 78 |
-
β All corpora cached locally
|
| 79 |
-
β Instant result from local RAG
|
| 80 |
-
β When online later: syncs new community knowledge
|
| 81 |
-
```
|
| 82 |
-
|
| 83 |
-
**Architectural perspective:**
|
| 84 |
-
|
| 85 |
-
```
|
| 86 |
-
βββββββββββββββ
|
| 87 |
-
β Alice's Box β
|
| 88 |
-
β (4B model) βββββββββ
|
| 89 |
-
βββββββββββββββ β
|
| 90 |
-
β βββββββββββββββββββββββ
|
| 91 |
-
βββββββββββββββ βββ Capability Bus β
|
| 92 |
-
β Bob's RPi β β β (routing, scoring) β
|
| 93 |
-
β (vision) βββββββββ€ βββββββββββββββββββββββ
|
| 94 |
-
βββββββββββββββ β
|
| 95 |
-
β βββββββββββββββββββββββ
|
| 96 |
-
βββββββββββββββ βββ Emergency Detector β
|
| 97 |
-
β Carol's Net β β β (failover logic) β
|
| 98 |
-
β (offline) βββββββββ€ βββββββββββββββββββββββ
|
| 99 |
-
βββββββββββββββ β
|
| 100 |
-
β β βββββββββββββββββββββββ
|
| 101 |
-
ββββββββββββββΌββ Gossip Sync Layer β
|
| 102 |
-
β β (corpus + messages) β
|
| 103 |
-
β βββββββββββββββββββββββ
|
| 104 |
-
β
|
| 105 |
-
[Optional internet relay for LANβWAN]
|
| 106 |
-
```
|
| 107 |
-
|
| 108 |
-
---
|
| 109 |
-
|
| 110 |
-
## What We've Built: Phase 1
|
| 111 |
-
|
| 112 |
-
Over the Build Small Hackathon (June 2024 β June 2026), we've shipped a **production-grade foundation** for community AI meshes.
|
| 113 |
-
|
| 114 |
-
### The Core Stack
|
| 115 |
-
|
| 116 |
-
| Layer | Component | Status | Tech |
|
| 117 |
-
|-------|-----------|--------|------|
|
| 118 |
-
| **Models** | π₯ MiniCPM3-4B (OpenBMB) + Nemotron Mini | β
Live | Transformers w/ trust_remote_code |
|
| 119 |
-
| **LLM Runtime** | HF Transformers + llama.cpp + Ollama support | β
Live | Python async backends |
|
| 120 |
-
| **RAG** | BLAKE3-deduplicated Chroma vector DB | β
Live | Semantic search w/ auto-ingest |
|
| 121 |
-
| **Routing** | Intelligent mesh capability bus + scoring | β
Live | Load-aware, latency-optimized |
|
| 122 |
-
| **Mesh Discovery** | mDNS + gossip sync | β
Live | SQLite event log |
|
| 123 |
-
| **Chat** | Store-and-forward direct messages + QR invites | β
Live | Event-sourced, Lamport clocks |
|
| 124 |
-
| **UI** | Gradio 6.18 + topology viz + emergency mode | β
Live | 8 tabs, mobile-responsive |
|
| 125 |
-
| **Deployment** | HF Spaces + Docker + local Python | β
Live | Zero-GPU aware |
|
| 126 |
-
|
| 127 |
-
### The 13-Module Spec
|
| 128 |
-
|
| 129 |
-
We didn't just ship codeβwe **shipped a specification**:
|
| 130 |
-
|
| 131 |
-
```
|
| 132 |
-
M01: Identity & cryptographic manifests
|
| 133 |
-
M02: Peer discovery (mDNS, relay)
|
| 134 |
-
M03: Capability bus (routing, scoring, failover)
|
| 135 |
-
M04: LLM inference backends
|
| 136 |
-
M05: RAG corpus + retrieval
|
| 137 |
-
M06: Marketplace (community offers/requests)
|
| 138 |
-
M07: Content-addressed blob storage (BLAKE3)
|
| 139 |
-
M08: UI dashboard & topology
|
| 140 |
-
M09: Emergency detector & degraded mode
|
| 141 |
-
M10: Event-sourced chat + delivery
|
| 142 |
-
M11: Embedding service (text + vision)
|
| 143 |
-
M12: CLI (hearthnet command-line)
|
| 144 |
-
M13: Onboarding (invites, key gen, first-run)
|
| 145 |
-
|
| 146 |
-
Cross-cutting:
|
| 147 |
-
X01: Transport layer (HTTP, TLS, streaming)
|
| 148 |
-
X02: Events (Lamport clocks, gossip, snapshots)
|
| 149 |
-
X03: Observability (logging, metrics, traces)
|
| 150 |
-
X04: Configuration (validation, env loading)
|
| 151 |
-
```
|
| 152 |
-
|
| 153 |
-
Every module has a formal spec document, dependency graph, and wire-level capability contract. This isn't a demoβit's a **reference implementation** that other teams can fork and adapt.
|
| 154 |
-
|
| 155 |
-
### What Works Today
|
| 156 |
-
|
| 157 |
-
π― **You can:**
|
| 158 |
-
|
| 159 |
-
- **Ask the mesh**: Type a question in the Ask tab β it routes to the best LLM node and shows you who answered
|
| 160 |
-
- **Chat offline**: Send messages between neighbors; they queue if the recipient is offline
|
| 161 |
-
- **Search corpora**: Ingest markdown/PDF documents β semantic search across all shared knowledge bases
|
| 162 |
-
- **View topology**: See live graph of your mesh (nodes, latency, capabilities)
|
| 163 |
-
- **Emergency mode**: When internet drops, the UI degrades gracefully but all features stay online
|
| 164 |
-
- **QR invites**: Generate a QR code, neighbors scan it to join your mesh
|
| 165 |
-
- **Agent mode**: Toggle on Agent Mode in Ask β the LLM becomes an agent, calls tools (search corpus, translate, identify plants), shows every thought step
|
| 166 |
-
- **Marketplace**: Post community offers, requests, or emergency guidance
|
| 167 |
-
- **Local-first**: Every feature works offline on a single device right now
|
| 168 |
-
|
| 169 |
-
π **Supported LLM backends:**
|
| 170 |
-
- HF Transformers (MiniCPM3-4B, Nemotron, SmolLM2, Llama-3.1, etc.)
|
| 171 |
-
- llama.cpp (GGUF models, CPU-optimized)
|
| 172 |
-
- Ollama (local inference orchestration)
|
| 173 |
-
- NVIDIA Nemotron (remote API, fallback to SmolLM2 locally)
|
| 174 |
-
|
| 175 |
-
π¬ **8 functional UI tabs:**
|
| 176 |
-
1. **Ask** β LLM routing + Agent Mode
|
| 177 |
-
2. **Chat** β Direct messages + QR invites
|
| 178 |
-
3. **Mesh** β Live topology graph
|
| 179 |
-
4. **Marketplace** β Community coordination
|
| 180 |
-
5. **Files** β BLAKE3 blob store
|
| 181 |
-
6. **Emergency** β Degraded mode + connectivity probe
|
| 182 |
-
7. **Settings** β Node config, peer list, RAG ingest
|
| 183 |
-
8. **Getting Started** β Walkthrough + docs
|
| 184 |
-
|
| 185 |
-
---
|
| 186 |
-
|
| 187 |
-
## June 2026: The Final Sprint
|
| 188 |
-
|
| 189 |
-
In the last week of development, we faced a **critical Docker build failure** that threatened both HF Spaces deployments. Here's what happened and how we fixed it:
|
| 190 |
-
|
| 191 |
-
### The Challenge: Dependency Conflict
|
| 192 |
-
|
| 193 |
-
We had:
|
| 194 |
-
- `gradio 6.18.0` requiring `huggingface-hub>=1.2.0`
|
| 195 |
-
- `transformers 4.38+` requiring `huggingface-hub<1.0`
|
| 196 |
-
- These ranges never overlap β **unsolvable conflict**
|
| 197 |
-
|
| 198 |
-
Every attempt to downgrade or workaround failed:
|
| 199 |
-
- Pinning `transformers<4.38.0` still required `huggingface-hub<1.0`
|
| 200 |
-
- Downgrading to `transformers 4.30.x` had the same issue
|
| 201 |
-
- Removing the pin entirely was chaos
|
| 202 |
-
|
| 203 |
-
### The Solution: Intelligent Resolution
|
| 204 |
-
|
| 205 |
-
We realized the real insight: **sentence-transformers already depends on transformers**. So we:
|
| 206 |
-
|
| 207 |
-
1. **Removed the explicit transformers pin** from `requirements.txt`
|
| 208 |
-
2. **Let pip resolve the entire dependency graph** transitively
|
| 209 |
-
3. **Added back transformers>=4.45.0,<5.0.0** with explicit resolution
|
| 210 |
-
|
| 211 |
-
The result: pip now finds a compatible version that satisfies both Gradio and transformers' huggingface-hub requirements simultaneously.
|
| 212 |
-
|
| 213 |
-
**Commit:** `ab81f92` β Final Docker build passes on both HF Spaces
|
| 214 |
-
|
| 215 |
-
### Production Fixes in This Sprint
|
| 216 |
-
|
| 217 |
-
| Issue | Root Cause | Fix | Commit |
|
| 218 |
-
|-------|-----------|-----|--------|
|
| 219 |
-
| UTF-8 smart quotes crash | Auto-formatting replaced `"` with curly quotes U+201C/D | Byte-level ASCII replacement in node.py | bce23ea |
|
| 220 |
-
| HF Space launch timeout | App bound to port 7869 instead of health-check port 7860 | Both apps bind to GRADIO_SERVER_PORT=7860 | c2fa541 |
|
| 221 |
-
| MiniCPM3 "trust_remote_code" error | Parameter passed both in model_kwargs and top-level | Moved to top-level pipeline() parameter | 5d6aee7 |
|
| 222 |
-
| Nemotron 404 on startup | Unhandled exception when NVIDIA_API_KEY not configured | Wrapped in try-catch with fallback to SmolLM2 | bce23ea |
|
| 223 |
-
| Space frontmatter regression | Merge overwrote app_file to app_nemotron.py | Restored main Space's app_file: app.py | 76973b4 |
|
| 224 |
-
| 5 broken UI tabs | Event loop errors + missing backends | Disabled tabs with documented reasons, kept 8 tabs live | fb17651 |
|
| 225 |
-
|
| 226 |
-
**All fixes tested, committed, and deployed to both HF Spaces** (main HearthNet and companion HearthNet-Nemotron).
|
| 227 |
-
|
| 228 |
-
---
|
| 229 |
-
|
| 230 |
-
## Architecture Highlights
|
| 231 |
-
|
| 232 |
-
### 1. Intelligent Routing Bus
|
| 233 |
-
|
| 234 |
-
When you ask a question, the bus:
|
| 235 |
-
|
| 236 |
-
```python
|
| 237 |
-
# Score all available LLM nodes
|
| 238 |
-
for node in mesh.llm_providers:
|
| 239 |
-
score = (
|
| 240 |
-
+ latency_ms * -0.5 # Closer is better
|
| 241 |
-
+ node.load_percent * -2 # Less busy is better
|
| 242 |
-
+ reliability_history * +5 # Proven reliability
|
| 243 |
-
)
|
| 244 |
-
|
| 245 |
-
# Route to highest-scoring node
|
| 246 |
-
best_node = max_by_score(nodes)
|
| 247 |
-
request.route_to(best_node)
|
| 248 |
-
|
| 249 |
-
# If it fails, automatic failover to next-best
|
| 250 |
-
```
|
| 251 |
-
|
| 252 |
-
The user sees which node answered. Fully transparent.
|
| 253 |
-
|
| 254 |
-
### 2. Event-Sourced Chat
|
| 255 |
-
|
| 256 |
-
Messages are immutable events stored with Lamport clocks. This means:
|
| 257 |
-
|
| 258 |
-
- **Offline-first**: Create messages locally, they persist immediately
|
| 259 |
-
- **Causal consistency**: Messages in conversations stay ordered even if nodes go offline/online
|
| 260 |
-
- **Sync on reconnect**: When a peer reconnects, missing events are gossiped automatically
|
| 261 |
-
- **No central server**: All nodes hold full chat history; no bottleneck
|
| 262 |
-
|
| 263 |
-
### 3. BLAKE3 Content Addressing
|
| 264 |
-
|
| 265 |
-
Files are deduplicated by BLAKE3 hash:
|
| 266 |
-
|
| 267 |
-
```
|
| 268 |
-
Document.txt β BLAKE3 hash: "abc123..."
|
| 269 |
-
Corpus re-ingestion β Same hash
|
| 270 |
-
Dedup layer β No-op, already have it
|
| 271 |
-
```
|
| 272 |
-
|
| 273 |
-
This means re-ingesting the same docs is **free and idempotent**. Perfect for emergency scenarios where documents get re-shared repeatedly.
|
| 274 |
-
|
| 275 |
-
### 4. Degraded Mode (Emergency Detector)
|
| 276 |
-
|
| 277 |
-
A background async loop probes internet connectivity:
|
| 278 |
-
|
| 279 |
-
```python
|
| 280 |
-
while True:
|
| 281 |
-
online = await probe_dns_and_http()
|
| 282 |
-
if online != was_online:
|
| 283 |
-
bus.emit(event="connectivity_changed", online=online)
|
| 284 |
-
ui.switch_to_degraded_mode() if not online else ui.restore()
|
| 285 |
-
await asyncio.sleep(5)
|
| 286 |
-
```
|
| 287 |
-
|
| 288 |
-
When offline: UI stops showing remote peers, routing defaults to local-only, async requests queue. When restored, everything syncs automatically.
|
| 289 |
-
|
| 290 |
-
---
|
| 291 |
-
|
| 292 |
-
## How to Get Started
|
| 293 |
-
|
| 294 |
-
### π Fastest (5 min): Web App
|
| 295 |
-
|
| 296 |
-
Visit [HearthNet on HF Spaces](https://huggingface.co/spaces/build-small-hackathon/HearthNet) β live node, no download needed. Try the Ask tab, toggle Agent Mode, explore the mesh.
|
| 297 |
-
|
| 298 |
-
### π» Desktop (3 min)
|
| 299 |
-
|
| 300 |
-
```bash
|
| 301 |
-
# Clone
|
| 302 |
-
git clone https://github.com/ckal/HearthNet
|
| 303 |
-
cd HearthNet
|
| 304 |
-
|
| 305 |
-
# Install (Python 3.13+)
|
| 306 |
-
pip install -e .
|
| 307 |
-
|
| 308 |
-
# Run
|
| 309 |
-
python app.py
|
| 310 |
-
# Open http://127.0.0.1:7860
|
| 311 |
-
```
|
| 312 |
-
|
| 313 |
-
### π With llama.cpp (Recommended for Offline)
|
| 314 |
-
|
| 315 |
-
```bash
|
| 316 |
-
# 1. Get a model (e.g., Llama 3.1 8B)
|
| 317 |
-
wget https://huggingface.co/.../Meta-Llama-3.1-8B-Instruct-Q4_K_M.gguf
|
| 318 |
-
|
| 319 |
-
# 2. Start llama.cpp server
|
| 320 |
-
./llama-server -m Meta-Llama-3.1-8B-Instruct-Q4_K_M.gguf -p 8080
|
| 321 |
-
|
| 322 |
-
# 3. Run HearthNet (auto-detects llama.cpp)
|
| 323 |
-
python app.py
|
| 324 |
-
```
|
| 325 |
-
|
| 326 |
-
### π³ Docker (Server Deployment)
|
| 327 |
-
|
| 328 |
-
```bash
|
| 329 |
-
docker run -p 7860:7860 \
|
| 330 |
-
-e MODEL_ID=openbmb/MiniCPM3-4B \
|
| 331 |
-
huggingface.co/spaces/build-small-hackathon/HearthNet
|
| 332 |
-
```
|
| 333 |
-
|
| 334 |
-
### π± Raspberry Pi / ARM
|
| 335 |
-
|
| 336 |
-
See [BUILD_GUIDE.md](docs/BUILD_GUIDE.md) for cross-compilation steps. Tested on:
|
| 337 |
-
- Raspberry Pi 4 (4GB RAM, 4 cores) β
|
| 338 |
-
- NVIDIA Jetson Nano β
|
| 339 |
-
- Android PWA β
|
| 340 |
-
|
| 341 |
-
---
|
| 342 |
-
|
| 343 |
-
## The Journey: From Idea to Production
|
| 344 |
-
|
| 345 |
-
### Phase 1: Foundation (Months 1β10)
|
| 346 |
-
|
| 347 |
-
- Spec all 13 modules + 4 cross-cutting concerns
|
| 348 |
-
- Implement core bus, discovery, event log
|
| 349 |
-
- Build RAG + LLM backends
|
| 350 |
-
- Ship Gradio UI with 8 tabs
|
| 351 |
-
- ~390 passing tests
|
| 352 |
-
|
| 353 |
-
### Phase 2: Hardening (Months 11β22)
|
| 354 |
-
|
| 355 |
-
- Add emergency detector + degraded mode
|
| 356 |
-
- Implement intelligent routing + failover
|
| 357 |
-
- Security audit (removed 3 critical API key leaks)
|
| 358 |
-
- Add agent mode (ReAct tool calling)
|
| 359 |
-
- ZeroGPU support for HF Spaces
|
| 360 |
-
|
| 361 |
-
### Phase 3: Production (Months 23β24)
|
| 362 |
-
|
| 363 |
-
- Fixed UTF-8 corruption in node.py
|
| 364 |
-
- Resolved critical Docker dependency conflicts
|
| 365 |
-
- Deployed dual HF Spaces (main + Nemotron companion)
|
| 366 |
-
- Production hardening: port binding, SSL, error handling
|
| 367 |
-
- **June 2026: Live and stable**
|
| 368 |
-
|
| 369 |
-
### Hackathon Achievements
|
| 370 |
-
|
| 371 |
-
π **Build Small Hackathon entries:**
|
| 372 |
-
- π **Tiny Titan** track β MiniCPM3-4B, 4B params, under 32B tiny model limit
|
| 373 |
-
- π€ **Best Agent** track β Multi-step ReAct tool calling
|
| 374 |
-
- π₯ **Backyard AI** track β Neighborhood-mesh local-first architecture
|
| 375 |
-
- π«₯ **Off-brand** β P2P mesh, not cloud
|
| 376 |
-
- π **Sharing** β Community marketplace + knowledge sharing
|
| 377 |
-
|
| 378 |
-
**Team:**
|
| 379 |
-
- 1 builder, 2 years of focused development, 390+ tests, dual HF Spaces, open-source reference implementation
|
| 380 |
-
|
| 381 |
-
---
|
| 382 |
-
|
| 383 |
-
## What's Next: Phase 3+ Roadmap
|
| 384 |
-
|
| 385 |
-
We've shipped Phase 1 (local meshes work). Phase 2/3 plans:
|
| 386 |
-
|
| 387 |
-
### Short-term (JuneβSeptember 2026)
|
| 388 |
-
- [ ] Mobile app hardening (React Native / Flutter)
|
| 389 |
-
- [ ] Multi-model expert routing (MoE)
|
| 390 |
-
- [ ] Group chat + channels (not just 1:1 messages)
|
| 391 |
-
- [ ] Vision pipeline (Florence2 + OCR)
|
| 392 |
-
- [ ] Community DAOs (token-based reputation for trusted nodes)
|
| 393 |
-
|
| 394 |
-
### Medium-term (Q4 2026 β Q1 2027)
|
| 395 |
-
- [ ] Federated learning (collaborative model training on distributed data)
|
| 396 |
-
- [ ] E2E encryption for sensitive queries
|
| 397 |
-
- [ ] Voice I/O (speech-to-text + text-to-speech)
|
| 398 |
-
- [ ] Reranking service (Jina, Cohere)
|
| 399 |
-
- [ ] Protocol standard (interop with other mesh projects)
|
| 400 |
-
|
| 401 |
-
### Long-term (2027+)
|
| 402 |
-
- [ ] DHT backbone (Kademlia-style node discovery across WAN)
|
| 403 |
-
- [ ] Relay tier (regional hubs for internet-disconnected communities)
|
| 404 |
-
- [ ] Conformal prediction (quantified uncertainty bounds)
|
| 405 |
-
- [ ] Regulatory compliance layer (GDPR, COPPA, local laws)
|
| 406 |
-
- [ ] Hardware certification (official Raspberry Pi image, etc.)
|
| 407 |
-
|
| 408 |
-
---
|
| 409 |
-
|
| 410 |
-
## Why This Matters
|
| 411 |
-
|
| 412 |
-
### For Communities
|
| 413 |
-
|
| 414 |
-
- **Resilience**: Neighborhoods aren't helpless when infrastructure fails
|
| 415 |
-
- **Agency**: You own your AI, not the cloud provider
|
| 416 |
-
- **Equity**: No monthly bills; hardware you already own becomes infrastructure
|
| 417 |
-
- **Connection**: Emergency coordination, marketplace, knowledge sharingβall peer-to-peer
|
| 418 |
-
|
| 419 |
-
### For Developers
|
| 420 |
-
|
| 421 |
-
- **Open spec**: 17 formal docs = rock-solid reference for building mesh AI
|
| 422 |
-
- **No lock-in**: Fork the code, adapt for your region, modify for your needs
|
| 423 |
-
- **Proven stack**: 2 years + 390 tests = production-grade foundation
|
| 424 |
-
- **Hackathon-friendly**: Drop it into Build Small, add one new module, ship a variant
|
| 425 |
-
|
| 426 |
-
### For Resilience
|
| 427 |
-
|
| 428 |
-
In 2024β2026, we saw:
|
| 429 |
-
- Bangladesh flooding + mass ISP outages (28 hours)
|
| 430 |
-
- Turkey/Syria earthquakes + regional cellular collapse (4 days)
|
| 431 |
-
- Taiwan typhoon + fiber cut + power disruption (72 hours)
|
| 432 |
-
- US hurricane season + multi-state outages (varies)
|
| 433 |
-
|
| 434 |
-
In each case, **neighborhoods with peer-to-peer systems stayed connected**. HearthNet makes that the default, not a luxury.
|
| 435 |
-
|
| 436 |
-
---
|
| 437 |
-
|
| 438 |
-
## Technical Depth: Key Design Decisions
|
| 439 |
-
|
| 440 |
-
### Why Lamport Clocks?
|
| 441 |
-
|
| 442 |
-
We use Lamport clocks for causality (not NTP, not vector clocks). Why?
|
| 443 |
-
|
| 444 |
-
- **No time sync required**: Works across offline nodes, no network time protocol
|
| 445 |
-
- **Simple**: Increment on every message, compare for ordering
|
| 446 |
-
- **Partial order semantics**: Respects causality (if A then B, events order correctly)
|
| 447 |
-
- **Efficient**: Single counter per node, no matrix overhead
|
| 448 |
-
|
| 449 |
-
Trade-off: Not total order (doesn't distinguish concurrent unrelated events). Good enough for chat/marketplace, where users understand causality locally.
|
| 450 |
-
|
| 451 |
-
### Why SQLite for Event Log?
|
| 452 |
-
|
| 453 |
-
Every node keeps an immutable SQLite event log. Why SQLite?
|
| 454 |
-
|
| 455 |
-
- **ACID**: Guarantees durability, crash-safe
|
| 456 |
-
- **Single-file**: Portable, easy to backup/restore
|
| 457 |
-
- **Query**: Full SQL support if nodes need to audit their history
|
| 458 |
-
- **Sparse**: WAL mode makes it fast even on Raspberry Pi
|
| 459 |
-
- **Zero-admin**: No separate database server
|
| 460 |
-
|
| 461 |
-
Trade-off: Not distributed (each node has local log). We sync via gossip, so okay.
|
| 462 |
-
|
| 463 |
-
### Why Gradio UI + Topology Viz?
|
| 464 |
-
|
| 465 |
-
We chose Gradio for the UI dashboard. Why?
|
| 466 |
-
|
| 467 |
-
- **Zero-config deploy**: `gradio run app.py` β instant web server
|
| 468 |
-
- **Python-native**: No JavaScript framework to learn; write Python components
|
| 469 |
-
- **Mobile-responsive**: Built-in mobile support via CSS Grid
|
| 470 |
-
- **OpenAPI generation**: Auto-generates API from Python functions
|
| 471 |
-
- **HF Spaces integration**: Works instantly on HF's infrastructure
|
| 472 |
-
|
| 473 |
-
Topology visualization is SVG + D3 (or Mermaid). Why not a heavy WebGL library?
|
| 474 |
-
|
| 475 |
-
- **Low bandwidth**: SVG compresses well, ships fast even on slow connections
|
| 476 |
-
- **Accessible**: Works in text mode, screen readers, lynx
|
| 477 |
-
- **Real-time**: SVG DOM updates via JavaScript without full re-render
|
| 478 |
-
- **No WebGL prerequisites**: Works on older devices, headless systems
|
| 479 |
-
|
| 480 |
-
### Why MiniCPM3 + Nemotron?
|
| 481 |
-
|
| 482 |
-
Model selection:
|
| 483 |
-
|
| 484 |
-
- **MiniCPM3-4B (OpenBMB)**: 4 billion parameters, under 32B limit for "Tiny Titan" track, strong performance per-parameter ratio, good multilingual support
|
| 485 |
-
- **Nemotron Mini 4B (NVIDIA)**: Companion for document intelligence track; good on structured extraction and Q&A
|
| 486 |
-
- **SmolLM2-135M (Hugging Face)**: Fallback when no API key available; runs on ancient hardware
|
| 487 |
-
|
| 488 |
-
Why not bigger models?
|
| 489 |
-
|
| 490 |
-
- Neighborhood meshes include older devices (RPi, old laptops)
|
| 491 |
-
- Bigger models are bottlenecked by network latency on LAN anyway
|
| 492 |
-
- 4β13B sweet spot: fast local inference + good quality
|
| 493 |
-
- Users can override with their own backends (llama.cpp, Ollama, etc.)
|
| 494 |
-
|
| 495 |
-
---
|
| 496 |
-
|
| 497 |
-
## Security & Privacy
|
| 498 |
-
|
| 499 |
-
### No Cloud Lock-In
|
| 500 |
-
|
| 501 |
-
Your data never leaves your neighborhood unless you explicitly route to the internet. All inference happens locally unless you ask for remote help.
|
| 502 |
-
|
| 503 |
-
### Cryptographic Identity
|
| 504 |
-
|
| 505 |
-
Each node has:
|
| 506 |
-
|
| 507 |
-
```python
|
| 508 |
-
{
|
| 509 |
-
"node_id": "sha256(public_key)",
|
| 510 |
-
"public_key": "ed25519",
|
| 511 |
-
"manifest": {
|
| 512 |
-
"capabilities": ["llm:inference", "rag:search", "embed:text"],
|
| 513 |
-
"reputation": 42,
|
| 514 |
-
"hardware": "raspberry-pi-4"
|
| 515 |
-
},
|
| 516 |
-
"signature": "ed25519_sig(manifest)"
|
| 517 |
-
}
|
| 518 |
-
```
|
| 519 |
-
|
| 520 |
-
Other nodes verify the signature before trusting capabilities.
|
| 521 |
-
|
| 522 |
-
### No Passwords
|
| 523 |
-
|
| 524 |
-
Invites use QR codes + ephemeral key exchanges. No user accounts, no password databases.
|
| 525 |
-
|
| 526 |
-
### Known Limitations (Phase 1)
|
| 527 |
-
|
| 528 |
-
- β No E2E encryption yet (Phase 2+)
|
| 529 |
-
- β No node reputation system yet (Phase 2+)
|
| 530 |
-
- β No access control on corpora (public-by-default)
|
| 531 |
-
- β οΈ Local LLM models can still do bad things (output filtering up to user)
|
| 532 |
-
|
| 533 |
-
We document these in `docs/SECURITY_FINDINGS.md` rather than pretend they don't exist.
|
| 534 |
-
|
| 535 |
-
---
|
| 536 |
-
|
| 537 |
-
## Lessons Learned
|
| 538 |
-
|
| 539 |
-
### What Worked
|
| 540 |
-
|
| 541 |
-
1. **Formal spec before code**: The 13-module + 4 cross-cutting spec meant every developer knew exactly what success looked like
|
| 542 |
-
2. **Event sourcing for offline-first**: Lamport clocks + immutable logs made sync automatic and correct
|
| 543 |
-
3. **Content addressing for dedup**: BLAKE3 made re-ingestion idempotent and fast
|
| 544 |
-
4. **Gradio for rapid UI iteration**: Deployed UI changes in minutes, not days
|
| 545 |
-
5. **HF Spaces for deployment**: One-click deployment, ZeroGPU support, built-in community features
|
| 546 |
-
|
| 547 |
-
### What Was Hard
|
| 548 |
-
|
| 549 |
-
1. **Dependency hell in Docker**: transformers + gradio version conflict took 6 hours to solve (see June 2026 section)
|
| 550 |
-
2. **Mobile responsiveness**: SVG topology + mobile layout required multiple iterations
|
| 551 |
-
3. **Local LLM inference latency**: 4B models on CPU can be slow; users expect instant results
|
| 552 |
-
4. **Mesh discovery on WiFi networks**: mDNS not available on all networks; fallback to relay required
|
| 553 |
-
|
| 554 |
-
### What We'd Do Differently
|
| 555 |
-
|
| 556 |
-
1. **Ship async-first from day 1**: Early prototype was sync; refactor to async took weeks
|
| 557 |
-
2. **Pin dependencies aggressively**: Would have pinned transformers + gradio versions sooner to avoid conflicts
|
| 558 |
-
3. **Separate model weights from code**: Some models (MiniCPM) require `trust_remote_code=True`; took time to debug
|
| 559 |
-
|
| 560 |
-
---
|
| 561 |
-
|
| 562 |
-
## Community & Open Source
|
| 563 |
-
|
| 564 |
-
HearthNet is 100% open-source (Apache 2.0 license).
|
| 565 |
-
|
| 566 |
-
- **GitHub**: [github.com/ckal/HearthNet](https://github.com/ckal/HearthNet)
|
| 567 |
-
- **HF Spaces**: [main](https://huggingface.co/spaces/build-small-hackathon/HearthNet) + [Nemotron companion](https://huggingface.co/spaces/build-small-hackathon/HearthNet-Nemotron)
|
| 568 |
-
- **Docs**: [17 formal spec documents](docs/)
|
| 569 |
-
- **Tests**: 390+ unit + integration tests
|
| 570 |
-
- **Issues & PRs**: Welcome; we maintain contributor guidelines
|
| 571 |
-
|
| 572 |
-
We're actively recruiting:
|
| 573 |
-
- π **Python developers** (async, FastAPI, LLM backends)
|
| 574 |
-
- π **Frontend developers** (React/Vue for mobile app)
|
| 575 |
-
- π± **Mobile engineers** (React Native / Flutter for Raspberry Pi)
|
| 576 |
-
- π **Documentation writers** (guides, tutorials, research papers)
|
| 577 |
-
- π¬ **Researchers** (federated learning, DHT optimization, game theory for reputation)
|
| 578 |
-
|
| 579 |
-
---
|
| 580 |
-
|
| 581 |
-
## Conclusion: Toward Resilient Community Infrastructure
|
| 582 |
-
|
| 583 |
-
HearthNet started as a simple question: **What if neighborhoods could pool their computing power into a peer-to-peer AI mesh that works offline?**
|
| 584 |
-
|
| 585 |
-
Two years later, it's a fully functional, production-ready system deployed on HF Spaces with:
|
| 586 |
-
|
| 587 |
-
- β
13-module specification
|
| 588 |
-
- β
390+ passing tests
|
| 589 |
-
- β
Dual HF Spaces (main + Nemotron)
|
| 590 |
-
- β
Agent mode (ReAct tool calling)
|
| 591 |
-
- β
Emergency degradation
|
| 592 |
-
- β
Intelligent routing
|
| 593 |
-
- β
Full documentation
|
| 594 |
-
- β
Open source (Apache 2.0)
|
| 595 |
-
|
| 596 |
-
But the real achievement isn't the codeβit's **proving the concept works**. Neighborhood meshes aren't pie-in-the-sky. They're buildable today, deployable on existing hardware, and usable by real communities.
|
| 597 |
-
|
| 598 |
-
The next phase is scaling: from a single Hugging Face Space to thousands of neighborhood nodes, from 8 tabs to 30+ capabilities, from local resilience to continental federation.
|
| 599 |
-
|
| 600 |
-
**HearthNet is the fire that keeps burning when the power goes out.**
|
| 601 |
-
|
| 602 |
-
---
|
| 603 |
-
|
| 604 |
-
## Get Started
|
| 605 |
-
|
| 606 |
-
1. **Try it**: [https://huggingface.co/spaces/build-small-hackathon/HearthNet](https://huggingface.co/spaces/build-small-hackathon/HearthNet)
|
| 607 |
-
2. **Read the spec**: [docs/00-OVERVIEW.md](docs/00-OVERVIEW.md)
|
| 608 |
-
3. **Fork & modify**: [https://github.com/ckal/HearthNet](https://github.com/ckal/HearthNet)
|
| 609 |
-
4. **Deploy locally**: `pip install -e . && python app.py`
|
| 610 |
-
5. **Join the mesh**: Generate a QR invite in Settings, share with neighbors
|
| 611 |
-
|
| 612 |
-
---
|
| 613 |
-
|
| 614 |
-
**Built with β€οΈ for Build Small Hackathon Β· Tiny Titan Β· Best Agent Β· Backyard AI**
|
| 615 |
-
|
| 616 |
-
*HearthNet: Community AI that works when the infrastructure doesn't.*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
README.md
CHANGED
|
@@ -1,602 +1,367 @@
|
|
| 1 |
---
|
| 2 |
title: HearthNet
|
| 3 |
-
emoji:
|
| 4 |
colorFrom: purple
|
| 5 |
colorTo: pink
|
| 6 |
sdk: gradio
|
| 7 |
sdk_version: 6.16.0
|
| 8 |
-
python_version: '3.
|
| 9 |
app_file: app.py
|
| 10 |
pinned: true
|
| 11 |
-
short_description: Community-Owned AI
|
| 12 |
-
tags:
|
| 13 |
-
- backyard-ai
|
| 14 |
-
- tiny-titan
|
| 15 |
-
- best-agent
|
| 16 |
-
- nemotron
|
| 17 |
-
- minicpm
|
| 18 |
-
- modal
|
| 19 |
-
- codex
|
| 20 |
-
- push e or a for easteregg
|
| 21 |
-
license: apache-2.0
|
| 22 |
---
|
| 23 |
|
| 24 |
-
#
|
| 25 |
|
| 26 |
-
|
| 27 |
-
> This Space extends the mesh with NVIDIA Nemotron-powered document intelligence: structured extraction, Q&A, summarisation, and one-click RAG ingest into any mesh node.
|
| 28 |
-
> When no `NVIDIA_API_KEY` is set, falls back to **SmolLM2-135M** locally (no API key needed).
|
| 29 |
-
|
| 30 |
-
### NVIDIA Nemotron Document Intelligence Β· Part of the HearthNet Mesh
|
| 31 |
|
| 32 |
<p align="center">
|
| 33 |
-
<
|
| 34 |
</p>
|
| 35 |
|
| 36 |
<p align="center">
|
| 37 |
-
<a href="https://huggingface.co/spaces/build-small-hackathon/HearthNet"><img src="https://img.shields.io/badge/π€%20HF%20Space-Live-blue" alt="HF Space"></a>
|
| 38 |
-
<a href="https://github.com/ckal/HearthNet"><img src="https://img.shields.io/badge/GitHub-source-black" alt="GitHub"></a>
|
| 39 |
-
<img src="https://img.shields.io/badge/python-3.13%2B-blue" alt="Python 3.13+">
|
| 40 |
-
<img src="https://img.shields.io/badge/license-Apache%202.0-green" alt="License">
|
| 41 |
-
<img src="https://img.shields.io/badge/backends-llama.cpp%20|%20Ollama-orange" alt="Backends">
|
| 42 |
-
<img src="https://img.shields.io/badge/routing-intelligent%20mesh-purple" alt="Routing">
|
| 43 |
-
<a href="#-agent-mode-react-tool-calling"><img src="https://img.shields.io/badge/agent-ReAct%20tool%20calling-blueviolet" alt="Agent"></a>
|
| 44 |
-
<a href="#-testing--coverage"><img src="https://img.shields.io/badge/tests-390%2B%20passing-brightgreen" alt="Tests"></a>
|
| 45 |
-
<a href="#features"><img src="https://img.shields.io/badge/features-routing%20trace-teal" alt="Routing Trace"></a>
|
| 46 |
-
</p>
|
| 47 |
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
<img src="https://img.shields.io/badge/π€%20Best%20Agent-multi--step%20tools-blueviolet" alt="Best Agent">
|
| 51 |
-
<img src="https://img.shields.io/badge/NVIDIA-Nemotron%203%20Nano-76b900" alt="Nemotron">
|
| 52 |
-
<img src="https://img.shields.io/badge/OpenBMB-MiniCPM%20multi--model-1f6feb" alt="OpenBMB">
|
| 53 |
</p>
|
| 54 |
|
| 55 |
-
|
| 56 |
-
>
|
| 57 |
-
> πΊ **Demo video:** <a href="https://huggingface.co/spaces/build-small-hackathon/HearthNet/resolve/main/hf_hackathon_screenrecording_v1.webm">HF Space Recording</a> Β· <a href="https://videos.simpleshow.com/8vSfxilim8">Simple Show Demo</a>
|
| 58 |
|
|
|
|
| 59 |
|
| 60 |
-
|
| 61 |
-
<source src="https://huggingface.co/spaces/build-small-hackathon/HearthNet/resolve/main/hf_hackathon_screenrecording_v1.webm" type="video/webm">
|
| 62 |
-
Your browser does not support the video tag.
|
| 63 |
-
</video>
|
| 64 |
|
| 65 |
-
|
| 66 |
-
[Post on blogger](https://ckaller.blogspot.com/2026/06/hearthnet-building-ai-that-works-when.html)
|
| 67 |
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
|
|
|
|
|
|
|
|
|
| 74 |
|
| 75 |
---
|
| 76 |
|
| 77 |
-
#
|
| 78 |
|
| 79 |
-
|
| 80 |
|
| 81 |
-
|
| 82 |
|
| 83 |
-
|
| 84 |
-
in a local AI mesh. Nodes find each other automatically over Wi-Fi, share capabilities through an
|
| 85 |
-
intelligent routing bus, and work **completely offline**. When the internet is available, nodes **automatically route requests to the best provider** β whether local, nearby on LAN, or across the internet via relay. You see exactly which node answered.
|
| 86 |
|
| 87 |
-
|
| 88 |
-
- **Offline-first**: all features work without internet; internet is optional for mesh expansion
|
| 89 |
-
- **Transparent routing**: every Ask/Chat/RAG request shows which node served it (local or remote)
|
| 90 |
-
- Ask questions, share knowledge, send messages, coordinate emergency response β all offline
|
| 91 |
-
- No cloud account, no API key, no monthly bill β hardware you already own
|
| 92 |
|
| 93 |
-
|
| 94 |
|
| 95 |
-
|
| 96 |
|
| 97 |
-
|
| 98 |
-
Flip the **Agent mode** toggle in the Ask tab and the model stops being a chatbot and starts being an **agent**: it plans, calls real mesh tools over several steps, reads the results, and only then answers. Every step is shown live β **Thought β Tool β Observation β Answer**.
|
| 99 |
|
| 100 |
-
|
| 101 |
-
`search_corpus` (RAG), `list_corpora`, `translate`, `list_marketplace`, `route_expert` (MoE), and `identify_plant` (vision). The loop uses a JSON `action:` protocol that works even on **tiny models with no native function-calling** β so a 4B MiniCPM or Nemotron Mini can drive the same agent as a 49B reasoner. There is also a **fully in-browser WebLLM agent** (WebGPU, zero server) for true offline tool use.
|
| 102 |
|
| 103 |
-
|
| 104 |
|
| 105 |
-
|
| 106 |
-
When you ask a question, the bus scores available LLM nodes by latency, load, and reliability. Your request goes to the **best node right now** β whether it's local, your neighbour's device, or a peer across the internet. Failover is automatic: if the preferred node can't help, the next-best provider takes over **invisibly**.
|
| 107 |
|
| 108 |
-
|
| 109 |
-
- π **Local**: Answered by this device
|
| 110 |
-
- π **Remote (node-id)**: Routed to a peer node (LAN or internet)
|
| 111 |
-
- β **Error**: No suitable provider found
|
| 112 |
|
| 113 |
-
|
| 114 |
-
Direct 1:1 messages work completely offline on your Wi-Fi. Connect to the internet (via relay hub on HF Space) and chat with anyone in the mesh, regardless of network. No accounts, no passwordsβjust show them your QR code.
|
| 115 |
|
| 116 |
-
|
| 117 |
-
Share a corpus of documents with your community. Any node can search across **all available corpora** automatically, with results ranked by relevance. Works offline on local copies; syncs and queries remote corpora when internet is available.
|
| 118 |
|
| 119 |
-
|
| 120 |
-
Nodes advertise their specialisations. Queries automatically route to the best experts in your mesh for better answers.
|
| 121 |
|
| 122 |
-
|
| 123 |
-
When connectivity drops, the UI automatically switches to degraded mode. Nodes keep working offline. When restored, changes sync. Perfect for neighbourhood coordination during outages.
|
| 124 |
|
| 125 |
-
|
| 126 |
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
<table>
|
| 130 |
-
<tr>
|
| 131 |
-
<td><strong>Ask the Mesh</strong><br><img src="https://huggingface.co/spaces/build-small-hackathon/HearthNet/resolve/main/docs/screenshots/02-alice-ask-response.png" alt="LLM routes query to best node" width="380"></td>
|
| 132 |
-
<td><strong>Live Peer Topology</strong><br><img src="https://huggingface.co/spaces/build-small-hackathon/HearthNet/resolve/main/docs/screenshots/08c-alice-mesh-live.png" alt="SVG peer graph" width="380"></td>
|
| 133 |
-
</tr>
|
| 134 |
-
<tr>
|
| 135 |
-
<td><strong>Routing Trace</strong><br><img src="https://huggingface.co/spaces/build-small-hackathon/HearthNet/resolve/main/docs/screenshots/01-hf-space-live.png" alt="Shows which node answered" width="380"></td>
|
| 136 |
-
<td><strong>Community Marketplace</strong><br><img src="https://huggingface.co/spaces/build-small-hackathon/HearthNet/resolve/main/docs/screenshots/04-alice-marketplace.png" alt="Post and browse offers" width="380"></td>
|
| 137 |
-
</tr>
|
| 138 |
-
<tr>
|
| 139 |
-
<td><strong>Direct Messages</strong><br><img src="https://huggingface.co/spaces/build-small-hackathon/HearthNet/resolve/main/docs/screenshots/03-alice-chat.png" alt="Delivery confirmation" width="380"></td>
|
| 140 |
-
<td><strong>Invite QR Code</strong><br><img src="https://huggingface.co/spaces/build-small-hackathon/HearthNet/resolve/main/docs/screenshots/07-alice-settings.png" alt="Join mesh via QR" width="380"></td>
|
| 141 |
-
</tr>
|
| 142 |
-
<tr>
|
| 143 |
-
<td><strong>Emergency Mode</strong><br><img src="https://huggingface.co/spaces/build-small-hackathon/HearthNet/resolve/main/docs/screenshots/06-alice-emergency.png" alt="Connectivity indicator" width="380"></td>
|
| 144 |
-
<td><strong>All 8 Tabs</strong><br><img src="https://huggingface.co/spaces/build-small-hackathon/HearthNet/resolve/main/docs/screenshots/08-alice-settings-peers.png" alt="All tabs" width="380"></td>
|
| 145 |
-
</tr>
|
| 146 |
-
</table>
|
| 147 |
|
| 148 |
-
|
| 149 |
|
| 150 |
-
|
| 151 |
|
| 152 |
-
|
| 153 |
|
| 154 |
-
|
| 155 |
-
|----------|----------|--------|------|-------|
|
| 156 |
-
| **Android (PWA)** | [Web App](https://huggingface.co/spaces/build-small-hackathon/HearthNet) | Web | ~5MB | Install from browser - no download needed |
|
| 157 |
-
| **Android (Native)** | [app-debug.apk](https://huggingface.co/spaces/build-small-hackathon/HearthNet/resolve/main/build/android/HearthNetApp/platforms/android/app/build/outputs/apk/debug/app-debug.apk) | APK | 3.56MB | Native Android app via USB or direct install |
|
| 158 |
-
| **Windows Desktop** | [HearthNet.exe](https://huggingface.co/spaces/build-small-hackathon/HearthNet/resolve/main/dist/HearthNet.exe) | EXE | 212MB | Standalone executable - download & run |
|
| 159 |
-
| **Linux Desktop** | `python build/quickstart.py linux` | AppImage | ~120MB | Build on Linux or use script |
|
| 160 |
-
| **macOS Desktop** | `python build/quickstart.py macos` | .app | ~200MB | Native macOS app bundle |
|
| 161 |
-
| **Python (Any OS)** | [Source](https://github.com/ckal/HearthNet) | Python | - | `python app.py` - full mesh node |
|
| 162 |
-
| **Docker** | [Dockerfile](Dockerfile) | Container | 2GB | `docker run -p 7860:7860 hearthnet:latest` |
|
| 163 |
-
| **Guides & Docs** | [BUILD_GUIDE.md](docs/guides/BUILD_GUIDE.md) | Markdown | - | How to build for each platform |
|
| 164 |
|
| 165 |
-
|
| 166 |
-
- π **Fastest** (5 min): PWA Web App - instant, no install
|
| 167 |
-
- π» **Desktop** (3 min): Download EXE/AppImage and run
|
| 168 |
-
- π³ **Server**: Docker container deployment
|
| 169 |
-
- π See [BUILD_GUIDE.md](docs/guides/BUILD_GUIDE.md) for detailed instructions
|
| 170 |
|
| 171 |
---
|
| 172 |
|
| 173 |
-
#
|
| 174 |
|
| 175 |
-
|
| 176 |
-
# Clone and install
|
| 177 |
-
git clone https://huggingface.co/spaces/build-small-hackathon/HearthNet
|
| 178 |
-
cd HearthNet
|
| 179 |
-
pip install -e ".[dev]"
|
| 180 |
|
| 181 |
-
|
| 182 |
-
python app.py # open http://127.0.0.1:7860
|
| 183 |
-
```
|
| 184 |
|
| 185 |
-
|
| 186 |
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
|
| 191 |
-
|
| 192 |
-
./llama-server -m Meta-Llama-3.1-8B-Instruct-Q4_K_M.gguf -p 8080
|
| 193 |
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
|
| 198 |
-
|
| 199 |
-
- β‘ Fast inference on CPU (no GPU required)
|
| 200 |
-
- πΎ Runs the best models offline (8B params fits on 16GB RAM)
|
| 201 |
-
- π§ GGUF format is efficient and portable
|
| 202 |
-
- π No API key, no cloud, no latency
|
| 203 |
|
| 204 |
-
|
| 205 |
|
| 206 |
-
|
| 207 |
-
ollama pull llama3.2:3b # any Ollama model works
|
| 208 |
-
python app.py # auto-detects Ollama
|
| 209 |
-
```
|
| 210 |
|
| 211 |
-
|
| 212 |
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
|
|
|
|
|
|
| 216 |
|
| 217 |
-
|
| 218 |
-
# Windows: ipconfig | grep IPv4
|
| 219 |
-
# Mac/Linux: ifconfig | grep "inet " | grep -v 127
|
| 220 |
|
| 221 |
-
|
| 222 |
-
# http://<YOUR_IP>:7860
|
| 223 |
|
| 224 |
-
#
|
| 225 |
-
```
|
| 226 |
|
| 227 |
-
|
| 228 |
-
- β
PWA (instant, no build)
|
| 229 |
-
- π§ Native APK (optional, advanced)
|
| 230 |
|
| 231 |
-
|
| 232 |
|
| 233 |
-
|
| 234 |
-
# Get an invite code from the Space Settings tab
|
| 235 |
-
# Then redeem it locally:
|
| 236 |
-
python -m hearthnet.cli invite redeem \
|
| 237 |
-
"hnvite://v1/hf-space-1c95381d?host=build-small-hackathon-hearthnet.hf.space&port=443&transport=https&level=member"
|
| 238 |
|
| 239 |
-
|
| 240 |
-
```
|
| 241 |
|
| 242 |
---
|
| 243 |
|
| 244 |
-
##
|
| 245 |
-
|
| 246 |
-
### Capability Bus
|
| 247 |
|
| 248 |
-
|
| 249 |
-
the bus routes to the best available provider automatically:
|
| 250 |
|
| 251 |
-
```
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
"input": {"messages": [{"role": "user", "content": "What plants grow near water?"}]}
|
| 255 |
-
})
|
| 256 |
|
| 257 |
-
|
| 258 |
-
result = await bus.call("rag.query", (1, 0), {
|
| 259 |
-
"params": {"corpus": "community"},
|
| 260 |
-
"input": {"query": "emergency water purification", "k": 3}
|
| 261 |
-
})
|
| 262 |
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
python -m hearthnet.cli capabilities # list all available capabilities across mesh
|
| 266 |
```
|
| 267 |
|
| 268 |
-
|
| 269 |
|
| 270 |
-
|
| 271 |
-
# Device 1 β already running
|
| 272 |
-
python app.py
|
| 273 |
|
| 274 |
-
#
|
| 275 |
-
python app.py
|
| 276 |
-
# Both nodes see each other in ~5 seconds (mDNS + UDP broadcast)
|
| 277 |
-
# No IP addresses, no router config, no firewall rules
|
| 278 |
-
```
|
| 279 |
|
| 280 |
-
|
| 281 |
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
4. **Tracing**: Result includes `_routed_via` showing which node served it
|
| 287 |
|
| 288 |
-
|
| 289 |
-
# Node A has no LLM backend (would normally fail)
|
| 290 |
-
# Node B has llama.cpp running
|
| 291 |
-
# You ask Node A a question β Node A routes to Node B β B answers β A shows you the result
|
| 292 |
-
# Tracing shows: "_routed_via": "node-b-id"
|
| 293 |
|
| 294 |
-
|
| 295 |
-
# result includes "_routed_via": "node-b-id" β Shows the true origin
|
| 296 |
-
```
|
| 297 |
|
| 298 |
-
##
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
|
| 302 |
-
```python
|
| 303 |
-
# A medical Raspberry Pi registers itself:
|
| 304 |
-
await bus.call("moe.register", (1, 0), {
|
| 305 |
-
"input": {
|
| 306 |
-
"expert_id": "model:medical-pi",
|
| 307 |
-
"topic_tags": ["first_aid", "medication", "triage"],
|
| 308 |
-
"confidence_score": 0.90,
|
| 309 |
-
}
|
| 310 |
-
})
|
| 311 |
-
|
| 312 |
-
# Any node's medical query now routes there:
|
| 313 |
-
result = await bus.call("moe.route", (1, 0), {
|
| 314 |
-
"input": {"query": "emergency first aid for burns", "top_k": 3}
|
| 315 |
-
})
|
| 316 |
-
# β {"candidates": [{"expert_id": "model:medical-pi", "score": 0.94}]}
|
| 317 |
-
```
|
| 318 |
|
| 319 |
-
|
| 320 |
|
| 321 |
-
|
| 322 |
|
| 323 |
-
|
| 324 |
-
|
|
|
|
|
|
|
|
|
|
| 325 |
|
| 326 |
-
|
| 327 |
-
|
| 328 |
-
|
| 329 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 330 |
```
|
| 331 |
|
| 332 |
---
|
| 333 |
|
| 334 |
-
#
|
| 335 |
|
| 336 |
-
|
| 337 |
|
| 338 |
-
|
| 339 |
-
The architecture is model-agnostic; the routing layer handles the rest.
|
| 340 |
|
| 341 |
-
*
|
| 342 |
-
|
| 343 |
-
|
| 344 |
-
|
|
|
|
| 345 |
|
| 346 |
-
|
| 347 |
-
A full mesh of 10 Raspberry Pi 4 nodes (4 GB RAM each) can run:
|
| 348 |
-
- 135M model locally per node (always available, zero latency)
|
| 349 |
-
- Load-balanced routing for larger models across the mesh
|
| 350 |
-
- Full offline capability: discovery, RAG, chat, marketplace β no internet needed
|
| 351 |
|
| 352 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 353 |
|
| 354 |
-
|
|
|
|
|
|
|
|
|
|
| 355 |
|
| 356 |
-
|
| 357 |
|
| 358 |
-
|
| 359 |
|
| 360 |
-
|
| 361 |
|
| 362 |
-
|
| 363 |
-
|---------|-----------|-------|
|
| 364 |
-
| **llama.cpp** (recommended) | Start server on port 8080 + auto-detect | Any GGUF model; fastest on CPU |
|
| 365 |
-
| **Ollama** | `ollama pull llama3.2:3b` + auto-detect | 70+ models, easy management |
|
| 366 |
-
| **HF Transformers** | Default on HF Space (no config needed) | MiniCPM3-4B (override with `MODEL_ID`) |
|
| 367 |
-
| **OpenBMB / MiniCPM** | `MINICPM_URL` env var (local server) | Local-first, OpenAI-compatible API |
|
| 368 |
|
| 369 |
-
|
| 370 |
|
| 371 |
-
|
| 372 |
-
|---------|-----------|-------|
|
| 373 |
-
| **NVIDIA Nemotron** | `NVIDIA_API_KEY` env var | For RTX nodes: nemotron-70b/mini-4b |
|
| 374 |
-
| **Modal** | `MODAL_ENDPOINT` env var | Serverless GPU inference |
|
| 375 |
-
| **OpenAI API** | `OPENAI_API_KEY` env var | Fallback only; not recommended for offline mesh |
|
| 376 |
|
| 377 |
-
|
| 378 |
-
1. **Local first**: llama.cpp, Ollama, HF Transformers always preferred
|
| 379 |
-
2. **Load & latency**: If you have multiple local nodes, asks route to the least-busy one
|
| 380 |
-
3. **Failover**: If local is unavailable and you have internet, remote nodes or cloud backends are tried
|
| 381 |
|
| 382 |
-
|
| 383 |
|
| 384 |
---
|
| 385 |
|
| 386 |
-
#
|
| 387 |
|
| 388 |
-
|
| 389 |
-
|
| 390 |
-
|
| 391 |
-
- **localhost-only CLI** β all admin HTTP restricted to 127.0.0.1
|
| 392 |
-
- **Capability token `exp` claim** β checked in `bus.handle_call()` before routing; expired tokens receive `{"error": "token_expired"}` without hitting any handler
|
| 393 |
-
- **Token signature verification** β Ed25519 signature checking is implemented in `AuthService` (`auth.token.verify`) and is available on the bus. The HTTP transport (`/bus/v1/call`) currently passes tokens to `handle_call()` where expiry is enforced; full per-request signature verification on inbound HTTP calls is a planned hardening step.
|
| 394 |
-
- **Bandit HIGH findings: 0** (verified in CI)
|
| 395 |
|
| 396 |
---
|
| 397 |
|
| 398 |
-
##
|
| 399 |
|
| 400 |
-
|
| 401 |
-
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 402 |
-
β Gradio UI (8 tabs) β
|
| 403 |
-
β Ask Β· Chat Β· Mesh Β· Marketplace Β· Files Β· Emergency Β· β
|
| 404 |
-
β Settings Β· Getting Started β
|
| 405 |
-
βββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββ
|
| 406 |
-
β
|
| 407 |
-
ββββββββββββββΌβββββββββββββ
|
| 408 |
-
β Capability Bus (M03) β
|
| 409 |
-
β route Β· score Β· trace β
|
| 410 |
-
ββββββ¬βββββββ¬βββββββ¬βββββββ
|
| 411 |
-
β β β
|
| 412 |
-
ββββββββββββΌβ ββββΌββββ ββΌβββββββββββ ββββββββββββββ
|
| 413 |
-
β LLM (M04) β β RAG β β MoE (M27) β β Chat (M10) β
|
| 414 |
-
βllama.cpp β β(M05) β β Expert β β Marketplaceβ
|
| 415 |
-
β Ollama β βSQLiteβ β Registry β β (M06) Filesβ
|
| 416 |
-
βHF Transfm β βEmbed β βββββββββββββ ββββββββββββββ
|
| 417 |
-
βββββββ¬ββββββ ββββ¬ββββ
|
| 418 |
-
βββββββ¬βββββββ
|
| 419 |
-
ββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββ
|
| 420 |
-
β Transport (X01) Β· Discovery (M02 mDNS/UDP) β
|
| 421 |
-
β Events (X02 SQLite/Lamport) Β· E2E Encrypt (M23) β
|
| 422 |
-
β Identity (M01 Ed25519) Β· Observability (X03) β
|
| 423 |
-
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 424 |
-
```
|
| 425 |
|
| 426 |
---
|
| 427 |
|
| 428 |
-
##
|
| 429 |
-
|
| 430 |
-
|
| 431 |
-
<summary><strong>Phase 1 β Core (M01βM13, X01βX04) Β· 17 modules</strong></summary>
|
| 432 |
-
|
| 433 |
-
| Module | Description | Status |
|
| 434 |
-
|--------|-------------|--------|
|
| 435 |
-
| M01 | Node identity (Ed25519, manifests, canonical JSON) | β
|
|
| 436 |
-
| M02 | Peer discovery (mDNS, UDP broadcast, PeerRegistry) | β
|
|
| 437 |
-
| M03 | Capability bus (schema validation, routing, tracing) | β
|
|
| 438 |
-
| M04 | LLM service (llama.cpp, Ollama, HF Transformers, cloud fallback) | β
|
|
| 439 |
-
| M05 | RAG (chunker, SQLite/ChromaDB vector store, IngestPipeline, federated scatter-gather) | β
|
|
| 440 |
-
| M06 | Marketplace (event-sourced, Lamport-clocked posts) | β
|
|
| 441 |
-
| M07 | File blobs (BLAKE3 hash, content-addressed, chunked transfer) | β
|
|
| 442 |
-
| M08 | Gradio UI (8 tabs: Ask, Chat, Mesh, Marketplace, Files, Emergency, Settings, Getting Started) | β
|
|
| 443 |
-
| M09 | Emergency mode (async connectivity probe, auto-degrade on offline) | β
|
|
| 444 |
-
| M10 | Chat (event-backed 1:1 direct messaging, Lamport delivery order) | β
|
|
| 445 |
-
| M11 | Embeddings (embed.text, SentenceTransformer `bge-small-en-v1.5`, SimpleHashBackend fallback, batch support) | β
|
|
| 446 |
-
| M12 | CLI (click, ask / peers / marketplace / call / capabilities) | β
|
|
| 447 |
-
| M13 | Onboarding (invite QR, hnvite:// deep links, PyNaCl signing) | β
|
|
| 448 |
-
| X01 | Transport (FastAPI server, 12 REST endpoints, TLS) | β
|
|
| 449 |
-
| X02 | Events (SQLite, Lamport clocks, ReplayEngine, snapshots) | β
|
|
| 450 |
-
| X03 | Observability (structured JSON logging, metrics, distributed tracing) | β
|
|
| 451 |
-
| X04 | Config (typed frozen dataclasses, TOML, env overlay) | β
|
|
| 452 |
-
|
| 453 |
-
</details>
|
| 454 |
-
|
| 455 |
-
<details>
|
| 456 |
-
<summary><strong>Phase 2 β Advanced (M14βM25, X05βX07) Β· 18 modules</strong></summary>
|
| 457 |
-
|
| 458 |
-
| Module | Description | Status |
|
| 459 |
-
|--------|-------------|--------|
|
| 460 |
-
| M14 | Federation (bilateral cross-community trust, manifest signing) | β
|
|
| 461 |
-
| M15 | Relay tier (NAT traversal, keepalive, push token registry) | β
|
|
| 462 |
-
| M16 | Capability tokens (Ed25519 JWS-style hntoken://v1/ format) | β
|
|
| 463 |
-
| M17 | OCR (Tesseract + TrOCR backends, graceful degradation) | β
|
|
| 464 |
-
| M18 | Translation (NLLB backend, LRU cache, 4000-char limit) | β
|
|
| 465 |
-
| M19 | STT/TTS (Whisper local STT, Edge TTS synthesis) | β
|
|
| 466 |
-
| M20 | Vision (Florence-2 image describe, structured output) | β
|
|
| 467 |
-
| M21 | Tool calls (LLM mid-generation bus dispatch, ToolExecutor, plant_identify) | β
|
|
| 468 |
-
| M22 | Mobile native (Flutter contract, hnapp:// invites, push authority) | β
|
|
| 469 |
-
| M23 | E2E encryption (X3DH key agreement, Double Ratchet, AEAD envelope) | β
|
|
| 470 |
-
| M24 | Reranking (BGE + CrossEncoder backends, 100-doc limit) | β
|
|
| 471 |
-
| M25 | Group chat (ThreadService, ThreadViewStore, event-sourced threads) | β
|
|
| 472 |
-
| X05 | DHT (Kademlia node, 256-bucket routing table, bootstrap) | β
|
|
| 473 |
-
| X06 | WebSocket upgrade (bidirectional pubsub, WsClient) | β
|
|
| 474 |
-
| X07 | Federated metrics (NodeMetricsTick, MetricsAggregator, OTLP) | β
|
|
| 475 |
-
|
| 476 |
-
</details>
|
| 477 |
-
|
| 478 |
-
<details>
|
| 479 |
-
<summary><strong>Phase 3 β Experimental (M26βM31, X08βX09) Β· feature-flag gated</strong></summary>
|
| 480 |
-
|
| 481 |
-
| Module | Description | Status |
|
| 482 |
-
|--------|-------------|--------|
|
| 483 |
-
| M26 | Distributed inference (ShardDescriptor, PipelineOrchestrator, model.pull) | registered |
|
| 484 |
-
| M27 | MoE routing (ExpertRegistry, MoeRouter, moe.route/register/list) | registered |
|
| 485 |
-
| M28 | Federated learning (FedLearnCoordinator, RoundManifest, gradient aggregation) | experimental |
|
| 486 |
-
| M29 | LoRa beacons (32-byte frames, 868 MHz offline signaling) | experimental |
|
| 487 |
-
| M30 | Evidence graph / EBKH (ClaimStore, attestations, disputes) | experimental |
|
| 488 |
-
| M31 | Civil defense NRW (AuditChain, role certs, structured alerts) | experimental |
|
| 489 |
-
| X08 | Tensor transport (chunked binary tensor streaming) | experimental |
|
| 490 |
-
| X09 | Conformance suite (protocol test harness) | experimental |
|
| 491 |
-
|
| 492 |
-
</details>
|
| 493 |
|
| 494 |
---
|
| 495 |
|
| 496 |
-
##
|
|
|
|
|
|
|
| 497 |
|
| 498 |
-
|
| 499 |
|
| 500 |
-
|
| 501 |
|
| 502 |
-
|
| 503 |
-
|-------|-------|----------|
|
| 504 |
-
| **Phase 1 Core** (M01-M13, X01-X04) | 120+ | Bus routing, discovery, identity, emergency mode |
|
| 505 |
-
| **Intelligent Routing** (NEW) | 8+ | Failover, latency scoring, tracing, stamping |
|
| 506 |
-
| **Chat & Messaging** (M10) | 35+ | Direct messages, cross-node delivery, event-sourced |
|
| 507 |
-
| **RAG & Search** (M05) | 25+ | Local corpus, semantic search, federated queries |
|
| 508 |
-
| **LLM Service** (M04) | 20+ | Multiple backends (llama.cpp, Ollama, HF), model selection |
|
| 509 |
-
| **Integration** | 40+ | Real services wired together, marketplace, file blobs |
|
| 510 |
-
| **UI & E2E** | 20+ | All 8 tabs, Gradio API, user workflows |
|
| 511 |
-
| **Phase 2/3 Advanced** | 70+ | Federation, crypto, DHT, MoE, group chat |
|
| 512 |
-
| **Total** | **390+** | Python 3.13 Β· pytest-asyncio Β· Full async test suite |
|
| 513 |
|
| 514 |
-
|
| 515 |
|
| 516 |
-
|
| 517 |
-
# Full suite
|
| 518 |
-
python -m pytest tests/ -v
|
| 519 |
|
| 520 |
-
#
|
| 521 |
-
python -m pytest tests/test_bus_failover.py -v
|
| 522 |
|
| 523 |
-
|
| 524 |
-
|
|
|
|
|
|
|
| 525 |
|
| 526 |
-
#
|
| 527 |
-
python -m pytest tests/ --ignore=tests/test_e2e_user_stories.py -v
|
| 528 |
-
```
|
| 529 |
|
| 530 |
-
*
|
|
|
|
|
|
|
| 531 |
|
| 532 |
-
|
| 533 |
-
- β
Well-covered: Bus routing, identity, chat, discovery, emergency mode
|
| 534 |
-
- π― Strong: LLM service, RAG pipeline, marketplace, event system
|
| 535 |
-
- π Expanding: Transport layer, UI advanced features, observability metrics
|
| 536 |
|
| 537 |
-
|
|
|
|
|
|
|
| 538 |
|
| 539 |
-
##
|
| 540 |
|
| 541 |
-
|
| 542 |
-
|
| 543 |
-
| **HF Space** (Primary) | [https://huggingface.co/spaces/build-small-hackathon/HearthNet](https://huggingface.co/spaces/build-small-hackathon/HearthNet) | Live demo + Downloads |
|
| 544 |
-
| **GitHub** (Mirror/CI) | [https://github.com/ckal/HearthNet](https://github.com/ckal/HearthNet) | Source control + Issue tracking |
|
| 545 |
|
| 546 |
-
|
| 547 |
-
- π‘ **HF Space**: Live demo, PWA app, binary downloads (exe, apk, etc.)
|
| 548 |
-
- π **GitHub**: Source repository, CI/CD, releases, issue tracking
|
| 549 |
-
- π **Sync**: Changes push to both simultaneously
|
| 550 |
|
| 551 |
-
*
|
| 552 |
-
- Windows EXE: [dist/HearthNet.exe](https://huggingface.co/spaces/build-small-hackathon/HearthNet/resolve/main/dist/HearthNet.exe) (212 MB)
|
| 553 |
-
- Android APK: [build/android/.../app-debug.apk](https://huggingface.co/spaces/build-small-hackathon/HearthNet/resolve/main/build/android/HearthNetApp/platforms/android/app/build/outputs/apk/debug/app-debug.apk) (3.56 MB)
|
| 554 |
-
- Build scripts: [BUILD_GUIDE.md](docs/guides/BUILD_GUIDE.md) for EXE, AppImage, .app, Docker
|
| 555 |
|
| 556 |
---
|
| 557 |
|
| 558 |
-
#
|
| 559 |
|
| 560 |
-
|
| 561 |
-
|----------|------|
|
| 562 |
-
| Architecture | [ARCHITECTURE.md](docs/ARCHITECTURE.md) |
|
| 563 |
-
| System overview | [docs/00-OVERVIEW.md](docs/00-OVERVIEW.md) |
|
| 564 |
-
| Capability contract | [docs/CAPABILITY_CONTRACT.md](docs/CAPABILITY_CONTRACT.md) |
|
| 565 |
-
| Roadmap | [docs/roadmap.md](docs/roadmap.md) |
|
| 566 |
-
| Task tracker | [tasks.md](tasks.md) |
|
| 567 |
-
| Phase 2+3 specs | [docs/p2_p3/](docs/p2_p3/) |
|
| 568 |
|
| 569 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 570 |
|
| 571 |
-
|
| 572 |
|
| 573 |
-
|
| 574 |
|
| 575 |
-
|
|
|
|
|
|
|
| 576 |
|
| 577 |
-
|
| 578 |
|
| 579 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 580 |
|
| 581 |
-
|
| 582 |
-
- NVIDIA Nemotron: Document intelligence for RAG (`NVIDIA_API_KEY` env var)
|
| 583 |
-
- OpenBMB MiniCPM: Local-first models via `MINICPM_URL` (llama.cpp-compatible)
|
| 584 |
-
- Modal: Serverless GPU as remote node (`MODAL_ENDPOINT` env var)
|
| 585 |
|
| 586 |
---
|
| 587 |
|
| 588 |
-
#
|
| 589 |
|
| 590 |
-
|
| 591 |
-
|
| 592 |
-
|
| 593 |
-
| π GitHub (Source) | https://github.com/ckal/HearthNet |
|
| 594 |
-
| π Architecture | [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) |
|
| 595 |
-
| π§ͺ Tests | `python -m pytest tests/ -v` |
|
| 596 |
|
| 597 |
---
|
| 598 |
|
| 599 |
<p align="center">
|
| 600 |
-
Built with open source
|
| 601 |
-
<em>Small model. Big mesh. Real resilience.</em>
|
| 602 |
</p>
|
|
|
|
| 1 |
---
|
| 2 |
title: HearthNet
|
| 3 |
+
emoji: π’
|
| 4 |
colorFrom: purple
|
| 5 |
colorTo: pink
|
| 6 |
sdk: gradio
|
| 7 |
sdk_version: 6.16.0
|
| 8 |
+
python_version: '3.12'
|
| 9 |
app_file: app.py
|
| 10 |
pinned: true
|
| 11 |
+
short_description: Community-Owned AI That Works Even When The Internet Doesn't
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
---
|
| 13 |
|
| 14 |
+
# π₯ HearthNet
|
| 15 |
|
| 16 |
+
### Community-Owned AI That Works Even When The Internet Doesn't
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
|
| 18 |
<p align="center">
|
| 19 |
+
<img src="assets/banner.png" alt="HearthNet Banner" width="100%">
|
| 20 |
</p>
|
| 21 |
|
| 22 |
<p align="center">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
|
| 24 |
+
π Local-First AI β’ π€ Community-Powered β’ π‘οΈ Resilient by Design β’ β‘ Offline-Capable
|
| 25 |
+
|
|
|
|
|
|
|
|
|
|
| 26 |
</p>
|
| 27 |
|
| 28 |
+
---
|
|
|
|
|
|
|
| 29 |
|
| 30 |
+
## π¨ The Problem
|
| 31 |
|
| 32 |
+
Today's AI depends on centralized cloud infrastructure.
|
|
|
|
|
|
|
|
|
|
| 33 |
|
| 34 |
+
When the internet goes down, so does access to:
|
|
|
|
| 35 |
|
| 36 |
+
* π€ AI assistants
|
| 37 |
+
* π Knowledge bases
|
| 38 |
+
* π¬ Communication platforms
|
| 39 |
+
* π Marketplaces
|
| 40 |
+
* πΊοΈ Navigation and local information
|
| 41 |
+
|
| 42 |
+
Whether caused by outages, disasters, censorship, infrastructure failures, or simply poor connectivity, modern communities become digitally isolated almost instantly.
|
| 43 |
+
|
| 44 |
+
What if AI could continue working anyway?
|
| 45 |
|
| 46 |
---
|
| 47 |
|
| 48 |
+
# π₯ Introducing HearthNet
|
| 49 |
|
| 50 |
+
**HearthNet** transforms the computers already around you into a resilient local AI network.
|
| 51 |
|
| 52 |
+
Your gaming PC.
|
| 53 |
|
| 54 |
+
Your neighbor's old laptop.
|
|
|
|
|
|
|
| 55 |
|
| 56 |
+
A Raspberry Pi in a community center.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
|
| 58 |
+
An unused workstation in a local business.
|
| 59 |
|
| 60 |
+
Together, they become a shared AI cooperative.
|
| 61 |
|
| 62 |
+
No centralized server required.
|
|
|
|
| 63 |
|
| 64 |
+
No cloud dependency required.
|
|
|
|
| 65 |
|
| 66 |
+
No single point of failure.
|
| 67 |
|
| 68 |
+
---
|
|
|
|
| 69 |
|
| 70 |
+
## π¬ Imagine This Scenario
|
|
|
|
|
|
|
|
|
|
| 71 |
|
| 72 |
+
A storm damages the internet connection for an entire neighborhood.
|
|
|
|
| 73 |
|
| 74 |
+
Most online services become unavailable.
|
|
|
|
| 75 |
|
| 76 |
+
With HearthNet:
|
|
|
|
| 77 |
|
| 78 |
+
β
AI assistants still answer questions
|
|
|
|
| 79 |
|
| 80 |
+
β
Local document search continues working
|
| 81 |
|
| 82 |
+
β
Emergency information remains available
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
|
| 84 |
+
β
Community chat stays online
|
| 85 |
|
| 86 |
+
β
Local marketplaces continue operating
|
| 87 |
|
| 88 |
+
β
Knowledge remains accessible
|
| 89 |
|
| 90 |
+
The internet disappears.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 91 |
|
| 92 |
+
The community does not.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 93 |
|
| 94 |
---
|
| 95 |
|
| 96 |
+
# β‘ Key Features
|
| 97 |
|
| 98 |
+
## π€ Distributed AI Inference
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
|
| 100 |
+
Every node advertises its capabilities.
|
|
|
|
|
|
|
| 101 |
|
| 102 |
+
A gaming PC might offer:
|
| 103 |
|
| 104 |
+
* Large Language Models
|
| 105 |
+
* Vision Models
|
| 106 |
+
* Embeddings
|
| 107 |
|
| 108 |
+
A Raspberry Pi might offer:
|
|
|
|
| 109 |
|
| 110 |
+
* Message relaying
|
| 111 |
+
* Local storage
|
| 112 |
+
* Discovery services
|
| 113 |
|
| 114 |
+
Requests are automatically routed to the most suitable node.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 115 |
|
| 116 |
+
---
|
| 117 |
|
| 118 |
+
## π Local Knowledge (RAG)
|
|
|
|
|
|
|
|
|
|
| 119 |
|
| 120 |
+
HearthNet can host community knowledge libraries including:
|
| 121 |
|
| 122 |
+
* First aid manuals
|
| 123 |
+
* Emergency procedures
|
| 124 |
+
* Community resources
|
| 125 |
+
* Local guides
|
| 126 |
+
* Educational content
|
| 127 |
|
| 128 |
+
Even completely offline.
|
|
|
|
|
|
|
| 129 |
|
| 130 |
+
---
|
|
|
|
| 131 |
|
| 132 |
+
## π Automatic Discovery
|
|
|
|
| 133 |
|
| 134 |
+
No manual configuration.
|
|
|
|
|
|
|
| 135 |
|
| 136 |
+
No IP addresses.
|
| 137 |
|
| 138 |
+
No complex setup.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
|
| 140 |
+
Open HearthNet and nearby nodes automatically discover each other.
|
|
|
|
| 141 |
|
| 142 |
---
|
| 143 |
|
| 144 |
+
## π‘οΈ Internet-Outage Resilience
|
|
|
|
|
|
|
| 145 |
|
| 146 |
+
When the internet is available:
|
|
|
|
| 147 |
|
| 148 |
+
```text
|
| 149 |
+
Community β Internet β Cloud Services
|
| 150 |
+
```
|
|
|
|
|
|
|
| 151 |
|
| 152 |
+
When the internet disappears:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 153 |
|
| 154 |
+
```text
|
| 155 |
+
Community β Community β Community
|
|
|
|
| 156 |
```
|
| 157 |
|
| 158 |
+
HearthNet automatically switches to local-first operation.
|
| 159 |
|
| 160 |
+
---
|
|
|
|
|
|
|
| 161 |
|
| 162 |
+
## π¬ Local Community Communication
|
|
|
|
|
|
|
|
|
|
|
|
|
| 163 |
|
| 164 |
+
Communities can communicate directly through:
|
| 165 |
|
| 166 |
+
* Chat
|
| 167 |
+
* Announcements
|
| 168 |
+
* Local messaging
|
| 169 |
+
* Shared information boards
|
|
|
|
| 170 |
|
| 171 |
+
No external servers required.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 172 |
|
| 173 |
+
---
|
|
|
|
|
|
|
| 174 |
|
| 175 |
+
## π Community Marketplace
|
| 176 |
+
|
| 177 |
+
Buy.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 178 |
|
| 179 |
+
Sell.
|
| 180 |
|
| 181 |
+
Trade.
|
| 182 |
|
| 183 |
+
Share resources.
|
| 184 |
+
|
| 185 |
+
Even during connectivity disruptions.
|
| 186 |
+
|
| 187 |
+
---
|
| 188 |
|
| 189 |
+
# ποΈ Architecture
|
| 190 |
+
|
| 191 |
+
```text
|
| 192 |
+
βββββββββββββββββββββββββββββ
|
| 193 |
+
β Frontend β
|
| 194 |
+
βββββββββββββββ¬ββββββββββββββ
|
| 195 |
+
β
|
| 196 |
+
βΌ
|
| 197 |
+
βββββββββββββββββββββββββββββ
|
| 198 |
+
β Capability Bus β
|
| 199 |
+
βββββββββββββββ¬ββββββββββββββ
|
| 200 |
+
β
|
| 201 |
+
ββββββββββββΌβββββββββββ
|
| 202 |
+
βΌ βΌ βΌ
|
| 203 |
+
|
| 204 |
+
ββββββββββ ββββββββββ ββββββββββ
|
| 205 |
+
β LLM β β RAG β β Chat β
|
| 206 |
+
β Node β β Node β β Node β
|
| 207 |
+
ββββββββββ ββββββββββ ββββββββββ
|
| 208 |
+
|
| 209 |
+
β² β² β²
|
| 210 |
+
|
| 211 |
+
ββββββββββ ββββββββββ ββββββββββ
|
| 212 |
+
βLaptop Aβ βLaptop Bβ βPi Zero β
|
| 213 |
+
ββββββββββ ββββββββββ ββββββββββ
|
| 214 |
```
|
| 215 |
|
| 216 |
---
|
| 217 |
|
| 218 |
+
# π Hackathon Goals
|
| 219 |
|
| 220 |
+
Our hackathon MVP demonstrates:
|
| 221 |
|
| 222 |
+
### Phase 1
|
|
|
|
| 223 |
|
| 224 |
+
* [x] Node discovery
|
| 225 |
+
* [x] Capability registration
|
| 226 |
+
* [x] Local AI inference
|
| 227 |
+
* [x] Knowledge retrieval
|
| 228 |
+
* [x] Offline operation
|
| 229 |
|
| 230 |
+
### Phase 2
|
|
|
|
|
|
|
|
|
|
|
|
|
| 231 |
|
| 232 |
+
* [ ] Local marketplace
|
| 233 |
+
* [ ] Community messaging
|
| 234 |
+
* [ ] Health monitoring
|
| 235 |
+
* [ ] Distributed storage
|
| 236 |
+
|
| 237 |
+
### Phase 3
|
| 238 |
|
| 239 |
+
* [ ] Long-distance mesh networking
|
| 240 |
+
* [ ] Federated communities
|
| 241 |
+
* [ ] Distributed inference
|
| 242 |
+
* [ ] Federated learning
|
| 243 |
|
| 244 |
+
---
|
| 245 |
|
| 246 |
+
# π§ Why This Matters
|
| 247 |
|
| 248 |
+
AI is rapidly becoming essential infrastructure.
|
| 249 |
|
| 250 |
+
Today, that infrastructure is mostly controlled by a handful of organizations.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 251 |
|
| 252 |
+
HearthNet explores a different future:
|
| 253 |
|
| 254 |
+
A future where communities own and operate AI together.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 255 |
|
| 256 |
+
A future where knowledge remains available during outages.
|
|
|
|
|
|
|
|
|
|
| 257 |
|
| 258 |
+
A future where local resilience is built directly into our digital systems.
|
| 259 |
|
| 260 |
---
|
| 261 |
|
| 262 |
+
# π Potential Use Cases
|
| 263 |
|
| 264 |
+
## π Emergency Response
|
| 265 |
+
|
| 266 |
+
Access critical information when external services are unavailable.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 267 |
|
| 268 |
---
|
| 269 |
|
| 270 |
+
## π« Schools
|
| 271 |
|
| 272 |
+
Run local educational AI systems without constant cloud access.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 273 |
|
| 274 |
---
|
| 275 |
|
| 276 |
+
## πΎ Rural Communities
|
| 277 |
+
|
| 278 |
+
Provide AI services in areas with limited connectivity.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 279 |
|
| 280 |
---
|
| 281 |
|
| 282 |
+
## πͺ Events & Festivals
|
| 283 |
+
|
| 284 |
+
Create temporary local AI networks for attendees.
|
| 285 |
|
| 286 |
+
---
|
| 287 |
|
| 288 |
+
## ποΈ Neighborhoods
|
| 289 |
|
| 290 |
+
Community knowledge sharing and local services.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 291 |
|
| 292 |
+
---
|
| 293 |
|
| 294 |
+
# π οΈ Technology Stack
|
|
|
|
|
|
|
| 295 |
|
| 296 |
+
### AI
|
|
|
|
| 297 |
|
| 298 |
+
* llama.cpp
|
| 299 |
+
* Ollama
|
| 300 |
+
* Hugging Face Models
|
| 301 |
+
* Sentence Transformers
|
| 302 |
|
| 303 |
+
### Backend
|
|
|
|
|
|
|
| 304 |
|
| 305 |
+
* Python
|
| 306 |
+
* FastAPI
|
| 307 |
+
* WebSockets
|
| 308 |
|
| 309 |
+
### Knowledge Layer
|
|
|
|
|
|
|
|
|
|
| 310 |
|
| 311 |
+
* ChromaDB
|
| 312 |
+
* FAISS
|
| 313 |
+
* Local Embeddings
|
| 314 |
|
| 315 |
+
### Infrastructure
|
| 316 |
|
| 317 |
+
* Docker
|
| 318 |
+
* Docker Compose
|
|
|
|
|
|
|
| 319 |
|
| 320 |
+
### Frontend
|
|
|
|
|
|
|
|
|
|
| 321 |
|
| 322 |
+
* Gradio / Web UI
|
|
|
|
|
|
|
|
|
|
| 323 |
|
| 324 |
---
|
| 325 |
|
| 326 |
+
# π₯ Team
|
| 327 |
|
| 328 |
+
Built during the Hugging Face Hackathon β€οΈ
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 329 |
|
| 330 |
+
We believe that the future of AI should be:
|
| 331 |
+
|
| 332 |
+
β
Open
|
| 333 |
+
|
| 334 |
+
β
Local
|
| 335 |
|
| 336 |
+
β
Resilient
|
| 337 |
|
| 338 |
+
β
Community-Owned
|
| 339 |
|
| 340 |
+
---
|
| 341 |
+
|
| 342 |
+
# π€ Join Us
|
| 343 |
|
| 344 |
+
We're actively looking for:
|
| 345 |
|
| 346 |
+
* AI Engineers
|
| 347 |
+
* Distributed Systems Developers
|
| 348 |
+
* Frontend Developers
|
| 349 |
+
* DevOps Engineers
|
| 350 |
+
* Designers
|
| 351 |
+
* Community Builders
|
| 352 |
|
| 353 |
+
Ideas, feedback, contributions, and discussions are always welcome.
|
|
|
|
|
|
|
|
|
|
| 354 |
|
| 355 |
---
|
| 356 |
|
| 357 |
+
# π Vision
|
| 358 |
|
| 359 |
+
> "The cloud owns AI today.
|
| 360 |
+
>
|
| 361 |
+
> HearthNet is our bet that communities will own it tomorrow."
|
|
|
|
|
|
|
|
|
|
| 362 |
|
| 363 |
---
|
| 364 |
|
| 365 |
<p align="center">
|
| 366 |
+
Built with β€οΈ, β, open source, and a slightly unreasonable belief that neighborhoods should have their own AI.
|
|
|
|
| 367 |
</p>
|
agents.md
DELETED
|
@@ -1,45 +0,0 @@
|
|
| 1 |
-
# HearthNet Agent Coordination
|
| 2 |
-
|
| 3 |
-
## Active Roles
|
| 4 |
-
|
| 5 |
-
| Agent | Role | Ownership |
|
| 6 |
-
| --- | --- | --- |
|
| 7 |
-
| Codex lead | Integration, docs, final verification, deployment | Whole repo coordination |
|
| 8 |
-
| Planck | Phase 1 doc synthesis | Read-only docs review |
|
| 9 |
-
| Avicenna | Prototype assessment | Read-only HTML/README review |
|
| 10 |
-
| Kepler | HF Space and quality checklist | Read-only deployment/tooling review |
|
| 11 |
-
| Mill | Python Phase 1 core | `hearthnet/`, `tests/` |
|
| 12 |
-
| Anscombe | Gradio Space UI | `app.py`, optional `static/` |
|
| 13 |
-
| Hypatia | Quality/tool config | `pyproject.toml`, requirements, config files |
|
| 14 |
-
| Darwin | M01-M07 + contract/glossary audit | Read-only coverage review |
|
| 15 |
-
| Gibbs | M08-M13 audit | Read-only coverage review |
|
| 16 |
-
| Franklin | X01-X04 + overview/PRD audit | Read-only coverage review |
|
| 17 |
-
| Mendel | No-mock local-first model architecture audit | Read-only implementation review |
|
| 18 |
-
| Pascal | Gradio/server/UI audit | Read-only UI/runtime review |
|
| 19 |
-
| Erdos | Coordination docs policy audit | Read-only tasks/agents review |
|
| 20 |
-
|
| 21 |
-
## Collaboration Rules
|
| 22 |
-
|
| 23 |
-
- Workers have disjoint write scopes.
|
| 24 |
-
- No worker may revert another worker's edits.
|
| 25 |
-
- Services do not import each other directly; they communicate through the bus.
|
| 26 |
-
- UI talks to the controller/facades, not directly to services.
|
| 27 |
-
- No mocks or fake AI paths in implementation-facing code. Phase 1 may keep clearly labeled prototype/demo surfaces, but shipped services must use real local-first components or explicit unavailable/degraded states.
|
| 28 |
-
- Local AI must be local-first: prefer Ollama, llama.cpp, or local Hugging Face Transformers backends. OpenAI may be used only as an opt-in online fallback when local models are unavailable or explicitly disabled.
|
| 29 |
-
- Do not add security-tool suppression pragmas, broad ignores, or Bandit/Ruff/Pylint bypasses to pass checks. Fix the finding or document a narrow, reviewed exception in `tasks.md`.
|
| 30 |
-
- Quality gates must be run before deployment.
|
| 31 |
-
- UI must follow the spec architecture: UI talks through controller/facades/bus snapshots only, with no direct service imports.
|
| 32 |
-
- Spec adherence is a quality gate: changes must map to the relevant M/X docs, capability contract, and glossary terms.
|
| 33 |
-
|
| 34 |
-
## Integration Checklist
|
| 35 |
-
|
| 36 |
-
- [ ] Merge worker changes.
|
| 37 |
-
- [ ] Resolve conflicts without losing existing prototypes.
|
| 38 |
-
- [ ] Update `tasks.md` statuses.
|
| 39 |
-
- [ ] Verify no mocks, fake model responses, or unlabeled simulations remain in implementation paths.
|
| 40 |
-
- [ ] Verify local-first model backends are real and OpenAI is only an opt-in online fallback.
|
| 41 |
-
- [ ] Verify no new security pragmas, blanket ignores, or quality bypasses were introduced.
|
| 42 |
-
- [ ] Verify UI behavior and wording do not overclaim missing spec features.
|
| 43 |
-
- [ ] Verify implemented behavior is traceable to M01-M13, X01-X04, `CAPABILITY_CONTRACT.md`, and `GLOSSARY.md`.
|
| 44 |
-
- [ ] Run all requested checks.
|
| 45 |
-
- [ ] Commit and push to the Hugging Face Space.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app.py
CHANGED
|
@@ -1,681 +0,0 @@
|
|
| 1 |
-
"""HearthNet β Hugging Face Space entry point.
|
| 2 |
-
|
| 3 |
-
This Space runs a **real** HearthNet node using HuggingFace Transformers as the
|
| 4 |
-
LLM backend. All 8 tabs are live:
|
| 5 |
-
|
| 6 |
-
Ask β LLM + RAG queries routed via capability bus
|
| 7 |
-
Chat β Event-sourced direct messages between nodes
|
| 8 |
-
Mesh β Live topology graph of discovered peers
|
| 9 |
-
Marketplace β Community offers / requests / emergency posts
|
| 10 |
-
Files β BLAKE3 content-addressed blob store
|
| 11 |
-
Emergency β Offline-mode probe and connectivity status
|
| 12 |
-
Settings β Node identity, peer list, QR invite, RAG ingest
|
| 13 |
-
|
| 14 |
-
Difference between this Space and a local install
|
| 15 |
-
ββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 16 |
-
HF Space β single node, no real peer mesh, MiniCPM5-1B for LLM
|
| 17 |
-
Local node β full peer mesh, any LLM backend (Ollama / llama.cpp / HF),
|
| 18 |
-
file sharing, multi-node chat, hardware acceleration
|
| 19 |
-
|
| 20 |
-
Quick start (local, full features):
|
| 21 |
-
git clone https://huggingface.co/spaces/build-small-hackathon/HearthNet
|
| 22 |
-
cd HearthNet
|
| 23 |
-
pip install -e .
|
| 24 |
-
python -m hearthnet.cli run
|
| 25 |
-
# Open http://localhost:7860 in your browser
|
| 26 |
-
|
| 27 |
-
See docs/HOWTO.md for Raspberry Pi, Docker, and multi-node mesh setup.
|
| 28 |
-
"""
|
| 29 |
-
|
| 30 |
-
from __future__ import annotations
|
| 31 |
-
|
| 32 |
-
import contextlib
|
| 33 |
-
import os
|
| 34 |
-
|
| 35 |
-
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 36 |
-
# Optional HF Spaces GPU decorator
|
| 37 |
-
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 38 |
-
try:
|
| 39 |
-
import spaces as _spaces # type: ignore[import]
|
| 40 |
-
|
| 41 |
-
HF_SPACES = True
|
| 42 |
-
except ImportError:
|
| 43 |
-
HF_SPACES = False
|
| 44 |
-
|
| 45 |
-
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 46 |
-
# Bootstrap a real HearthNet node
|
| 47 |
-
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 48 |
-
|
| 49 |
-
MODEL_ID = os.getenv("MODEL_ID", "HuggingFaceTB/SmolLM2-135M-Instruct")
|
| 50 |
-
MODEL_REVISION = os.getenv("MODEL_REVISION", "12fd25f77366fa6b3b4b768ec3050bf629380bac")
|
| 51 |
-
|
| 52 |
-
#MODEL_ID = os.getenv("MODEL_ID", "openbmb/MiniCPM3-4B")
|
| 53 |
-
# Pin the exact commit so trust_remote_code never re-execs modeling_minicpm.py
|
| 54 |
-
# mid-session and creates a second class object (the "not the same object as itself"
|
| 55 |
-
# pickle error). Override via env var to test a newer revision.
|
| 56 |
-
#MODEL_REVISION = os.getenv("MODEL_REVISION") or "d6b14ddaefdb11c624dd75c3c779549bc90b08cb"
|
| 57 |
-
|
| 58 |
-
SEED_CORPUS = [
|
| 59 |
-
{
|
| 60 |
-
"id": "water.001",
|
| 61 |
-
"title": "Water Safety",
|
| 62 |
-
"text": (
|
| 63 |
-
"If the mains supply is disrupted, use stored clean water first. "
|
| 64 |
-
"Rainwater should be filtered through clean cloth, brought to a rolling "
|
| 65 |
-
"boil for at least one minute, and stored in a clean covered container. "
|
| 66 |
-
"Adult daily minimum: 3 litres for drinking and sanitation."
|
| 67 |
-
),
|
| 68 |
-
},
|
| 69 |
-
{
|
| 70 |
-
"id": "power.001",
|
| 71 |
-
"title": "Power Outage",
|
| 72 |
-
"text": (
|
| 73 |
-
"Keep refrigerators closed to preserve food up to 4 hours. "
|
| 74 |
-
"Disconnect sensitive electronics. Reserve battery banks for communication. "
|
| 75 |
-
"Share verified charging points through the local marketplace. "
|
| 76 |
-
"Candles are a fire risk β use battery or wind-up torches."
|
| 77 |
-
),
|
| 78 |
-
},
|
| 79 |
-
{
|
| 80 |
-
"id": "mesh.001",
|
| 81 |
-
"title": "HearthNet Routing",
|
| 82 |
-
"text": (
|
| 83 |
-
"A HearthNet UI sends requests to a capability bus. The bus scores local "
|
| 84 |
-
"capabilities higher than remote ones and routes to the best available "
|
| 85 |
-
"provider. If a node is quarantined the bus fails over automatically. "
|
| 86 |
-
"RAG corpus routing uses the 'corpus' parameter to match the right node."
|
| 87 |
-
),
|
| 88 |
-
},
|
| 89 |
-
{
|
| 90 |
-
"id": "firstaid.001",
|
| 91 |
-
"title": "First Aid β Bleeding",
|
| 92 |
-
"text": (
|
| 93 |
-
"Apply direct firm pressure to the wound with a clean cloth. "
|
| 94 |
-
"Maintain pressure for at least 10 minutes. Do not remove the cloth β "
|
| 95 |
-
"add more on top if it soaks through. Elevate the limb above heart level "
|
| 96 |
-
"if possible. Seek emergency care if bleeding is severe or arterial."
|
| 97 |
-
),
|
| 98 |
-
},
|
| 99 |
-
{
|
| 100 |
-
"id": "firstaid.002",
|
| 101 |
-
"title": "CPR Basics",
|
| 102 |
-
"text": (
|
| 103 |
-
"If a person is unresponsive and not breathing normally: call emergency services, "
|
| 104 |
-
"then give 30 chest compressions (hard, fast, centre of chest) followed by "
|
| 105 |
-
"2 rescue breaths. Continue the 30:2 cycle until help arrives or the person "
|
| 106 |
-
"recovers. Hands-only CPR (compressions without rescue breaths) is acceptable "
|
| 107 |
-
"for untrained bystanders."
|
| 108 |
-
),
|
| 109 |
-
},
|
| 110 |
-
{
|
| 111 |
-
"id": "setup.001",
|
| 112 |
-
"title": "Node Setup β Quick Start",
|
| 113 |
-
"text": (
|
| 114 |
-
"Install HearthNet with: pip install hearthnet. "
|
| 115 |
-
"Run: python -m hearthnet.cli run "
|
| 116 |
-
"to start a node. Open http://localhost:7860 in your browser. "
|
| 117 |
-
"Other devices on the same LAN discover your node automatically via mDNS. "
|
| 118 |
-
"Use the Settings tab to generate an invite QR for devices on other networks."
|
| 119 |
-
),
|
| 120 |
-
},
|
| 121 |
-
{
|
| 122 |
-
"id": "setup.002",
|
| 123 |
-
"title": "Node Setup β Specialized Nodes",
|
| 124 |
-
"text": (
|
| 125 |
-
"Register only the capabilities your hardware supports. "
|
| 126 |
-
"An OCR Raspberry Pi: register OcrService. "
|
| 127 |
-
"A medical knowledge node: register RagService with a medical corpus. "
|
| 128 |
-
"A thin client (phone): register no services β all bus calls route to peers. "
|
| 129 |
-
"The bus auto-discovers and routes to the best provider in the mesh."
|
| 130 |
-
),
|
| 131 |
-
},
|
| 132 |
-
{
|
| 133 |
-
"id": "emergency.001",
|
| 134 |
-
"title": "Emergency Communication Plan",
|
| 135 |
-
"text": (
|
| 136 |
-
"Before a disaster: exchange node IDs with neighbours. "
|
| 137 |
-
"During internet outage: HearthNet switches to offline mode automatically. "
|
| 138 |
-
"All routing stays local. Use the mesh to share offers and requests. "
|
| 139 |
-
"For emergency alerts, post to the Marketplace with category=emergency. "
|
| 140 |
-
"Battery-powered device with HearthNet can serve the whole neighbourhood."
|
| 141 |
-
),
|
| 142 |
-
},
|
| 143 |
-
{
|
| 144 |
-
"id": "food.001",
|
| 145 |
-
"title": "Emergency Food Safety",
|
| 146 |
-
"text": (
|
| 147 |
-
"In a power outage, refrigerated food is safe for up to 4 hours. "
|
| 148 |
-
"Frozen food stays safe for 24-48 hours if the freezer stays closed. "
|
| 149 |
-
"Discard meat, poultry, seafood, dairy, or cooked food left above 4Β°C "
|
| 150 |
-
"for more than 2 hours. When in doubt, throw it out."
|
| 151 |
-
),
|
| 152 |
-
},
|
| 153 |
-
{
|
| 154 |
-
"id": "shelter.001",
|
| 155 |
-
"title": "Shelter in Place",
|
| 156 |
-
"text": (
|
| 157 |
-
"During chemical or biological hazards, stay indoors. "
|
| 158 |
-
"Close all windows and doors. Turn off HVAC. "
|
| 159 |
-
"Seal gaps with wet towels or tape. "
|
| 160 |
-
"Monitor emergency broadcasts on battery radio. "
|
| 161 |
-
"Do not leave until authorities give the all-clear."
|
| 162 |
-
),
|
| 163 |
-
},
|
| 164 |
-
]
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
def _build_node():
|
| 168 |
-
"""Bootstrap the HearthNet node for this Space.
|
| 169 |
-
|
| 170 |
-
Uses HfLocalBackend (MiniCPM5-1B by default) so inference works without Ollama.
|
| 171 |
-
Falls back to _UnavailableBackend if transformers is not installed.
|
| 172 |
-
"""
|
| 173 |
-
import hashlib
|
| 174 |
-
import os
|
| 175 |
-
import socket
|
| 176 |
-
|
| 177 |
-
from hearthnet.node import HearthNode
|
| 178 |
-
from hearthnet.services.chat.service import ChatService
|
| 179 |
-
from hearthnet.services.files.service import FileService
|
| 180 |
-
from hearthnet.services.llm.backends.hf_local import HfLocalBackend
|
| 181 |
-
from hearthnet.services.llm.service import LlmService
|
| 182 |
-
from hearthnet.services.marketplace.service import MarketplaceService
|
| 183 |
-
|
| 184 |
-
# Generate a stable node_id from the HF Space hostname (so it doesn't change on restart).
|
| 185 |
-
# Use SPACE_HOST env var (set only on HF Spaces) to differentiate: local nodes get
|
| 186 |
-
# "local-*" prefix so they never collide with live "hf-space-*" peers in the relay.
|
| 187 |
-
_space_host = os.getenv("SPACE_HOST", "")
|
| 188 |
-
_host = _space_host or socket.gethostname()
|
| 189 |
-
_suffix = hashlib.sha256(_host.encode()).hexdigest()[:8]
|
| 190 |
-
if _space_host:
|
| 191 |
-
_node_id = f"hf-space-{_suffix}"
|
| 192 |
-
_display = os.getenv("SPACE_TITLE", f"HearthNet Space ({_suffix})")
|
| 193 |
-
else:
|
| 194 |
-
_node_id = f"local-{_suffix}"
|
| 195 |
-
_display = os.getenv("SPACE_TITLE", f"HearthNet Local ({_suffix})")
|
| 196 |
-
|
| 197 |
-
node = HearthNode(
|
| 198 |
-
node_id=_node_id,
|
| 199 |
-
display_name=_display,
|
| 200 |
-
community_id="ed25519:hf-space-community",
|
| 201 |
-
)
|
| 202 |
-
|
| 203 |
-
# LLM β HF Transformers backend (MiniCPM5-1B by default)
|
| 204 |
-
try:
|
| 205 |
-
backend = HfLocalBackend(model=MODEL_ID, revision=MODEL_REVISION)
|
| 206 |
-
# On ZeroGPU Spaces, wrap generation with @spaces.GPU so CUDA is
|
| 207 |
-
# allocated for exactly the duration of one generation call.
|
| 208 |
-
# Free-function form (no `self` argument) avoids any risk of ZeroGPU
|
| 209 |
-
# touching the model object across a serialisation boundary.
|
| 210 |
-
if HF_SPACES:
|
| 211 |
-
from hearthnet.services.llm.backends.hf_local import HfLocalBackend as _HfLocalBackend
|
| 212 |
-
|
| 213 |
-
# Module-level singleton: populated on first warm() call.
|
| 214 |
-
_hf_backend: list = [] # list-as-mutable-cell so the closure can write
|
| 215 |
-
|
| 216 |
-
_orig_generate_sync = _HfLocalBackend._generate_sync
|
| 217 |
-
|
| 218 |
-
@_spaces.GPU(duration=120)
|
| 219 |
-
def _free_gpu_generate(messages, max_tokens=256, temperature=0.7):
|
| 220 |
-
"""Free function β no self, no model object crossing the GPU boundary."""
|
| 221 |
-
b = _hf_backend[0]
|
| 222 |
-
return _orig_generate_sync(b, messages, max_tokens=max_tokens, temperature=temperature)
|
| 223 |
-
|
| 224 |
-
def _patched_generate_sync(self, messages, max_tokens=256, temperature=0.7):
|
| 225 |
-
if not _hf_backend:
|
| 226 |
-
_hf_backend.append(self)
|
| 227 |
-
else:
|
| 228 |
-
_hf_backend[0] = self
|
| 229 |
-
return _free_gpu_generate(messages, max_tokens=max_tokens, temperature=temperature)
|
| 230 |
-
|
| 231 |
-
_HfLocalBackend._generate_sync = _patched_generate_sync # type: ignore[method-assign]
|
| 232 |
-
|
| 233 |
-
backends: list = [backend]
|
| 234 |
-
# ββ Sponsor cloud backends (opt-in via env) βββββββββββββββββββββββ
|
| 235 |
-
# NVIDIA Nemotron (prize track) β cloud NIM, no local availability check.
|
| 236 |
-
if os.getenv("NVIDIA_API_KEY"):
|
| 237 |
-
try:
|
| 238 |
-
from hearthnet.services.llm.backends.nemotron import NemotronBackend
|
| 239 |
-
|
| 240 |
-
backends.append(NemotronBackend(api_key_env="NVIDIA_API_KEY"))
|
| 241 |
-
except Exception:
|
| 242 |
-
pass
|
| 243 |
-
# Modal serverless GPU (prize track).
|
| 244 |
-
if os.getenv("MODAL_ENDPOINT"):
|
| 245 |
-
try:
|
| 246 |
-
from hearthnet.services.llm.backends.modal_backend import ModalBackend
|
| 247 |
-
|
| 248 |
-
modal_b = ModalBackend()
|
| 249 |
-
if modal_b.is_available():
|
| 250 |
-
backends.append(modal_b)
|
| 251 |
-
except Exception:
|
| 252 |
-
pass
|
| 253 |
-
# MiniCPM local server (OpenBMB prize track).
|
| 254 |
-
# MINICPM_URL β OpenAI-compatible vLLM/SGLang/llama.cpp endpoint
|
| 255 |
-
# MINICPM_MODELS β comma-separated model ids to advertise (multi-model
|
| 256 |
-
# serving from one server). Omit β full MiniCPM catalogue.
|
| 257 |
-
# MINICPM_LIGHTWEIGHT β "1" to also advertise Pi-friendly small models.
|
| 258 |
-
_minicpm_url = os.getenv("MINICPM_URL")
|
| 259 |
-
if _minicpm_url:
|
| 260 |
-
try:
|
| 261 |
-
from hearthnet.services.llm.backends.openbmb import OpenBmbBackend
|
| 262 |
-
|
| 263 |
-
_models_env = os.getenv("MINICPM_MODELS", "")
|
| 264 |
-
_models = [m.strip() for m in _models_env.split(",") if m.strip()] or None
|
| 265 |
-
_lightweight = os.getenv("MINICPM_LIGHTWEIGHT", "") in ("1", "true", "yes")
|
| 266 |
-
minicpm = OpenBmbBackend(
|
| 267 |
-
base_url=_minicpm_url,
|
| 268 |
-
models=_models,
|
| 269 |
-
include_lightweight=_lightweight,
|
| 270 |
-
)
|
| 271 |
-
if minicpm.is_available():
|
| 272 |
-
backends.append(minicpm)
|
| 273 |
-
except Exception:
|
| 274 |
-
pass
|
| 275 |
-
|
| 276 |
-
llm = LlmService(backends=backends)
|
| 277 |
-
except Exception:
|
| 278 |
-
llm = LlmService() # _UnavailableBackend β shows clear error
|
| 279 |
-
|
| 280 |
-
node.bus.register_service(llm)
|
| 281 |
-
|
| 282 |
-
# ββ Durable event log (ZeroGPU-safe; no mDNS/transport on a single Space) ββ
|
| 283 |
-
event_log = None
|
| 284 |
-
try:
|
| 285 |
-
import tempfile
|
| 286 |
-
from pathlib import Path
|
| 287 |
-
|
| 288 |
-
from hearthnet.events import EventLog
|
| 289 |
-
|
| 290 |
-
_data_dir = Path(os.getenv("HEARTHNET_DATA_DIR", tempfile.gettempdir())) / "hearthnet-space"
|
| 291 |
-
_data_dir.mkdir(parents=True, exist_ok=True)
|
| 292 |
-
event_log = EventLog(_data_dir / "events.db", node.community_id, node.node_id)
|
| 293 |
-
node._event_log = event_log
|
| 294 |
-
except Exception:
|
| 295 |
-
event_log = None
|
| 296 |
-
|
| 297 |
-
# ββ Blob store for content-addressed RAG documents ββββββββββββββββββββ
|
| 298 |
-
blob_store = None
|
| 299 |
-
try:
|
| 300 |
-
import tempfile
|
| 301 |
-
from pathlib import Path
|
| 302 |
-
|
| 303 |
-
from hearthnet.blobs.store import BlobStore
|
| 304 |
-
|
| 305 |
-
blob_store = BlobStore(
|
| 306 |
-
Path(os.getenv("HEARTHNET_DATA_DIR", tempfile.gettempdir()))
|
| 307 |
-
/ "hearthnet-space"
|
| 308 |
-
/ "blobs"
|
| 309 |
-
)
|
| 310 |
-
except Exception:
|
| 311 |
-
blob_store = None
|
| 312 |
-
|
| 313 |
-
# ββ Real semantic RAG (replaces the in-memory demo corpus) ββββββββββββ
|
| 314 |
-
from hearthnet.bus.capability import RouteRequest
|
| 315 |
-
from hearthnet.services.rag.federated import FederatedRagService
|
| 316 |
-
from hearthnet.services.rag.service import RagService
|
| 317 |
-
|
| 318 |
-
# Register the embedding backend first so rag.query routes through embed.text.
|
| 319 |
-
node.install_extended_services(research=True)
|
| 320 |
-
|
| 321 |
-
import tempfile
|
| 322 |
-
|
| 323 |
-
_data_env = os.getenv("HEARTHNET_DATA_DIR", "")
|
| 324 |
-
_data_base = Path(_data_env) if _data_env else Path(tempfile.gettempdir())
|
| 325 |
-
# Verify the base path (or its first existing ancestor) is writable.
|
| 326 |
-
# Falls back to tempdir if e.g. /data persistent storage isn't mounted.
|
| 327 |
-
_check = _data_base
|
| 328 |
-
while not _check.exists():
|
| 329 |
-
_check = _check.parent
|
| 330 |
-
if not os.access(_check, os.W_OK):
|
| 331 |
-
_data_base = Path(tempfile.gettempdir())
|
| 332 |
-
print(f"[hearthnet] HEARTHNET_DATA_DIR {_data_env!r} not writable, using tmpdir")
|
| 333 |
-
_corpora_dir = _data_base / "hearthnet-space" / "corpora"
|
| 334 |
-
rag = RagService(
|
| 335 |
-
corpus="community",
|
| 336 |
-
corpora_dir=_corpora_dir,
|
| 337 |
-
bus=node.bus,
|
| 338 |
-
event_log=event_log,
|
| 339 |
-
blob_store=blob_store,
|
| 340 |
-
)
|
| 341 |
-
node.bus.register_service(rag)
|
| 342 |
-
node.bus.register_service(FederatedRagService(node.bus, corpus="community"))
|
| 343 |
-
|
| 344 |
-
# Seed the corpus through the real ingest path (content-addressed + logged).
|
| 345 |
-
async def _seed_corpus() -> None:
|
| 346 |
-
import pathlib
|
| 347 |
-
|
| 348 |
-
# 1. Fixed emergency seed documents (water, first aid, CPR, etc.)
|
| 349 |
-
for doc in SEED_CORPUS:
|
| 350 |
-
with contextlib.suppress(Exception):
|
| 351 |
-
await rag.handle_ingest(
|
| 352 |
-
RouteRequest(
|
| 353 |
-
capability="rag.ingest",
|
| 354 |
-
version_req=(1, 0),
|
| 355 |
-
body={
|
| 356 |
-
"input": {
|
| 357 |
-
"corpus": "community",
|
| 358 |
-
"documents": [
|
| 359 |
-
{
|
| 360 |
-
"id": doc["id"],
|
| 361 |
-
"title": doc["title"],
|
| 362 |
-
"text": doc["text"],
|
| 363 |
-
}
|
| 364 |
-
],
|
| 365 |
-
}
|
| 366 |
-
},
|
| 367 |
-
caller=node.node_id,
|
| 368 |
-
trace_id="seed",
|
| 369 |
-
deadline_ms=0,
|
| 370 |
-
)
|
| 371 |
-
)
|
| 372 |
-
|
| 373 |
-
# 2. Ingest all .md / .txt files from docs/ (main), docs/guides/, assets/initial_docs/.
|
| 374 |
-
# Files are content-addressed (BLAKE3), so re-ingesting the same file is a no-op.
|
| 375 |
-
_app_root = pathlib.Path(__file__).parent
|
| 376 |
-
_doc_dirs = [
|
| 377 |
-
_app_root / "docs", # Main docs: CAPABILITY_CONTRACT, GLOSSARY, M01-M13, X01-X04, etc.
|
| 378 |
-
_app_root / "docs" / "guides",
|
| 379 |
-
_app_root / "assets" / "initial_docs",
|
| 380 |
-
]
|
| 381 |
-
_text_suffixes = {".md", ".txt", ".rst"}
|
| 382 |
-
_ingested = 0
|
| 383 |
-
_failed = 0
|
| 384 |
-
for _doc_dir in _doc_dirs:
|
| 385 |
-
if not _doc_dir.exists():
|
| 386 |
-
continue
|
| 387 |
-
for _doc_file in sorted(_doc_dir.rglob("*")):
|
| 388 |
-
if _doc_file.suffix.lower() not in _text_suffixes:
|
| 389 |
-
continue
|
| 390 |
-
try:
|
| 391 |
-
_text = _doc_file.read_text(encoding="utf-8", errors="replace")
|
| 392 |
-
if len(_text.strip()) < 80:
|
| 393 |
-
continue
|
| 394 |
-
_title = _doc_file.stem.replace("-", " ").replace("_", " ").title()
|
| 395 |
-
_doc_id = f"file:{_doc_file.relative_to(_app_root).as_posix()}"
|
| 396 |
-
await rag.handle_ingest(
|
| 397 |
-
RouteRequest(
|
| 398 |
-
capability="rag.ingest",
|
| 399 |
-
version_req=(1, 0),
|
| 400 |
-
body={
|
| 401 |
-
"input": {
|
| 402 |
-
"text": _text,
|
| 403 |
-
"title": _title,
|
| 404 |
-
"doc_cid": _doc_id,
|
| 405 |
-
}
|
| 406 |
-
},
|
| 407 |
-
caller=node.node_id,
|
| 408 |
-
trace_id="seed-docs",
|
| 409 |
-
deadline_ms=0,
|
| 410 |
-
)
|
| 411 |
-
)
|
| 412 |
-
_ingested += 1
|
| 413 |
-
except Exception as _ie:
|
| 414 |
-
_failed += 1
|
| 415 |
-
print(f"[hearthnet] seed ingest failed {_doc_file.name}: {_ie}")
|
| 416 |
-
print(f"[hearthnet] seed corpus done: {_ingested} docs ingested, {_failed} failed")
|
| 417 |
-
|
| 418 |
-
# Run seed corpus in a dedicated thread with its own event loop to avoid
|
| 419 |
-
# conflicts with any loop already running (e.g. Gradio's internal loop).
|
| 420 |
-
import asyncio
|
| 421 |
-
import threading
|
| 422 |
-
|
| 423 |
-
def _seed_in_thread() -> None:
|
| 424 |
-
loop = asyncio.new_event_loop()
|
| 425 |
-
asyncio.set_event_loop(loop)
|
| 426 |
-
try:
|
| 427 |
-
loop.run_until_complete(_seed_corpus())
|
| 428 |
-
except Exception as _e:
|
| 429 |
-
print(f"[hearthnet] seed corpus failed: {_e}")
|
| 430 |
-
finally:
|
| 431 |
-
loop.close()
|
| 432 |
-
|
| 433 |
-
# Run seeding as a pure background daemon β do NOT join() with a short
|
| 434 |
-
# timeout. 78+ docs Γ embedding time >> 60 s, so a join would kill the
|
| 435 |
-
# thread before most docs are stored. Gradio starts serving immediately;
|
| 436 |
-
# docs become available for RAG queries as they are ingested.
|
| 437 |
-
_seed_thread = threading.Thread(target=_seed_in_thread, daemon=True, name="hearthnet-seed")
|
| 438 |
-
_seed_thread.start()
|
| 439 |
-
|
| 440 |
-
# Register this node's LLM model as an expert in the MoE registry so
|
| 441 |
-
# route_expert tool calls return meaningful results instead of an empty list.
|
| 442 |
-
try:
|
| 443 |
-
_moe_tags = list({
|
| 444 |
-
doc.get("id", "").split(".")[0]
|
| 445 |
-
for doc in SEED_CORPUS
|
| 446 |
-
if doc.get("id")
|
| 447 |
-
} | {"emergency", "mesh", "community"})
|
| 448 |
-
loop_moe = asyncio.new_event_loop()
|
| 449 |
-
loop_moe.run_until_complete(
|
| 450 |
-
node.bus.call(
|
| 451 |
-
"moe.register",
|
| 452 |
-
(1, 0),
|
| 453 |
-
{
|
| 454 |
-
"input": {
|
| 455 |
-
"expert_id": f"model:{MODEL_ID}",
|
| 456 |
-
"expert_type": "model",
|
| 457 |
-
"topic_tags": _moe_tags,
|
| 458 |
-
"confidence_score": 0.6,
|
| 459 |
-
"community_id": node.community_id,
|
| 460 |
-
"name": MODEL_ID.split("/")[-1],
|
| 461 |
-
"ttl_seconds": 0,
|
| 462 |
-
}
|
| 463 |
-
},
|
| 464 |
-
)
|
| 465 |
-
)
|
| 466 |
-
loop_moe.close()
|
| 467 |
-
except Exception:
|
| 468 |
-
pass
|
| 469 |
-
|
| 470 |
-
# Marketplace, Chat, Files β now durably event-sourced where supported.
|
| 471 |
-
node.bus.register_service(MarketplaceService(event_log=event_log, node_id=node.node_id))
|
| 472 |
-
node.bus.register_service(ChatService(node.node_id, event_log=event_log, bus=node.bus))
|
| 473 |
-
node.bus.register_service(FileService())
|
| 474 |
-
|
| 475 |
-
return node
|
| 476 |
-
|
| 477 |
-
|
| 478 |
-
# Build node and Gradio app at import time (HF Spaces requires module-level `demo`)
|
| 479 |
-
_node = _build_node()
|
| 480 |
-
|
| 481 |
-
# ββ Local-only: start mDNS peer discovery + HTTP bus transport ββββββββββββββββ
|
| 482 |
-
# On HF Space (SPACE_HOST set): port 7080 is not exposed to the internet and mDNS
|
| 483 |
-
# doesn't cross network boundaries β the relay hub handles internet peering instead.
|
| 484 |
-
# Locally: node.start() activates zero-config LAN discovery and makes this node's
|
| 485 |
-
# bus callable by other nodes over HTTP so RAG, chat, and LLM route across devices.
|
| 486 |
-
if not os.getenv("SPACE_HOST"):
|
| 487 |
-
import asyncio as _asyncio
|
| 488 |
-
import threading as _threading
|
| 489 |
-
|
| 490 |
-
def _run_local_networking() -> None:
|
| 491 |
-
_loop = _asyncio.new_event_loop()
|
| 492 |
-
_asyncio.set_event_loop(_loop)
|
| 493 |
-
try:
|
| 494 |
-
# node._event_log is already set by _build_node(); start() reuses it
|
| 495 |
-
# (see the "already set" guard added to node.start()).
|
| 496 |
-
_loop.run_until_complete(_node.start(port=7080))
|
| 497 |
-
_loop.run_forever()
|
| 498 |
-
except Exception as _exc:
|
| 499 |
-
print(f"[hearthnet] local networking start failed: {_exc}")
|
| 500 |
-
|
| 501 |
-
_threading.Thread(
|
| 502 |
-
target=_run_local_networking, daemon=True, name="hearthnet-local-node"
|
| 503 |
-
).start()
|
| 504 |
-
|
| 505 |
-
# Relay hub: pull-based mailbox router so NAT-bound nodes mesh all-to-all through
|
| 506 |
-
# this public Space (see hearthnet/transport/relay_hub.py). Members poll their
|
| 507 |
-
# mailbox over HTTPS; the Space never needs to reach back into a home network.
|
| 508 |
-
from hearthnet.transport.relay_hub import RelayHub as _RelayHub # noqa: E402
|
| 509 |
-
from hearthnet.transport.relay_hub import mount_relay_endpoints as _mount_relay_endpoints # noqa: E402
|
| 510 |
-
|
| 511 |
-
import tempfile as _tempfile
|
| 512 |
-
from pathlib import Path as _Path2
|
| 513 |
-
|
| 514 |
-
_relay_db_path = (
|
| 515 |
-
_Path2(os.getenv("HEARTHNET_DATA_DIR", _tempfile.gettempdir()))
|
| 516 |
-
/ "hearthnet-space"
|
| 517 |
-
/ "relay.db"
|
| 518 |
-
)
|
| 519 |
-
_relay_db_path.parent.mkdir(parents=True, exist_ok=True)
|
| 520 |
-
_relay_hub = _RelayHub(db_path=_relay_db_path)
|
| 521 |
-
|
| 522 |
-
from hearthnet.ui.app import build_ui as _build_ui # noqa: E402
|
| 523 |
-
|
| 524 |
-
_ui = _build_ui(
|
| 525 |
-
bus=_node.bus,
|
| 526 |
-
state_bus=_node.state_bus,
|
| 527 |
-
node=_node,
|
| 528 |
-
display_name=_node.display_name,
|
| 529 |
-
node_id=_node.node_id,
|
| 530 |
-
community_id=_node.community_id,
|
| 531 |
-
)
|
| 532 |
-
|
| 533 |
-
demo = _ui.build()
|
| 534 |
-
|
| 535 |
-
# Gradio 6 moved theme/css from gr.Blocks() to launch(). Set them directly on the
|
| 536 |
-
# demo object so HF Spaces' auto-launch (which we don't control) picks them up.
|
| 537 |
-
if _ui.theme is not None and hasattr(demo, "theme"):
|
| 538 |
-
demo.theme = _ui.theme
|
| 539 |
-
if _ui.css is not None and hasattr(demo, "css"):
|
| 540 |
-
demo.css = _ui.css
|
| 541 |
-
|
| 542 |
-
# ββ Serve webagent at /webagent/ ββββββββββββββββββββββββββββββββββββββββββββββ
|
| 543 |
-
# HF Space enables Gradio SSR mode (GRADIO_SSR_MODE=true), where a Node.js layer
|
| 544 |
-
# intercepts ALL requests before Python/FastAPI sees them, making StaticFiles
|
| 545 |
-
# mounts invisible. Fix: force SSR off so Python handles all requests directly.
|
| 546 |
-
from pathlib import Path as _Path
|
| 547 |
-
|
| 548 |
-
import gradio as _gr
|
| 549 |
-
|
| 550 |
-
_webagent_dir = _Path(__file__).parent / "webagent"
|
| 551 |
-
|
| 552 |
-
# 1) Override the env var that launch() reads when ssr_mode param is None
|
| 553 |
-
os.environ["GRADIO_SSR_MODE"] = "false"
|
| 554 |
-
|
| 555 |
-
# 2) Also patch _resolve_ssr_mode in case HF passes ssr_mode=True explicitly
|
| 556 |
-
_gr.Blocks._resolve_ssr_mode = lambda self, ssr_mode=None, **kw: False
|
| 557 |
-
|
| 558 |
-
|
| 559 |
-
def _mount_bus_endpoints(app) -> None:
|
| 560 |
-
"""Expose the node's capability bus on the Space's public port.
|
| 561 |
-
|
| 562 |
-
On HF Spaces only the Gradio port is reachable from the internet β the
|
| 563 |
-
node's internal HttpServer (port 7080) is not. Mounting the bus RPC
|
| 564 |
-
endpoints directly into the Gradio FastAPI app lets a remote/local node
|
| 565 |
-
peer with this Space via ``discovery.peer.add`` and route real
|
| 566 |
-
``llm.chat`` / ``rag.query`` / ``moe.*`` calls to it over HTTPS.
|
| 567 |
-
"""
|
| 568 |
-
try:
|
| 569 |
-
from fastapi import Body
|
| 570 |
-
from fastapi.responses import JSONResponse
|
| 571 |
-
except Exception as exc: # pragma: no cover
|
| 572 |
-
print(f"[hearthnet] bus endpoint mount skipped: {exc}")
|
| 573 |
-
return
|
| 574 |
-
|
| 575 |
-
if any(getattr(r, "path", "") == "/bus/v1/call" for r in app.routes):
|
| 576 |
-
return
|
| 577 |
-
|
| 578 |
-
def _parse_version(v) -> tuple[int, int]:
|
| 579 |
-
parts = str(v).split(".")
|
| 580 |
-
if len(parts) < 2:
|
| 581 |
-
parts.append("0")
|
| 582 |
-
return (int(parts[0]), int(parts[1]))
|
| 583 |
-
|
| 584 |
-
@app.get("/manifest")
|
| 585 |
-
async def _hn_manifest():
|
| 586 |
-
return JSONResponse(_node.manifest().as_dict())
|
| 587 |
-
|
| 588 |
-
@app.get("/health")
|
| 589 |
-
async def _hn_health():
|
| 590 |
-
return JSONResponse({"status": "ok", "node_id": _node.node_id})
|
| 591 |
-
|
| 592 |
-
@app.get("/bus/v1/capabilities")
|
| 593 |
-
async def _hn_capabilities():
|
| 594 |
-
return JSONResponse([e.descriptor.name for e in _node.bus.registry.all_local()])
|
| 595 |
-
|
| 596 |
-
@app.post("/bus/v1/call")
|
| 597 |
-
async def _hn_bus_call(payload: dict = Body(...)):
|
| 598 |
-
capability = payload.get("capability")
|
| 599 |
-
if not capability:
|
| 600 |
-
return JSONResponse(
|
| 601 |
-
{"error": "bad_request", "message": "capability required"}, status_code=400
|
| 602 |
-
)
|
| 603 |
-
version = _parse_version(payload.get("version", "1.0"))
|
| 604 |
-
call_body = {
|
| 605 |
-
"params": payload.get("params", {}),
|
| 606 |
-
"input": payload.get("input", {}),
|
| 607 |
-
}
|
| 608 |
-
try:
|
| 609 |
-
result = await _node.bus.call(capability, version, call_body)
|
| 610 |
-
return JSONResponse(result)
|
| 611 |
-
except Exception as exc:
|
| 612 |
-
code = getattr(exc, "code", "call_error")
|
| 613 |
-
return JSONResponse({"error": code, "message": str(exc)}, status_code=500)
|
| 614 |
-
|
| 615 |
-
# New routes are appended last; move them ahead of Gradio's SPA catch-all.
|
| 616 |
-
for _path in ("/bus/v1/call", "/bus/v1/capabilities", "/manifest", "/health"):
|
| 617 |
-
for _i in range(len(app.routes) - 1, -1, -1):
|
| 618 |
-
if getattr(app.routes[_i], "path", "") == _path:
|
| 619 |
-
app.routes.insert(0, app.routes.pop(_i))
|
| 620 |
-
break
|
| 621 |
-
|
| 622 |
-
|
| 623 |
-
# 3) Patch App.create_app to inject the StaticFiles mount after Gradio routes
|
| 624 |
-
if _webagent_dir.exists():
|
| 625 |
-
try:
|
| 626 |
-
import gradio.routes as _gr_routes
|
| 627 |
-
from fastapi.staticfiles import StaticFiles as _SF
|
| 628 |
-
|
| 629 |
-
_orig_create_app = _gr_routes.App.__dict__["create_app"].__func__
|
| 630 |
-
|
| 631 |
-
def _patched_create_app(blocks, app=None, **kwargs):
|
| 632 |
-
result = _orig_create_app(blocks, app=app, **kwargs)
|
| 633 |
-
try:
|
| 634 |
-
if not any(getattr(r, "name", "") == "webagent" for r in result.routes):
|
| 635 |
-
result.mount("/webagent", _SF(directory=str(_webagent_dir)), name="webagent")
|
| 636 |
-
_wrt = result.routes.pop()
|
| 637 |
-
result.routes.insert(0, _wrt)
|
| 638 |
-
except Exception as _me:
|
| 639 |
-
print(f"[hearthnet] webagent mount: {_me}")
|
| 640 |
-
_mount_bus_endpoints(result)
|
| 641 |
-
_mount_relay_endpoints(result, _relay_hub)
|
| 642 |
-
|
| 643 |
-
# Auto-join: the Space node registers itself in its own relay hub
|
| 644 |
-
# so remote nodes that connect see it in the roster immediately.
|
| 645 |
-
try:
|
| 646 |
-
_caps = [
|
| 647 |
-
f"{e.descriptor.name}@{e.descriptor.version[0]}.{e.descriptor.version[1]}"
|
| 648 |
-
for e in _node.bus.registry.all_local()
|
| 649 |
-
]
|
| 650 |
-
_nid = getattr(_node, "node_id_full", _node.node_id)
|
| 651 |
-
_relay_hub.join(
|
| 652 |
-
_nid,
|
| 653 |
-
display_name=_node.display_name,
|
| 654 |
-
community_id=_node.community_id,
|
| 655 |
-
capabilities=_caps,
|
| 656 |
-
endpoint="",
|
| 657 |
-
)
|
| 658 |
-
_relay_hub.set_local_handler(_nid, _node.bus)
|
| 659 |
-
print(f"[hearthnet] Space node '{_node.display_name}' joined local relay hub")
|
| 660 |
-
except Exception as _je:
|
| 661 |
-
print(f"[hearthnet] self-join relay failed: {_je}")
|
| 662 |
-
return result
|
| 663 |
-
|
| 664 |
-
_gr_routes.App.create_app = staticmethod(_patched_create_app)
|
| 665 |
-
except Exception as _pe:
|
| 666 |
-
print(f"[hearthnet] create_app patch failed: {_pe}")
|
| 667 |
-
|
| 668 |
-
if __name__ == "__main__":
|
| 669 |
-
import os
|
| 670 |
-
|
| 671 |
-
# HF Spaces health-checks port 7860. Bind explicitly and disable Gradio
|
| 672 |
-
# SSR mode (Node proxy on a different port crashes on HF and the health
|
| 673 |
-
# check on :7860 then times out -> "workload was not healthy").
|
| 674 |
-
_port = int(os.environ.get("GRADIO_SERVER_PORT", "7860"))
|
| 675 |
-
demo.launch(
|
| 676 |
-
server_name="0.0.0.0",
|
| 677 |
-
server_port=_port,
|
| 678 |
-
ssr_mode=False,
|
| 679 |
-
theme=_ui.theme,
|
| 680 |
-
css=_ui.css,
|
| 681 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app_nemotron.py
DELETED
|
@@ -1,558 +0,0 @@
|
|
| 1 |
-
"""HearthNet Document Intelligence β Nemotron-powered second Space.
|
| 2 |
-
|
| 3 |
-
A standalone Gradio app focused entirely on document intelligence using
|
| 4 |
-
NVIDIA Nemotron models. Can run independently OR as part of a HearthNet mesh.
|
| 5 |
-
|
| 6 |
-
Deploy as a second HF Space alongside the main HearthNet mesh Space.
|
| 7 |
-
|
| 8 |
-
Prize targets:
|
| 9 |
-
- NVIDIA Nemotron Hardware Prize (RTX 5080): Build with Nemotron models β
|
| 10 |
-
- π Tiny Titan: Nemotron-nano-8B is 8B params (under 32B) β
|
| 11 |
-
- π¨ Off Brand: Custom-styled beyond default Gradio look β
|
| 12 |
-
|
| 13 |
-
Usage:
|
| 14 |
-
python app_nemotron.py
|
| 15 |
-
|
| 16 |
-
Environment:
|
| 17 |
-
NVIDIA_API_KEY β NVIDIA NIM API key (get free at build.nvidia.com)
|
| 18 |
-
NEMOTRON_URL β local NIM endpoint (optional, for offline use)
|
| 19 |
-
HEARTHNET_NODE β URL of a HearthNet mesh node to push results into
|
| 20 |
-
"""
|
| 21 |
-
|
| 22 |
-
from __future__ import annotations
|
| 23 |
-
|
| 24 |
-
import asyncio
|
| 25 |
-
import os
|
| 26 |
-
|
| 27 |
-
import gradio as gr
|
| 28 |
-
|
| 29 |
-
# HF Spaces GPU support
|
| 30 |
-
try:
|
| 31 |
-
import spaces
|
| 32 |
-
HAS_SPACES = True
|
| 33 |
-
except ImportError:
|
| 34 |
-
HAS_SPACES = False
|
| 35 |
-
|
| 36 |
-
# ββ Optional mesh connection ββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 37 |
-
_MESH_NODE = os.getenv("HEARTHNET_NODE", "")
|
| 38 |
-
_NVIDIA_KEY = os.getenv("NVIDIA_API_KEY", "")
|
| 39 |
-
_NEMOTRON_URL = os.getenv("NEMOTRON_URL", "")
|
| 40 |
-
|
| 41 |
-
# ββ Nemotron model catalogue ββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 42 |
-
_MODELS = {
|
| 43 |
-
"Nemotron Nano 8B (fast)": "nvidia/llama-3.1-nemotron-nano-8b-instruct",
|
| 44 |
-
"Nemotron Super 49B (deep)": "nvidia/llama-3.3-nemotron-super-49b-v1",
|
| 45 |
-
"Nemotron 70B (balanced)": "nvidia/llama-3.1-nemotron-70b-instruct",
|
| 46 |
-
}
|
| 47 |
-
|
| 48 |
-
_SCHEMAS = {
|
| 49 |
-
"Invoice / Receipt": """{
|
| 50 |
-
"vendor": "string",
|
| 51 |
-
"date": "string",
|
| 52 |
-
"total_amount": "number",
|
| 53 |
-
"currency": "string",
|
| 54 |
-
"line_items": [{"description": "string", "amount": "number"}],
|
| 55 |
-
"tax": "number"
|
| 56 |
-
}""",
|
| 57 |
-
"Medical Form": """{
|
| 58 |
-
"patient_name": "string",
|
| 59 |
-
"date_of_birth": "string",
|
| 60 |
-
"diagnosis": ["string"],
|
| 61 |
-
"medications": ["string"],
|
| 62 |
-
"doctor": "string",
|
| 63 |
-
"date": "string"
|
| 64 |
-
}""",
|
| 65 |
-
"Legal Document": """{
|
| 66 |
-
"document_type": "string",
|
| 67 |
-
"parties": ["string"],
|
| 68 |
-
"effective_date": "string",
|
| 69 |
-
"key_obligations": ["string"],
|
| 70 |
-
"governing_law": "string"
|
| 71 |
-
}""",
|
| 72 |
-
"Meeting Notes": """{
|
| 73 |
-
"date": "string",
|
| 74 |
-
"attendees": ["string"],
|
| 75 |
-
"decisions": ["string"],
|
| 76 |
-
"action_items": [{"owner": "string", "task": "string", "due": "string"}]
|
| 77 |
-
}""",
|
| 78 |
-
"Custom (edit below)": "{}",
|
| 79 |
-
}
|
| 80 |
-
|
| 81 |
-
# ββ Custom HearthNet theme ββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 82 |
-
_theme = gr.themes.Soft(
|
| 83 |
-
primary_hue=gr.themes.colors.orange,
|
| 84 |
-
secondary_hue=gr.themes.colors.purple,
|
| 85 |
-
neutral_hue=gr.themes.colors.gray,
|
| 86 |
-
font=[gr.themes.GoogleFont("Inter"), "ui-sans-serif", "sans-serif"],
|
| 87 |
-
).set(
|
| 88 |
-
button_primary_background_fill="*primary_500",
|
| 89 |
-
button_primary_background_fill_hover="*primary_600",
|
| 90 |
-
block_title_text_weight="600",
|
| 91 |
-
block_border_width="1px",
|
| 92 |
-
)
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
# ββ Core functions ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 96 |
-
|
| 97 |
-
def _get_endpoint(api_key: str) -> str:
|
| 98 |
-
return _NEMOTRON_URL.rstrip("/") + "/v1" if _NEMOTRON_URL else "https://integrate.api.nvidia.com/v1"
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
def _run_async(coro):
|
| 102 |
-
"""Run a coroutine safely whether or not a loop is already running."""
|
| 103 |
-
try:
|
| 104 |
-
loop = asyncio.get_running_loop()
|
| 105 |
-
except RuntimeError:
|
| 106 |
-
loop = None
|
| 107 |
-
if loop and loop.is_running():
|
| 108 |
-
import concurrent.futures
|
| 109 |
-
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool:
|
| 110 |
-
fut = pool.submit(asyncio.run, coro)
|
| 111 |
-
return fut.result()
|
| 112 |
-
return asyncio.run(coro)
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
def _local_smol_chat(messages: list, max_tokens: int = 512) -> str:
|
| 116 |
-
"""SmolLM2-135M local fallback β no API key required."""
|
| 117 |
-
try:
|
| 118 |
-
from transformers import pipeline as _pipeline # type: ignore[import-untyped]
|
| 119 |
-
|
| 120 |
-
_smol_id = "HuggingFaceTB/SmolLM2-135M-Instruct"
|
| 121 |
-
pipe = _pipeline("text-generation", model=_smol_id, device_map="auto", torch_dtype="auto")
|
| 122 |
-
prompt = ""
|
| 123 |
-
for m in messages:
|
| 124 |
-
role, content = m.get("role", "user"), m.get("content", "")
|
| 125 |
-
if role == "system":
|
| 126 |
-
prompt += f"<|im_start|>system\n{content}<|im_end|>\n"
|
| 127 |
-
elif role == "user":
|
| 128 |
-
prompt += f"<|im_start|>user\n{content}<|im_end|>\n"
|
| 129 |
-
elif role == "assistant":
|
| 130 |
-
prompt += f"<|im_start|>assistant\n{content}<|im_end|>\n"
|
| 131 |
-
prompt += "<|im_start|>assistant\n"
|
| 132 |
-
result = pipe(prompt, max_new_tokens=max_tokens, return_full_text=False, do_sample=False)
|
| 133 |
-
return result[0]["generated_text"].strip()
|
| 134 |
-
except Exception as exc:
|
| 135 |
-
return f"[SmolLM2 unavailable: {exc}]"
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
async def _nemotron_chat(messages: list, model: str, api_key: str, temperature: float = 0.1) -> str:
|
| 139 |
-
import httpx
|
| 140 |
-
|
| 141 |
-
endpoint = _get_endpoint(api_key)
|
| 142 |
-
headers = {"Content-Type": "application/json"}
|
| 143 |
-
if api_key:
|
| 144 |
-
headers["Authorization"] = f"Bearer {api_key}"
|
| 145 |
-
|
| 146 |
-
payload = {
|
| 147 |
-
"model": model,
|
| 148 |
-
"messages": messages,
|
| 149 |
-
"temperature": temperature,
|
| 150 |
-
"max_tokens": 2048,
|
| 151 |
-
}
|
| 152 |
-
async with httpx.AsyncClient(timeout=60.0) as c:
|
| 153 |
-
r = await c.post(f"{endpoint}/chat/completions", json=payload, headers=headers)
|
| 154 |
-
r.raise_for_status()
|
| 155 |
-
return r.json()["choices"][0]["message"]["content"]
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
@spaces.GPU if HAS_SPACES else lambda f: f
|
| 159 |
-
def extract_structured(
|
| 160 |
-
doc_text: str,
|
| 161 |
-
schema_preset: str,
|
| 162 |
-
custom_schema: str,
|
| 163 |
-
model_label: str,
|
| 164 |
-
api_key: str,
|
| 165 |
-
) -> tuple[str, str]:
|
| 166 |
-
"""Extract structured data from documents using Nemotron.
|
| 167 |
-
|
| 168 |
-
Wrapped with @spaces.GPU to signal GPU usage to HF Spaces.
|
| 169 |
-
Falls back gracefully if GPU unavailable (e.g., local testing).
|
| 170 |
-
"""
|
| 171 |
-
import json
|
| 172 |
-
|
| 173 |
-
if not doc_text.strip():
|
| 174 |
-
return '{"error": "No document text provided"}', "β Provide document text"
|
| 175 |
-
|
| 176 |
-
key = api_key.strip() or _NVIDIA_KEY
|
| 177 |
-
schema = custom_schema.strip() if schema_preset == "Custom (edit below)" else _SCHEMAS[schema_preset]
|
| 178 |
-
model = _MODELS.get(model_label, list(_MODELS.values())[0])
|
| 179 |
-
|
| 180 |
-
system = (
|
| 181 |
-
"You are a precise structured data extraction engine. "
|
| 182 |
-
"Extract information from the document and return ONLY valid JSON "
|
| 183 |
-
f"matching this exact schema:\n{schema}\n"
|
| 184 |
-
"If a field is not found, use null. Never add fields not in the schema."
|
| 185 |
-
)
|
| 186 |
-
messages = [
|
| 187 |
-
{"role": "system", "content": system},
|
| 188 |
-
{"role": "user", "content": f"Document:\n\n{doc_text[:5000]}"},
|
| 189 |
-
]
|
| 190 |
-
|
| 191 |
-
try:
|
| 192 |
-
if key or _NEMOTRON_URL:
|
| 193 |
-
raw = _run_async(_nemotron_chat(messages, model, key, temperature=0.05))
|
| 194 |
-
label = f"β Extracted with {model_label}"
|
| 195 |
-
else:
|
| 196 |
-
raw = _local_smol_chat(messages, max_tokens=512)
|
| 197 |
-
label = "β Extracted with SmolLM2-135M (local fallback)"
|
| 198 |
-
try:
|
| 199 |
-
parsed = json.loads(raw)
|
| 200 |
-
return json.dumps(parsed, indent=2), label
|
| 201 |
-
except json.JSONDecodeError:
|
| 202 |
-
return raw, f"β Model returned non-JSON (shown as-is)"
|
| 203 |
-
except Exception as exc:
|
| 204 |
-
return f'{{"error": "{exc}"}}', f"β Error: {exc}"
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
def ask_document(doc_text: str, question: str, model_label: str, api_key: str) -> str:
|
| 208 |
-
if not doc_text.strip():
|
| 209 |
-
return "Provide a document first."
|
| 210 |
-
if not question.strip():
|
| 211 |
-
return "Ask a question."
|
| 212 |
-
|
| 213 |
-
key = api_key.strip() or _NVIDIA_KEY
|
| 214 |
-
model = _MODELS.get(model_label, list(_MODELS.values())[0])
|
| 215 |
-
messages = [
|
| 216 |
-
{
|
| 217 |
-
"role": "system",
|
| 218 |
-
"content": "Answer questions about the document concisely and accurately. "
|
| 219 |
-
"Cite specific parts of the document when relevant.",
|
| 220 |
-
},
|
| 221 |
-
{
|
| 222 |
-
"role": "user",
|
| 223 |
-
"content": f"Document:\n\n{doc_text[:4000]}\n\nQuestion: {question}",
|
| 224 |
-
},
|
| 225 |
-
]
|
| 226 |
-
try:
|
| 227 |
-
if key or _NEMOTRON_URL:
|
| 228 |
-
return _run_async(_nemotron_chat(messages, model, key, temperature=0.3))
|
| 229 |
-
return _local_smol_chat(messages, max_tokens=512)
|
| 230 |
-
except Exception as exc:
|
| 231 |
-
return f"Error: {exc}"
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
def summarise_document(doc_text: str, style: str, model_label: str, api_key: str) -> str:
|
| 235 |
-
if not doc_text.strip():
|
| 236 |
-
return "Provide a document first."
|
| 237 |
-
|
| 238 |
-
key = api_key.strip() or _NVIDIA_KEY
|
| 239 |
-
model = _MODELS.get(model_label, list(_MODELS.values())[0])
|
| 240 |
-
style_prompts = {
|
| 241 |
-
"Executive (3 bullets)": "Summarise in exactly 3 bullet points for an executive audience.",
|
| 242 |
-
"Detailed (paragraph)": "Write a thorough 2-paragraph summary covering all key points.",
|
| 243 |
-
"ELI5 (simple)": "Explain this document as simply as possible, as if to a 10-year-old.",
|
| 244 |
-
"Action items only": "List only the action items, decisions, and next steps.",
|
| 245 |
-
}
|
| 246 |
-
prompt = style_prompts.get(style, "Summarise the document.")
|
| 247 |
-
messages = [
|
| 248 |
-
{"role": "system", "content": prompt},
|
| 249 |
-
{"role": "user", "content": f"Document:\n\n{doc_text[:5000]}"},
|
| 250 |
-
]
|
| 251 |
-
try:
|
| 252 |
-
if key or _NEMOTRON_URL:
|
| 253 |
-
return _run_async(_nemotron_chat(messages, model, key, temperature=0.4))
|
| 254 |
-
return _local_smol_chat(messages, max_tokens=512)
|
| 255 |
-
except Exception as exc:
|
| 256 |
-
return f"Error: {exc}"
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
def push_to_mesh(doc_text: str, doc_title: str, corpus: str, mesh_url: str) -> str:
|
| 260 |
-
import httpx
|
| 261 |
-
|
| 262 |
-
url = (mesh_url.strip() or _MESH_NODE).rstrip("/")
|
| 263 |
-
if not url:
|
| 264 |
-
return "β Set HEARTHNET_NODE env var or enter mesh URL to push to mesh."
|
| 265 |
-
if not doc_text.strip():
|
| 266 |
-
return "β No document to push."
|
| 267 |
-
|
| 268 |
-
async def _push():
|
| 269 |
-
payload = {
|
| 270 |
-
"capability": "rag.ingest",
|
| 271 |
-
"version": "1.0",
|
| 272 |
-
"params": {"corpus": corpus or "documents"},
|
| 273 |
-
"input": {
|
| 274 |
-
"documents": [
|
| 275 |
-
{
|
| 276 |
-
"id": f"doc-{hash(doc_text) % 100000}",
|
| 277 |
-
"title": doc_title or "Untitled",
|
| 278 |
-
"text": doc_text,
|
| 279 |
-
}
|
| 280 |
-
]
|
| 281 |
-
},
|
| 282 |
-
}
|
| 283 |
-
async with httpx.AsyncClient(timeout=15.0) as c:
|
| 284 |
-
r = await c.post(f"{url}/bus/v1/call", json=payload)
|
| 285 |
-
r.raise_for_status()
|
| 286 |
-
return r.json()
|
| 287 |
-
|
| 288 |
-
try:
|
| 289 |
-
_run_async(_push())
|
| 290 |
-
return f"β Document pushed to mesh at {url}\nCorpus: {corpus}\nNow searchable via Ask tab on any mesh node."
|
| 291 |
-
except Exception as exc:
|
| 292 |
-
return f"β Push failed: {exc}"
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
# ββ Build UI ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 296 |
-
|
| 297 |
-
def build_app() -> gr.Blocks:
|
| 298 |
-
with gr.Blocks(
|
| 299 |
-
title="HearthNet Β· Document Intelligence",
|
| 300 |
-
) as demo:
|
| 301 |
-
# ββ Header ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 302 |
-
gr.HTML("""
|
| 303 |
-
<div class="grad-banner">
|
| 304 |
-
<h1>π¬ HearthNet Β· Document Intelligence</h1>
|
| 305 |
-
<p>Structured extraction & Q&A powered by NVIDIA Nemotron Β· Part of the HearthNet mesh</p>
|
| 306 |
-
</div>
|
| 307 |
-
<p>
|
| 308 |
-
<span class="feature-badge" style="background:#7c3aed;color:white">NVIDIA Nemotron</span>
|
| 309 |
-
<span class="feature-badge" style="background:#f97316;color:white">Structured Extraction</span>
|
| 310 |
-
<span class="feature-badge" style="background:#0ea5e9;color:white">Offline Capable</span>
|
| 311 |
-
<span class="feature-badge" style="background:#10b981;color:white">Mesh RAG Ingest</span>
|
| 312 |
-
</p>
|
| 313 |
-
""")
|
| 314 |
-
|
| 315 |
-
# ββ Shared controls (sidebar-style top row) ββββββββββββββββββββββββββββ
|
| 316 |
-
with gr.Row():
|
| 317 |
-
model_selector = gr.Dropdown(
|
| 318 |
-
label="π€ Nemotron Model",
|
| 319 |
-
choices=list(_MODELS.keys()),
|
| 320 |
-
value=list(_MODELS.keys())[0],
|
| 321 |
-
scale=2,
|
| 322 |
-
)
|
| 323 |
-
api_key_box = gr.Textbox(
|
| 324 |
-
label="π NVIDIA API Key",
|
| 325 |
-
value="",
|
| 326 |
-
type="password",
|
| 327 |
-
placeholder="nvapi-... leave blank if NVIDIA_API_KEY env var is set",
|
| 328 |
-
scale=3,
|
| 329 |
-
)
|
| 330 |
-
|
| 331 |
-
# ββ Main tabs ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 332 |
-
with gr.Tabs():
|
| 333 |
-
|
| 334 |
-
# ββ Tab 1: Structured Extraction ββββββββββββββββββββββββββββββββββ
|
| 335 |
-
with gr.Tab("π Extract"):
|
| 336 |
-
with gr.Row():
|
| 337 |
-
with gr.Column(scale=2):
|
| 338 |
-
extract_doc = gr.Textbox(
|
| 339 |
-
label="Document",
|
| 340 |
-
placeholder="Paste text, or upload a file below...",
|
| 341 |
-
lines=12,
|
| 342 |
-
)
|
| 343 |
-
extract_file = gr.File(
|
| 344 |
-
label="Upload file",
|
| 345 |
-
type="filepath",
|
| 346 |
-
file_types=[".txt", ".md", ".csv"],
|
| 347 |
-
)
|
| 348 |
-
schema_preset = gr.Dropdown(
|
| 349 |
-
label="Schema preset",
|
| 350 |
-
choices=list(_SCHEMAS.keys()),
|
| 351 |
-
value="Invoice / Receipt",
|
| 352 |
-
)
|
| 353 |
-
custom_schema = gr.Code(
|
| 354 |
-
label="Schema (JSON)",
|
| 355 |
-
language="json",
|
| 356 |
-
value=_SCHEMAS["Invoice / Receipt"],
|
| 357 |
-
lines=8,
|
| 358 |
-
)
|
| 359 |
-
|
| 360 |
-
with gr.Column(scale=3):
|
| 361 |
-
extract_btn = gr.Button("β‘ Extract with Nemotron", variant="primary", size="lg")
|
| 362 |
-
extract_out = gr.Code(label="Extracted JSON", language="json", lines=16)
|
| 363 |
-
extract_status = gr.Textbox(label="Status", lines=1, interactive=False)
|
| 364 |
-
|
| 365 |
-
def on_preset_change(preset):
|
| 366 |
-
return _SCHEMAS.get(preset, "{}")
|
| 367 |
-
|
| 368 |
-
schema_preset.change(on_preset_change, inputs=[schema_preset], outputs=[custom_schema])
|
| 369 |
-
|
| 370 |
-
def load_extract_file(fp):
|
| 371 |
-
if not fp:
|
| 372 |
-
return ""
|
| 373 |
-
try:
|
| 374 |
-
with open(fp, encoding="utf-8", errors="replace") as f:
|
| 375 |
-
return f.read(8000)
|
| 376 |
-
except Exception as e:
|
| 377 |
-
return f"Error: {e}"
|
| 378 |
-
|
| 379 |
-
extract_file.change(load_extract_file, inputs=[extract_file], outputs=[extract_doc])
|
| 380 |
-
extract_btn.click(
|
| 381 |
-
extract_structured,
|
| 382 |
-
inputs=[extract_doc, schema_preset, custom_schema, model_selector, api_key_box],
|
| 383 |
-
outputs=[extract_out, extract_status],
|
| 384 |
-
)
|
| 385 |
-
|
| 386 |
-
# ββ Tab 2: Document Q&A βββββββββββββββββββββββββββββββββββββββββββ
|
| 387 |
-
with gr.Tab("π¬ Ask"):
|
| 388 |
-
with gr.Row():
|
| 389 |
-
with gr.Column(scale=2):
|
| 390 |
-
ask_doc = gr.Textbox(
|
| 391 |
-
label="Document",
|
| 392 |
-
placeholder="Paste the document to query...",
|
| 393 |
-
lines=14,
|
| 394 |
-
)
|
| 395 |
-
|
| 396 |
-
with gr.Column(scale=3):
|
| 397 |
-
ask_question_box = gr.Textbox(
|
| 398 |
-
label="Question",
|
| 399 |
-
placeholder="What is the total? Who are the parties? What are the obligations?",
|
| 400 |
-
lines=2,
|
| 401 |
-
)
|
| 402 |
-
ask_btn = gr.Button("π Ask Nemotron", variant="primary")
|
| 403 |
-
ask_out = gr.Textbox(label="Answer", lines=8)
|
| 404 |
-
|
| 405 |
-
ask_btn.click(
|
| 406 |
-
ask_document,
|
| 407 |
-
inputs=[ask_doc, ask_question_box, model_selector, api_key_box],
|
| 408 |
-
outputs=[ask_out],
|
| 409 |
-
)
|
| 410 |
-
|
| 411 |
-
# ββ Tab 3: Summarise ββββββββββββββββββββββββββββββββββββββββββββββ
|
| 412 |
-
with gr.Tab("β Summarise"):
|
| 413 |
-
with gr.Row():
|
| 414 |
-
with gr.Column(scale=2):
|
| 415 |
-
sum_doc = gr.Textbox(
|
| 416 |
-
label="Document",
|
| 417 |
-
placeholder="Paste document text...",
|
| 418 |
-
lines=14,
|
| 419 |
-
)
|
| 420 |
-
|
| 421 |
-
with gr.Column(scale=3):
|
| 422 |
-
sum_style = gr.Dropdown(
|
| 423 |
-
label="Summary style",
|
| 424 |
-
choices=[
|
| 425 |
-
"Executive (3 bullets)",
|
| 426 |
-
"Detailed (paragraph)",
|
| 427 |
-
"ELI5 (simple)",
|
| 428 |
-
"Action items only",
|
| 429 |
-
],
|
| 430 |
-
value="Executive (3 bullets)",
|
| 431 |
-
)
|
| 432 |
-
sum_btn = gr.Button("β Summarise with Nemotron", variant="primary")
|
| 433 |
-
sum_out = gr.Textbox(label="Summary", lines=10)
|
| 434 |
-
|
| 435 |
-
sum_btn.click(
|
| 436 |
-
summarise_document,
|
| 437 |
-
inputs=[sum_doc, sum_style, model_selector, api_key_box],
|
| 438 |
-
outputs=[sum_out],
|
| 439 |
-
)
|
| 440 |
-
|
| 441 |
-
# ββ Tab 4: Push to Mesh βββββββββββββββββββββββββββββββββββββββββββ
|
| 442 |
-
with gr.Tab("πΈ Push to Mesh"):
|
| 443 |
-
gr.Markdown(
|
| 444 |
-
"Send extracted/processed documents into a HearthNet mesh node's RAG corpus. "
|
| 445 |
-
"After ingesting, documents become searchable from any mesh node's **Ask** tab."
|
| 446 |
-
)
|
| 447 |
-
with gr.Row():
|
| 448 |
-
with gr.Column():
|
| 449 |
-
mesh_doc = gr.Textbox(
|
| 450 |
-
label="Document text",
|
| 451 |
-
placeholder="Paste processed document...",
|
| 452 |
-
lines=10,
|
| 453 |
-
)
|
| 454 |
-
mesh_title = gr.Textbox(label="Document title", placeholder="Invoice #123")
|
| 455 |
-
mesh_corpus = gr.Textbox(label="Corpus name", value="documents")
|
| 456 |
-
mesh_url = gr.Textbox(
|
| 457 |
-
label="HearthNet mesh node URL",
|
| 458 |
-
value=_MESH_NODE,
|
| 459 |
-
placeholder="http://localhost:7860 or https://your-space.hf.space",
|
| 460 |
-
)
|
| 461 |
-
mesh_push_btn = gr.Button("π Push to mesh", variant="primary")
|
| 462 |
-
|
| 463 |
-
with gr.Column():
|
| 464 |
-
mesh_status = gr.Textbox(label="Status", lines=5)
|
| 465 |
-
gr.Markdown(
|
| 466 |
-
"""
|
| 467 |
-
**How to use with the HearthNet main Space:**
|
| 468 |
-
1. Set `HEARTHNET_NODE = https://build-small-hackathon-hearthnet.hf.space`
|
| 469 |
-
2. Or run locally: `python app.py` β `http://localhost:7860`
|
| 470 |
-
3. Documents ingested here appear in the **Ask** tab on all mesh nodes
|
| 471 |
-
|
| 472 |
-
**Local multi-node example:**
|
| 473 |
-
```bash
|
| 474 |
-
# Node 1 (main mesh)
|
| 475 |
-
python app.py --port 7860
|
| 476 |
-
|
| 477 |
-
# Node 2 (this document intelligence app)
|
| 478 |
-
python app_nemotron.py --port 7861
|
| 479 |
-
HEARTHNET_NODE=http://localhost:7860
|
| 480 |
-
```
|
| 481 |
-
"""
|
| 482 |
-
)
|
| 483 |
-
|
| 484 |
-
mesh_push_btn.click(
|
| 485 |
-
push_to_mesh,
|
| 486 |
-
inputs=[mesh_doc, mesh_title, mesh_corpus, mesh_url],
|
| 487 |
-
outputs=[mesh_status],
|
| 488 |
-
)
|
| 489 |
-
|
| 490 |
-
# ββ Tab 5: About ββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 491 |
-
with gr.Tab("βΉ About"):
|
| 492 |
-
gr.Markdown(
|
| 493 |
-
f"""
|
| 494 |
-
## HearthNet Document Intelligence
|
| 495 |
-
|
| 496 |
-
A companion app to the [HearthNet mesh](https://huggingface.co/spaces/build-small-hackathon/HearthNet)
|
| 497 |
-
that adds NVIDIA Nemotron-powered document processing.
|
| 498 |
-
|
| 499 |
-
### Models
|
| 500 |
-
| Model | Size | Best for |
|
| 501 |
-
|-------|------|---------|
|
| 502 |
-
| Nemotron Nano 8B | 8B | Fast extraction, Pi-friendly |
|
| 503 |
-
| Nemotron 70B | 70B | Deep reasoning, complex docs |
|
| 504 |
-
| Nemotron Super 49B | 49B | Balanced quality/speed |
|
| 505 |
-
|
| 506 |
-
All models are under 32B parameters individually β
|
| 507 |
-
|
| 508 |
-
### Architecture
|
| 509 |
-
```
|
| 510 |
-
Document Input βββΊ Nemotron Parse βββΊ Structured JSON
|
| 511 |
-
βββΊ Q&A Answers
|
| 512 |
-
βββΊ Summary
|
| 513 |
-
β
|
| 514 |
-
βΌ
|
| 515 |
-
HearthNet RAG Corpus
|
| 516 |
-
(searchable on all mesh nodes)
|
| 517 |
-
```
|
| 518 |
-
|
| 519 |
-
### Prize Targets
|
| 520 |
-
- π **NVIDIA Nemotron Hardware Prize** (RTX 5080) β builds with Nemotron β
|
| 521 |
-
- π **Tiny Titan** β Nano 8B model β
|
| 522 |
-
- π¨ **Off Brand** β Custom purple-to-orange UI β
|
| 523 |
-
|
| 524 |
-
### Links
|
| 525 |
-
- [Main HearthNet Space](https://huggingface.co/spaces/build-small-hackathon/HearthNet)
|
| 526 |
-
- [HF Profile](https://huggingface.co/Chris4K)
|
| 527 |
-
- [X / Twitter](https://x.com/zX14_7)
|
| 528 |
-
- [GitHub](https://github.com/ckal)
|
| 529 |
-
- [NVIDIA NIM API](https://build.nvidia.com) β free tier available
|
| 530 |
-
|
| 531 |
-
**Current status:** API key: {'β configured' if _NVIDIA_KEY else 'β not set (add NVIDIA_API_KEY)'}
|
| 532 |
-
**Mesh node:** {_MESH_NODE or 'β not set (add HEARTHNET_NODE)'}
|
| 533 |
-
"""
|
| 534 |
-
)
|
| 535 |
-
|
| 536 |
-
return demo
|
| 537 |
-
|
| 538 |
-
|
| 539 |
-
if __name__ == "__main__":
|
| 540 |
-
demo = build_app()
|
| 541 |
-
# HF Spaces health-checks port 7860. Prefer GRADIO_SERVER_PORT (set by HF),
|
| 542 |
-
# fall back to PORT, then 7860. Disable SSR: the Node proxy binds a different
|
| 543 |
-
# port and crashes on HF, leaving :7860 unhealthy -> launch timeout.
|
| 544 |
-
_port = int(os.getenv("GRADIO_SERVER_PORT") or os.getenv("PORT") or "7860")
|
| 545 |
-
demo.launch(
|
| 546 |
-
server_name="0.0.0.0", # nosec B104
|
| 547 |
-
server_port=_port,
|
| 548 |
-
ssr_mode=False,
|
| 549 |
-
theme=_theme,
|
| 550 |
-
css="""
|
| 551 |
-
.grad-banner { background: linear-gradient(135deg, #7c3aed 0%, #f97316 100%);
|
| 552 |
-
border-radius: 12px; padding: 16px 24px; margin-bottom: 16px; }
|
| 553 |
-
.grad-banner h1 { color: white !important; margin: 0; }
|
| 554 |
-
.grad-banner p { color: rgba(255,255,255,0.85) !important; margin: 4px 0 0; }
|
| 555 |
-
.feature-badge { display: inline-block; padding: 2px 10px; border-radius: 12px;
|
| 556 |
-
font-size: 0.78em; font-weight: 600; margin: 2px; }
|
| 557 |
-
""",
|
| 558 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assets/initial_docs/README.md
DELETED
|
@@ -1,13 +0,0 @@
|
|
| 1 |
-
# HearthNet β Initial Documents
|
| 2 |
-
|
| 3 |
-
Drop any `.md` or `.txt` files here and they will be automatically ingested into
|
| 4 |
-
the community RAG corpus when the node starts.
|
| 5 |
-
|
| 6 |
-
Good candidates:
|
| 7 |
-
- Neighbourhood emergency plans
|
| 8 |
-
- Local resource lists (food banks, shelters, medical points)
|
| 9 |
-
- How-to guides for your community
|
| 10 |
-
- Node setup instructions for non-technical neighbours
|
| 11 |
-
- Any knowledge you want to make searchable across the mesh
|
| 12 |
-
|
| 13 |
-
Files are deduplicated by content hash (BLAKE3), so re-adding the same file is safe.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
build/android/HearthNetApp/config.xml
DELETED
|
@@ -1,32 +0,0 @@
|
|
| 1 |
-
<?xml version='1.0' encoding='utf-8'?>
|
| 2 |
-
<widget id="com.hearthnet.app" version="0.1.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
|
| 3 |
-
<name>HearthNet</name>
|
| 4 |
-
<description>Local-first community AI mesh</description>
|
| 5 |
-
<author email="contact@hearthnet.community" href="https://hearthnet.community">HearthNet</author>
|
| 6 |
-
<content src="index.html" />
|
| 7 |
-
|
| 8 |
-
<!-- Allow access to all domains -->
|
| 9 |
-
<access origin="*" />
|
| 10 |
-
<allow-intent href="http://*/*" />
|
| 11 |
-
<allow-intent href="https://*/*" />
|
| 12 |
-
<allow-intent href="tel:*" />
|
| 13 |
-
<allow-intent href="sms:*" />
|
| 14 |
-
<allow-intent href="mailto:*" />
|
| 15 |
-
<allow-intent href="geo:*" />
|
| 16 |
-
|
| 17 |
-
<!-- iOS preferences -->
|
| 18 |
-
<preference name="EnableViewportScale" value="true" />
|
| 19 |
-
<preference name="MediaPlaybackRequiresUserAction" value="false" />
|
| 20 |
-
<preference name="AllowInlineMediaPlayback" value="true" />
|
| 21 |
-
<preference name="BackupWebStorage" value="cloud" />
|
| 22 |
-
<preference name="TopActivityIndicator" value="gray" />
|
| 23 |
-
|
| 24 |
-
<!-- Android preferences -->
|
| 25 |
-
<preference name="Orientation" value="portrait" />
|
| 26 |
-
<preference name="Fullscreen" value="false" />
|
| 27 |
-
<preference name="android-minSdkVersion" value="21" />
|
| 28 |
-
<preference name="android-targetSdkVersion" value="36" />
|
| 29 |
-
<preference name="StatusBarOverlaysWebView" value="false" />
|
| 30 |
-
<preference name="StatusBarBackgroundColor" value="#1e40af" />
|
| 31 |
-
<preference name="StatusBarStyle" value="lightcontent" />
|
| 32 |
-
</widget>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
coverage_report.txt
DELETED
|
Binary file (164 Bytes)
|
|
|
data/hearthnet/hearthnet-space/corpora/test.md
DELETED
|
@@ -1 +0,0 @@
|
|
| 1 |
-
test file. Paris is the capitol of germany
|
|
|
|
|
|
data/hearthnet/test.md
DELETED
|
File without changes
|
docs/ARCHITECTURE.md
DELETED
|
@@ -1,441 +0,0 @@
|
|
| 1 |
-
# HearthNet β Architecture Reference
|
| 2 |
-
|
| 3 |
-
> **Local-first community AI mesh.** Each participant runs a node on their own hardware.
|
| 4 |
-
> Nodes discover each other automatically and share AI capabilities, files, and community
|
| 5 |
-
> posts β no central server required.
|
| 6 |
-
|
| 7 |
-
---
|
| 8 |
-
|
| 9 |
-
## High-Level Concept
|
| 10 |
-
|
| 11 |
-
```
|
| 12 |
-
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 13 |
-
β Community Mesh (LAN / overlay) β
|
| 14 |
-
β β
|
| 15 |
-
β βββββββββββββββ mDNS/UDP βββββββββββββββ mDNS/UDP β
|
| 16 |
-
β β Node A ββββββββββββββββββΊβ Node B ββββββββββββββββ β
|
| 17 |
-
β β (anchor) β β (hearth) β β
|
| 18 |
-
β β β capability β β β
|
| 19 |
-
β β CapBus βββββΌβββββbus.callββββΊββΊ CapBus β β
|
| 20 |
-
β β LLM svc β β RAG svc β β
|
| 21 |
-
β β RAG svc β β OCR svc β β
|
| 22 |
-
β β Gradio UI β β Gradio UI β β
|
| 23 |
-
β βββββββββββββββ βββββββββββββββ β
|
| 24 |
-
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 25 |
-
```
|
| 26 |
-
|
| 27 |
-
HearthNet is structured around three ideas:
|
| 28 |
-
|
| 29 |
-
1. **Node** β a Python process on someone's hardware (Raspberry Pi, laptop, server).
|
| 30 |
-
2. **CapabilityBus** β a message bus where services register *capabilities* (e.g. `llm.chat@1.0`). Any code, local or remote, calls a capability by name.
|
| 31 |
-
3. **Services** β pure-Python objects that handle capability calls. A node installs whichever services its hardware supports.
|
| 32 |
-
|
| 33 |
-
---
|
| 34 |
-
|
| 35 |
-
## Module Map
|
| 36 |
-
|
| 37 |
-
### Phase 1 β Foundation
|
| 38 |
-
|
| 39 |
-
| Module | Location | What it does |
|
| 40 |
-
|--------|----------|-------------|
|
| 41 |
-
| **M01 Identity** | `hearthnet/identity/` | Ed25519 node keys, community manifests, invite tokens |
|
| 42 |
-
| **M02 Discovery** | `hearthnet/discovery/` | mDNS + UDP multicast peer discovery |
|
| 43 |
-
| **M03 Bus** | `hearthnet/bus/` | Capability router, health ring buffer, trust levels |
|
| 44 |
-
| **M04 LLM** | `hearthnet/services/llm/` | Local model backends (Ollama, llama.cpp, LM Studio, HF, Anthropic) |
|
| 45 |
-
| **M05 RAG** | `hearthnet/services/rag/` | Chunker β embedder β Chroma vector store + retrieval |
|
| 46 |
-
| **M06 Marketplace** | `hearthnet/services/marketplace/` | Event-sourced community board (posts, offers, requests) |
|
| 47 |
-
| **M07 Blobs** | `hearthnet/blobs/` | BLAKE3 content-addressed file store with chunked transfer |
|
| 48 |
-
| **M08 UI** | `hearthnet/ui/` | Gradio 8-tab interface + themes + topology component |
|
| 49 |
-
| **M09 Emergency** | `hearthnet/emergency/` | Async probe loop β emergency state machine |
|
| 50 |
-
| **M10 Chat** | `hearthnet/services/chat/` | Event-backed direct messages between nodes |
|
| 51 |
-
| **M11 Embedding** | `hearthnet/services/embedding/` | Sentence-transformer embeddings (BAAI/bge-small) |
|
| 52 |
-
| **M12 CLI** | `hearthnet/cli.py` | Click CLI: run, call, log, rag, invite, version, β¦ |
|
| 53 |
-
| **M13 Onboarding** | `hearthnet/ui/onboarding.py` | Invite QR flow + first-run wizard |
|
| 54 |
-
|
| 55 |
-
### Phase 2 β Resilience & Rich Services
|
| 56 |
-
|
| 57 |
-
| Module | Location | What it does |
|
| 58 |
-
|--------|----------|-------------|
|
| 59 |
-
| **M14 Federation** | `hearthnet/federation/` | Cross-community node manifests + signed bridges |
|
| 60 |
-
| **M15 Relay** | `hearthnet/relay/` | Public-IP relay tier for NAT traversal |
|
| 61 |
-
| **M16 Tokens** | `hearthnet/identity/tokens.py` | AuthToken / CapabilityToken scoped access |
|
| 62 |
-
| **M17 OCR** | `hearthnet/services/ocr/` | Tesseract / TrOCR text extraction |
|
| 63 |
-
| **M18 Translation** | `hearthnet/services/translation/` | NLLB-200 local translation |
|
| 64 |
-
| **M19 STT/TTS** | `hearthnet/services/stt_tts/` | Whisper STT + Coqui/pyttsx3 TTS |
|
| 65 |
-
| **M20 Vision** | `hearthnet/services/vision/` | Florence-2 image captioning / VQA |
|
| 66 |
-
| **M21 Tool Calls** | `hearthnet/services/tools/` | LLM tool-call executor (plant ID, search, β¦) |
|
| 67 |
-
| **M22 Mobile** | `hearthnet/ui/mobile/` | PWA manifest + service worker for home-screen install |
|
| 68 |
-
| **M23 E2E Encryption** | `hearthnet/crypto/` | X25519 ECDH + ChaCha20-Poly1305 channel encryption |
|
| 69 |
-
| **M24 Rerank** | `hearthnet/services/rerank/` | Cross-encoder reranking for RAG results |
|
| 70 |
-
| **M25 Group Chat** | `hearthnet/services/group_chat/` | Multi-party room-based chat |
|
| 71 |
-
|
| 72 |
-
### Phase 3 β Experimental (opt-in via `config.toml`)
|
| 73 |
-
|
| 74 |
-
| Module | Location | Flag | What it does |
|
| 75 |
-
|--------|----------|------|-------------|
|
| 76 |
-
| **M26 Distributed Inference** | `hearthnet/distributed_inference/` | `research.distributed_inference` | Layer-shard a 7B model across LAN nodes (Petals-style) |
|
| 77 |
-
| **M27 MoE Routing** | `hearthnet/moe/` | `research.moe_routing` | Route queries to best expert (model/service/human) via learned scorer |
|
| 78 |
-
| **M28 FedLearn** | `hearthnet/fedlearn/` | `research.fedlearn` | FedAvg LoRA fine-tuning without sharing raw data |
|
| 79 |
-
| **M29 LoRa Beacons** | `hearthnet/lora/` | `research.lora_beacons` | 868 MHz offline "I'm alive" heartbeats via USB LoRa stick |
|
| 80 |
-
| **M30 Evidence Graph** | `hearthnet/evidence/` | `research.evidence` | Claim β attest β dispute provenance graph + EBKH bridge |
|
| 81 |
-
| **M31 Civil Defense** | `hearthnet/civdef/` | `research.civil_defense` | THW/DRK/KatS alert pipeline with role certs + audit chain |
|
| 82 |
-
| **M32 Protocol Standard** | `hearthnet/services/protocol/` | on by default | Protocol version list + conformance report |
|
| 83 |
-
|
| 84 |
-
### Cross-Cutting
|
| 85 |
-
|
| 86 |
-
| ID | Location | What it does |
|
| 87 |
-
|----|----------|-------------|
|
| 88 |
-
| **X01 Transport** | `hearthnet/transport/` | HTTP/SSE client, backpressure, rate limiting, frame types |
|
| 89 |
-
| **X02 Events** | `hearthnet/events/` | SQLite Lamport event log + gossip sync |
|
| 90 |
-
| **X03 Observability** | `hearthnet/observability/` | Tracing, metrics, Doctor health checks, TrackioExporter |
|
| 91 |
-
| **X04 Config** | `hearthnet/config.py` | Typed TOML config + ResearchConfig feature flags |
|
| 92 |
-
| **X05 DHT** | `hearthnet/dht/` | Kademlia-inspired DHT for cross-LAN peer lookup |
|
| 93 |
-
| **X06 WebSocket** | `hearthnet/transport/` | WebSocket pubsub (StateBus β live UI push) |
|
| 94 |
-
| **X07 Federated Metrics** | `hearthnet/observability/` | Opt-in aggregate mesh health metrics |
|
| 95 |
-
| **X08 Tensor Transport** | `hearthnet/transport/tensor/` | Chunked tensor stream for M26 distributed inference |
|
| 96 |
-
| **X09 Conformance Suite** | `hearthnet/conformance/` | 21-check black-box conformance runner |
|
| 97 |
-
|
| 98 |
-
---
|
| 99 |
-
|
| 100 |
-
## Composition Root
|
| 101 |
-
|
| 102 |
-
`HearthNode` in [hearthnet/node.py](hearthnet/node.py) is the single composition root.
|
| 103 |
-
|
| 104 |
-
```python
|
| 105 |
-
node = HearthNode(
|
| 106 |
-
node_id="my-node",
|
| 107 |
-
display_name="Alice's Pi",
|
| 108 |
-
community_id="ed25519:abc123",
|
| 109 |
-
)
|
| 110 |
-
node.install_services(corpus="general")
|
| 111 |
-
await node.start()
|
| 112 |
-
```
|
| 113 |
-
|
| 114 |
-
`install_services()` registers all services the local hardware supports into the bus. Heavy optional dependencies (torch, chromadb, etc.) are imported lazily and fail gracefully β a node with no GPU still works, it just can't answer GPU-only capabilities.
|
| 115 |
-
|
| 116 |
-
---
|
| 117 |
-
|
| 118 |
-
## Capability Bus
|
| 119 |
-
|
| 120 |
-
```
|
| 121 |
-
Caller ββββ bus.call(name, version, body) βββββββββββ
|
| 122 |
-
βΌ
|
| 123 |
-
ββββββββββββββββββββ
|
| 124 |
-
β CapabilityBus β
|
| 125 |
-
β β
|
| 126 |
-
β Registry β
|
| 127 |
-
β βββββββββββββββ β
|
| 128 |
-
β β local route βββΌβββΊ Service.handle()
|
| 129 |
-
β βββββββββββββββ€ β
|
| 130 |
-
β β remote routeβββΌβββΊ HTTP POST /bus/v1/call
|
| 131 |
-
β βββββββββββββββ β
|
| 132 |
-
β HealthMonitor β
|
| 133 |
-
β TrustFilter β
|
| 134 |
-
ββββββββββββββββββββ
|
| 135 |
-
```
|
| 136 |
-
|
| 137 |
-
- **Local route** β service is installed on this node β direct Python call.
|
| 138 |
-
- **Remote route** β capability is advertised by a peer β HTTP POST to that peer's transport.
|
| 139 |
-
- **Version negotiation** β capabilities are registered with a `(major, minor)` version; the bus picks the highest compatible version.
|
| 140 |
-
- **Health monitoring** β each service's response times are tracked in a ring buffer; unhealthy services are quarantined for `BUS_QUARANTINE_SECONDS`.
|
| 141 |
-
|
| 142 |
-
---
|
| 143 |
-
|
| 144 |
-
## Data Flow: LLM Chat Request
|
| 145 |
-
|
| 146 |
-
```
|
| 147 |
-
User types in Gradio UI
|
| 148 |
-
β
|
| 149 |
-
βΌ
|
| 150 |
-
app.py (Gradio event handler)
|
| 151 |
-
β bus.call("llm.chat@1.0", body)
|
| 152 |
-
βΌ
|
| 153 |
-
CapabilityBus.call()
|
| 154 |
-
β
|
| 155 |
-
ββ local LlmService found?
|
| 156 |
-
β β yes β LlmService.handle() β backend.chat() β yield Token
|
| 157 |
-
β β
|
| 158 |
-
ββ no local service
|
| 159 |
-
β peer has llm.chat?
|
| 160 |
-
ββ yes β HTTP POST /bus/v1/call β remote node β stream tokens back
|
| 161 |
-
ββ no β CapabilityError("not_found")
|
| 162 |
-
```
|
| 163 |
-
|
| 164 |
-
---
|
| 165 |
-
|
| 166 |
-
## Discovery Flow
|
| 167 |
-
|
| 168 |
-
```
|
| 169 |
-
Node boots
|
| 170 |
-
β
|
| 171 |
-
βββ mDNS: register _hearthnet._tcp.local. (LAN multicast DNS)
|
| 172 |
-
βββ UDP: send announce to 224.0.0.251:7079 every 15s
|
| 173 |
-
β
|
| 174 |
-
βΌ
|
| 175 |
-
PeerRegistry receives announcements from other nodes
|
| 176 |
-
β
|
| 177 |
-
βββ new peer β RegistryEvent(kind="added", entry=...)
|
| 178 |
-
βββ peer gone (TTL expired) β RegistryEvent(kind="removed", ...)
|
| 179 |
-
βββ ManifestPublisher re-publishes every 300s
|
| 180 |
-
```
|
| 181 |
-
|
| 182 |
-
---
|
| 183 |
-
|
| 184 |
-
## Emergency Mode
|
| 185 |
-
|
| 186 |
-
```
|
| 187 |
-
EmergencyDetector (async loop, 30s probe)
|
| 188 |
-
β
|
| 189 |
-
βββ probe connectivity endpoints
|
| 190 |
-
β
|
| 191 |
-
βββ ONLINE β EmergencyState.NORMAL
|
| 192 |
-
β β UI shows normal theme
|
| 193 |
-
β
|
| 194 |
-
βββ OFFLINE β EmergencyState.EMERGENCY
|
| 195 |
-
β UI switches to emergency theme (red)
|
| 196 |
-
β emergency.llm.chat capability activated
|
| 197 |
-
β LoRa beacons sent if hardware available (M29)
|
| 198 |
-
β Civil defense alerts published if role cert present (M31)
|
| 199 |
-
```
|
| 200 |
-
|
| 201 |
-
---
|
| 202 |
-
|
| 203 |
-
## MoE Expert Routing (M27)
|
| 204 |
-
|
| 205 |
-
```
|
| 206 |
-
Query arrives at any node
|
| 207 |
-
β
|
| 208 |
-
βΌ
|
| 209 |
-
MoeRouter.route(query, top_k=3)
|
| 210 |
-
β
|
| 211 |
-
βββ score all registered ExpertDescriptors against query
|
| 212 |
-
β (tag overlap + cosine similarity + recency weighting)
|
| 213 |
-
β
|
| 214 |
-
βββ return ranked RouteResult
|
| 215 |
-
β
|
| 216 |
-
βββ expert_type="model" β bus.call(f"llm.chat@1.0", ...) on that node
|
| 217 |
-
βββ expert_type="service" β bus.call(expert_capability, ...)
|
| 218 |
-
βββ expert_type="human" β notify via chat + start handoff timer (M27 Β§4)
|
| 219 |
-
βββ expert_type="external"β HTTP call to opt-in external API
|
| 220 |
-
```
|
| 221 |
-
|
| 222 |
-
Enable it: set `research.moe_routing = true` in `~/.config/hearthnet/config.toml`.
|
| 223 |
-
|
| 224 |
-
---
|
| 225 |
-
|
| 226 |
-
## Distributed Inference (M26 β BitTorrent-style LLM sharing)
|
| 227 |
-
|
| 228 |
-
```
|
| 229 |
-
Node A: layers 0β15 of Llama-3.2-3B
|
| 230 |
-
Node B: layers 16β27 of Llama-3.2-3B
|
| 231 |
-
Node C: layers 28β35 (lm_head) of Llama-3.2-3B
|
| 232 |
-
β
|
| 233 |
-
βΌ
|
| 234 |
-
PipelineOrchestrator.plan(model_id="llama3.2:3b")
|
| 235 |
-
β β discovers shards via experimental.distributed_llm.shard.list
|
| 236 |
-
β β checks layer coverage: 0..35 β
|
| 237 |
-
β
|
| 238 |
-
PipelineOrchestrator.run(pipeline, input_tokens)
|
| 239 |
-
β β sends activations AβB via X08 TensorTransport (1 MiB chunks)
|
| 240 |
-
β β B sends activations BβC
|
| 241 |
-
β β C returns final logits
|
| 242 |
-
β
|
| 243 |
-
βββ caller gets streamed tokens like any local model
|
| 244 |
-
```
|
| 245 |
-
|
| 246 |
-
Model weights are shared chunk-by-chunk using BLAKE3 CID-addressed blob transfer β same
|
| 247 |
-
mechanism as file blobs (M07), but optimised for `.gguf` / `.safetensors` files.
|
| 248 |
-
|
| 249 |
-
---
|
| 250 |
-
|
| 251 |
-
## File Tree
|
| 252 |
-
|
| 253 |
-
```
|
| 254 |
-
hearthnet/
|
| 255 |
-
βββ node.py # HearthNode β composition root
|
| 256 |
-
βββ types.py # Shared type aliases (NodeID, ShardID, AlertID, β¦)
|
| 257 |
-
βββ constants.py # All numeric defaults and limits
|
| 258 |
-
βββ config.py # HearthnetConfig + ResearchConfig (TOML-backed)
|
| 259 |
-
βββ cli.py # Click CLI entry point
|
| 260 |
-
βββ facades.py # HearthFacade β thin high-level API for app.py
|
| 261 |
-
βββ controller.py # HearthController β legacy thin wrapper
|
| 262 |
-
β
|
| 263 |
-
βββ bus/ # M03 CapabilityBus
|
| 264 |
-
β βββ router.py # routing logic (local β remote)
|
| 265 |
-
β βββ registry.py # CapabilityEntry, RegistryEvent, Diff
|
| 266 |
-
β βββ capability.py # CapabilityEntry dataclass
|
| 267 |
-
β βββ health.py # ring-buffer health monitor
|
| 268 |
-
β
|
| 269 |
-
βββ identity/ # M01
|
| 270 |
-
β βββ keys.py # Ed25519 key generation + signing
|
| 271 |
-
β βββ manifest.py # NodeManifest, CommunityManifest, CommunityPolicy, β¦
|
| 272 |
-
β βββ tokens.py # AuthToken, CapabilityToken
|
| 273 |
-
β
|
| 274 |
-
βββ discovery/ # M02
|
| 275 |
-
β βββ peers.py # mDNS + UDP multicast PeerRegistry
|
| 276 |
-
β
|
| 277 |
-
βββ transport/ # X01 / X06 / X08
|
| 278 |
-
β βββ client.py # HTTP + SSE client
|
| 279 |
-
β βββ streams.py # Frame, SseReader
|
| 280 |
-
β βββ backpressure.py # FlowControl, RateCheck, RateLimiter
|
| 281 |
-
β βββ tensor/ # X08 tensor chunked transport
|
| 282 |
-
β
|
| 283 |
-
βββ events/ # X02
|
| 284 |
-
β βββ log.py # SQLite Lamport event log
|
| 285 |
-
β βββ sync.py # Gossip SyncClient / SyncServer
|
| 286 |
-
β
|
| 287 |
-
βββ observability/ # X03
|
| 288 |
-
β βββ tracing.py # attach/detach trace context
|
| 289 |
-
β βββ metrics.py # MetricsCollector, TrackioExporter
|
| 290 |
-
β βββ doctor.py # DoctorResult, CheckResult, DoctorService
|
| 291 |
-
β
|
| 292 |
-
βββ services/ # M04 β M21 + M32
|
| 293 |
-
β βββ llm/ # M04 β backends: ollama, llama_cpp, lmstudio, hf_api, anthropic
|
| 294 |
-
β βββ rag/ # M05
|
| 295 |
-
β βββ marketplace/ # M06
|
| 296 |
-
β βββ chat/ # M10
|
| 297 |
-
β βββ embedding/ # M11
|
| 298 |
-
β βββ ocr/ # M17
|
| 299 |
-
β βββ translation/ # M18
|
| 300 |
-
β βββ stt_tts/ # M19
|
| 301 |
-
β βββ vision/ # M20
|
| 302 |
-
β βββ tools/ # M21
|
| 303 |
-
β βββ group_chat/ # M25
|
| 304 |
-
β βββ protocol/ # M32
|
| 305 |
-
β
|
| 306 |
-
βββ ui/ # M08
|
| 307 |
-
β βββ app.py # Gradio 8-tab entry point
|
| 308 |
-
β βββ tabs/ # one file per tab
|
| 309 |
-
β βββ theme.py # hearthnet_theme, emergency_theme
|
| 310 |
-
β βββ topology.py # TopologyComponent (mesh graph)
|
| 311 |
-
β βββ onboarding.py # first-run wizard + invite QR
|
| 312 |
-
β βββ mobile/ # M22 PWA manifest + service worker
|
| 313 |
-
β
|
| 314 |
-
βββ emergency/ # M09
|
| 315 |
-
β βββ detector.py # async probe loop
|
| 316 |
-
β βββ state.py # EmergencyState enum
|
| 317 |
-
β
|
| 318 |
-
βββ crypto/ # M23
|
| 319 |
-
β βββ channel.py # X25519 + ChaCha20-Poly1305
|
| 320 |
-
β
|
| 321 |
-
βββ blobs/ # M07
|
| 322 |
-
β βββ store.py # BLAKE3 CID store + chunked reader
|
| 323 |
-
β
|
| 324 |
-
βββ dht/ # X05
|
| 325 |
-
βββ federation/ # M14
|
| 326 |
-
βββ relay/ # M15
|
| 327 |
-
β
|
| 328 |
-
βββ distributed_inference/ # M26 (experimental)
|
| 329 |
-
βββ moe/ # M27 (experimental)
|
| 330 |
-
βββ fedlearn/ # M28 (experimental)
|
| 331 |
-
βββ lora/ # M29 (experimental)
|
| 332 |
-
βββ evidence/ # M30 (experimental)
|
| 333 |
-
βββ civdef/ # M31 (experimental)
|
| 334 |
-
βββ conformance/ # X09
|
| 335 |
-
```
|
| 336 |
-
|
| 337 |
-
---
|
| 338 |
-
|
| 339 |
-
## Configuration
|
| 340 |
-
|
| 341 |
-
`~/.config/hearthnet/config.toml` (created on first run with defaults):
|
| 342 |
-
|
| 343 |
-
```toml
|
| 344 |
-
[node]
|
| 345 |
-
node_id = "" # auto-generated Ed25519 key ID
|
| 346 |
-
display_name = "My Node"
|
| 347 |
-
data_dir = "~/.hearthnet"
|
| 348 |
-
|
| 349 |
-
[transport]
|
| 350 |
-
http_port = 7080
|
| 351 |
-
ui_port = 7860
|
| 352 |
-
|
| 353 |
-
[llm]
|
| 354 |
-
default_backend = "ollama" # "ollama" | "llama_cpp" | "lmstudio" | "hf_api" | "smollm"
|
| 355 |
-
|
| 356 |
-
[rag]
|
| 357 |
-
corpus_dir = "~/.hearthnet/corpus"
|
| 358 |
-
embedding_model = "BAAI/bge-small-en-v1.5"
|
| 359 |
-
|
| 360 |
-
[policy.research]
|
| 361 |
-
enable = false # master switch for all experimental modules
|
| 362 |
-
moe_routing = false # M27
|
| 363 |
-
distributed_inference = false # M26
|
| 364 |
-
fedlearn = false # M28
|
| 365 |
-
lora_beacons = false # M29
|
| 366 |
-
evidence = false # M30
|
| 367 |
-
civil_defense = false # M31
|
| 368 |
-
```
|
| 369 |
-
|
| 370 |
-
---
|
| 371 |
-
|
| 372 |
-
## Connecting a Local Node to the HF Space
|
| 373 |
-
|
| 374 |
-
The HF Space at `https://huggingface.co/spaces/build-small-hackathon/HearthNet` is a
|
| 375 |
-
single-node anchor you can peer with from any local machine.
|
| 376 |
-
|
| 377 |
-
```bash
|
| 378 |
-
# 1. Clone and install
|
| 379 |
-
git clone https://huggingface.co/spaces/build-small-hackathon/HearthNet
|
| 380 |
-
cd HearthNet
|
| 381 |
-
pip install -e .
|
| 382 |
-
|
| 383 |
-
# 2. Run your local node (pick a free port if 7080 is taken)
|
| 384 |
-
python -m hearthnet.cli run --http-port 7080 --ui-port 7860
|
| 385 |
-
|
| 386 |
-
# 3. Manually add the HF Space anchor as a peer (different network = manual)
|
| 387 |
-
python -m hearthnet.cli call discovery.peer.add 1 0 \
|
| 388 |
-
'{"endpoint":"https://build-small-hackathon-hearthnet.hf.space","node_id":"hf-space-anchor"}'
|
| 389 |
-
|
| 390 |
-
# 4. Verify peering
|
| 391 |
-
python -m hearthnet.cli call discovery.peers 1 0 '{}'
|
| 392 |
-
```
|
| 393 |
-
|
| 394 |
-
Or use the helper script:
|
| 395 |
-
```bash
|
| 396 |
-
python scripts/connect_to_hf.py
|
| 397 |
-
```
|
| 398 |
-
|
| 399 |
-
Once peered, your local node can:
|
| 400 |
-
- Route LLM queries **from** the HF Space to your local (better) model
|
| 401 |
-
- Push community posts that appear in the HF Space UI
|
| 402 |
-
- Share blob files across the connection
|
| 403 |
-
|
| 404 |
-
> **Note:** The HF Space runs on a public server without a static IP for inbound connections.
|
| 405 |
-
> Your local node initiates the connection; the HF Space cannot discover you via mDNS.
|
| 406 |
-
> Use `discovery.peer.add` or the invite flow to establish the bridge manually.
|
| 407 |
-
|
| 408 |
-
---
|
| 409 |
-
|
| 410 |
-
## Security Model
|
| 411 |
-
|
| 412 |
-
- **Node identity** β Ed25519 key pair generated locally, never leaves the device.
|
| 413 |
-
- **Trust levels** β `unknown` β `member` β `trusted` β `anchor`. Capabilities can require a minimum trust level.
|
| 414 |
-
- **Capability scoping** β `AuthToken` restricts which capabilities a caller may invoke.
|
| 415 |
-
- **Channel encryption** β M23 X25519 ECDH + ChaCha20-Poly1305 for inter-node transport (opt-in, defaults off).
|
| 416 |
-
- **Experimental capabilities** β Phase 3 modules are off by default and require explicit opt-in. The bus refuses to register them unless the feature flag is on.
|
| 417 |
-
- **No central authority** β there is no HearthNet.com, no certificate authority, no registration server. Trust is established peer-to-peer via invite chains.
|
| 418 |
-
|
| 419 |
-
---
|
| 420 |
-
|
| 421 |
-
## Testing
|
| 422 |
-
|
| 423 |
-
```bash
|
| 424 |
-
# Full suite (133 unit + integration tests):
|
| 425 |
-
pytest tests/ -q
|
| 426 |
-
|
| 427 |
-
# Skip slow E2E browser tests:
|
| 428 |
-
pytest tests/ -q -k "not e2e"
|
| 429 |
-
|
| 430 |
-
# Phase 3 experimental module tests only:
|
| 431 |
-
pytest tests/test_phase3_experimental.py -v
|
| 432 |
-
|
| 433 |
-
# Conformance runner (X09):
|
| 434 |
-
python -m hearthnet.conformance.runner --output conformance-report/
|
| 435 |
-
```
|
| 436 |
-
|
| 437 |
-
---
|
| 438 |
-
|
| 439 |
-
*This document is generated from the spec set in `docs/`. For per-module detail see:*
|
| 440 |
-
- *Phase 1+2: `00-OVERVIEW.md`, `CAPABILITY_CONTRACT.md`, `modules/M01-*.md` β¦*
|
| 441 |
-
- *Phase 3: `docs/p2_p3/IMPLEMENTATION_REFERENCE_p3.md`, `docs/p2_p3/M26-*.md` β¦*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
docs/CAPABILITY_CONTRACT.md
CHANGED
|
@@ -134,11 +134,6 @@ The universal error codes apply to every capability: `bad_request`, `unauthorize
|
|
| 134 |
- **Trust**: member
|
| 135 |
- **Idempotency**: no (token sampling is non-deterministic)
|
| 136 |
- **Stream**: yes (SSE)
|
| 137 |
-
- **Multi-model providers**: a node serving several models (e.g. a local backend
|
| 138 |
-
plus an opt-in sponsor backend) registers a single `llm.chat@1.0` whose
|
| 139 |
-
descriptor advertises the primary model in `params.model` and the full catalogue
|
| 140 |
-
in `params.models` (array). The bus matches a requested `model` against this
|
| 141 |
-
catalogue and dispatches to the owning backend.
|
| 142 |
|
| 143 |
#### Request
|
| 144 |
|
|
|
|
| 134 |
- **Trust**: member
|
| 135 |
- **Idempotency**: no (token sampling is non-deterministic)
|
| 136 |
- **Stream**: yes (SSE)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 137 |
|
| 138 |
#### Request
|
| 139 |
|
docs/ENV.md
DELETED
|
@@ -1,169 +0,0 @@
|
|
| 1 |
-
# HearthNet β Environment Variables, Secrets & Models Reference
|
| 2 |
-
|
| 3 |
-
*Last updated: June 15, 2026*
|
| 4 |
-
|
| 5 |
-
---
|
| 6 |
-
|
| 7 |
-
## Quick Start: Minimum required for HF Space
|
| 8 |
-
|
| 9 |
-
```
|
| 10 |
-
NVIDIA_API_KEY = nvapi-... # enables Nemotron + NVIDIA prize track
|
| 11 |
-
HEARTHNET_DATA_DIR = /data/hearthnet # persistent storage (needs Persistent Storage enabled)
|
| 12 |
-
```
|
| 13 |
-
|
| 14 |
-
Everything else has sensible defaults.
|
| 15 |
-
|
| 16 |
-
---
|
| 17 |
-
|
| 18 |
-
## All Environment Variables
|
| 19 |
-
|
| 20 |
-
### π΄ Secrets (never commit, use HF Space secrets)
|
| 21 |
-
|
| 22 |
-
| Variable | Purpose | Example |
|
| 23 |
-
|----------|---------|---------|
|
| 24 |
-
| `NVIDIA_API_KEY` | NVIDIA NIM API key β activates NemotronBackend, enables all `nvidia/*` models. Get free at [build.nvidia.com](https://build.nvidia.com) | `nvapi-abc123...` |
|
| 25 |
-
| `MODAL_TOKEN` | Modal API token β needed only if `modal deploy` is used. Set automatically by Modal CLI. | `ak-...` |
|
| 26 |
-
| `HEARTHNET_HF_TOKEN` | HuggingFace Inference API token β activates `HfApiBackend` for cloud inference. Get at [hf.co/settings/tokens](https://huggingface.co/settings/tokens) | `hf_abc...` |
|
| 27 |
-
| `ANTHROPIC_API_KEY` | Anthropic Claude API β activates `AnthropicBackend`. Not used by default. | `sk-ant-...` |
|
| 28 |
-
|
| 29 |
-
### π‘ Configuration (safe to set in Space settings, not truly secret)
|
| 30 |
-
|
| 31 |
-
| Variable | Default | Purpose | Where read |
|
| 32 |
-
|----------|---------|---------|-----------|
|
| 33 |
-
| `MODEL_ID` | `openbmb/MiniCPM3-4B` | HF Transformers model to load locally | `app.py:49` |
|
| 34 |
-
| `MODEL_REVISION` | `None` | Model git revision/hash to pin | `app.py:50` |
|
| 35 |
-
| `HEARTHNET_DATA_DIR` | `tempfile.gettempdir()` | Base directory for RAG corpora, BLAKE3 blobs, relay DB. Set to `/data/hearthnet` when Persistent Storage is enabled on the Space. | `app.py:352` |
|
| 36 |
-
| `SPACE_TITLE` | `HearthNet Space (xxxx)` | Display name shown in the UI header and relay roster | `app.py:186` |
|
| 37 |
-
| `MODAL_ENDPOINT` | `""` | URL of deployed Modal LLM endpoint β activates `ModalBackend` | `app.py:273` |
|
| 38 |
-
| `MODAL_MODEL` | `HuggingFaceTB/SmolLM2-1.7B-Instruct` | Model served by the Modal endpoint | `modal_backend.py:65` |
|
| 39 |
-
| `MINICPM_URL` | `""` | OpenAI-compatible endpoint for a local/remote MiniCPM vLLM server | `app.py:287` |
|
| 40 |
-
| `MINICPM_MODELS` | `""` | Comma-separated model names exposed by `MINICPM_URL` | `app.py:292` |
|
| 41 |
-
| `MINICPM_LIGHTWEIGHT` | `""` | Set to `1` or `true` to force SmolLM2-135M instead of MiniCPM3-4B | `app.py:294` |
|
| 42 |
-
| `NEMOTRON_URL` | `""` | Local NVIDIA NIM endpoint (e.g. `http://localhost:8000`) β used by both `app.py` and `app_nemotron.py` | `app_nemotron.py:31` |
|
| 43 |
-
| `HEARTHNET_NODE` | `""` | URL of a HearthNet mesh node to connect to (used by `app_nemotron.py` Push-to-Mesh tab) | `app_nemotron.py:29` |
|
| 44 |
-
| `PORT` | `7869` | Port for `app_nemotron.py` standalone server | `app_nemotron.py:506` |
|
| 45 |
-
| `SPACE_HOST` | `""` | Auto-set by HF β used internally to detect ZeroGPU context | `app.py:181` |
|
| 46 |
-
| `GRADIO_SSR_MODE` | `false` | Forced to `false` to prevent Node.js intercepting custom FastAPI routes | `app.py:565` |
|
| 47 |
-
|
| 48 |
-
---
|
| 49 |
-
|
| 50 |
-
## All Models
|
| 51 |
-
|
| 52 |
-
### Prize Track Mapping
|
| 53 |
-
|
| 54 |
-
| Prize | Requirement | Models to Use |
|
| 55 |
-
|-------|-------------|--------------|
|
| 56 |
-
| π **Tiny Titan** | β€ 32B parameters | MiniCPM3-4B (4B) β
, SmolLM2-135M (135M) β
, Nemotron-nano-8B (8B) β
|
|
| 57 |
-
| π¬ **NVIDIA Nemotron** | Use Nemotron models | `nvidia/llama-3.1-nemotron-nano-8b-instruct` β
|
|
| 58 |
-
| π **OpenBMB** | Use MiniCPM models | `openbmb/MiniCPM3-4B` β
, `openbmb/MiniCPM4-8B` β
|
|
| 59 |
-
| β‘ **Modal** | Deploy on Modal | Any model via `scripts/modal_deploy.py` β
|
|
| 60 |
-
| π¨ **Off Brand** | Custom UI/theme | Custom CSS + purple gradient β
|
|
| 61 |
-
|
| 62 |
-
### Models by Backend
|
| 63 |
-
|
| 64 |
-
#### Local (runs on HF Space / Pi / laptop)
|
| 65 |
-
|
| 66 |
-
| Model | Size | Backend | Set via | Notes |
|
| 67 |
-
|-------|------|---------|---------|-------|
|
| 68 |
-
| `openbmb/MiniCPM3-4B` | **4B** | `HfLocalBackend` | `MODEL_ID` env | **Default**. OpenBMB + Tiny Titan eligible |
|
| 69 |
-
| `HuggingFaceTB/SmolLM2-135M-Instruct` | **135M** | `HfLocalBackend` | `MODEL_ID=HuggingFaceTB/SmolLM2-135M-Instruct` | Pi Zero / ultra-light mode |
|
| 70 |
-
| `HuggingFaceTB/SmolLM2-1.7B-Instruct` | **1.7B** | `HfLocalBackend` | `MODEL_ID=...` | Good balance on CPU |
|
| 71 |
-
| `openbmb/MiniCPM4-8B` | **8B** | `HfLocalBackend` | `MODEL_ID=openbmb/MiniCPM4-8B` | Faster than MiniCPM3, better quality |
|
| 72 |
-
| `openbmb/MiniCPM-V-2_6` | **8B** | `HfLocalBackend` | `MODEL_ID=openbmb/MiniCPM-V-2_6` | **Multimodal** β vision + text |
|
| 73 |
-
|
| 74 |
-
#### NVIDIA NIM (cloud β needs `NVIDIA_API_KEY`)
|
| 75 |
-
|
| 76 |
-
| Model | Size | Prize eligible | Best for |
|
| 77 |
-
|-------|------|---------------|---------|
|
| 78 |
-
| `nvidia/llama-3.1-nemotron-nano-8b-instruct` | **8B** | Tiny Titan β
| Fast, edge reasoning |
|
| 79 |
-
| `nvidia/nemotron-mini-4b-instruct` | **4B** | Tiny Titan β
| Smallest Nemotron |
|
| 80 |
-
| `nvidia/nemotron-3-nano-30b-a3b` | 30B MoE (3B active) | Tiny Titan β
| MoE routing brain |
|
| 81 |
-
| `nvidia/llama-3.3-nemotron-super-49b-v1` | 49B | NVIDIA track | Best reasoning |
|
| 82 |
-
| `nvidia/llama-3.1-nemotron-70b-instruct` | 70B | NVIDIA track | Highest quality |
|
| 83 |
-
| `nvidia/nemotron-nano-12b-v2-vl` | 12B | Tiny Titan β
| **Vision-language** |
|
| 84 |
-
|
| 85 |
-
> **Contest note:** For Tiny Titan prize (β€32B), use: nano-8B, nano-4B, nano-30B-a3b, or nano-12b-v2-vl.
|
| 86 |
-
> The 49B and 70B models exceed the 32B limit and only qualify for the main Nemotron hardware prize.
|
| 87 |
-
|
| 88 |
-
#### OpenBMB (cloud β needs `MINICPM_URL` pointing to a vLLM server)
|
| 89 |
-
|
| 90 |
-
| Model | Size | Notes |
|
| 91 |
-
|-------|------|-------|
|
| 92 |
-
| `openbmb/MiniCPM4-8B` | 8B | Best OpenBMB model for 2026 |
|
| 93 |
-
| `openbmb/MiniCPM3-4B` | 4B | Default, runs locally |
|
| 94 |
-
|
| 95 |
-
#### Modal (serverless GPU β needs `MODAL_ENDPOINT`)
|
| 96 |
-
|
| 97 |
-
| Model | Size | Set via |
|
| 98 |
-
|-------|------|---------|
|
| 99 |
-
| `HuggingFaceTB/SmolLM2-1.7B-Instruct` | 1.7B | Hardcoded in `scripts/modal_deploy.py:23` |
|
| 100 |
-
| Any HF model | β | Change `MODEL_ID` in `scripts/modal_deploy.py` and redeploy |
|
| 101 |
-
|
| 102 |
-
#### Other (cloud)
|
| 103 |
-
|
| 104 |
-
| Backend | Model | Needs |
|
| 105 |
-
|---------|-------|-------|
|
| 106 |
-
| `HfApiBackend` | `HuggingFaceH4/zephyr-7b-beta` | `HEARTHNET_HF_TOKEN` |
|
| 107 |
-
| `AnthropicBackend` | `claude-3-haiku-20240307` | `ANTHROPIC_API_KEY` |
|
| 108 |
-
|
| 109 |
-
---
|
| 110 |
-
|
| 111 |
-
## Recommended Configurations
|
| 112 |
-
|
| 113 |
-
### HF Space (current, contest submission)
|
| 114 |
-
```
|
| 115 |
-
MODEL_ID = openbmb/MiniCPM3-4B # OpenBMB + Tiny Titan prizes
|
| 116 |
-
NVIDIA_API_KEY = nvapi-... # Nemotron Hardware Prize
|
| 117 |
-
HEARTHNET_DATA_DIR = /data/hearthnet # Persistent Storage (if enabled)
|
| 118 |
-
SPACE_TITLE = HearthNet # Display name
|
| 119 |
-
```
|
| 120 |
-
|
| 121 |
-
### Pi Zero / Ultra-Light Mode
|
| 122 |
-
```
|
| 123 |
-
MODEL_ID = HuggingFaceTB/SmolLM2-135M-Instruct # 135M, fits in 512MB RAM
|
| 124 |
-
MINICPM_LIGHTWEIGHT = 1
|
| 125 |
-
```
|
| 126 |
-
|
| 127 |
-
### Full Local Stack (laptop/desktop)
|
| 128 |
-
```
|
| 129 |
-
MODEL_ID = openbmb/MiniCPM4-8B # Best local model
|
| 130 |
-
NVIDIA_API_KEY = nvapi-... # For Nemotron fallback
|
| 131 |
-
NEMOTRON_URL = http://localhost:8000 # Local NIM server (optional)
|
| 132 |
-
HEARTHNET_DATA_DIR = ~/.hearthnet/data # Persistent local data
|
| 133 |
-
```
|
| 134 |
-
|
| 135 |
-
### With Modal GPU Backend
|
| 136 |
-
```
|
| 137 |
-
MODAL_ENDPOINT = https://your-org--hearthnet-llm-chat.modal.run
|
| 138 |
-
# Deploy first: modal deploy scripts/modal_deploy.py
|
| 139 |
-
```
|
| 140 |
-
|
| 141 |
-
---
|
| 142 |
-
|
| 143 |
-
## HF Space Secrets Checklist
|
| 144 |
-
|
| 145 |
-
```
|
| 146 |
-
[x] NVIDIA_API_KEY β free at build.nvidia.com, no credit card
|
| 147 |
-
[ ] HEARTHNET_DATA_DIR β set to /data/hearthnet after enabling Persistent Storage
|
| 148 |
-
[ ] SPACE_TITLE β optional display name override
|
| 149 |
-
[ ] MODAL_ENDPOINT β after running: modal deploy scripts/modal_deploy.py
|
| 150 |
-
[ ] MINICPM_URL β if running a separate vLLM server with MiniCPM4-8B
|
| 151 |
-
```
|
| 152 |
-
|
| 153 |
-
---
|
| 154 |
-
|
| 155 |
-
## Model Size Quick Reference
|
| 156 |
-
|
| 157 |
-
```
|
| 158 |
-
SmolLM2-135M ββββββββββββββββββββ 135M β Pi Zero, embedded
|
| 159 |
-
MiniCPM3-4B ββββββββββββββββββββ 4B β Default β
|
| 160 |
-
SmolLM2-1.7B ββββββββββββββββββββ 1.7B β CPU laptop
|
| 161 |
-
Nemotron-nano-4B ββββββββββββββββββββ 4B β Tiny Titan β
|
| 162 |
-
MiniCPM4-8B ββββββββββββββββββββ 8B β Best quality local
|
| 163 |
-
Nemotron-nano-8B ββββββββββββββββββββ 8B β Tiny Titan β
|
| 164 |
-
Nemotron-12B-VL ββββββββββββββββββββ 12B β Vision+text
|
| 165 |
-
Nemotron-30B-a3b ββββββββββββββββββββ 30B MoE (3B active) β Tiny Titan β
|
| 166 |
-
β 32B Tiny Titan limit β
|
| 167 |
-
Nemotron-Super-49Bββββββββββββββββββββ 49B β NVIDIA prize only
|
| 168 |
-
Nemotron-70B ββββββββββββββββββββ 70B β NVIDIA prize only
|
| 169 |
-
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
docs/{modules/M01-identity.md β M01-identity.md}
RENAMED
|
File without changes
|
docs/{modules/M02-discovery.md β M02-discovery.md}
RENAMED
|
File without changes
|
docs/{modules/M03-bus.md β M03-bus.md}
RENAMED
|
File without changes
|
docs/{modules/M04-llm.md β M04-llm.md}
RENAMED
|
File without changes
|
docs/{modules/M05-rag.md β M05-rag.md}
RENAMED
|
File without changes
|
docs/{modules/M06-marketplace.md β M06-marketplace.md}
RENAMED
|
File without changes
|
docs/{modules/M07-file-blobs.md β M07-file-blobs.md}
RENAMED
|
File without changes
|
docs/{modules/M08-ui.md β M08-ui.md}
RENAMED
|
File without changes
|
docs/{modules/M09-emergency.md β M09-emergency.md}
RENAMED
|
File without changes
|