Flowback eliminates traditional surveys by capturing natural, invisible feedback during normal digital interactions. Users never "take a survey"—feedback emerges from behavior, micro-interactions, and optional 1-tap reactions.
- Non-intrusive: Passive signal capture + rare 1-tap prompts
- Consent-aware: Explicit consent, privacy-first, throttled
- Event-driven: Decoupled agents communicate via message bus
- AI-optional: Works fully without AI; AI only for clustering/summarization
- Human-controlled: No AI triggers prompts or makes decisions
- Node.js 18+
- Docker (for NATS, Redis, DuckDB)
npm install- Copy
backend/.envand set your values:
OPENAI_API_KEY=sk-...
NATS_URL=nats://localhost:4222
INGEST_PORT=3002
DASHBOARD_API_PORT=3000
- Widget demo uses the ingest port from
.env(currently 3002) viawindow.flowbackConfig.apiUrl.
Run each in its own terminal:
# Ingest (HTTP collector)
cd backend
npm run dev
# Dashboard API
cd backend
npm run agent:api
# Widget demo
cd widget
npm run dev # Vite will choose an open port (e.g., 5175)
# Dashboard UI
cd dashboard
npm run dev # Vite will choose an open port (e.g., 5176)Current defaults (after recent changes):
- Ingest: http://localhost:3002
- Dashboard API: http://localhost:3000
- Widget dev: http://localhost:5175 (Vite auto-picks if busy)
- Dashboard dev: http://localhost:5176 (Vite auto-picks if busy)
<script>
window.flowbackConfig = {
projectId: 'test-project-123',
apiUrl: 'http://localhost:3002',
consent: true
};
</script>
<script src="http://localhost:5175/flowback.js"></script>flowback/
├── widget/ # Embeddable JS SDK
│ ├── src/
│ │ ├── index.ts # Main entry point
│ │ ├── signal-capture.ts
│ │ ├── micro-prompt.ts
│ │ ├── consent.ts
│ │ ├── queue.ts
│ │ └── types.ts
│ └── package.json
│
├── backend/ # Event-driven services
│ ├── services/
│ │ ├── ingest.ts # HTTP collector + message bus router
│ │ └── api.ts # Dashboard API (GraphQL/REST)
│ │
│ ├── agents/ # Stateless event consumers
│ │ ├── signal-agent.ts
│ │ ├── feedback-agent.ts
│ │ ├── context-agent.ts
│ │ ├── ethics-agent.ts
│ │ ├── correlator-agent.ts
│ │ └── insight-agent.ts (optional AI)
│ │
│ ├── storage/ # Data layer
│ │ ├── redis-store.ts
│ │ ├── duckdb-store.ts
│ │ └── projections.ts
│ │
│ └── package.json
│
├── dashboard/ # Admin UI (React/Vite)
│ ├── src/
│ │ ├── pages/
│ │ │ ├── Hotspots.tsx
│ │ │ ├── Sentiment.tsx
│ │ │ ├── Evidence.tsx
│ │ │ └── Insights.tsx
│ │ ├── components/
│ │ ├── hooks/
│ │ └── App.tsx
│ └── package.json
│
├── ai/ # Optional AI components
│ ├── clustering.py # Batch clustering job
│ ├── summarization.py # Insight generation
│ └── requirements.txt
│
├── docs/ # Documentation & examples
│ ├── ARCHITECTURE.md
│ ├── EVENT_SCHEMA.md
│ ├── INTEGRATION.md
│ └── API.md
│
└── docker-compose.yml # Local dev environment
Agents communicate via a message bus (NATS/Kafka). Each agent is a stateless, topic-bound consumer:
| Event | Source | Purpose |
|---|---|---|
signal.raw |
Widget | Raw UI interactions (click, hover, scroll, dwell) |
signal.normalized |
Signal Agent | Normalized, deduplicated signals (rage-clicks, hesitation) |
feedback.recorded |
Feedback Agent | 1-tap reactions with context |
context.enriched |
Context Agent | Signals + metadata (page, device, cohort, consent) |
policy.updated |
Ethics Agent | Throttle state & permission to prompt |
session.friction |
Correlator Agent | Journey-level friction segments |
insight.summary |
Insight Agent (AI) | Clustered insights with hypotheses |
- Passive capture: Clicks, hovers, scroll depth, dwell time, hesitation, backtracks, rage-clicks
- Micro-prompts: 1-tap reactions (👍 👎 😕), contextual, throttled
- Consent-first: Explicit toggle, local cooldown storage
- Resilient: Offline queue, batched POST with backpressure
- Signal Agent: Normalizes raw signals, detects rage-clicks & hesitation
- Feedback Agent: Ties 1-tap reactions to session/page context
- Context Agent: Enriches all events with device, page, cohort, consent state
- Ethics/Throttling Agent: Enforces frequency caps, regional consent, suppression windows
- Correlator Agent: Stitches sequences into journeys, marks friction hotspots
- Insight Agent (optional AI): Clusters + summarizes patterns, generates hypotheses
- Hotspots map: Journeys ranked by friction; drill-down to raw signals
- Sentiment trends: Stacked area chart (👍 vs 👎 vs 😕) over time
- Evidence drawer: Anonymized event snippets tied to friction
- Insight cards: AI-generated or rule-based summaries with drill-down
- Controls: Segment by page/device/cohort, export, alerts
- Redis: Session state, throttle windows, consent flags
- DuckDB (or ClickHouse): OLAP analytics for hot paths, sentiment timelines, friction metrics
- Object storage (S3/local): Raw session envelopes for deep investigation
| Challenge | Flowback Solution |
|---|---|
| Static surveys are tedious | Zero forms; feedback via behavior + 1-tap |
| Low response rates | 1-tap, contextual, throttled; no time tax |
| Delayed insights | Real-time streams; continuous sentiment pulse |
| Friction invisible | Passive hesitation/backtrack detection + visual hotspots |
| Privacy concerns | Consent-first, anonymized sessions, data retention limits |
- Widget: signal capture, micro-prompt UI, consent, throttling
- Ingest service: HTTP collector, message bus router
- Signal Agent, Feedback Agent, Context Agent, Ethics Agent
- Redis + DuckDB setup with basic projections
- Dashboard hotspots page with chart & drill-down
- Docker Compose for local dev
- Correlator Agent: basic session stitching
- Dashboard sentiment page: chart only (no drill-down yet)
- AI: clustering job script (requires manual trigger)
- Session Correlator polish (advanced journey analysis)
- Full dashboard (evidence, insights, alerts)
- AI auto-trigger for weekly digests
- Event mesh integration (Solace)
- Voice summaries, wearable signal inputs
The message bus is abstracted; swap NATS for Kafka or Solace Agent Mesh without changing agent code:
// agents/base-agent.ts
export interface MessageBus {
subscribe(topic: string, handler: (msg: any) => Promise<void>): void;
publish(topic: string, msg: any): Promise<void>;
}Add wearable/focus signals as new signal.raw subtypes:
{
"sessionId": "...",
"type": "wearable",
"subtype": "stress",
"value": 0.75,
"device": "apple-watch"
}Extend dashboard with "record 30s note" → speech-to-text → insight clustering.
# 1. Install dependencies (per package)
cd backend && npm install
cd dashboard && npm install
cd widget && npm install
# 2. Start Redis if you want persistence (optional but recommended)
docker run -p 6379:6379 -d redis:7
# 3. Start ingest & API
cd backend
npm run dev # ingest on INGEST_PORT (default 3002)
npm run agent:api # dashboard API on DASHBOARD_API_PORT (default 3000)
# 4. Start widget and dashboard
cd widget && npm run dev # widget dev (Vite picks 5175 if free)
cd dashboard && npm run dev # dashboard dev (Vite picks 5176 if free)
# 5. Open
# Widget: http://localhost:5175
# Dashboard: http://localhost:5176/?projectId=defaultPOST http://localhost:3001/events
Content-Type: application/json
[
{
"sessionId": "sess_123",
"projectId": "proj_456",
"timestamp": 1705424400000,
"type": "signal.raw",
"payload": {
"action": "click",
"target": "#checkout-btn",
"dwellMs": 3200
}
}
]GET http://localhost:3000/api/hotspots?page=/checkout
GET http://localhost:3000/api/sentiment?from=2026-01-01&to=2026-01-16
GET http://localhost:3000/api/evidence?clusterId=pay-latency- Languages: TypeScript (backend/widget), React+TypeScript (dashboard), Python (AI)
- Message Bus: NATS (dev); Kafka-compatible for prod
- DB: DuckDB (analytics), Redis (cache), optionally ClickHouse
- AI: LLM-agnostic; examples use OpenAI
- Create a feature branch
- Implement agent/service following event-driven patterns
- Add tests
- Submit PR
Proprietary (Hackathon Project)
Built for SurveyMonkey's "Future of Feedback" Challenge — Hackathon 2026