Cross-Pollination Architecture Specification
1. [Overview](#1-overview) 2. [Prediction Engine](#2-prediction-engine) 3. [Safety Rails](#3-safety-rails) 4. [ACC Integration — Swipeable Prediction Cards](#4-acc-integration--swipeable-prediction-cards) 5. [Autonomous Mode](#5-autonomous-mode) 6. [Push Notifications](#6-push-notifications) 7. [Feedback Loop & DPO Training](#7-feedback-loop--dpo-training) 8. [API Reference](#8-api-reference) 9. [Data Models](#9-data-models) 10. [Deployment & Configuration](#10-deployment--configuration)
Full Public Reader
Cross-Pollination Architecture Specification
> PULSE V1 — Wave 1 Deliverable [A3]
> Status: COMPLETE | Author: Clawdbot Agent | Date: 2025-07-29
---
Table of Contents
1. [Overview](#1-overview)
2. [Prediction Engine](#2-prediction-engine)
3. [Safety Rails](#3-safety-rails)
4. [ACC Integration — Swipeable Prediction Cards](#4-acc-integration--swipeable-prediction-cards)
5. [Autonomous Mode](#5-autonomous-mode)
6. [Push Notifications](#6-push-notifications)
7. [Feedback Loop & DPO Training](#7-feedback-loop--dpo-training)
8. [API Reference](#8-api-reference)
9. [Data Models](#9-data-models)
10. [Deployment & Configuration](#10-deployment--configuration)
---
1. Overview
What Is Cross-Pollination?
Cross-pollination is the system by which PULSE agents proactively generate, predict, and execute work without explicit human instruction. A personal Twin model predicts the user's likely next actions, routes them to the appropriate agent channel, and optionally executes autonomously — all while maintaining strict safety rails and a human-in-the-loop escape hatch.
Why It Matters
Traditional AI assistants are reactive: the user speaks, the assistant responds. Cross-pollination inverts this. The system observes the user's patterns (time of day, project state, recent conversations, unfinished tasks) and generates the next move before the user asks. The user's role shifts from director to reviewer — swipe right to approve, swipe left to dismiss.
This is the core mechanism behind the "Age of Leisure" vision: agents that work while you walk.
System Boundaries
┌─────────────────────────────────────────────────────────────────────┐
│ PULSE Ecosystem │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │
│ │ ACC │ │ Life OS │ │VisionClaw│ │ Compass │ │
│ │ (phone) │ │ (watch) │ │(glasses) │ │ (web) │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └───────┬──────────┘ │
│ │ │ │ │ │
│ └──────────────┴──────────────┴──────────────────┘ │
│ │ │
│ ┌─────────▼──────────┐ │
│ │ Pulse Gateway API │ │
│ │ /api/v1/* │ │
│ └─────────┬──────────┘ │
│ │ │
│ ┌───────────────┼───────────────┐ │
│ │ │ │ │
│ ┌────────▼───────┐ ┌────▼────┐ ┌────────▼──────┐ │
│ │ CrossPoll │ │ Twin │ │ Prediction │ │
│ │ Engine │ │ Model │ │ Store │ │
│ └────────┬───────┘ └────┬────┘ └────────┬──────┘ │
│ │ │ │ │
│ └───────────────┴───────────────┘ │
│ │ │
│ ┌─────────▼──────────┐ │
│ │ Clawdbot Gateway │ │
│ │ (Agent Sessions) │ │
│ └────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘Key Terminology
| Term | Definition |
|---|---|
| Cross-pollination | An autonomous agent action triggered by prediction rather than user command |
| Prediction | A generated prompt + target channel + confidence score |
| Twin Model | User's fine-tuned LLM (SFT/DPO on conversation history) that mimics their decision style |
| Prediction Card | UI element in ACC showing a pending prediction (swipeable) |
| Autonomous Mode | 30-minute silence-triggered cron that generates and optionally executes predictions |
| DPO Pair | A (chosen, rejected) training example derived from user accept/reject of predictions |
| Safety Rail | Rate limits, cost caps, loop detectors, and kill switches that constrain autonomy |
---
2. Prediction Engine
2.1 Architecture
The Prediction Engine is the brain of cross-pollination. It determines what to suggest, where to route it, and how confident it is.
┌──────────────────────────────────────────────────────┐
│ Prediction Engine │
│ │
│ ┌─────────────┐ ┌──────────────┐ ┌────────────┐ │
│ │ Context │ │ Twin │ │ Channel │ │
│ │ Assembler │──▶│ Model │──▶│ Router │ │
│ │ │ │ (Together) │ │ │ │
│ └──────┬──────┘ └──────────────┘ └─────┬──────┘ │
│ │ │ │
│ ┌────▼────┐ ┌─────▼─────┐ │
│ │ Signals │ │ Prediction│ │
│ │ Collector│ │ Emitter │ │
│ └─────────┘ └───────────┘ │
└──────────────────────────────────────────────────────┘2.2 Signal Collection
The Context Assembler gathers input signals to form a prediction context:
| Signal | Source | Weight | Description |
|---|---|---|---|
| Time of day | System clock | 0.3 | Morning = review/plan, afternoon = deep work, evening = summarize |
| Silence duration | Last user message timestamp | 0.2 | Longer silence → higher autonomy willingness |
| Active tasks | Task Store (`/api/v1/tasks`) | 0.2 | Stale tasks get priority |
| Recent conversations | Agent session history | 0.15 | Continue threads, follow up on open questions |
| Git state | Repository hooks | 0.1 | Uncommitted changes, open PRs, failing CI |
| Calendar | Calendar integration | 0.05 | Upcoming meetings → prepare context |
2.3 Twin Model Prediction
When the Twin SFT/DPO model is available, the Prediction Engine delegates to it for high-fidelity predictions:
POST /api/v1/twin/serve (internal)
{
"model": "twin-sft-v1",
"messages": [
{
"role": "system",
"content": "You are a prediction engine for Mohamed's AI agent system. Based on the current context, predict the single most valuable action to take right now. Return JSON: {\"prompt\": string, \"channel\": string, \"confidence\": 0-1, \"reasoning\": string}"
},
{
"role": "user",
"content": "<assembled context: time, tasks, recent activity, silence duration>"
}
],
"temperature": 0.3,
"max_tokens": 200
}Fallback (pre-Twin): Template-based generation using time-of-day heuristics and channel rotation. This is the current implementation in `crosspoll-engine.ts`:
// Time-of-day prompt templates (simplified)
const PROMPT_TEMPLATES = {
morning: ["What's the most important task for today?", ...],
afternoon: ["What tasks have been stuck the longest?", ...],
evening: ["Summarize today's progress across all channels.", ...],
};2.4 Channel Routing
Each prediction targets a specific agent channel based on task type:
| Channel | Specialty | Example Predictions |
|---|---|---|
| `koji-core` | Code, builds, tests | "Run tests on the most recently changed modules" |
| `chronicle-hub` | Documentation, writing | "Update CHANGELOG for the last 5 commits" |
| `dream-weaver` | Creative exploration | "Brainstorm 3 alternative approaches to the caching problem" |
| `research` | Analysis, investigation | "Research competitor pricing changes this week" |
| `garden` | Knowledge graph, memory | "Update the project dependency graph" |
Routing logic:
1. Twin model suggests channel (if available) → use it
2. Task has explicit channel affinity (code → koji) → use affinity
3. Round-robin across channels not triggered recently → balance load
4. Weighted random based on time-of-day patterns → fallback2.5 Confidence Scoring
Every prediction receives a confidence score `[0.0, 1.0]`:
| Range | Meaning | Action |
|---|---|---|
| `0.0 - 0.29` | Low confidence | Discard, do not show to user |
| `0.3 - 0.59` | Medium confidence | Show as prediction card, do NOT auto-execute |
| `0.6 - 0.79` | High confidence | Show as prediction card with "suggested" badge |
| `0.8 - 1.0` | Very high confidence | Auto-execute in autonomous mode (with safety rails) |
Confidence factors:
confidence = (
twin_model_confidence * 0.4 + // Twin's self-reported confidence
pattern_match_score * 0.25 + // How well this matches historical patterns
task_urgency_score * 0.2 + // Stale tasks boost urgency
time_relevance_score * 0.15 // Morning review gets boost at 8am
)---
3. Safety Rails
Safety rails are non-negotiable. Autonomous agents without constraints are a liability. Every cross-pollination action passes through the safety pipeline before execution.
3.1 Safety Architecture
┌──────────────────────────────────────────────────────────────┐
│ Safety Pipeline │
│ │
│ Prediction ──▶ [Rate Limiter] ──▶ [Loop Detector] ──▶ │
│ │
│ ──▶ [Cost Cap] ──▶ [Content Filter] ──▶ [Human Gate] ──▶ │
│ │
│ ──▶ [Kill Switch Check] ──▶ ✅ Execute / ❌ Block │
│ │
└──────────────────────────────────────────────────────────────┘3.2 Rate Limiter
Prevents prediction/execution spam:
| Parameter | Default | Configurable | Description |
|---|---|---|---|
| `crossPollIntervalMs` | 600,000 (10 min) | Yes | Minimum time between triggers |
| Max triggers per hour | 6 | Derived | From interval × 60min |
| Burst allowance | 0 | No | No burst — strict interval enforcement |
Implementation (existing in `crosspoll-engine.ts`):
if (lastTriggerAt) {
const elapsed = Date.now() - new Date(lastTriggerAt).getTime();
if (elapsed < config.crossPollIntervalMs) {
return { triggered: false, message: `Rate limited: ${remaining}s` };
}
}3.3 Loop Detection
Detects and breaks agent-to-agent infinite loops:
Loop: Agent A predicts → triggers Agent B → Agent B predicts → triggers Agent A → ...Detection algorithm:
Track: recentChains[] = [{ from, to, timestamp }]
For each new trigger(from, to):
1. Check if (to → from) exists in recentChains within last 5 minutes
2. Check if chain depth > 3 (A→B→C→D = depth 4 → BLOCK)
3. Check if same (from, to) pair triggered > 2x in last 10 minutes
If any condition met → BLOCK + log warning + increment loopDetectionCountData structure:
interface ChainLink {
fromChannel: string;
toChannel: string;
predictionId: string;
timestamp: string;
}
// Rolling window of last 50 chain links
const recentChains: ChainLink[] = [];
const MAX_CHAIN_DEPTH = 3;
const LOOP_WINDOW_MS = 300_000; // 5 minutes
const DUPLICATE_THRESHOLD = 2;3.4 Cost Caps
Prevents runaway spending from autonomous operations:
| Cap | Default | Scope | Action on Breach |
|---|---|---|---|
| Per-hour cost | $2.00 | Sliding 1-hour window | Block all triggers until window resets |
| Per-day cost | $20.00 | Calendar day (midnight reset) | Block + notify user |
| Per-trigger estimate | $0.05 | Single execution | Used for tracking (1 agent turn ≈ $0.05) | ||
| Per-month budget | $200.00 | Calendar month | Hard stop + require manual re-enable |
Implementation:
function estimateCostThisHour(): number {
cleanHourlyTriggers();
return hourlyTriggers.length * COST_PER_TRIGGER;
}
if (estimateCostThisHour() >= config.crossPollCostCapPerHour) {
return { triggered: false, message: `Cost cap reached` };
}3.5 Content Filter
Prevents the prediction engine from generating harmful, destructive, or out-of-scope actions:
| Rule | Description |
|---|---|
| No destructive commands | Block predictions containing `rm -rf`, `drop table`, `delete production` |
| No external communication | Block predictions that would send emails, tweets, or public messages |
| No financial actions | Block predictions involving payments, transfers, purchases |
| Scope boundary | Predictions must target registered channels only |
3.6 Human-in-the-Loop Triggers
Certain conditions always escalate to the human, regardless of confidence:
┌─────────────────────────────────────────────────┐
│ Human-in-the-Loop Trigger Matrix │
├──────────────────────┬──────────────────────────┤
│ Condition │ Action │
├──────────────────────┼──────────────────────────┤
│ confidence < 0.6 │ Show card, don't execute │
│ Estimated cost > $1 │ Require explicit approval │
│ Targets production │ Always require approval │
│ Novel channel combo │ Show card + "NEW" badge │
│ 3+ rejections in row │ Pause autonomous mode │
│ Content filter hit │ Block + log for review │
│ Daily cost > 80% │ Notify + request guidance │
│ First run ever │ Onboarding mode: all cards│
└──────────────────────┴──────────────────────────┘3.7 Kill Switch
Emergency stop for all cross-pollination activity:
POST /api/v1/crosspoll/trigger { "force": false }
→ Checks config.crossPollEnabled
Gateway config:
plugins:
entries:
pulse-gateway-api:
config:
crossPollEnabled: false ← Master kill switchKill switch hierarchy:
1. Gateway config (`crossPollEnabled: false`) — survives restarts
2. API toggle (`POST /api/v1/crosspoll/config { enabled: false }`) — runtime only
3. ACC UI toggle — sends API toggle via Gateway
4. Watch complication — long-press to emergency stop
---
4. ACC Integration — Swipeable Prediction Cards
4.1 Mission Control Layout
Prediction cards appear in the ACC (Agent Command Center) Mission Control view as a horizontally swipeable card stack:
┌─────────────────────────────────────────────┐
│ Mission Control ⚙️ │
├─────────────────────────────────────────────┤
│ │
│ ┌─── Active Agents ───────────────────┐ │
│ │ 🤖 koji-core ● running 3.2k tk │ │
│ │ 📝 chronicle ● idle 1.1k tk │ │
│ │ 🔬 research ● running 5.4k tk │ │
│ └─────────────────────────────────────┘ │
│ │
│ ┌─── Cross-Pollination ───── ON 🟢 ──┐ │
│ │ │ │
│ │ ┌──────────────────────────────┐ │ │
│ │ │ 🔮 Prediction │ │ │
│ │ │ │ │ │
│ │ │ "Run tests on recently │ │ │
│ │ │ changed modules" │ │ │
│ │ │ │ │ │
│ │ │ → koji-core ⬤ 72% │ │ │
│ │ │ │ │ │
│ │ │ ← DISMISS APPROVE → │ │ │
│ │ └──────────────────────────────┘ │ │
│ │ · · ● · · (3 of 5 cards) │ │
│ └─────────────────────────────────────┘ │
│ │
│ ┌─── Recent Tasks ───────────────────┐ │
│ │ ✅ Deploy compass 2m ago │ │
│ │ 🔄 Update docs running │ │
│ │ ⏳ Twin fidelity queued │ │
│ └─────────────────────────────────────┘ │
└─────────────────────────────────────────────┘4.2 Card Data Model
// ACC (SwiftUI + TCA)
struct PredictionCard: Identifiable, Equatable {
let id: String // "pred_abc123"
let prompt: String // "Run tests on recently changed modules"
let targetChannel: String // "koji-core"
let confidence: Double // 0.72
let reasoning: String? // "3 modules changed since last test run"
let createdAt: Date
let source: PredictionSource // .twinModel | .templateEngine | .predictorHook
var status: PredictionStatus // .pending | .approved | .rejected | .expired
}
enum PredictionSource: String, Codable {
case twinModel = "twin-model"
case templateEngine = "pulse-store"
case predictorHook = "predictor-hook"
}
enum PredictionStatus: String, Codable {
case pending, approved, rejected, expired
}4.3 Swipe Gestures
┌────────────────────────────────────┐
│ │
│ ←── Swipe LEFT = DISMISS ──→ │
│ (sends reject to API) │
│ (card animates off-left) │
│ (DPO: negative signal) │
│ │
│ ←── Swipe RIGHT = APPROVE ──→ │
│ (sends confirm to API) │
│ (triggers execution) │
│ (DPO: positive signal) │
│ (haptic: success) │
│ │
│ ←── Swipe UP = SNOOZE ──→ │
│ (re-queue for later) │
│ (no DPO signal) │
│ │
│ ←── Long Press = DETAILS ──→ │
│ (show reasoning, cost est) │
│ (edit prompt before run) │
│ │
└────────────────────────────────────┘4.4 ACC ↔ Gateway API Flow
sequenceDiagram
participant ACC as ACC (iOS)
participant GW as Pulse Gateway
participant PS as Prediction Store
participant CE as CrossPoll Engine
Note over ACC: Mission Control opens
ACC->>GW: GET /api/v1/twin/predict?count=5
GW->>PS: latest(5)
PS-->>GW: [predictions...]
GW-->>ACC: { predictions, crossPollStatus }
ACC->>ACC: Render swipeable cards
Note over ACC: User swipes RIGHT on card
ACC->>GW: POST /api/v1/crosspoll/trigger<br/>{ targetChannel: "koji-core",<br/> hint: "Run tests..." }
GW->>CE: trigger()
CE->>CE: Safety pipeline check
CE->>PS: confirm(predictionId)
CE-->>GW: { triggered: true, prediction }
GW-->>ACC: 200 OK
Note over ACC: User swipes LEFT on card
ACC->>GW: POST /api/v1/predictions/{id}/reject
GW->>PS: reject(id)
GW-->>ACC: 200 OK4.5 Real-Time Updates (WebSocket)
ACC subscribes to WebSocket events for live prediction updates:
// WebSocket event types for cross-pollination
type CrossPollEvent =
| { type: "prediction.new"; prediction: Prediction }
| { type: "prediction.executed"; predictionId: string; result: TaskResult }
| { type: "prediction.expired"; predictionId: string }
| { type: "crosspoll.status"; status: CrossPollStatus }
| { type: "crosspoll.paused"; reason: string }
| { type: "crosspoll.resumed" };---
5. Autonomous Mode
5.1 Overview
Autonomous Mode activates when the user has been silent for ≥30 minutes. It runs a cron-based cycle that generates predictions, evaluates them against safety rails, and executes high-confidence actions without user intervention.
5.2 State Machine
┌──────────┐
┌─────────│ DISABLED │◄────── Kill switch / config
│ └────┬─────┘
│ │ crossPollEnabled = true
│ ▼
│ ┌──────────┐
│ ┌───▶│ WATCHING │◄──── User sends message
│ │ └────┬─────┘ │
│ │ │ silence ≥ 30min │
│ │ ▼ │
│ │ ┌──────────┐ │
│ │ │ PRIMING │ │
│ │ │(1st pred)│ │
│ │ └────┬─────┘ │
│ │ │ prediction ready│
│ │ ▼ │
│ │ ┌──────────┐ │
│ │ │AUTONOMOUS│───────────┘
│ │ │ (active) │ user speaks → reset
│ │ └────┬─────┘
│ │ │ 3 rejections or cost cap
│ │ ▼
│ │ ┌──────────┐
│ └────│ PAUSED │
│ └────┬─────┘
│ │ manual resume or 2h timeout
│ ▼
└─────────────(back to WATCHING)5.3 Cron Schedule
# Autonomous cross-pollination cron (managed by Clawdbot)
# Fires every 30 minutes, checks silence condition
*/30 * * * * clawdbot cron:run pulse-crosspoll-auto
# The cron handler:
1. Check last user message timestamp
2. If silence < 30min → skip (WATCHING state)
3. If silence ≥ 30min → enter PRIMING
4. Generate prediction via Twin model (or template fallback)
5. Run through safety pipeline
6. If confidence ≥ 0.8 AND all safety checks pass → execute
7. If confidence 0.3-0.79 → emit prediction card to ACC (no execute)
8. If confidence < 0.3 → discard
9. Log result to predictions.jsonl5.4 Autonomous Execution Flow
flowchart TD
CRON[30-min Cron Fires] --> SILENCE{User silent ≥30min?}
SILENCE -->|No| SKIP[Skip — reset timer]
SILENCE -->|Yes| CONTEXT[Assemble Context]
CONTEXT --> PREDICT[Twin Model Predict]
PREDICT --> SAFETY{Safety Pipeline}
SAFETY -->|Blocked| LOG_BLOCK[Log block reason]
SAFETY -->|Pass| CONFIDENCE{Confidence?}
CONFIDENCE -->|≥ 0.8| EXECUTE[Auto-Execute]
CONFIDENCE -->|0.3–0.79| CARD[Emit Prediction Card]
CONFIDENCE -->|< 0.3| DISCARD[Discard]
EXECUTE --> NOTIFY[Push Notification]
EXECUTE --> RECORD[Record for DPO]
CARD --> NOTIFY_CARD[Notification: New suggestion]
LOG_BLOCK --> METRICS[Update Safety Metrics]5.5 Session Boundaries
Autonomous operations run in isolated sessions to prevent context contamination:
// Each autonomous trigger gets its own session
const autonomousSession = {
id: `auto_${Date.now()}`,
parentPrediction: prediction.id,
maxTokens: 50_000, // Hard cap per autonomous session
maxDurationMs: 120_000, // 2 minute max execution time
allowedTools: [ // Restricted tool access
"read", "write", "edit", "exec",
"web_search", "web_fetch"
],
blockedTools: [ // Never in autonomous mode
"message", "tts", "browser"
],
};5.6 Quiet Hours
Autonomous mode respects quiet hours:
| Period | Behavior |
|---|---|
| 08:00 - 23:00 | Full autonomous mode |
| 23:00 - 08:00 | Predictions generated but never auto-executed; cards queued for morning |
| User-defined DND | Same as quiet hours — queue, don't execute |
---
6. Push Notifications
6.1 Notification Targets
Cross-pollination events push to all active PULSE surfaces:
┌──────────────────────────────────────────────────┐
│ Notification Router │
│ │
│ Event ──▶ ┌──────────┐ │
│ │ ACC │ ← Rich card notification │
│ │ (iPhone) │ with approve/reject │
│ ├──────────┤ │
│ │ Life OS │ ← Watch complication update │
│ │ (Watch) │ + haptic tap │
│ ├──────────┤ │
│ │VisionClaw│ ← Spoken summary via │
│ │(Glasses) │ bone conduction speaker │
│ ├──────────┤ │
│ │ Discord │ ← 🔮 embed in #bridge │
│ │ │ │
│ ├──────────┤ │
│ │ Compass │ ← Browser notification │
│ │ (Web) │ (if tab open) │
│ └──────────┘ │
└──────────────────────────────────────────────────┘6.2 Notification Types
| Event | ACC (Phone) | Life OS (Watch) | VisionClaw (Glasses) | Discord |
|---|---|---|---|---|
| New prediction (medium conf.) | Banner + card | Complication badge | — | — |
| New prediction (high conf.) | Banner + card | Haptic + complication | Spoken alert | 🔮 embed |
| Auto-executed action | Silent update in Mission Control | Complication refresh | — | ✅ reaction |
| Execution completed | Banner: "Done: {result}" | Haptic success | Spoken summary | Result embed |
| Execution failed | Alert: "Failed: {error}" | Haptic failure | — | ❌ reaction |
| Cost cap approaching | Alert: "80 | |||
| Autonomous mode paused | Banner: "Paused: 3 rejections" | Complication: ⏸ | — | Status update |
6.3 ACC Push Notification Payload
{
"aps": {
"alert": {
"title": "🔮 Cross-Pollination",
"subtitle": "koji-core • 72% confidence",
"body": "Run tests on recently changed modules"
},
"sound": "prediction.caf",
"badge": 1,
"category": "CROSSPOLL_PREDICTION",
"thread-id": "crosspoll"
},
"predictionId": "pred_abc123",
"targetChannel": "koji-core",
"confidence": 0.72,
"actions": ["approve", "reject", "snooze"]
}Actionable notification categories:
// iOS notification actions (no app open required)
let approveAction = UNNotificationAction(
identifier: "APPROVE",
title: "✅ Approve",
options: [.foreground]
)
let rejectAction = UNNotificationAction(
identifier: "REJECT",
title: "❌ Dismiss",
options: [.destructive]
)
let snoozeAction = UNNotificationAction(
identifier: "SNOOZE",
title: "⏰ Later",
options: []
)6.4 Watch Complication
Life OS surfaces cross-pollination status via Watch complications:
┌─────────────────────────────────┐
│ Circular Complication (small) │
│ │
│ 🔮 3 │
│ (3 pending predictions) │
│ │
│ Tap → opens Life OS │
│ Long press → toggle auto mode │
└─────────────────────────────────┘
┌─────────────────────────────────┐
│ Rectangular Complication │
│ │
│ 🔮 "Run tests on modules" │
│ koji-core • 72% • 2m ago │
│ │
│ Tap → approve │
│ Force tap → options │
└─────────────────────────────────┘6.5 Glasses (VisionClaw) Alerts
For high-confidence predictions while the user is wearing glasses:
[Bone conduction speaker, brief]
"Cross-pollination suggestion: Run tests on recently changed modules.
Confidence seventy-two percent. Say 'approve' or 'skip'."
User: "Approve"
→ Voice confirmation: "Running. I'll update you when done."---
7. Feedback Loop & DPO Training
7.1 Overview
Every user interaction with a prediction card generates a training signal. These signals are collected, formatted as DPO (Direct Preference Optimization) pairs, and periodically exported for Twin model fine-tuning.
┌──────────────────────────────────────────────────────────┐
│ Feedback Loop │
│ │
│ User Action ──▶ Signal Store ──▶ DPO Formatter ──▶ │
│ │
│ ──▶ Training Export ──▶ Together AI Fine-Tune ──▶ │
│ │
│ ──▶ Updated Twin Model ──▶ Better Predictions ──▶ ... │
│ │
└──────────────────────────────────────────────────────────┘7.2 Signal Collection
| User Action | Signal Type | DPO Value | Weight |
|---|---|---|---|
| Swipe right (approve) | Positive | Chosen | 1.0 |
| Swipe left (reject) | Negative | Rejected | 1.0 |
| Swipe up (snooze) | Neutral | — (excluded) | 0.0 |
| Auto-executed → no complaint | Implicit positive | Chosen | 0.5 |
| Auto-executed → user undoes | Implicit negative | Rejected | 0.8 |
| Card expires (ignored) | Weak negative | Rejected | 0.3 |
| User edits prompt then approves | Corrective | Both (original=rejected, edited=chosen) | 1.0 |
7.3 DPO Pair Format
{"prompt": "<context>", "chosen": "Run tests on changed modules in koji-core", "rejected": "Summarize today's progress in chronicle-hub", "metadata": {"timestamp": "2025-07-29T14:30:00Z", "confidence_chosen": 0.72, "confidence_rejected": 0.45}}
{"prompt": "<context>", "chosen": "Check for failed pipelines in CI", "rejected": "Explore creative brainstorming ideas", "metadata": {"timestamp": "2025-07-29T15:00:00Z", "confidence_chosen": 0.85, "confidence_rejected": 0.38}}Pair generation strategy:
For each approved prediction P_approved:
Find the most recent rejected prediction P_rejected with:
- Same or similar context window (within 1 hour)
- Different target channel (diverse negatives)
Emit DPO pair:
chosen = P_approved.prompt + " → " + P_approved.targetChannel
rejected = P_rejected.prompt + " → " + P_rejected.targetChannel
prompt = assembled context at P_approved.createdAt7.4 Training Pipeline
flowchart LR
SIGNALS[Signal Store<br/>predictions.jsonl] --> BATCH[Batch Processor<br/>every 24h]
BATCH --> FORMAT[DPO Formatter]
FORMAT --> VALIDATE[Quality Gate<br/>≥50 pairs minimum]
VALIDATE -->|Pass| EXPORT[Export to<br/>dpo-pairs.jsonl]
VALIDATE -->|Fail| ACCUMULATE[Accumulate<br/>more signals]
EXPORT --> UPLOAD[Upload to<br/>Together AI]
UPLOAD --> FINETUNE[DPO Fine-Tune<br/>on Twin model]
FINETUNE --> DEPLOY[Deploy new<br/>Twin version]
DEPLOY --> PREDICT[Prediction Engine<br/>uses new model]
PREDICT --> SIGNALS7.5 Training Schedule
| Milestone | Trigger | Action |
|---|---|---|
| Initial SFT | One-time (already submitted) | Base Twin model from 38K conversation records |
| Initial DPO | After SFT completes + 882 curated pairs | First preference alignment layer |
| Cross-poll DPO v1 | 50+ accept/reject pairs accumulated | First cross-poll-specific DPO round |
| Ongoing DPO | Every 100 new pairs OR weekly | Continuous improvement |
| Model swap | DPO job completes | Hot-swap model ID in Gateway config (no rebuild) |
7.6 Accuracy Tracking
The system tracks prediction accuracy over time:
interface PredictionMetrics {
totalPredictions: number;
approved: number;
rejected: number;
expired: number;
autoExecuted: number;
autoExecutedUndone: number;
// Derived
approvalRate: number; // approved / (approved + rejected)
autoSuccessRate: number; // (autoExecuted - autoExecutedUndone) / autoExecuted
avgConfidence: number; // mean confidence of all predictions
avgApprovedConfidence: number; // mean confidence of approved predictions
avgRejectedConfidence: number; // mean confidence of rejected predictions
// Calibration: is confidence score well-calibrated?
// Bucket predictions by confidence range, check if approval rate matches
calibration: {
bucket: string; // "0.3-0.4", "0.4-0.5", etc.
predictions: number;
approvalRate: number; // Should be close to the bucket midpoint
}[];
}Metrics endpoint:
GET /api/v1/crosspoll/metrics
→ {
"period": "last_7_days",
"totalPredictions": 142,
"approvalRate": 0.64,
"autoSuccessRate": 0.91,
"avgConfidence": 0.58,
"calibration": [
{ "bucket": "0.3-0.4", "predictions": 23, "approvalRate": 0.30 },
{ "bucket": "0.4-0.5", "predictions": 31, "approvalRate": 0.42 },
{ "bucket": "0.5-0.6", "predictions": 28, "approvalRate": 0.57 },
{ "bucket": "0.6-0.7", "predictions": 35, "approvalRate": 0.69 },
{ "bucket": "0.7-0.8", "predictions": 18, "approvalRate": 0.78 },
{ "bucket": "0.8-1.0", "predictions": 7, "approvalRate": 0.86 }
],
"trendDirection": "improving",
"lastDpoTraining": "2025-07-28T00:00:00Z"
}7.7 Cold Start Strategy
Before the Twin model is available or before enough DPO pairs accumulate:
Phase 0 — Template Only (Day 1-2):
- Time-of-day heuristics
- Channel round-robin
- Fixed confidence = 0.4 (always show card, never auto-execute)
- Goal: Collect initial accept/reject signals
Phase 1 — SFT Twin (Day 2-3):
- Twin model generates predictions
- Confidence from model's self-assessment
- Auto-execute threshold raised to 0.8
- Goal: Higher quality predictions, start accumulating DPO pairs
Phase 2 — DPO Twin (Day 3+):
- Preference-aligned predictions
- Calibrated confidence scores
- Full autonomous mode enabled
- Continuous improvement loop active---
8. API Reference
8.1 Endpoints
`GET /api/v1/twin/predict`
Returns pending predictions for display in ACC/Watch.
Query Parameters:
| Param | Type | Default | Description |
|---|---|---|---|
| `count` | int | 3 | Number of predictions to return (max: 10) |
| `channel` | string | — | Filter by target channel |
Response:
{
"predictions": [
{
"id": "pred_abc123",
"prompt": "Run tests on recently changed modules",
"targetAgent": "main",
"targetChannel": "koji-core",
"confidence": 0.72,
"reasoning": "3 modules changed since last test run; morning review pattern",
"createdAt": "2025-07-29T14:30:00Z",
"status": "pending",
"source": "twin-model"
}
],
"count": 1,
"timestamp": "2025-07-29T14:30:05Z",
"crossPollStatus": {
"enabled": true,
"lastTriggerAt": "2025-07-29T14:20:00Z",
"totalTriggers": 47,
"costThisHour": 0.15,
"costCapPerHour": 2.00
}
}`POST /api/v1/crosspoll/trigger`
Manually trigger a cross-pollination cycle (or approve a prediction).
Request Body:
{
"targetChannel": "koji-core",
"hint": "Run tests on recently changed modules",
"force": false,
"predictionId": "pred_abc123"
}Response:
{
"triggered": true,
"prediction": { "id": "pred_abc123", "..." : "..." },
"targetChannel": "koji-core",
"message": "Cross-pollination prompt generated for #koji-core",
"status": { "..." : "..." }
}`POST /api/v1/predictions/{id}/confirm`
Confirm (approve) a prediction. Generates a positive DPO signal.
`POST /api/v1/predictions/{id}/reject`
Reject (dismiss) a prediction. Generates a negative DPO signal.
`GET /api/v1/crosspoll/metrics`
Returns accuracy and calibration metrics (see §7.6).
`PATCH /api/v1/crosspoll/config`
Runtime configuration update (does not persist across restarts).
{
"enabled": true,
"intervalMs": 600000,
"costCapPerHour": 2.00,
"autoExecuteThreshold": 0.8,
"quietHoursStart": 23,
"quietHoursEnd": 8
}8.2 WebSocket Events
Subscribe to `ws://gateway/pulse` for real-time cross-pollination events:
// Client subscribes
ws.send(JSON.stringify({ type: "subscribe", channels: ["crosspoll"] }));
// Server pushes
{ "type": "prediction.new", "prediction": {...} }
{ "type": "prediction.executed", "predictionId": "pred_abc123", "taskId": "task_xyz" }
{ "type": "prediction.expired", "predictionId": "pred_abc123" }
{ "type": "crosspoll.status", "status": {...} }
{ "type": "crosspoll.paused", "reason": "3 consecutive rejections" }
{ "type": "crosspoll.resumed" }8.3 RPC Methods
For internal Clawdbot agent-to-agent communication:
// Gateway RPC
gateway.call("pulse.crosspoll.trigger", { channel: "koji-core", hint: "..." })
gateway.call("pulse.predictions.latest", { count: 5 })
gateway.call("pulse.crosspoll.status", {})---
9. Data Models
9.1 Supabase Schema (Future: Persistent Storage)
-- Predictions table
CREATE TABLE predictions (
id TEXT PRIMARY KEY, -- "pred_abc123"
user_id UUID REFERENCES auth.users, -- Owner
prompt TEXT NOT NULL,
target_agent TEXT DEFAULT 'main',
target_channel TEXT,
confidence REAL NOT NULL CHECK (confidence >= 0 AND confidence <= 1),
reasoning TEXT,
status TEXT NOT NULL DEFAULT 'pending'
CHECK (status IN ('pending','confirmed','rejected','expired','executed')),
source TEXT DEFAULT 'template', -- 'template' | 'twin-model' | 'predictor-hook'
created_at TIMESTAMPTZ DEFAULT NOW(),
confirmed_at TIMESTAMPTZ,
executed_at TIMESTAMPTZ,
task_id TEXT, -- Links to tasks table if executed
dpo_exported BOOLEAN DEFAULT FALSE -- Has this been exported for training?
);
-- RLS: users can only see their own predictions
ALTER TABLE predictions ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users see own predictions"
ON predictions FOR SELECT
USING (auth.uid() = user_id);
-- DPO training pairs
CREATE TABLE dpo_pairs (
id SERIAL PRIMARY KEY,
user_id UUID REFERENCES auth.users,
context TEXT NOT NULL, -- Assembled context at prediction time
chosen TEXT NOT NULL, -- Approved prediction
rejected TEXT NOT NULL, -- Rejected prediction
chosen_confidence REAL,
rejected_confidence REAL,
created_at TIMESTAMPTZ DEFAULT NOW(),
exported_at TIMESTAMPTZ -- When exported for training
);
-- Cross-pollination execution log
CREATE TABLE crosspoll_log (
id SERIAL PRIMARY KEY,
user_id UUID REFERENCES auth.users,
prediction_id TEXT REFERENCES predictions(id),
trigger_type TEXT NOT NULL CHECK (trigger_type IN ('manual','autonomous','cron')),
target_channel TEXT NOT NULL,
prompt TEXT NOT NULL,
result JSONB, -- Task result if executed
cost_estimate REAL DEFAULT 0.05,
blocked BOOLEAN DEFAULT FALSE,
block_reason TEXT,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Indexes
CREATE INDEX idx_predictions_status ON predictions(status);
CREATE INDEX idx_predictions_user ON predictions(user_id, created_at DESC);
CREATE INDEX idx_dpo_exported ON dpo_pairs(exported_at) WHERE exported_at IS NULL;
CREATE INDEX idx_crosspoll_log_user ON crosspoll_log(user_id, created_at DESC);9.2 In-Memory Models (Current Implementation)
The V1 implementation uses in-memory stores (see `prediction-store.ts`, `crosspoll-engine.ts`). Migration to Supabase happens in Wave 2-3 when auth is integrated.
// Current: In-memory prediction store
const MAX_PREDICTIONS = 100;
const predictions: Prediction[] = [];
// Future: Supabase-backed store
const { data } = await supabase
.from('predictions')
.select('*')
.eq('user_id', userId)
.eq('status', 'pending')
.order('created_at', { ascending: false })
.limit(count);9.3 File-Based Storage (Predictor Hook)
The next-prompt-predictor Clawdbot hook stores predictions in JSONL:
[home-path]
{"prediction":"Run tests","confidence":0.72,"channel":"koji-core","reasoning":"...","timestamp":"2025-07-29T14:30:00Z"}
{"prediction":"Update docs","confidence":0.45,"channel":"chronicle-hub","reasoning":"...","timestamp":"2025-07-29T14:40:00Z"}The `twin-predict` route reads both the in-memory store AND this file, merging results.
---
10. Deployment & Configuration
10.1 Plugin Configuration
# [home-path] (or equivalent)
plugins:
entries:
pulse-gateway-api:
enabled: true
config:
# Twin model
twinModelId: "ft:twin-sft-v1:together:2025-02"
twinApiKey: "${TOGETHER_API_KEY}"
# Cross-pollination
crossPollEnabled: true
crossPollIntervalMs: 600000 # 10 minutes
crossPollCostCapPerHour: 2.00 # $2/hour max
crossPollAutoExecuteThreshold: 0.8 # Auto-execute above this
# Autonomous mode
autonomousModeEnabled: true
silenceThresholdMs: 1800000 # 30 minutes
quietHoursStart: 23 # 11 PM
quietHoursEnd: 8 # 8 AM
# Storage
tasksDir: "[home-path]
predictionsDir: "[home-path]10.2 Feature Flags
| Flag | Default | Description |
|---|---|---|
| `crossPollEnabled` | `false` | Master switch for all cross-pollination |
| `autonomousModeEnabled` | `false` | Whether silence triggers autonomous execution |
| `twinModelEnabled` | `false` | Use Twin model vs template fallback |
| `dpoExportEnabled` | `false` | Export DPO pairs for training |
| `pushNotificationsEnabled` | `true` | Send push notifications for predictions |
| `watchComplicationsEnabled` | `true` | Update Watch complications |
| `glassesAlertsEnabled` | `false` | Spoken alerts on glasses (opt-in) |
10.3 Monitoring
Health check: GET /api/v1/health → includes crossPollStatus
Metrics: GET /api/v1/crosspoll/metrics → accuracy, calibration
Logs: [home-path]
Predictions: [home-path]
DPO pairs: [home-path]10.4 Sprint Task Dependencies
Wave 1: [A3] This spec ← YOU ARE HERE
[A6] Predictor spec (companion doc)
Wave 3: [C8] /crosspoll/predict endpoint (implements §8.1)
Wave 4: [D8] Safety rails (implements §3)
Wave 5: [E4] Autonomous mode (implements §5)
[E7] DPO export pipeline (implements §7.4)
Wave 6: [F1] Push notifications (implements §6)
[F2] ACC cross-poll UI (implements §4)
[F6] Swipeable prediction cards (implements §4.3)---
Appendix A: Complete End-to-End Flow (Age of Leisure)
14:00 Mohamed puts laptop away, puts on glasses, walks out.
└─ Last message timestamp: 14:00
14:30 [CRON] Silence ≥ 30min detected. Autonomous mode: PRIMING.
└─ Context assembled: 3 stale tasks, afternoon patterns, recent koji-core activity
└─ Twin model predicts: "Run vitest on pulse-gateway-api — 2 test files changed since last run"
└─ Confidence: 0.84 (above auto-execute threshold)
└─ Safety pipeline: ✅ rate OK, ✅ no loops, ✅ cost $0.15/$2.00, ✅ content safe
└─ AUTO-EXECUTE → fires on koji-core channel
14:31 [koji-core agent] Runs: cd Desktop/pulse-gateway-api && npm test
└─ Result: 24 tests passed, 0 failed
└─ Task created: task_test_run_001 (status: completed)
14:31 [PUSH] → ACC: Silent update in Mission Control
[PUSH] → Watch: Complication updates ✅
[PUSH] → Discord: ✅ reaction on prediction message
14:32 [PREDICTION] New prediction generated: "Update CROSSPOLL-SPEC.md with test results"
└─ Confidence: 0.52 (medium — show card, don't execute)
└─ ACC: Banner notification + swipeable card added
14:45 Mohamed glances at Watch. Sees ✅ and a 🔮 badge.
Raises wrist: "What happened?"
[Life OS] → Gateway → "Tests passed on pulse-gateway-api. I also have a suggestion
to update the spec with results. Want me to do that?"
Mohamed: "Yeah, go ahead."
[Life OS] → Gateway → POST /api/v1/predictions/pred_xyz/confirm
└─ DPO signal: POSITIVE (chosen = "update spec", rejected = previous expired card)
└─ Execution fires on chronicle-hub
15:00 [CRON] Next autonomous cycle.
└─ Twin predicts: "Check Shopify inventory levels for Serenity Soother"
└─ Confidence: 0.41 (below auto-execute, above card threshold)
└─ ACC: New prediction card pushed
15:15 Mohamed checks phone at a café. Opens ACC Mission Control.
Sees 2 cards: one about Shopify (swipes left — not now),
one about "brainstorm new meditation categories" (swipes right).
└─ DPO signals: reject(shopify), approve(brainstorm)
└─ dream-weaver agent starts brainstorming session
16:00 Mohamed heads home. Opens laptop.
└─ Autonomous mode: WATCHING (user active)
└─ Mission Control shows: 2 tasks completed autonomously,
1 approved manually, 1 rejected. DPO pairs: 3 new.---
Appendix B: Glossary
| Term | Definition |
|---|---|
| ACC | Agent Command Center — iOS app for managing agents and chains |
| Compass | Web dashboard for pipeline monitoring and agent control |
| Cross-pollination | System for autonomous agent action driven by prediction |
| DPO | Direct Preference Optimization — training technique using chosen/rejected pairs |
| Life OS | watchOS app providing wrist-based agent interaction |
| PULSE | The unified platform (7 surfaces, 1 Gateway) |
| SFT | Supervised Fine-Tuning — initial model training on conversation data |
| Twin | User's personalized AI model trained on their conversation history |
| VisionClaw | Smart glasses app providing visual AI and hands-free interaction |
---
Specification complete. Ready for implementation starting Wave 3 [C8].
Promotion Decision
Promote into a technical note or architecture paper with implementation anchors.
Source Anchor
pulse-gateway-api/docs/CROSSPOLL-SPEC.md
Detected Structure
Method · Evaluation · References · Code Anchors · Architecture