Watchlist → public-web fetch modules → baseline diff → counterposition brief → ask before notify. LangGraph agent shaped for DigitalOcean MARS (Managed Agents / Harness Runtime).
- 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
- 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
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.
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.
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.
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
- Create or open a MARS agent environment that accepts a LangGraph Agent Server app.
- Point it at this GitHub repo; pin commit SHA
{sha}. - Ensure root
langgraph.jsonis detected (graph export via module-level.compile(name="CompetitorPulse")). - Set harness inference env (names below). Do not hardcode keys in the repo.
- 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
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:
- Stages through
draftwithout side effects. - An ask interrupt when notify is on and
material == true; or a cleanemptyreport with no ask. - After Approve: stub notify evidence (
status: notified,notify_idstub). - After Deny:
status: deniedand no notify.
Local smoke (fixtures, no API key):
ALLOW_NET=0 python scripts/smoke_invoke.pypython -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.pyMARS path stays primary; local is for graph tests. Without an API key and with ALLOW_NET=0, the fixture pulse path is fully deterministic.
MIT