This view is limited to 50 files because it contains too many changes. See the raw diff here.
Files changed (50) hide show
  1. .claude/settings.json +0 -10
  2. .dockerignore +0 -26
  3. .gitattributes +0 -2
  4. .github/workflows/release.yml +0 -193
  5. .github/workflows/test.yml +0 -63
  6. .gitignore +0 -0
  7. .live_test_node/events.db +0 -0
  8. .playwright-mcp/console-2026-06-12T13-03-23-327Z.log +0 -2
  9. .playwright-mcp/console-2026-06-12T13-12-18-364Z.log +0 -13
  10. .playwright-mcp/console-2026-06-12T13-16-09-405Z.log +0 -81
  11. .playwright-mcp/page-2026-06-10T14-22-38-882Z.yml +0 -3
  12. .playwright-mcp/page-2026-06-10T14-27-49-510Z.yml +0 -60
  13. .playwright-mcp/page-2026-06-10T14-28-44-543Z.yml +0 -67
  14. .playwright-mcp/page-2026-06-10T14-28-52-588Z.yml +0 -53
  15. .playwright-mcp/page-2026-06-10T14-29-00-723Z.yml +0 -110
  16. .playwright-mcp/page-2026-06-10T14-52-32-151Z.yml +0 -3
  17. .playwright-mcp/page-2026-06-10T14-52-36-986Z.yml +0 -98
  18. .playwright-mcp/page-2026-06-10T15-12-22-534Z.yml +0 -0
  19. .playwright-mcp/page-2026-06-10T15-16-39-829Z.yml +0 -3
  20. .playwright-mcp/page-2026-06-10T15-17-36-795Z.yml +0 -15
  21. .playwright-mcp/page-2026-06-10T15-18-10-485Z.yml +0 -15
  22. .playwright-mcp/page-2026-06-10T15-20-12-385Z.yml +0 -3
  23. .playwright-mcp/page-2026-06-10T23-26-07-697Z.yml +0 -3
  24. .playwright-mcp/page-2026-06-12T13-07-53-601Z.yml +0 -0
  25. .playwright-mcp/page-2026-06-12T13-16-10-970Z.yml +0 -25
  26. .playwright-mcp/page-2026-06-13T06-41-44-411Z.yml +0 -113
  27. .playwright-mcp/page-2026-06-13T06-52-18-615Z.yml +0 -113
  28. 6.0.0 +0 -18
  29. BLOG_COMPREHENSIVE.md +0 -616
  30. README.md +211 -446
  31. agents.md +0 -45
  32. app.py +0 -681
  33. app_nemotron.py +0 -558
  34. assets/initial_docs/README.md +0 -13
  35. build/android/HearthNetApp/config.xml +0 -32
  36. coverage_report.txt +0 -0
  37. data/hearthnet/hearthnet-space/corpora/test.md +0 -1
  38. data/hearthnet/test.md +0 -0
  39. docs/ARCHITECTURE.md +0 -441
  40. docs/CAPABILITY_CONTRACT.md +0 -5
  41. docs/ENV.md +0 -169
  42. docs/{modules/M01-identity.md β†’ M01-identity.md} +0 -0
  43. docs/{modules/M02-discovery.md β†’ M02-discovery.md} +0 -0
  44. docs/{modules/M03-bus.md β†’ M03-bus.md} +0 -0
  45. docs/{modules/M04-llm.md β†’ M04-llm.md} +0 -0
  46. docs/{modules/M05-rag.md β†’ M05-rag.md} +0 -0
  47. docs/{modules/M06-marketplace.md β†’ M06-marketplace.md} +0 -0
  48. docs/{modules/M07-file-blobs.md β†’ M07-file-blobs.md} +0 -0
  49. docs/{modules/M08-ui.md β†’ M08-ui.md} +0 -0
  50. 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.10'
9
  app_file: app.py
10
  pinned: true
11
- short_description: Community-Owned AI Mesh That Works When The Internet Doesn't
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
- # πŸ”¬ HearthNet Β· Document Intelligence
25
 
26
- > **Companion Space** to [πŸ”₯ HearthNet](https://huggingface.co/spaces/build-small-hackathon/HearthNet) β€” the main community AI mesh.
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
- <strong>Local-First &nbsp;Β·&nbsp; Peer-to-Peer &nbsp;Β·&nbsp; Offline-Capable &nbsp;Β·&nbsp; Emergency-Ready</strong>
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
- <p align="center">
49
- <img src="https://img.shields.io/badge/🐜%20Tiny%20Titan-MiniCPM3--4B%20|%20Nemotron%20Mini%204B-ff69b4" alt="Tiny Titan">
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
- > **Build Small Hackathon entry** β€” Backyard AI track Β· 🐜 Tiny Titan Β· πŸ€– Best Agent πŸ«₯ press e or a to see the easter egg.
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
- <video width="640" height="360" controls>
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
- > πŸ“£ **Social post:** [tweet on x](https://twitter.com/zX14_7/status/2064853015622775047) [tweet on x](https://twitter.com/zX14_7/status/2064853015622775047)
66
- [Post on blogger](https://ckaller.blogspot.com/2026/06/hearthnet-building-ai-that-works-when.html)
67
 
68
- [LinkedIn Post](https://www.linkedin.com/posts/christof-kaller-6b043733_ai-opensource-huggingface-share-7472317969595863040-cK6Z/?utm_source=share&utm_medium=member_desktop&rcm=ACoAAAcBRiQBdJnC2ODS2UoAdsqfUNZlkb_lFJk)
69
- >
70
- > **June 14 bug-fix release:** 8 critical bugs fixed β€” seed corpus now actually ingested,
71
- > node lifecycle corrected (`stop()` previously silently no-oped), sticky session memory
72
- > leak patched, corpus writes go to the right directory.
73
- > See [hackathon_final_step.md](hackathon_final_step.md) for the full analysis.
 
 
 
74
 
75
  ---
76
 
77
- ## The Idea
78
 
79
- What happens to your neighbourhood's AI when the power grid flickers, the ISP goes down, or the cloud API bill hits?
80
 
81
- **HearthNet answers: nothing changes.** It keeps running.
82
 
83
- Every household with a Raspberry Pi, an old laptop, or any device running Python becomes a **node**
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
- - A neighbourhood of 10 homes gets **10Γ— the AI capacity** of any single device
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
- ## Features
96
 
97
- ### Agent Mode (ReAct tool calling)
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
- The agent's tools are bound to **real capabilities already on the bus** (no mock handlers):
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
- > πŸ’‘ **Try the browser agent:** press **`a`** (or just type **`hearthnet`**) anywhere on the dashboard to open the in-browser WebLLM agent showcase. Press **`e`** for the live mesh/news ticker, **`Esc`** to close.
104
 
105
- ### 🧠 Intelligent Routing (NEW)
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
- **Routing Trace** shows you exactly where your request was served:
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
- ### πŸ’¬ Chat Over LAN & Internet
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
- ### πŸ” Federated RAG
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
- ### πŸ€– MOE Expert Routing
120
- Nodes advertise their specialisations. Queries automatically route to the best experts in your mesh for better answers.
121
 
122
- ### 🚨 Emergency Mode
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
- ## Screenshots
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
- ## πŸ“¦ Downloads & Builds
151
 
152
- Get HearthNet for your platform:
153
 
154
- | Platform | Download | Format | Size | Notes |
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
- **Recommended Paths:**
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
- ## Quick Start
174
 
175
- ```bash
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
- # Run
182
- python app.py # open http://127.0.0.1:7860
183
- ```
184
 
185
- ### With llama.cpp (recommended β€” fast, offline)
186
 
187
- ```bash
188
- # 1. Download a GGUF model
189
- wget https://huggingface.co/lmstudio-community/Meta-Llama-3.1-8B-Instruct-GGUF/resolve/main/Meta-Llama-3.1-8B-Instruct-Q4_K_M.gguf
190
 
191
- # 2. Start llama.cpp server
192
- ./llama-server -m Meta-Llama-3.1-8B-Instruct-Q4_K_M.gguf -p 8080
193
 
194
- # 3. Run HearthNet (auto-detects llama.cpp on port 8080)
195
- python app.py
196
- ```
197
 
198
- **Why llama.cpp?**
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
- ### Alternative: Ollama
205
 
206
- ```bash
207
- ollama pull llama3.2:3b # any Ollama model works
208
- python app.py # auto-detects Ollama
209
- ```
210
 
211
- ### On Android (PWA - Recommended)
212
 
213
- ```bash
214
- # 1. Start HearthNet on your computer (Windows, Mac, or Linux)
215
- python app.py
 
 
216
 
217
- # 2. Find your computer IP address
218
- # Windows: ipconfig | grep IPv4
219
- # Mac/Linux: ifconfig | grep "inet " | grep -v 127
220
 
221
- # 3. Open on Android device in Chrome/Firefox:
222
- # http://<YOUR_IP>:7860
223
 
224
- # 4. Tap menu β†’ "Install app" or "Add to Home screen"
225
- ```
226
 
227
- **πŸ“± Full Android Setup Guide:** [ANDROID_DEPLOYMENT_GUIDE.md](docs/guides/ANDROID_DEPLOYMENT_GUIDE.md)
228
- - βœ… PWA (instant, no build)
229
- - πŸ”§ Native APK (optional, advanced)
230
 
231
- ### Connect your local node to the live HF Space
232
 
233
- ```bash
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
- python -m hearthnet.cli peers # Space node should appear
240
- ```
241
 
242
  ---
243
 
244
- ## How It Works
245
-
246
- ### Capability Bus
247
 
248
- Every feature is a **named capability** on the bus. Any node can call any capability;
249
- the bus routes to the best available provider automatically:
250
 
251
- ```python
252
- # LLM inference β€” routes to fastest/best node in the mesh
253
- result = await bus.call("llm.chat", (1, 0), {
254
- "input": {"messages": [{"role": "user", "content": "What plants grow near water?"}]}
255
- })
256
 
257
- # RAG β€” routes to the node holding that corpus
258
- result = await bus.call("rag.query", (1, 0), {
259
- "params": {"corpus": "community"},
260
- "input": {"query": "emergency water purification", "k": 3}
261
- })
262
 
263
- # Or from the CLI β€” no Python needed
264
- python -m hearthnet.cli call llm.chat 1 0 '{"input":{"messages":[{"role":"user","content":"Hello!"}]}}'
265
- python -m hearthnet.cli capabilities # list all available capabilities across mesh
266
  ```
267
 
268
- ### Zero-Config Discovery
269
 
270
- ```bash
271
- # Device 1 β€” already running
272
- python app.py
273
 
274
- # Device 2 β€” same Wi-Fi
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
- ### Intelligent Routing & Failover
281
 
282
- When you ask a question:
283
- 1. **Scoring**: Bus evaluates all LLM providers by latency, load, reliability, and local preference
284
- 2. **Selection**: Request goes to the best provider
285
- 3. **Failover**: If that node can't help (error or unavailable), automatically try the next-best alternative
286
- 4. **Tracing**: Result includes `_routed_via` showing which node served it
287
 
288
- ```python
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
- result = await bus.call("llm.chat", (1, 0), {...})
295
- # result includes "_routed_via": "node-b-id" ← Shows the true origin
296
- ```
297
 
298
- ### MoE Expert Routing
299
-
300
- Nodes advertise specialisations. Queries route to the best expert automatically:
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
- ### Offline Model Distribution
320
 
321
- A node without internet pulls model weights from a LAN peer, chunk by chunk:
322
 
323
- ```python
324
- models = await bus.call("model.list", (1, 0), {"input": {}})
 
 
 
325
 
326
- job = await bus.call("model.pull", (1, 0), {
327
- "input": {"model_name": "llama3.2:3b", "source_node": "peer-id"}
328
- })
329
- # Progress via model.status; BLAKE3 content-addressed so never duplicated
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
330
  ```
331
 
332
  ---
333
 
334
- ## What Makes This "Tiny"
335
 
336
- The HF Space demo uses **MiniCPM3-4B** β€” 4B params, strong instruction following, under the 32B Tiny Titan limit. Set `MODEL_ID=HuggingFaceTB/SmolLM2-135M-Instruct` to run 135M ultra-light mode on Pi-class devices.
337
 
338
- For local installs, any GGUF model works (1B–8B for significantly better quality).
339
- The architecture is model-agnostic; the routing layer handles the rest.
340
 
341
- **Real semantic RAG, not a toy:** when `sentence-transformers` is installed the
342
- embedding service loads `BAAI/bge-small-en-v1.5` (~130 MB, CPU-friendly) so
343
- `rag.query` performs genuine semantic retrieval. Without it, the service falls
344
- back to a deterministic hash embedder and says so β€” no silent fakery.
 
345
 
346
- **Why this qualifies for Tiny Titan:**
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
- ## Local AI Backends
 
 
 
355
 
356
- **No mocks. No fake responses. Real local inference only.**
357
 
358
- HearthNet prioritizes local, private models. Cloud backends are **opt-in only** (env vars).
359
 
360
- ### Local Backends (Primary)
361
 
362
- | Backend | Activation | Notes |
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
- ### Optional Cloud Backends (Opt-In via Env Vars)
370
 
371
- | Backend | Activation | Notes |
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
- All configured backends are registered on the `llm.chat` capability. The routing bus selects the best backend based on:
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
- If no suitable backend is available: clear error message returned. Never silent, never fabricated.
383
 
384
  ---
385
 
386
- ## Security
387
 
388
- - **Ed25519** β€” all node manifests and invite links signed with PyNaCl
389
- - **X3DH + Double Ratchet** β€” end-to-end encrypted chat (M23)
390
- - **BLAKE3** β€” content-addressed file blobs (tamper-evident)
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
- ## Architecture
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
- ## Module Reference
429
-
430
- <details>
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
- ## πŸ§ͺ Testing & Coverage
 
 
497
 
498
- ### Comprehensive Test Suite: 390+ Tests
499
 
500
- HearthNet includes rigorous tests for all core capabilities:
501
 
502
- | Suite | Count | Coverage |
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
- ### Run Tests Locally
515
 
516
- ```bash
517
- # Full suite
518
- python -m pytest tests/ -v
519
 
520
- # Specific module (e.g., routing tests)
521
- python -m pytest tests/test_bus_failover.py -v
522
 
523
- # With coverage report
524
- python -m pytest tests/ --cov=hearthnet --cov-report=term-missing
 
 
525
 
526
- # Skip slow E2E tests
527
- python -m pytest tests/ --ignore=tests/test_e2e_user_stories.py -v
528
- ```
529
 
530
- **All tests pass** on Python 3.13 with pytest-asyncio.
 
 
531
 
532
- **Focus areas:**
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
- ## πŸ”— Deployment & Source
540
 
541
- | Resource | Purpose |
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
- **Deployment Architecture:**
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
- **Build Artifacts Available:**
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
- ## Contributing & Docs
559
 
560
- | Resource | Link |
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
- ## Hackathon Entry
572
 
573
- **Track:** πŸ•οΈ Backyard AI (Practical)
574
 
575
- **Why HearthNet wins:**
 
 
576
 
577
- 🐜 **Tiny Titan:** Runs on MiniCPM3-4B (4B params, under 32B limit). Ultra-light mode with SmolLM2-135M (135M) via `MODEL_ID` env var for Raspberry Pi and edge devices.
578
 
579
- πŸ€– **Best Agent:** Capability bus + intelligent routing = distributed agentic system. Nodes score, select, and failover to the best provider autonomously. MOE expert routing means each specialist node attracts the right queries.
 
 
 
 
 
580
 
581
- **Optional integrations:**
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
- ## Links
589
 
590
- | | |
591
- |--|--|
592
- | πŸ€— HF Space (Live) | https://huggingface.co/spaces/build-small-hackathon/HearthNet |
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 models and the belief that communities should own their AI.<br>
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 &amp; Q&amp;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