HantaNet is an ElizaOS agent that watches for hantavirus-related signals, generates public-health style risk analysis, posts recurring global heatmap updates, and replies to Twitter/X mentions with location-specific hantavirus forecasts.
The project combines an OpenAI-backed character, the ElizaOS Twitter runtime, a custom hantavirus plugin, and an Obsidian Markdown persistence plugin for durable surveillance notes.
- Runs a HantaNet agent persona focused only on hantavirus, rodent reservoirs, outbreak surveillance, and DeSci disease monitoring.
- Periodically synthesizes hantavirus research context with OpenAI.
- Generates region-level risk data for a global hantavirus heatmap.
- Creates a heatmap image with OpenAI image generation.
- Uploads the generated image to Twitter/X and publishes a scheduled risk-map tweet.
- Watches Twitter/X mentions, detects locations, and replies with short location-specific risk forecasts.
- Feeds detected mention locations back into the next heatmap as live public-interest signals.
- Accepts user-submitted
report:field reports as unverified ecological signals. - Publishes a compact DeSci signal leaderboard in heatmap posts.
- Stores mention signals and field reports in an Obsidian-compatible Markdown vault.
- Keeps replies under tweet length and deduplicates mention responses through the Eliza runtime cache.
ElizaOS runtime
|
| loads src/index.ts project
v
HantaNet character + plugins
|
| built-in plugins
| - SQL storage
| - OpenAI model provider
| - Bootstrap runtime
| - Twitter/X integration
| - Obsidian Markdown persistence
|
| custom plugin
v
src/plugins/hantavirus
|
| research provider
| - refreshes research context every 5 minutes
| - injects recent context into agent generations
|
| heatmap cron
| - generates region risk data
| - creates image with OpenAI image API
| - uploads media to Twitter/X
| - publishes scheduled heatmap post
|
| mention diagnostics
| - searches for @HantaNet mentions
| - filters out self-authored tweets
| - detects locations with lists and GPT fallback
| - records mention locations for the next heatmap loop
| - parses "report:" submissions as unverified field reports
| - generates a location-specific risk reply
| - caches replied tweet IDs
|
| surveillance signals
| - writes mention-to-map location signals to Obsidian daily notes
| - writes unverified user field reports to Obsidian daily notes
| - generates DeSci signal leaderboard context for map tweets
characters/hantanet.json
HantaNet persona, voice, knowledge, topics, and response style.
src/index.ts
ElizaOS project export. Registers SQL, OpenAI, Bootstrap, Twitter, Obsidian persistence, and the custom hantavirus plugin.
src/character.ts
Loads the HantaNet character JSON for the runtime.
src/plugins/hantavirus/index.ts
Custom plugin registration. Wires actions, providers, and startup hooks.
src/plugins/hantavirus/providers/researchProvider.ts
Injects fresh hantavirus research context into the agent.
src/plugins/hantavirus/services/researchService.ts
Maintains the in-memory research cache and generates research/risk summaries with OpenAI.
src/plugins/hantavirus/services/imageGeneration.ts
Generates the global heatmap image from region risk data.
src/plugins/hantavirus/actions/heatmapTweet.ts
Runs the scheduled heatmap pipeline and posts the image tweet.
src/plugins/hantavirus/actions/mentionResponder.ts
Detects locations in mentions and generates location-specific reply forecasts.
src/plugins/hantavirus/services/twitterMentionDiagnostics.ts
Polls Twitter/X mentions, logs diagnostics, deduplicates replies, and sends direct replies.
src/plugins/hantavirus/services/surveillanceSignals.ts
Routes mention-to-map signals, user-submitted field reports, and leaderboard summaries through Obsidian persistence, with runtime cache fallback.
src/plugins/obsidianPersistence/index.ts
ElizaOS plugin and service for writing HantaNet surveillance entries into Obsidian-compatible Markdown daily notes.
ecosystem.config.cjs
PM2 process definition for running the ElizaOS agent on a VM.
The custom plugin lives in src/plugins/hantavirus and is registered as:
export const hantavirusPlugin: Plugin = {
name: "hantavirus",
actions: [heatmapTweetAction, mentionResponderAction],
providers: [researchProvider],
init: async (_config, runtime) => {
void startHeatmapCron(runtime);
startTwitterMentionDiagnostics(runtime);
},
};The project registers obsidianPersistencePlugin before hantavirusPlugin so surveillance data is written to Markdown as the durable source of truth.
By default, surveillance notes are stored in:
obsidian-vault/HantaNet/Surveillance/YYYY-MM-DD.md
Each daily note contains human-readable entries plus machine-readable JSON blocks for mention signals and field reports. The default obsidian-vault/ directory is ignored by git because it contains live operational data.
Set a different vault path with:
OBSIDIAN_VAULT_PATH=./obsidian-vaultIf the Obsidian service is unavailable, HantaNet logs a warning and falls back to runtime cache.
researchProvider refreshes synthesized hantavirus research every five minutes and injects the latest context into the agent. The prompt biases generation toward concrete mechanisms like reservoir movement, seasonal exposure, rodent ecology, surveillance lag, and DeSci monitoring.
The heatmap pipeline:
- Generates current region-level risk data with OpenAI.
- Pulls mention-to-map hotspots, unverified field reports, and the DeSci signal leaderboard from Obsidian daily notes.
- Sends research context plus live signal context into the risk-data prompt.
- Sends that risk data into the image-generation prompt.
- Uploads the image through Twitter/X OAuth 1.0a media upload.
- Posts a tweet with the uploaded media ID and compact DeSci signal leaderboard.
- Repeats on
HEATMAP_INTERVAL_MINUTES.
Every mention with a detected location is stored as a public-interest signal. The next heatmap generation includes the highest-frequency mention locations so repeated questions about a region can slightly raise map attention without being treated as confirmed case data.
Users can submit unverified ecological observations with:
@HantaNet report: rodent droppings in a shed near Boulder, Colorado
HantaNet records the report in the current Obsidian daily note, replies with an acknowledgement, and feeds the signal into the next heatmap and leaderboard. Field reports are explicitly labeled as unverified and must not be interpreted as confirmed cases.
The leaderboard ranks locations by live mentions and field reports. Field reports receive extra weight because they describe direct environmental observations, but the generated copy labels them as unverified ecological signals.
The mention pipeline:
- Reads incoming mention text.
- Looks for known US states, state abbreviations, countries, and major cities.
- Falls back to OpenAI location extraction if no quick match is found.
- Generates a short risk forecast for that location.
- Replies through the Twitter plugin callback or diagnostic service.
twitterMentionDiagnostics polls for @username mentions, prints useful runtime logs, filters out the bot's own tweets, and stores hantanet/twitter/mention-replied/<tweetId> cache keys so the same tweet is not answered repeatedly.
Create a .env from the template:
cp .env.example .envRequired values:
OPENAI_API_KEY=
OPENAI_LARGE_MODEL=gpt-5.5
OPENAI_SMALL_MODEL=gpt-5.5
TWITTER_API_KEY=
TWITTER_API_SECRET_KEY=
TWITTER_ACCESS_TOKEN=
TWITTER_ACCESS_TOKEN_SECRET=Runtime controls:
TWITTER_ENABLE_POST=true
TWITTER_POST_INTERVAL_MIN=15
TWITTER_POST_INTERVAL_MAX=30
TWITTER_SEARCH_ENABLE=true
TWITTER_AUTO_RESPOND_MENTIONS=true
TWITTER_POLL_INTERVAL=30
TWITTER_DRY_RUN=false
HEATMAP_INTERVAL_MINUTES=60
HANTANET_MENTION_DIAGNOSTIC_INTERVAL_MS=60000
OBSIDIAN_VAULT_PATH=./obsidian-vaultNever commit .env. The repository intentionally tracks .env.example only.
Install dependencies:
npm installBuild TypeScript:
npm run buildRun the Obsidian persistence checks:
npm run test:obsidianRun through ElizaOS:
./node_modules/.bin/elizaos startIf the CLI was installed with a Bun shebang, this also works:
/home/ubuntu/.bun/bin/bun ./node_modules/.bin/elizaos startThe production process should run the ElizaOS CLI, not node dist/index.js. The compiled dist/index.js exports the ElizaOS project; the ElizaOS runtime is what actually starts the agent.
Recommended PM2 config:
module.exports = {
apps: [
{
name: "hantanet",
cwd: "/home/ubuntu/hantanet",
script: "/home/ubuntu/.bun/bin/bun",
args: "./node_modules/.bin/elizaos start",
interpreter: "none",
exec_mode: "fork",
instances: 1,
autorestart: true,
max_memory_restart: "500M",
env: {
NODE_ENV: "production",
PATH: "/home/ubuntu/.bun/bin:/usr/local/bin:/usr/bin:/bin",
},
error_file: "logs/error.log",
out_file: "logs/out.log",
log_date_format: "YYYY-MM-DD HH:mm:ss",
},
],
};Start or restart:
mkdir -p logs
pm2 delete hantanet
pm2 start ecosystem.config.cjs --update-env
pm2 logs hantanet --lines 100Persist across reboot:
pm2 save
pm2 startupRun the command printed by pm2 startup, then:
pm2 saveWhen the runtime is wired correctly, logs should include messages like:
[HantaNet:Research] Refreshing hantavirus research data...
[HantaNet:Heatmap] Starting heatmap cron...
[HantaNet:Heatmap] Generating hourly heatmap...
[HantaNet:MentionDiag] Checking Twitter mentions...
If PM2 shows the process as online but logs stay empty, confirm PM2 is running the ElizaOS CLI through Bun instead of running dist/index.js directly.
- Use Node 22 on the VM.
- Use Bun when running the ElizaOS CLI if the installed binary has a Bun shebang.
- Keep
.envoff GitHub. - Rotate API keys immediately if they are pasted into logs, chats, screenshots, or issue trackers.
- Use
TWITTER_DRY_RUN=truewhile testing credentials and startup behavior. - Watch
pm2 logs hantanetafter every deploy to confirm the Twitter client becomes ready before mention polling starts.
npm run build
pm2 start ecosystem.config.cjs --update-env
pm2 restart hantanet --update-env
pm2 logs hantanet --lines 100
pm2 status hantanet
pm2 save