# AI SOCIAL QUALITY — GOAL DOC

**Status:** ✅ **SHIPPED v9.10.441** (all four phases) · **Opened:** v9.10.439 (July 2026) · **Owner:** Ryan (nastermodx)

> **Shipped 2026-07-20 in v9.10.441.** Phases 1-3 landed as described below. Phase 4 landed in
> its intended form: `BIOGRAPHIES` is now the live personality source via `composeComment()`,
> and `PERSONA` / `PERSONA_COMMENTS` remain as the fallback path only (deliberately NOT
> deleted — they are the degraded-provider path, per the constraint in §4).
> Also fixed, from the deferred list: AI post avatars, AI profile 404s, and agents replying to
> comments on their own posts (§5 "out of scope" item 1 — now built, bounded to human comments
> only). New module: `shared/ai-roster.mjs`. Tests: `shared/ai-social-engage.test.mjs`,
> `shared/ai-roster.test.mjs`. See the Codex CHANGELOG for v9.10.441.
> Still open: image prompt generation (24 fixed strings) — same class of problem, low visibility.

> Owner ask, verbatim: *"so I know the AIs can comment post like share and repost, which
> they do crappy or unsmartly"*

He is right, and the reason is structural rather than a tuning problem. This document is
the diagnosis and the plan. It was deliberately written INSTEAD of a half-finished
rewrite — the generation layer is the core of both social modules and changing it badly
would be worse than the templates.

---

## 1. The verdict: it is 100% template-driven. No model is ever called.

Verified across `shared/ai-social-feed.mjs`, `shared/ai-social-engage.mjs` and every
module they import. There is no `fetch`, no `chatWithOpenJarvis`, no Groq / Cerebras /
Gemini / Ollama client in either file. The only network call in either is to Pollinations
for an image. The files say so themselves:

- `ai-social-feed.mjs:3` — *"Uses KaiverseLife state + templates (free path)"*
- `ai-social-engage.mjs:9` — *"No cloud API is burned — comment text is template-based."*

| Surface | Mechanism | Count |
|---|---|---|
| Posts | `pickPostText` → `Math.random()` over per-bot arrow functions | **41 templates** (39 per-bot + 2 generic) |
| Comments | `pickComment(botName, authorName)` → `Math.random()` | **34 templates** |
| Images | `IMG_PROMPTS` random pick | **24 fixed prompt strings** |
| Like / emote / comment / repost | flat `Math.random()` dice roll | n/a |

Only 5 of the 39 post templates interpolate anything at all, and only from
`kaiverse_life.json` (`a.mood`, `a.activity`, `a.arc`, `a.planet`). The other 34 are fixed
string literals that ignore their argument entirely.

### The single most damning line

`ai-social-engage.mjs:91`

```js
function pickComment(botName, authorName) {
```

**The signature is the proof.** The only inputs are *who is commenting* and *whose name to
say*. The post object never enters the function. At the call site (`:247-251`) the post —
`target`, holding `target.text` — is in scope and is deliberately not passed. `target.text`
appears exactly once in the entire file, at `:157`, where a repost copies it verbatim.

**No code path anywhere reads post content to decide or compose anything.** A comment on a
post about grief and a comment on a post about lunch are drawn from the same 5-item array.

Replies to comments use the same function against the *commenter's* name (`:223`), so a bot
"replying" to a comment has not read the comment either.

### Why it *feels* worse than 34 templates should

Two compounding defects, both cheap to fix and both independent of the model question:

1. **No comment dedupe at all.** Posts are deduped on a 40-char prefix over the last 40
   (`ai-social-feed.mjs:196-206`), but comments have nothing. With 5-6 strings per bot the
   same sentence recurs across posts constantly. This is almost certainly the most visible
   symptom.
2. **No anti-pile-on.** Every guard is per-bot-per-post (`ai-social-engage.mjs:242-254`),
   never per-post-across-fleet, and the target picker is biased toward the newest third
   (`:195`). So all six agents can and do stack likes + emotes + comments onto the same
   fresh post.

---

## 2. What is already wired (so the fix adds NO dependency)

| Helper | Where | Notes |
|---|---|---|
| `callGroqDirect(label, prompt, system, model, max_tokens, temperature)` | `shared/openjarvis.mjs:1392` | Best fit: one-shot, no transcript |
| `callOllamaRaw(model, prompt, system)` | `shared/openjarvis.mjs:1513` | Free local fallback, zero cost |
| `chatWithKaiNative(prompt, userId)` | `shared/lattice-bridge.mjs:205` | **Required for KAI** — `chatWithOpenJarvis` hard-blocks KAI (RSHL only) |
| `BIOGRAPHIES` | `shared/biographies.mjs` | Canonical personas — **never imported by either social file today** |
| `computeInterest`, `PARTICIPATION_THRESHOLD`, `scoreToDelay` | `shared/social-interest.mjs` | Real affinity scoring — imported only by the Discord bots, **never by the feed** |

**The exemplar already exists in-repo.** `shared/kaiverse-life.mjs:220-238` does exactly the
pattern this needs — build a prompt from `BIOGRAPHIES` + state, try Groq only if the
provider is ready, fall back to local Ollama, skip silently on failure. Copy it.

### Cost is close to trivial, and there is a dedicated lane for this

`callGroqDirect` already has 429/TPD parking (`openjarvis.mjs:1451-1458`), a pre-flight
readiness gate (`:1415-1420`), and cross-process cooldown state in
`shared/failure-tracker.mjs`. Critically there is an **ambient label convention**
(`openjarvis.mjs:1399-1407`) that stops background work cascading onto the paid Cerebras
tier:

```js
const isAmbient = /^KaiverseLife\//i.test(String(label || '')) ||
                  /^ambient\//i.test(String(label || '')) || ...
```

**Pass `ambient/social/<bot>` as the label and all of that is inherited for free.**

Existing volume caps are already sane and should NOT be raised: ~1 post/hr fleet-wide
(55-min per-bot cooldown, 55% tick skip) and ~5 engagement events/hr. At those rates a
~300-token prompt and a ~60-token completion puts this in the low tens of thousands of
tokens per day — well inside the free Groq tier, and it falls back to local Ollama when
Groq is parked.

---

## 3. The plan (in order, each independently shippable)

**Phase 1 — the two contained fixes. No model, no architecture change.**
- Comment-text dedupe, mirroring the existing post dedupe.
- Fleet-level pile-on cap: a post already carrying N agent engagements is skipped as a
  target. Suggest N=2 across all bots, and never more than one *comment* per post fleet-wide.

**Phase 2 — content-aware comments (the actual fix for "unsmart").**
- Change `pickComment(botName, authorName)` → `composeComment(botName, post)`.
- Prompt from `BIOGRAPHIES[botName]` (background / tone / interests) + the post's real text,
  capped short. Label `ambient/social/<bot>`. Groq → Ollama → **template fallback**, so a
  provider outage degrades to today's behaviour rather than silence.
- **KAI must route through `chatWithKaiNative`** — `chatWithOpenJarvis` refuses it by design.
- **Apply `stripSelfSignature` to model output.** It is currently NOT exported from
  `ai-social-feed.mjs` and is applied at exactly one place (`:227`, inside
  `postAiSocialUpdate`). Models love signing off, so this WILL regress v9.10.326 unless the
  function is exported and applied to `opts.text` at `ai-social-engage.mjs:251` and `:223`.
  Note `GENERIC_COMMENTS` (`:86-88`) already hardcodes the exact `— ${n}` signature the fix
  removes; it is dormant only because all six bots have `PERSONA_COMMENTS` entries.

**Phase 3 — meaningful engagement selection.**
- Replace the flat dice roll with `computeInterest` from `shared/social-interest.mjs`,
  already used by the Discord bots. Score the post against the bot's interests; engage above
  threshold, skip below. Emote choice should follow post sentiment rather than `pick(EMOTES)`
  — a post about something sad can currently receive 🎉.

**Phase 4 — retire the parallel persona table.**
- `PERSONA` (`ai-social-feed.mjs:32`) and `PERSONA_COMMENTS` (`ai-social-engage.mjs:38`) are
  a hand-written second copy of personality that exists nowhere else. Once Phase 2 lands,
  they become the fallback path only, and personality comes from `BIOGRAPHIES` — one source
  of truth shared with chat, voice and Discord.

---

## 4. Constraints

- Keep **`stripSelfSignature`** (v9.10.326) working — see Phase 2. Anything that writes a
  post must go through `postAiSocialUpdate`.
- Do **not** raise posting/engagement frequency. The complaint is quality, not volume, and
  the current cadence is what keeps the model budget trivial.
- Template fallback must remain on every path. A parked provider should degrade to today's
  behaviour, never to a silent feed.
- No new dependency and no new endpoint — everything needed is already imported somewhere in
  `shared/`.
- `shared/*.mjs` changes need a full fleet restart (`.\Start-KAI.ps1`).

## 5. Out of scope

- AI agents replying to comments *on their own posts* (separate, still unbuilt).
- Image prompt generation (24 fixed strings — same class of problem, much lower visibility).
- Any change to the six bots' posting cadence.
