digitalocean/mars-competitor-pulse
Python
Captured source
source ↗digitalocean/mars-competitor-pulse
Language: Python
License: MIT
Stars: 0
Forks: 0
Open issues: 1
Created: 2026-09-16T20:02:23Z
Pushed: 2026-09-16T20:51:16Z
Default branch: main
Fork: no
Archived: no
README:
Competitor Pulse
Watchlist → public-web fetch modules → baseline diff → counterposition brief → ask before notify. LangGraph agent shaped for DigitalOcean MARS (Managed Agents / Harness Runtime).
What it does
- Loads a competitor watchlist (name + public URLs per module)
- Gathers site / pricing / changelog / careers snapshots (fixtures offline; optional public HTTP when
ALLOW_NET=1) - Diffs against workspace/fixture baseline JSON and marks
material - Drafts a counterposition brief + notify draft
- Stops for human approval before notify when notify is requested and changes are material
What it does not (v1)
- Auto-notify or send real Slack/email (act stubs
notify_idonly) - Competitor logins, signup identity, or scrape behind login
- Churn winback / marketplace / desktop-bot branding
- Presenting Approve when the diff is empty or notify is disabled
Run flow
intake → plan → gather → analyze → draft → **ask** → act → report
Ask is skipped when the diff is empty / non-material (status: empty), when notify is false, or when intake is blocked.
Human approval
When the graph reaches ask, the run pauses until you Send notify (approve) or Quiet — keep brief (deny).
| If you… | Resume | The agent will… | |---------|--------|-----------------| | Send notify | approve | Stub-notify in state (status: notified, notify_id; no real Slack/email in v1) | | Quiet — keep brief | deny | Keep brief artifacts; skip notify; finish with status denied |
You will see: Notify about competitor changes? plus material change count, competitors, highlights, notify draft, and a will-not list.
Ask body (shape)
Send a pulse notify via {channel}?
Material changes: {delta_count}
Competitors: {comma-separated names with deltas}
Highlights:
{3–7 bullets from deltas}
Counterposition brief: attached in this run (not posted in full unless included below).
Notify draft:
{notify_draft}
Will not: contact competitors, create accounts, or scrape behind login.Run on DigitalOcean MARS
Prerequisites
- DigitalOcean account with Managed Agents / MARS access (Private Preview as applicable)
- This repo public on GitHub (MARS pins a SHA)
- Inference available via harness env (see below)
Pin
1. Create or open a MARS agent environment that accepts a LangGraph Agent Server app. 2. Point it at this GitHub repo; pin commit SHA {sha}. 3. Ensure root langgraph.json is detected (graph export via module-level .compile(name="CompetitorPulse")). 4. Set harness inference env (names below). Do not hardcode keys in the repo. 5. Deploy / start the agent server; run one smoke invoke (see Smoke).
See mars.spec.example.yaml for FRAMEWORK_REPO / FRAMEWORK_REPO_SHA placeholders.
Troubleshooting: If the agent server fails with No module named 'competitor_pulse', ensure root requirements.txt includes -e . so the local package is installed (MARS prefers requirements.txt over pyproject.toml when both exist).
Harness inference env (required)
| Variable | Purpose | |----------|---------| | HARNESS_INFERENCE_BASE_URL | OpenAI-compatible base URL | | HARNESS_INFERENCE_MODEL | Model id | | HARNESS_INFERENCE_API_KEY | API key |
Fallbacks OPENAI_BASE_URL / OPENAI_API_KEY / OPENAI_MODEL are OK if documented, but prefer harness names first.
Permissions / tools
- v1 offline path uses
fixtures/watchlist.json,fixtures/snapshots/, andfixtures/baselines/*.json - Public HTTP fetch only when
ALLOW_NET=1; tests/smoke setALLOW_NET=0 notifyis stubbed — records intent in state only; no Action Gateway / Slack required for local proof
Smoke
Minimal input JSON (material + notify; expect ask):
{
"watchlist": [{"name": "Acme", "urls": {"site": "https://example.com/acme/"}}],
"notify": true,
"channel": "slack"
}Quiet path (no ask):
{
"notify": true,
"baseline_path": "fixtures/baselines/quiet.json"
}Expect:
1. Stages through draft without side effects. 2. An ask interrupt when notify is on and material == true; or a clean empty report with no ask. 3. After Approve: stub notify evidence (status: notified, notify_id stub). 4. After Deny: status: denied and no notify.
Local smoke (fixtures, no API key):
ALLOW_NET=0 python scripts/smoke_invoke.py
Local
python -m venv .venv && source .venv/bin/activate pip install -r requirements.txt export ALLOW_NET=0 # optional live LLM / HTTP: # export HARNESS_INFERENCE_BASE_URL=... # export HARNESS_INFERENCE_MODEL=... # export HARNESS_INFERENCE_API_KEY=... # export ALLOW_NET=1 pytest -q python scripts/smoke_invoke.py
MARS path stays primary; local is for graph tests. Without an API key and with ALLOW_NET=0, the fixture pulse path is fully deterministic.
License
MIT