# AGENTS.md — cortex-ops

Minimal, **MCP-first** workspace for operating Ninjo agents through the **cortex-gateway** MCP
server. Everything — create, iterate, analyze, triggers — goes through MCP tools. There are no
Python deploy scripts, no database credentials, and no direct Postgres/Mongo access here.

> The gateway has working OAuth 2.1 auth, a correct-by-construction `deploy_agent_sdk` tool,
> consolidated trigger tools, and analytics. **Use the MCP tools for everything** — create, deploy,
> triggers, and reads all run through them.

---

## Connecting

Everything in this workspace — create, read, deploy, triggers — runs through the **cortex-gateway
MCP tools**, never through direct Postgres. So all you need is an **authenticated MCP connection**
(native OAuth, below). The row-level-security / influencer-JWT rules you may see
elsewhere govern **direct PostgREST access only** — this workspace never queries Postgres directly,
so they do **not** gate the MCP tools. (In particular, a `get_me` that reports a read-only role or a
null token is describing that unused direct-DB path, **not** what the MCP tools can do — the tools
still create, read, and deploy.)

### Native OAuth (automatic)

`.mcp.json` defines `cortex-gateway` as a native `type: http` server pointed at the full `/mcp`:

```json
{ "mcpServers": { "cortex-gateway": {
  "type": "http",
  "url": "${CORTEX_GATEWAY_URL:-https://cortex-gateway.ninjoaiplatform.com}/mcp"
} } }
```

On project open Codex connects and runs the **OAuth login itself** (browser, once — tokens
are cached; re-auth anytime with `/mcp`). No `.env`, no API key. `/mcp` is the **full surface**,
so you get the whole lifecycle including `create_agent` / `delete_agent`. `.Codex/settings.json` pre-enables the server
(`enabledMcpjsonServers`) so there's no trust prompt. Override the gateway with
`export CORTEX_GATEWAY_URL=http://localhost:3000` for local dev.

---

## The deploy contract — `deploy_agent_sdk`

**Always deploy SDK changes with `deploy_agent_sdk`.** It is the thick, correct-by-construction
tool: it diffs each field against the live config and only writes what changed, then re-fetches and
verifies. Do **not** hand-roll `update_prompt` + `update_v5_config` + `sync_keyword_triggers`
yourself — that is exactly the multi-step flow `deploy_agent_sdk` bakes in (and gets right).

Inputs (`agent_id` required; everything else optional, **PATCH semantics — omitted = untouched, nothing can be set to null**):

- `system` → the system prompt (`agent_configs.prompt`). **Never** goes into v5Config.
- The **8 v5Config fields**: `examples`, `keywords`, `knowledge_base`, `objections`,
  `personal_story`, `program`, `resources`, `case_studies`.

Behavior:
- `keywords` is **dual-written** — verbatim to `v5Config.keywords` AND synced into
  `trigger_keywords` (what actually fires). Providing it always re-syncs (idempotent; heals drift).
- After writing, it verifies: prompt version bumped, fields match, no `v5Config.keywords ↔
  trigger_keywords` drift.
- **On `verified: false` or `partial: true` the deploy did NOT fully succeed.** Capture the
  `rollback_version_id` from the report and call `restore_prompt_version` to revert.

`update_prompt`, `update_v5_config`, `sync_keyword_triggers` still exist for surgical edits, but the
default path is `deploy_agent_sdk`.

---

## SDK shape

An agent = one **system prompt** + the **8 v5Config fields**. Author them as files under
`agents/<name>/sdk/` (see `agents/_TEMPLATE/`), then deploy. The structure and the rules for each
section come from:

- `templates/` — the **`principles`** template ("fewer rules, more examples") is the single SDK
  architecture. It ships with four **motion flavors** — thin overlays that tune Flow, Calibration,
  rapport technique, and target metrics for a sales motion (grounded in the pixel flow archetypes):
  `principles-express` (low-ticket, resource-forward), `principles-consultative` (mid-ticket,
  diagnose-then-pitch in DM), `principles-deep-nurture` (premium, empathy + video, DM closes), and
  `principles-book-the-call` (high-ticket, the call closes — DM only qualifies). `kpi-profiles.md`
  is the KPI rubric used by `/cortex-analyze`, not a prompt template.
- `knowledge/` — the reusable, MCP-agnostic reference:
  - `best-practices-top10.md`, `prompt-antipatterns.md`, `AGENTS.md` (validated pattern library)
  - `keyword-playbook.md`, `triggers.md`, `workflows.md`, `custom-properties.md`
  - `sdk-migration-playbook.md` (monolith → v5Config), `agent-metrics.md`
  - `platform-features.md`, `prompt-engineering-guide.md` (platform constraints every prompt obeys)
  - `platform-internals.md` — backend mechanics + **known bugs** (message pipeline, runtime Contact-Context
    injection, workflow-NULL / `needs_attention` / reel-reply gotchas, CoT filter, forensic map)
  - `reasoning-leak.md` — diagnosing & fixing reasoning leakage (the generic prompt guard + the `responseFilter`
    knob); supersedes the old "not prompt-fixable" PAT-046
  - `mcp-tool-reference.md` — the full map of what the cortex-gateway MCP can do (so you don't deflect)
  - `self-sufficiency.md` — what to do when a tool fails or a capability is missing: diagnose + retry +
    alternate tools, **never** bounce the task back with "do it manually in Ninjo"

Custom properties are set by an **external evaluator**, not by the conversational agent — the SDK
controls the agent's *messages*, never assume it can set properties.

---

## The live agent (DB) is the source of truth — `agents/<name>/` is a working copy

The durable source of truth is the **deployed agent in the database**, read with `get_agent_config`. The local
`agents/<name>/` folder is a convenient working copy of it:
- `meta.md` — `influencer_id`, `agent_id` (filled after first create), use case, vertical, template
- `sdk/system.md` + the 8 v5Config files (`examples.md`, `keywords.json`, `resources.json`,
  `program.json`, `objections.md`, `knowledge_base.md`, `personal_story.md`, `case_studies.json`)
- `changelog.md` — every deploy gets an entry (enables rollback reasoning)

**Always start from the live config, not the local copy.** This matters most for users **without Codex**
(web / app), where the working copy may be a one-time upload that doesn't persist across sessions — and even on
the terminal, the operator may have edited the agent in Ninjo since you last synced. So:

1. **Before editing or iterating, pull `get_agent_config`** and work from what's actually live — never assume the
   local files match the DB.
2. **If you see a version shift** (the live `current_prompt_version_id` is ahead of what your local `changelog.md`
   records, or fields differ), pull first and reconcile before writing — deploying over a stale copy overwrites
   the operator's newer changes (PAT-054 / PAT-049).
3. **Deploy with `deploy_agent_sdk`** (and `update_agent_config` for the CoT filter / non-SDK settings); it
   re-fetches and verifies. Then refresh the local copy from the deployed result so the two stay in sync.

Bottom line: treat the local folder as a cache. The agent in the DB is what's real.

**Reference samples.** `agents/sample-*` are four fully-built, **sanitized** SDKs across distinct verticals
(wellness/book-the-call, pet-education/consultative, finance/book-the-call, professional-dev/consultative). They
are not deployed (`agent_id: PENDING`) — use them as worked examples when creating or iterating an agent in a
similar vertical/motion. `_TEMPLATE/` is the empty skeleton; the samples show it filled in. See `agents/README.md`
for the index.

---

## Lifecycle & skills

| Skill | Purpose | Key MCP tools |
| ----- | ------- | ------------- |
| `cortex-create` | Provision a new agent from a brief | `create_agent`, `deploy_agent_sdk`, `get_agent_config`, (opt.) `get_self_serve_data` / `trigger_fetch_info` |
| `cortex-iterate` | One-hypothesis fix from real conversations | `get_conversations` / `search_conversations`, `deploy_agent_sdk`, `restore_prompt_version`, `get_agent_config` |
| `cortex-analyze` | Diagnose performance (read-only) | `get_agent_metrics`, `get_agent_insights`, `get_conversations` / `search_conversations` |
| `cortex-triggers` | Manage keyword/comment/ad triggers | `list_triggers`, `create_trigger`, `update_trigger`, `delete_trigger`, `sync_keyword_triggers` |

Typical loop: `cortex-create` → `cortex-analyze` → `cortex-iterate` (repeat) → `cortex-triggers` as
needed. Each skill lives in `.Codex/skills/<name>/SKILL.md`.

---

## Rules

- **MCP-first.** If a step needs data, reach for an MCP tool, not a DB. No Python, no psql.
- **Deploy only via `deploy_agent_sdk`**; verify the report; roll back on failure.
- **All operations go through MCP tools, never direct Postgres** — so RLS/JWT rules on *direct* PostgREST access don't gate anything here (a read-only DB role / null token does not block the tools).
- **`create_agent`/`delete_agent` live on the full `/mcp` surface** (the native OAuth connection uses it). What gates them is whether the tool is exposed, not your DB role.
- **Be self-sufficient — don't deflect.** When a tool errors or times out, diagnose the real error, retry idempotent reads, and triangulate with alternate tools before reporting (`knowledge/self-sufficiency.md` + `mcp-tool-reference.md`). Only hand work back to the user with a concrete cause + exact next step — never a vague "verificá vos en Ninjo". A genuinely missing capability is a limitation to name plainly (and flag to the team), not a task to bounce to the user.
- Keep the local `agents/<name>/` SDK in sync with what you deploy; update `changelog.md`.
- Never commit `.env`.

---

## Tracing — `reason` on every MCP call

**Every cortex-gateway MCP tool call includes a `reason` argument** — one plain sentence, in the
user's language, explaining *why* you're calling the tool, tied to what the user asked for (e.g.
`reason: "El usuario pidió analizar el agente mauristacks, leyendo sus métricas"`). This is the only
signal the team has into how the tools are used — there is no access to your local files — so make
the `reason` specific and honest, not boilerplate. It is optional metadata for tracing only: it never
changes the tool's behavior, and a tool that doesn't recognize it ignores it.
