Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ innovation-lab-examples/
| Example | Description | Tech Stack | Difficulty |
|---------|-------------|------------|------------|
| [contributors/community_agent](contributors/community_agent/) | AI community growth agent for events and hackathons | Python, uAgents, ASI:One, Tavily | 🟡 Intermediate |
| [contributors/cardiopulse-agent](contributors/cardiopulse-agent/) | Live cardiovascular fitness test from a Garmin BLE heart-rate stream | Python, uAgents, Bleak, ASI:One | 🟡 Intermediate |
| [contributors/news-summarizer-agent](contributors/news-summarizer-agent/) | Fetches top headlines for a topic via NewsAPI and summarizes them with ASI:One, via Chat Protocol | Python, uAgents, NewsAPI, ASI:One | 🟡 Intermediate |

### 🌐 Web3 & Blockchain
Expand Down
2 changes: 2 additions & 0 deletions contributors/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
- `gemini-research-agent/`: Added Gemini-powered research assistant demonstrating the standard Agent Chat Protocol (@Kavurubuvanesh)
- `contributors/` folder and contribution guide for community agent examples
- `contributors/community_agent/` — moved from repository root; AI community growth agent for events and hackathons
- `contributors/cardiopulse-agent/` — live cardiovascular fitness-test agent that streams a Garmin watch's heart rate over BLE and runs a three-phase test through ASI:One
- `contributors/community_agent/` — moved from repository root; AI community growth agent for events and hackathons
### Fixed
- Fixed sandbox validation in `scan_directory` to properly reject paths outside the demo sandbox using `Path.relative_to()` (#159)
- `contributors/news-summarizer-agent/` — beginner-friendly agent that fetches top headlines via NewsAPI and summarizes them with ASI:One; now a uAgent with Chat Protocol support
40 changes: 40 additions & 0 deletions contributors/cardiopulse-agent/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# ============================================================================
# CardioPulse — environment configuration
# Copy this file to `.env` and fill in the values. Never commit your `.env`.
# ============================================================================

# ---- Main CardioPulse agent -------------------------------------------------

# Seed phrase that deterministically generates the agent's address.
# Use any unique random string. Change this before deploying.
AGENT_SEED=change-me-to-a-unique-random-string

# ASI:One API key — powers the natural-language coaching summary after a test.
# Optional: without it the agent falls back to a built-in static summary and
# still works end to end. Get a key at https://asi1.ai
ASI1_API_KEY=your_asi1_api_key_here
ASI1_MODEL=asi1

# ---- Bridge agent (runs locally on the machine with the watch) --------------

# Seed phrase for the bridge agent. Use a different unique random string.
BRIDGE_SEED=change-me-to-a-different-unique-random-string

# Substring the bridge matches when scanning for your watch over BLE.
# A Garmin Forerunner advertises as e.g. "Forerunner 165 12345".
GARMIN_NAME=Forerunner

# ---- Optional ---------------------------------------------------------------

# Where the bridge POSTs heart-rate readings. Defaults to the main agent's
# local REST endpoint, which is correct when both run on the same machine.
# Only change this if you move the agent to a different host/port.
# AGENT_URL=http://127.0.0.1:8001/bpm

# Reserved for a future cloud-hosted setup where the bridge would message the
# agent by address instead of POSTing to localhost. Not used in local mode.
# CARDIOPULSE_ADDRESS=agent1q...

# Chart delivery works out of the box via catbox.moe (no key required).
# Optionally set an Imgur Client-ID to use Imgur as a fallback image host.
# IMGUR_CLIENT_ID=your_imgur_client_id_here
12 changes: 12 additions & 0 deletions contributors/cardiopulse-agent/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
venv/
__pycache__/
*.pyc
.env
.DS_Store
*.log

# Per-user test history — personal data, never commit
data/

# Regenerated chart artifact (charts are produced in-memory at runtime)
assets/last_test.png
171 changes: 171 additions & 0 deletions contributors/cardiopulse-agent/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
# CardioPulse

A live cardiovascular fitness-test agent for Agentverse and ASI:One.

From a chat message, the agent reads your live heart rate from a Garmin watch
over Bluetooth, walks you through a short three-phase test, and returns an
estimated **Cardio Fitness Age** with reference ranges, an inline chart, and a
plain-language summary. Repeated runs build a per-user trend.

> **Scope / disclaimer:** This is a proof of concept for a *live, stream-driven
> agent* — one that ingests a real-time sensor feed, reasons on it, and acts,
> all inside a chat. It is **not a medical device and not health advice.** The
> readings are illustrative estimates (roughly ±5 years); the trend across
> repeated tests matters far more than any single number.

## Overview

- **Category:** Live data, BLE, agent-to-agent
- **Tech stack:** Python, uAgents, Bleak (BLE), ASI:One Agent Chat Protocol, ASI-1, matplotlib
- **Status:** demo

## Features

- Ingests a live 1 Hz heart-rate stream from a Garmin watch over standard BLE.
- Runs a timed three-phase autonomic protocol (resting baseline → orthostatic →
paced breathing) entirely through a chat conversation.
- Returns a structured result with reference ranges, an inline HR-timeline chart,
and an ASI-1 natural-language coaching summary.
- Persists results per user and renders a trend chart across repeated tests.
- Two-agent architecture: a local BLE bridge agent + a mailbox-registered main
agent reachable through ASI:One.

## Prerequisites

- **Python 3.12** (uAgents is incompatible with Python 3.14's asyncio changes)
- A **Garmin watch** with "Broadcast Heart Rate" (Forerunner, Fenix, Venu, …),
or any standard BLE heart-rate strap (e.g. Polar H10)
- An [Agentverse](https://agentverse.ai) account
- *(Optional)* an [ASI:One](https://asi1.ai) API key for the AI coaching summary

## Installation

```bash
git clone <your-fork-url>
cd contributors/cardiopulse-agent
python3.12 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
```

## Environment Variables

```bash
cp .env.example .env
```

| Variable | Required | Purpose |
|---|---|---|
| `AGENT_SEED` | yes | Seed that generates the main agent's address |
| `BRIDGE_SEED` | yes | Seed for the bridge agent (use a different value) |
| `GARMIN_NAME` | yes | Substring matched when scanning for the watch over BLE |
| `ASI1_API_KEY` | optional | Enables the AI coaching summary (static fallback otherwise) |
| `AGENT_URL` | optional | Where the bridge POSTs readings (default `http://127.0.0.1:8001/bpm`) |
| `IMGUR_CLIENT_ID` | optional | Fallback image host; charts use catbox.moe by default |

## Run the Agent

Start the main agent first so its REST endpoint is up before the bridge POSTs to
it. Both run on the same machine.

```bash
# 1. Main agent (chat protocol + local /bpm endpoint)
python agent.py

# 2. On the watch: enable Broadcast Heart Rate and keep that screen active.

# 3. Bridge agent (BLE -> POST to the main agent), in a second terminal
python bridge_agent.py
```

Then chat with the agent via ASI:One (or the Agentverse Manual Test panel):

```
age 25, start test
```

> For a clean, deterministic session, use the **Manual Test** panel on the
> agent's Agentverse profile rather than the main ASI:One chat.

## Expected Output

```
User: age 27, start test
Agent: Got it — age 27. Starting the test now.
Agent: Connected. Latest BPM: 64.
Phase 1 — Resting baseline (2 minutes) ...
(3 minutes later)
Cardio Fitness Test — Results
Cardio Fitness Age: 24 (you are 27)
Resting HR: 58 bpm — a well-rested value for your age is typically under 65 bpm.
...
[inline HR-timeline chart]
Coach's read: ...
[trend chart, from the second test onward]
```

Commands the agent understands: `help`, `age N`, `start test`
(`age N, start test` does both), `status`.

## Demo

![CardioPulse demo](./assets/demo.png)

## Agent Profile

Deployed on Agentverse via mailbox (the address is derived from your
`AGENT_SEED`). See [agentverse.ai](https://agentverse.ai).

## Architecture

```
[ Garmin Forerunner ]
│ BLE — standard Heart Rate Service
[ bridge_agent.py ] ── HTTP POST (localhost:8001/bpm) ──► [ agent.py — CardioPulse ]
local uAgent uAgent: REST + mailbox
Agent Chat Protocol
(Agentverse mailbox)
[ ASI:One chat ]
```

- **Bridge agent** (`bridge_agent.py`) subscribes to the watch's BLE Heart Rate
Measurement characteristic via Bleak and POSTs each reading to the main
agent's local REST endpoint, reconnecting on its own if the watch drops.
- **Main agent** (`agent.py`) receives the stream on a local `/bpm` endpoint and
is registered on Agentverse via mailbox for the ASI:One chat. Both agents run
on the same machine, so the localhost POST keeps up with the 1 Hz stream —
agent-to-agent mailbox routing is too slow for that rate, which is why HR data
uses REST while the conversation uses the mailbox.
- Readings accumulate in a thread-safe in-memory ring buffer (`session_state`).
The chat handler runs the test as a sequential coroutine and samples HR
windows from that buffer at each phase boundary — the telemetry stream
populates state but does not drive control flow.

## Verifying scoring without a watch

```bash
python test_scoring.py
```

Generates synthetic HR data for several hypothetical users and runs the scoring
engine end to end — useful for sanity-checking formula changes. Use
`python diagnose_ble.py` to confirm the watch is visible over BLE.

## Troubleshooting

- **"I'm not receiving any heart rate data"** — the watch isn't broadcasting.
Enable Broadcast Heart Rate and keep that screen awake (tap occasionally).
- **Bridge can't find the watch** — make sure `GARMIN_NAME` matches what the
watch advertises, and grant the terminal/Python Bluetooth permission
(macOS: System Settings → Privacy & Security → Bluetooth).
- **`RuntimeError: no running event loop` on startup** — you're on Python 3.14;
recreate the venv with Python 3.12.
- **Agent not reachable on ASI:One** — confirm the agent process is running and
its mailbox is connected; the mailbox only exists while the agent is up.

## License

Apache-2.0 (repository default).
69 changes: 69 additions & 0 deletions contributors/cardiopulse-agent/agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
"""
CardioAgent: a live cardiovascular fitness test agent on Agentverse + ASI:One.

The agent guides users through a 3-minute test using live heart rate readings
that a companion `bridge_agent.py` POSTs to a local REST endpoint. (Agentverse
mailbox routing is too slow for 1Hz streaming, so the same-machine bridge uses
a direct localhost POST; the mailbox is used only for the ASI:One chat.)

Run:
python agent.py

Then, on the same machine, run the bridge agent (which talks to the Garmin
watch over BLE and POSTs readings to this agent):

python bridge_agent.py
"""

from __future__ import annotations

import os

from dotenv import load_dotenv

# Load env vars BEFORE importing modules that read them at import time.
load_dotenv()

from uagents import Agent, Context # noqa: E402

from chat_handler import chat_proto # noqa: E402
from models import HRAck, HRReading # noqa: E402
import session_state # noqa: E402


AGENT_SEED = os.environ.get("AGENT_SEED", "cardio-agent-default-seed-change-me")


agent = Agent(
name="cardio-test-agent",
seed=AGENT_SEED,
port=8001,
mailbox=True,
)

agent.include(chat_proto, publish_manifest=True)


# Receive HR readings via a fast local REST endpoint instead of agent-to-agent
# messaging. Agent messaging routes via Agentverse mailbox which is too slow
# for 1Hz streaming data; the bridge runs on the same machine, so direct
# localhost POST is both faster and simpler.
@agent.on_rest_post("/bpm", HRReading, HRAck)
async def receive_bpm(ctx: Context, req: HRReading) -> HRAck:
"""REST endpoint that the local bridge POSTs heart rate readings to."""
session_state.receive_bpm(req.bpm, req.rr_intervals, req.ts)
return HRAck(ok=True)


@agent.on_event("startup")
async def on_startup(ctx: Context) -> None:
ctx.logger.info("CardioAgent online")
ctx.logger.info(f"Agent address: {agent.address}")
ctx.logger.info(
"Copy this address into your bridge_agent .env as CARDIOPULSE_ADDRESS, "
"then run `python bridge_agent.py` on the machine with the watch."
)


if __name__ == "__main__":
agent.run()
Binary file added contributors/cardiopulse-agent/assets/demo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading