# Ninjo Platform: Feature Reference for Cortex

This document maps every platform capability that affects agent behavior. Cortex reads this to reason about the full stack when designing, iterating, or reviewing agents, not just the prompt layer.

**Source**: Extracted from mk1-chat (frontend), mk1-tasks (backend), webhooks-server (DB schema + API), knowledge-base docs, and Scribe operational guides.

**Last updated**: 2026-02-20

---

## 1. Agent Configuration

### Personality Prompt
The agent's core instruction set. This is what Cortex creates and iterates via `/cortex-create` and `/cortex-iterate`. Stored in `AgentConfig.prompt`.

### Model & Version
- **Model selection**: GPT-4o, GPT-5
- **Agent version**: v1 (standard) or v2 (supports temperature, reasoning)
- **Temperature**: 0.0 to 1.0 (controls randomness/creativity)
- **Language**: Default "en", configurable per agent

### Response Timing
- **Response delay**: Min/max seconds (random within range). Default 10s, max 1800s (30 min)
- **Message chunking**: Split long responses into multiple messages (1-5 chunks)

### Agent Flags
- **isActive**: Enable/disable agent entirely
- **setOnConversation**: Auto-assign agent to new conversations
- **isTestAgent**: Sandbox mode (RESET command clears conversation)
- **hidePrompt**: Hide prompt from UI (used for workflow agents)
- **tracingEnabled**: Debug logging

### Agent Context (RAG)
- Upload PDFs, docs, images as agent context
- Stored on S3, injected at runtime
- Per-agent document management

### Creator Resources
Shared media library per creator (separate from agent-specific RAG docs):
- **File types**: Documents, texts, images, audio, video
- **Usage in follow-ups**: Select a creator resource as the response type in any follow-up workflow
- **Usage in manual chat**: Attach creator resources directly via the chat attachment icon
- **Usage mid-conversation (agent-sent)**: The agent can send **image/audio** clips on its own *during* a chat when a resource is marked agent-usable (with a `usage_context` telling it when). Only **IMAGE** (JPG/PNG/GIF) and **AUDIO** (M4A/AAC — MP3 rejected) are agent-deliverable; documents/texts/video are not. Enable per-agent via `update_resource_sending` → `config.resourceSending` (`enabled`, `allowed_types`, `max_per_conversation`). Distinct from trigger audios, which play on trigger match.
- **Management**: Upload/delete per category (Documents, Texts, Images, Audios, Videos). The agent-usable image/audio catalog is also managed over MCP — `list_creator_resources`, a two-step presigned upload (`create_resource_upload_url` → PUT to S3 → `register_creator_resource`), `update_creator_resource`, and `delete_creator_resource` (see `knowledge/mcp-tool-reference.md` §Creator resources).

### Prompt Version History
- Track all prompt iterations with version numbering
- **Restore previous versions**: Compare any two versions with 3 view modes (colored diff, side-by-side, raw text)
- Supports rollback to any previous version

---

## 2. Triggers (When the Agent Activates)

Triggers define WHAT makes an agent respond. Multiple trigger types can coexist on one agent.

### Message-Based Triggers
| Trigger | What It Does |
|---------|-------------|
| **All DMs** | Agent responds to every incoming DM |
| **All Comments** | Agent responds to every post comment |
| **Keyword** | Agent responds only when message contains specific keywords (case-insensitive) |
| **Story Replies** | Agent responds to replies on specific Instagram stories |
| **Comment Keywords** | Custom responses to keywords in post comments |
| **Ad Referrals** | Route based on which Meta ad drove the conversation |
| **Outgoing Messages** | Activate agent when a manual outbound is sent. Agent auto-assigns to conversations from external platforms (ManyChat, etc.) when it detects a configured keyword/phrase in outbound messages |
| **CSV Upload** | Bulk import contacts and assign to agent |

### Prompt Evaluator
An optional secondary LLM call on any trigger that evaluates whether the agent should actually respond. Acts as a gate: even if the trigger fires, the evaluator can say "no, skip this one."

**DB location**: `trigger_all_messages.prompt_evaluator` (text column). Only available on ALL_DM triggers — keyword triggers do NOT support this feature. Evaluations logged in `agent_activation_evaluations` with reasoning, confidence, and cost.

**When to use**: When ALL_DM triggers are too broad and you need nuanced filtering (e.g., "only respond if the message is about fitness, not general questions"). Not available for keyword triggers.

### Negative Triggers (Blocklist)
Negative triggers make the agent NOT respond when a message matches the trigger. Can coexist with positive triggers on the same keyword.

- **Permanent blocking**: Agent will never reply when matched (functions as a blocklist). Supports CSV import for bulk blocklists.
- **Temporary blocking**: Set a specific date/time when the block expires. Timezone-aware scheduling.
- **Property conditions**: e.g., "don't trigger if country = AR"
- **Combinable**: Create both negative and positive versions of the same trigger to work together.

### Should Respond Check
A pre-response evaluation flag (`shouldRespondCheckEnabled`). When enabled, the system evaluates whether the agent should respond before sending.

---

## 3. Conversation Controls (Pause, Resume, Takeover)

### Agent Active Flag
`Conversation.isAgentActive`: Per-conversation toggle. When FALSE, agent stops responding to that specific conversation. Human can manually pause/resume.

### Pause on Manual Outbound (External Message Detection)
When a human operator or external platform sends a message:
- **Enabled/disabled**: Toggle this behavior
- **Mode**: Temporary (pause for X minutes, default 5 min) or indefinite (agent won't resume without manual intervention)
- **Duration**: Configurable minutes
- **Ignore first message**: Option to not pause on the very first contact message
- **External platforms**: Detects messages from Instagram, ManyChat, and other external sources

### Needs Attention Flag
`Conversation.needsAttention`: When TRUE, blocks follow-up workflows from running. Used to flag conversations for human review.

### Conversation Status
- **ACTIVE**: Normal operation
- Other statuses pause agent interaction

---

## 4. Multi-Agent Workflows

The platform supports 4 agent modes. Set via `Agent.mode`.

### INDIVIDUAL (Default)
Single agent per contact. Straightforward 1:1.

### INTENT_WORKFLOW (Intention-Based Routing)
An "intelligent receptionist" that analyzes every message and routes to the right specialist.

- **Intent detection**: Every inbound message is analyzed for intent
- **Intent verification**: If a contact switches topics mid-conversation (e.g., from sales to support), the system detects the shift and re-routes
- **Conversation summary**: When switching agents, a summary is generated so the new agent has context
- **Fallback agent**: Required. Handles "no-match" cases
- **Pin to Conversation**: Must be ENABLED at workflow level so intent is re-evaluated per message

**Endika uses this**: Routes between setter and customer support. Current gap: no path for "neither should respond."

### A_B_WORKFLOW (A/B Testing)
Split incoming conversations between two or more agents (e.g., 50/50).

- **Child agents**: Must have Pin to Conversation ENABLED (contact stays with same agent)
- **Parent workflow**: Must have Pin to Conversation DISABLED (allows random distribution)
- **Evaluation**: Track which agent performs better via custom properties

### ROUTER_WORKFLOW (Custom Rules)
Manual routing rules defined in the router's prompt. Most flexible option.

- **Manual rules**: You write the logic (e.g., "if contact mentions VIP, route to Senior Agent")
- **Modularity**: Can route to agents OR to other workflows (nested)
- **Pin to Conversation**: Configurable per use case

### Workflow Agent Relations
- Parent agent with N child agents via `AgentWorkflowRelation`
- Cycle detection prevents infinite loops
- Each workflow agent can have its own prompt, triggers, and config

### Workflow Trigger Rules
- **Triggers are ONLY for the parent agent** (the sole conversation entry point)
- Child agents should NOT use identical triggers
- If a child agent needs a trigger, use unique complex keywords that users wouldn't naturally type

---

## 5. Follow-Ups & Automations

### Workflow Types
| Type | Purpose |
|------|---------|
| **FOLLOW_UP** | Send follow-up messages to unanswered conversations |
| **CUSTOM_NOTIFICATION** | Alert the team/creator via Slack, email, SMS, etc. |
| **CUSTOM_ACTION** | Execute LLM prompt or send notification (flexible) |
| **SCHEDULE_REMINDER** | Send reminder X minutes before an appointment |

### Follow-Up Configuration
- **Interval**: Run every N minutes
- **Execution window**: Only run between start/end times (supports cross-midnight)
- **Min silence threshold**: Only run if no inbound for N minutes (default ~22 hours)
- **Run-once mode**: Execute max once per conversation
- **Static vs dynamic messages**: Fixed text OR LLM-generated per contact
- **Task tracking prompt**: LLM evaluates if the task is complete (prevents unnecessary follow-ups)
- **Property conditions**: Only run when contact properties match criteria (10+ operators: equals, contains, greater_than, etc.)
- **Property-delay trigger**: Run N minutes after a specific property is set

### Follow-Up Skip Logic
Follow-ups are skipped when:
- Conversation is not ACTIVE
- No agent assigned
- Agent is inactive on that conversation
- needsAttention = TRUE
- Last inbound message is a COMMENT (not a DM)
- only_run_once already completed
- track_task_state already completed
- Outside execution window
- Contact not enrolled in workflow

### First Contact Messages
Automated welcome sequence for new contacts:
- Per-channel configuration (IG, WhatsApp, etc.)
- Multi-message sequences with ordering
- Option to hand off to agent after sequence (`continueWithAgent`)

---

## 6. Contact Limits

Property-based caps on how many contacts an agent handles:
- **Per-agent limits**: Max N contacts matching criteria
- **Custom property conditions**: Complex filters (AND/OR logic)
- **Enforcement**: Agent stops responding beyond limit

**When to use**: Prevent an agent from being overwhelmed, or cap a campaign to a specific audience size.

---

## 7. Custom Properties

Dynamic contact attributes extracted from conversations.

### Definition
- **Label**: Display name
- **Type**: String, Integer, Boolean, DateTime, Select (dropdown)
- **Default value**: Pre-fill if not detected
- **Description**: Context for the LLM on when/what to extract. Supports dynamic references like `[[contact.first_name]]`
- **Include in LLM**: Inject this property's value into agent context
- **Check until match**: Stop monitoring/updating once target value is reached

### Extraction
Two levels:
1. **Real-time (lean)**: Tool calling during conversation. Low latency.
2. **Batch (deep)**: "Analyze Conversations" button. Full history sweep.

After every processed message, mk1-tasks enqueues a property extraction task that uses LLM to pull values from the latest message.

### Sync
Bidirectional sync with external platforms:
- GoHighLevel, Zapier, n8n
- Configurable direction: push, pull, or bidirectional
- Conflict strategy: local wins, remote wins, or most recent

---

## 8. Tools & Integrations

### Agent Tools
| Tool Type | What It Does |
|-----------|-------------|
| **Google Calendar** | Check availability, book appointments |
| **GoHighLevel** | CRM sync, calendar, contact management |
| **Calendly** | Calendar events |
| **Needs Attention** | Flag conversation for human review |
| **Resource** | Custom resource access |
| **Custom HTTP** | Call external APIs (POST/GET) with headers and params |

Each tool has: name, description, prompt, credentials, settings, enabled flag.

### Webhook Mappings
Map external platform events to contact properties:
- Platform: GoHighLevel, Zapier, n8n, etc.
- Value type: Static (fixed) or Dynamic (JSON path extraction)
- Logged via `WebhookEvent` for debugging

---

## 9. Voice & Audio

- **Voice reply**: Enable audio responses
- **Reply modes**: Always audio, audio-only-if-inbound-is-audio, or disabled
- **Rate limits**: Max audios per conversation/hour/day/week
- **Incoming audio limit**: Max seconds for incoming audio messages
- **Exceeded message**: Custom message when audio is too long
- **Conditional voice**: Only reply with audio if specific property has a value

---

## 10. Notifications

### Custom Notifications
Named notification events with multi-channel dispatch:
- **Channels**: Email, SMS, WhatsApp, WhatsApp Template, Telegram, Slack, SNS
- **Levels**: INFO, WARNING, ERROR, SUCCESS, MESSAGE (affects formatting)
- **Conditions**: Property-based rules (all conditions must pass)

### Notification Destinations
Per-notification channel configuration with from-address, template selection, etc.

---

## 11. Scheduling & Appointments

- **Source**: Agent-booked, Google Calendar, GoHighLevel, Calendly
- **Fields**: Name, datetime, duration, URL, status (ACTIVE/CANCELLED)
- **Reminders**: Workflow type SCHEDULE_REMINDER sends alerts X minutes before

---

## 12. Connected Accounts

### Meta Platforms
- Instagram, WhatsApp, Facebook connections via OAuth
- Sub-accounts (Pages, IG Accounts, WhatsApp Numbers)
- Per-agent account selection

### External Integrations
- n8n automation workflows
- CRM sync connections (bidirectional)
- Webhook secrets for security

---

## 13. Testing & Evaluation

- **Simulated test runs**: Synthetic conversations to test agent
- **Chat simulator**: Send test messages as if from a contact
- **Activation evaluation**: LLM evaluates if agent should activate (confidence scoring)
- **Message feedback**: Like/dislike on agent responses (training signal)
- **Copilot**: AI-powered prompt editing assistant (suggests improvements, shows diffs). Also supports natural language config like "when they send keyword X, send this link"
- **Insights Agent (Copilot BETA)**: Auto-generates performance reports per creator, supports free-form questions about data, and suggests campaigns based on lead pipeline

---

## 13b. Analytics & Tracking

### Google Analytics Integration
Track agent-driven conversions on the creator's website:
- **UTM structure**: `?utm_source=ninjo.ai&utm_medium=NinjoBot&utm_term=[[CONTACT_ID]]`
- `[[CONTACT_ID]]` is mandatory and cannot be modified
- Requires Google Tag Manager setup with custom HTML tags for click and page view events
- Tracks: custom_property_click and custom_property_view events
- Needs Ninjo API key configured in GTM tags

### Notification Destinations
Pre-configured alert channels for system events:
- **Token expired alerts**: Slack webhook destination for OAuth token expiration
- Setup: Automations > Notifications > New Destination > paste Slack webhook URL

---

## 14. Backend Processing Pipeline

### Message Lifecycle
1. User sends message via Meta (IG/WhatsApp/FB)
2. `webhooks-server` receives, assigns Correlation ID, pushes to inbound queue
3. `mk1-tasks` processes: locks conversation, forwards to Studio API
4. Studio API (`/api/messaging/main-handler`) decides which agent responds
5. Agent generates response, sent via outbound queue
6. `webhooks-consumer` delivers to Meta API
7. After processing, custom properties extraction is enqueued

### Key Backend Behaviors
- **Conversation locking**: Redis-based, prevents race conditions per conversation
- **Meta polling**: Every 20 minutes, checks for unresponded messages (max 1000/account)
- **Task queues**: critical (messages, 5min timeout), follow-up, notification, custom_properties, default (recovery, 30min timeout)
- **Cron schedules**: Follow-ups every 30min, notifications every 10min (configurable via env vars)

---

## 15. Decision Guide: When to Use What

| Scenario | Best Platform Feature |
|----------|----------------------|
| Agent should not respond to certain message types | **Prompt Evaluator** on the trigger |
| Agent should never re-enter after human intervenes | **Pause on manual outbound** (indefinite mode) |
| Agent should pause temporarily after human message | **Pause on manual outbound** (temporary, X minutes) |
| Some contacts should be excluded | **Negative Config** (property-based or time-based) |
| Cap the number of contacts an agent handles | **Contact Limits** |
| Route messages to different specialists | **Intent Workflow** or **Router Workflow** |
| Test two prompt versions | **A/B Workflow** |
| Send follow-up if no reply | **Follow-Up Workflow** with min silence threshold |
| Send reminder before appointment | **Schedule Reminder** workflow |
| Alert team when something happens | **Custom Notification** |
| Extract data from conversations | **Custom Properties** with Include in LLM |
| Integrate external calendar/CRM | **Agent Tools** (Google Calendar, GoHighLevel, etc.) |
| No agent should respond at all (multi-agent) | **Prompt Evaluator**, **Fallback agent that stays silent**, or **shouldRespondCheck** |
| Block specific contacts permanently | **Negative Triggers** (permanent mode, supports CSV bulk import) |
| Block contacts temporarily | **Negative Triggers** (temporary mode, timezone-aware expiry) |
| Track conversions on creator's website | **Google Analytics** integration with `[[CONTACT_ID]]` UTM tracking |
| Send specific link when keyword is received | Configure via **Copilot** editor or directly in prompt |
| Use media files in follow-ups or chat | Upload to **Creator Resources**, then select in follow-up or attach in chat |

---

## Maintenance

This doc should be updated when:
- New platform features are added
- Existing features change behavior
- New integration types are supported

To refresh from source code: `git submodule update --remote` then re-explore the platform repos.
