Twin Agent Deployment Plan — CF Worker First
⚠️ PARTIALLY SUPERSEDED (v3.7, DECISION 22). The Twin runs entirely in Cloudflare
(services/iw-agent-runtime) with a native in-app UI — there is no OpenWebUI in the product.
The OWUI /vps-operations-stack/openwebuisteps below are legacy and apply only to the
internal ops-plane (Customer Zero), never the product. Canonical: AGENTS.md DECISION 22.
Date: 2026-06-09
Status: Superseded (CF-native Twin shipped — see iw-agent-runtime)
Goal: Deploy Twin Orchestrator (Agent Creation) → Wire OWUI to Twin Runtime
Overview
Phase 1: AGENT CREATION (CF Worker)
Deploy twin-orchestrator.dev.integratewise.ai
↓
Phase 2: AGENT RUNTIME (OWUI Integration)
Wire OWUI to Twin Runtime endpoint
↓
Phase 3: CUSTOMER ZERO LOADING
Load entities, signals, proposals, memory
Phase 1: AGENT CREATION (Deploy CF Worker)
Step 1.1: Deploy Intelligence Worker (Dependency)
The Twin Orchestrator depends on the Intelligence worker for AI reasoning.
cd /Users/nirmal/Github/integratewise-live/services/intelligence
# Unset API token (use OAuth session)
unset CLOUDFLARE_API_TOKEN
# Deploy to dev
wrangler deploy --env dev
Expected Output:
✨ Successfully deployed intelligence (dev)
🌍 https://intelligence.dev.integratewise.ai
Verify:
curl https://intelligence.dev.integratewise.ai/health
Expected Response:
{
"status": "ok",
"service": "intelligence",
"components": ["think", "act", "govern", "agents", "ai-router"],
"ai": {
"workers_ai": "available",
"openrouter": "configured"
}
}
Step 1.2: Deploy Twin Orchestrator (The Agent)
cd /Users/nirmal/Github/integratewise-live/services/twin-orchestrator
# Unset API token (use OAuth session)
unset CLOUDFLARE_API_TOKEN
# Deploy to dev
wrangler deploy --env dev
Expected Output:
✨ Successfully deployed twin-orchestrator (dev)
🌍 https://twin.dev.integratewise.ai
📅 Cron trigger: */5 * * * * (every 5 minutes)
Verify:
curl https://twin.dev.integratewise.ai/health
Expected Response:
{
"status": "ok",
"service": "twin-orchestrator",
"role": "cognitive-orchestrator",
"capabilities": ["observe", "reason", "propose", "coordinate", "learn"],
"infrastructure": {
"mcp_connector": "bound",
"intelligence": "bound",
"pipeline": "bound",
"knowledge": "bound",
"continuity": "bound"
}
}
Step 1.3: Test Twin Observe Loop (Manual Trigger)
curl -X POST https://twin.dev.integratewise.ai/v1/twin/observe \
-H "x-tenant-id: iw-customer-zero" \
-H "Content-Type: application/json"
Expected Response Structure:
{
"success": true,
"data": {
"context": {
"memory": { "recent": [], "pending_reviews": [] },
"signals": { "unprocessed": [], "high_priority": [] },
"proposals": { "pending_approval": [], "recently_approved": [], "recently_rejected": [] },
"operations": { "queue_health": {}, "worker_status": {}, "error_rate": 0 },
"time": { "current": "2026-06-09T...", "last_observe": "never", "staleness_items": [] }
},
"reasoning": {
"timestamp": "2026-06-09T...",
"context_summary": "...",
"observations": [...],
"concerns": [...],
"opportunities": [...],
"suggested_actions": [...]
},
"proposals": []
}
}
Step 1.4: Monitor Cron Execution
# Tail Twin logs (watch for 5-minute cron triggers)
wrangler tail twin-orchestrator --env dev
Expected Log Output (every 5 minutes):
[Twin Orchestrator] Scheduled trigger: */5 * * * *
[Twin] Context built: { memory_items: 0, signals: 0, proposals: 0 }
[Twin] Reasoning complete: { observations: 2, concerns: 0, suggested_actions: 0 }
[Twin] Observe loop complete
Phase 2: AGENT RUNTIME (Wire OWUI to Twin)
Architecture Change
Before:
OWUI → LiteLLM → OpenRouter/Ollama → Response
After:
OWUI → Twin Runtime API → Intelligence → OpenRouter Agents → Response
Step 2.1: Add Twin Runtime Endpoint to Twin Orchestrator
The Twin Orchestrator needs an OpenAI-compatible /v1/chat/completions endpoint so OWUI can talk to it.
File: /Users/nirmal/Github/integratewise-live/services/twin-orchestrator/src/index.ts
Add this endpoint:
// ============================================================================
// OpenAI-Compatible Chat Endpoint (for OWUI)
// ============================================================================
app.post("/v1/chat/completions", async (c) => {
const tenantId = c.req.header("x-tenant-id") || "iw-customer-zero";
const body = await c.req.json();
try {
const { messages, model, stream = false, ...options } = body;
// Build Twin context (what the Twin knows right now)
const context = await buildTwinContext(c.env, tenantId);
// Inject Twin context into system message
const systemMessage = {
role: "system",
content: `You are the Twin cognitive orchestrator for IntegrateWise.
Current Twin Context:
- Memory: ${context.memory.recent.length} recent items
- Signals: ${context.signals.unprocessed.length} unprocessed (${context.signals.high_priority.length} high priority)
- Proposals: ${context.proposals.pending_approval.length} pending approval
- Last observe: ${context.time.last_observe}
You can:
1. Answer questions about system state
2. Propose actions (requires approval)
3. Query memory, signals, proposals via tools
4. Coordinate execution (via governance)
You cannot:
- Execute actions directly (always propose → approve → execute)
- Write to memory directly (via Triage Bot only)
- Write to Spine directly (via Pipeline only)`,
};
// Combine system message + user messages
const augmentedMessages = [systemMessage, ...messages];
// Forward to Intelligence worker (which uses OpenRouter Agents)
const response = await c.env.INTELLIGENCE.fetch(
new Request("http://internal/v1/ai/agent", {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-tenant-id": tenantId,
},
body: JSON.stringify({
model: model || "openrouter/auto",
messages: augmentedMessages,
stream,
...options,
}),
})
);
// Return OpenAI-compatible response
if (stream) {
return new Response(response.body, {
headers: {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
Connection: "keep-alive",
},
});
} else {
const data = await response.json();
return c.json({
id: `chatcmpl-${Date.now()}`,
object: "chat.completion",
created: Math.floor(Date.now() / 1000),
model: model || "twin-orchestrator",
choices: [
{
index: 0,
message: {
role: "assistant",
content: data.content || data.data?.content || "",
},
finish_reason: "stop",
},
],
usage: data.usage || { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },
});
}
} catch (err: any) {
console.error("[Twin Chat] Error:", err);
return c.json({ error: { message: err.message, type: "twin_error" } }, { status: 500 });
}
});
// Also add models endpoint for OWUI compatibility
app.get("/v1/models", (c) => {
return c.json({
object: "list",
data: [
{
id: "twin-orchestrator",
object: "model",
created: 1717900800,
owned_by: "integratewise",
permission: [],
root: "twin-orchestrator",
parent: null,
},
],
});
});
Step 2.2: Redeploy Twin Orchestrator with Chat Endpoint
cd /Users/nirmal/Github/integratewise-live/services/twin-orchestrator
unset CLOUDFLARE_API_TOKEN
wrangler deploy --env dev
Test the Chat Endpoint:
curl -X POST https://twin.dev.integratewise.ai/v1/chat/completions \
-H "Content-Type: application/json" \
-H "x-tenant-id: iw-customer-zero" \
-d '{
"model": "twin-orchestrator",
"messages": [
{"role": "user", "content": "What is the current system state?"}
]
}'
Expected Response:
{
"id": "chatcmpl-1234567890",
"object": "chat.completion",
"created": 1717900800,
"model": "twin-orchestrator",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Based on my observation:\n- Memory: 0 recent items\n- Signals: 0 unprocessed\n- Proposals: 0 pending approval\n\nThe system is currently idle. No actions are needed at this time."
},
"finish_reason": "stop"
}
],
"usage": { "prompt_tokens": 150, "completion_tokens": 50, "total_tokens": 200 }
}
Step 2.3: Update OWUI to Use Twin Runtime
VPS SSH:
ssh root@187.127.166.105
cd /root/vps-operations-stack/openwebui
Edit .env:
nano .env
Add:
# Twin Runtime API (Cloudflare Worker)
TWIN_RUNTIME_URL=https://twin.dev.integratewise.ai
Edit docker-compose.yml:
nano docker-compose.yml
Update open-webui service environment:
services:
open-webui:
# ... existing config ...
environment:
# ... existing vars ...
# CHANGE THIS: Point to Twin Runtime instead of LiteLLM
OPENAI_API_BASE_URL: https://twin.dev.integratewise.ai
# Keep LiteLLM key for auth (or use a Twin-specific key)
OPENAI_API_KEY: ${LITELLM_MASTER_KEY}
# ... rest of config ...
Restart OWUI:
docker-compose down
docker-compose up -d
Step 2.4: Test OWUI → Twin Runtime
- Open https://twin.operations.integratewise.ai
- Start a new chat
- Select model: "twin-orchestrator" (should appear in model list)
- Send message: "What is the current system state?"
- Verify: Response includes Twin context (memory, signals, proposals)
Expected Response:
Based on my observation:
- Memory: 0 recent items
- Signals: 0 unprocessed
- Proposals: 0 pending approval
The system is currently idle. No actions are needed at this time.
Phase 3: CUSTOMER ZERO LOADING
Now that Twin Runtime is deployed and OWUI is wired, load Customer Zero data.
Step 3.1: Create Load Script
File: /Users/nirmal/Github/integratewise-ops/vps-operations-stack/scripts/load-customer-zero-cf.mjs
#!/usr/bin/env node
/**
* Load Customer Zero data into Cloudflare Workers
* - Entities → Pipeline → Spine
* - Signals → Intelligence
* - Proposals → Governance (via MCP)
* - Memory → Triage Bot → Conversational Memory
*/
const TENANT_ID = "iw-customer-zero";
const PIPELINE_URL = "https://pipeline.dev.integratewise.ai";
const MCP_URL = "https://mcp.dev.integratewise.ai";
const GATEWAY_URL = "https://gateway.dev.integratewise.ai";
// Customer Zero Entities
const entities = [
{
type: "Person",
name: "Nirmal Patel",
email: "nirmal@integratewise.ai",
role: "Founder",
traits: ["decision_maker", "technical_user"],
},
{
type: "Account",
name: "IntegrateWise",
domain: "integratewise.ai",
industry: "SaaS",
employees: 1,
traits: ["b2b", "technical"],
},
{
type: "Deal",
name: "Customer Zero Onboarding",
value: 0,
stage: "active",
close_date: "2026-06-30",
traits: ["self_serve", "internal"],
},
];
// Customer Zero Signals
const signals = [
{
entity_id: "deal-customer-zero",
signal_type: "milestone_approaching",
severity: "medium",
description: "Customer Zero onboarding deadline: 2026-06-30 (21 days)",
suggested_action: "Review onboarding progress and adjust timeline if needed",
},
];
// Customer Zero Proposals
const proposals = [
{
type: "review_system_state",
title: "Daily System Health Review",
description: "Twin should review system health daily and surface any concerns",
impact: "medium",
risk: "low",
urgency: "medium",
confidence: 0.9,
},
];
// Customer Zero Memory Episodes
const memoryEpisodes = [
{
type: "episode",
title: "Twin Architecture Finalized — 2026-06-09",
content:
"Canonical architecture established: One Twin, One Spine, One Memory, One Governance, One Bridge. Twin Runtime deployed as CF Worker.",
scope: "org",
tags: ["architecture", "twin", "milestone"],
},
{
type: "decision",
title: "CF Worker First (Not VPS)",
content:
"Decision: Deploy Twin Runtime as Cloudflare Worker first, not VPS. Reasoning: Better integration with existing CF infrastructure, edge performance, easier scaling.",
alternatives_considered: ["VPS deployment", "Hybrid approach"],
scope: "org",
tags: ["architecture", "twin", "decision"],
},
];
async function loadCustomerZero() {
console.log("🚀 Loading Customer Zero data...\n");
// 1. Load Entities
console.log("1️⃣ Loading entities...");
for (const entity of entities) {
try {
const response = await fetch(`${PIPELINE_URL}/v1/intake`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-tenant-id": TENANT_ID,
},
body: JSON.stringify({
source: "customer-zero-load",
data: entity,
}),
});
const result = await response.json();
console.log(` ✅ ${entity.type}: ${entity.name} → ${result.spine_id || result.entity_id}`);
} catch (err) {
console.error(` ❌ ${entity.type}: ${entity.name} → ${err.message}`);
}
}
// 2. Load Signals
console.log("\n2️⃣ Loading signals...");
for (const signal of signals) {
try {
const response = await fetch(`${GATEWAY_URL}/api/v1/signals`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-tenant-id": TENANT_ID,
},
body: JSON.stringify(signal),
});
const result = await response.json();
console.log(` ✅ Signal: ${signal.signal_type}`);
} catch (err) {
console.error(` ❌ Signal: ${signal.signal_type} → ${err.message}`);
}
}
// 3. Load Proposals
console.log("\n3️⃣ Loading proposals...");
for (const proposal of proposals) {
try {
const response = await fetch(`${MCP_URL}/tools/proposal.create`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-tenant-id": TENANT_ID,
},
body: JSON.stringify(proposal),
});
const result = await response.json();
console.log(` ✅ Proposal: ${proposal.title}`);
} catch (err) {
console.error(` ❌ Proposal: ${proposal.title} → ${err.message}`);
}
}
// 4. Load Memory Episodes
console.log("\n4️⃣ Loading memory episodes...");
for (const episode of memoryEpisodes) {
try {
const response = await fetch(`${MCP_URL}/tools/memory.write_conversational`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-tenant-id": TENANT_ID,
},
body: JSON.stringify({
session_id: "customer-zero-onboarding",
user_id: "nirmal",
role: "system",
content: JSON.stringify(episode),
}),
});
const result = await response.json();
console.log(` ✅ Memory: ${episode.title}`);
} catch (err) {
console.error(` ❌ Memory: ${episode.title} → ${err.message}`);
}
}
console.log("\n✅ Customer Zero data loaded!\n");
console.log("🔍 Verify:");
console.log(
" curl https://twin.dev.integratewise.ai/v1/twin/observe -H 'x-tenant-id: iw-customer-zero' -X POST"
);
}
loadCustomerZero();
Step 3.2: Run Load Script
cd /Users/nirmal/Github/integratewise-ops/vps-operations-stack/scripts
chmod +x load-customer-zero-cf.mjs
node load-customer-zero-cf.mjs
Expected Output:
🚀 Loading Customer Zero data...
1️⃣ Loading entities...
✅ Person: Nirmal Patel → person-123
✅ Account: IntegrateWise → account-456
✅ Deal: Customer Zero Onboarding → deal-789
2️⃣ Loading signals...
✅ Signal: milestone_approaching
3️⃣ Loading proposals...
✅ Proposal: Daily System Health Review
4️⃣ Loading memory episodes...
✅ Memory: Twin Architecture Finalized — 2026-06-09
✅ Memory: CF Worker First (Not VPS)
✅ Customer Zero data loaded!
🔍 Verify:
curl https://twin.dev.integratewise.ai/v1/twin/observe -H 'x-tenant-id: iw-customer-zero' -X POST
Step 3.3: Verify Twin Sees Customer Zero Data
curl -X POST https://twin.dev.integratewise.ai/v1/twin/observe \
-H "x-tenant-id: iw-customer-zero" \
-H "Content-Type: application/json" | jq
Expected Response:
{
"success": true,
"data": {
"context": {
"memory": {
"recent": [
{"title": "Twin Architecture Finalized — 2026-06-09", ...},
{"title": "CF Worker First (Not VPS)", ...}
]
},
"signals": {
"unprocessed": [
{"signal_type": "milestone_approaching", ...}
],
"high_priority": []
},
"proposals": {
"pending_approval": [
{"title": "Daily System Health Review", ...}
]
},
"spine": {
"entity_count": 3,
"recent_changes": [...]
}
},
"reasoning": {
"observations": [
"Customer Zero onboarding is active",
"1 signal requires attention (milestone approaching)",
"1 proposal pending approval"
],
"concerns": ["Milestone approaching: Customer Zero onboarding deadline in 21 days"],
"suggested_actions": [
{
"type": "review_milestone",
"reasoning": "Customer Zero onboarding deadline approaching",
"impact": "medium",
"urgency": "medium"
}
]
}
}
}
Step 3.4: Test OWUI with Customer Zero Context
- Open https://twin.operations.integratewise.ai
- Start new chat with Twin
- Ask: "What needs my attention today?"
- Verify Twin response includes:
- Customer Zero onboarding milestone
- Pending proposal for daily health review
- System state summary
Expected Response:
Here's what needs your attention:
🔴 Milestone Approaching
Customer Zero onboarding deadline is in 21 days (2026-06-30). Review progress to ensure we're on track.
📋 Pending Proposal
"Daily System Health Review" is awaiting approval. This would enable me to proactively monitor system health and surface concerns.
📊 System State
- 3 entities in Spine (1 person, 1 account, 1 deal)
- 1 unprocessed signal
- 1 pending proposal
- 2 memory items
Would you like me to propose any actions?
Success Criteria
✅ Phase 1 Complete When:
- Intelligence worker deployed (
intelligence.dev.integratewise.ai) - Twin Orchestrator deployed (
twin.dev.integratewise.ai) - Health checks return 200 OK
- Cron triggers fire every 5 minutes
- Manual
/v1/twin/observeworks
✅ Phase 2 Complete When:
- Twin Runtime has
/v1/chat/completionsendpoint - OWUI
OPENAI_API_BASE_URLpoints to Twin Runtime - OWUI chat shows "twin-orchestrator" model
- Chat responses include Twin context
✅ Phase 3 Complete When:
- Customer Zero entities loaded (3 entities in Spine)
- Customer Zero signals loaded (1 signal)
- Customer Zero proposals loaded (1 proposal)
- Customer Zero memory loaded (2 episodes)
- Twin observe returns populated context
- OWUI chat includes Customer Zero data in responses
Next Steps After This
- Wire OWUI Skills — Add MCP tool server for Twin to use tools
- Deploy Browser Agent — Enable Twin to execute via browser automation
- Implement Triage Bot — Memory governance worker
- Enable Governance Queue — HITL approval in OWUI or L2
- Morning Brief Workflow — Daily summary via voice/email
Deployment Commands Summary
# Phase 1: Deploy CF Workers
cd /Users/nirmal/Github/integratewise-live/services/intelligence
unset CLOUDFLARE_API_TOKEN
wrangler deploy --env dev
cd ../twin-orchestrator
unset CLOUDFLARE_API_TOKEN
wrangler deploy --env dev
# Phase 2: Update OWUI
ssh root@187.127.166.105
cd /root/vps-operations-stack/openwebui
# Edit docker-compose.yml → OPENAI_API_BASE_URL=https://twin.dev.integratewise.ai
docker-compose down && docker-compose up -d
# Phase 3: Load Customer Zero
cd /Users/nirmal/Github/integratewise-ops/vps-operations-stack/scripts
node load-customer-zero-cf.mjs
Ready to execute! 🚀
The Twin Runtime (Agent) is already built, just needs deployment + wiring.