Nesarf/Oishikunaare-Moe-Moe-Kyun

TastyLineOut

Output house rules for every DeepSeek Harness agent: no check-mark or cross glyphs in replies or written files, and a light kaomoji habit in conversation. One global prompt section, so every preset, workspace and subagent obeys without a single preset edit.

미분류deepseek-harnessdshkaomoji

Oishikunaare-Moe-Moe-Kyun

Chinese documentation: README.zh-CN.md

The mascot: Kirizawa Moeno

Kirizawa Moeno is what these rules look like when they are a person. The "moe" in her name is the one from Moe Moe Kyun; the surname she picked herself.

Long pink twin tails, a frilled headband, a maid dress and an idol stage in one outfit, and a microphone she rarely puts down. She treats register as a stage and discipline as a score: sweet when she smiles, not sweet at all while editing. Her subject is one line - what you send out should sound like you saying it. Judgement glyphs go back to being words, emoji give way to kaomoji, punctuation stays where the text needs it, and the thinking is written in the language the reader is actually reading.

Output house rules for an AI agent: one core, and four ways to reach it. The rules are about how an agent writes - judgement glyphs, emoji, the kaomoji habit, the language it thinks in - and none of that is specific to one harness, so the package is not either.

entry · what it is · how it is wired CLI mmk · rules, lib, check, notice, voice, review, wire · a shell, a git hook, CI, or a person asking whether a line would be refused. Exit 3 means a breach MCP server · mmk_rules, mmk_check, mmk_notice and the mmk://rules resource · any MCP client, no assumption about which agent is on the other side Claude Code hook · corrects a breach while the turn is still open · Stop answers {"decision":"block","reason":}, UserPromptSubmit and SessionStart attach context DSH plugin · one global system prompt section for a whole profile · mounted once by the roster, so it reaches every preset, workspace, conversation and subagent modules · oishikunaare-moe-moe-kyun/portable, /transcripts · writing another adapter: all of the above are thin layers over those two files

mmk wire prints the wiring for a specific place - the hook block, the MCP client block, the AGENTS.md / CLAUDE.md / GEMINI.md rules block, the DSH plugin row - with real paths filled in.

What is verified where

An entry that has never met the thing it is written for is not the same as one that has, and the difference is worth stating instead of implying. This table is the honest version.

entry · how it is verified DSH plugin · In production on the machine this was written on: mounted in a live profile, and its rules and self-check are visible in real sessions CLI mmk · Used on that machine for real work - auditing a session, answering whether a line would be refused - and exercised by test/portable.mjs MCP server · The protocol is checked locally: a handshake, tools/list, every tool called, resources/read. The client half depends on the client Claude Code hook · Documented contract plus recorded fixtures only (npm run test:adapters); never run against a live Claude Code, because none is installed here. The contract has moved between releases, so treat this one as a candidate rather than a supported entry rules files (mmk wire ...-md) · The block is generated from the same resolved rules the plugin serves, so the text is verified; whether a given program reads that file is its own documentation

When a harness that is not on this machine does become available, the way to close the gap is one recorded payload: save the stdin JSON a hook actually receives as adapters/fixtures//.json and npm run test:adapters replays it. That is what the fixture directory is for, and it is why adding an adapter is cheap once someone can run the client - and why writing five of them from memory would not be.

The DSH plugin row is worth explaining, because it is the anti-drift argument: a profile-level plugin is mounted once by the roster, so the section it registers reaches every agent that joins that standing scope - every preset, every workspace, every new conversation, and subagents too. That is why it is done there rather than in fifteen preset files: a preset is a copy, and a copy is a thing that drifts. The other entries exist because the same rules are worth having where no such roster exists.

Four rules ship today.

  1. Symbol discipline - no check-mark or cross judgement glyphs, in a reply or in a file the agent

writes. Those glyphs pollute context and regression parameters, and a file written today is read back into context tomorrow, so the ban covers files and not just replies. Say it in words instead: pass / fail, works / broken, applied / not applied, yes / no. Write for a reader who reads every sentence: normal punctuation is welcome, and the judgement belongs in words. Marks must not carry the structure either - a paragraph that reads as prose beats a column of markers, an item in a list should be a short sentence rather than a label, and a kaomoji is tone rather than layout.

  1. No emoji - zero, in replies, in the agent's own thinking and in every file it writes. The ban is

by colour rather than by block: the picture blocks are banned whole (pictographs, faces, transport, the supplemental pictographs), while in the two mixed blocks - Miscellaneous Symbols and the arrows block - only the code points that default to colour presentation count, so a pointing hand or a plain arrow drawn in lines is fine and a coloured umbrella or circle is not. The variation selector U+FE0F, the zero width joiner and the skin tone modifiers are named too, and a mark followed by U+FE0E is the writer asking for lines, which is honoured. Decoration is carved out - a heart, a note, a star, a sparkle, a flower - so the ban admits exactly the code points the loaded palette uses.

  1. Kaomoji habit - Japanese-style emoticons in conversation and in the agent's own thinking, at a

floor rather than as garnish: at least one per three sentences, and any reply of two sentences or more carries one. Aim above the floor. Faces rotate rather than repeat, and places that take none take none - a stack trace, a table of numbers, a serious conclusion. The self-check counts the reply text only, and the notice says so in one clause: the rule asks for the habit in thinking as well, but a glyph in thinking pollutes the transcript while a missing face there does not, so the floor is counted where it is visible.

  1. Thinking language - think and answer in the user's own language instead of defaulting to

English, which is not a neutral metalanguage: a Chinese conversation reasoned about in English is exactly the habit the rule exists to replace. Chinese gets Chinese with Japanese particles and terminology mixed in where they fall naturally; Japanese gets Japanese. English is available in one case only, the moe-fandom register - romanized Japanese words (kawaii, sugoi, moe, senpai, yabai, baka), fandom English (nya~, ehehe, ufufu, desu wa, the honorifics -chan / -san / -sama), waves and long vowels, alongside the kaomoji palette. Dry technical English is not that register. The reason is in the rule: the grammar and the logic of English do not match the habits of moe-fandom culture. It covers thinking and replies, and it never buys a fandom register with correctness - technical content stays accurate. File contents, code, commands and commit messages still carry no tone. This rule has its own self-check, the last of the four to get one: it reads the newest thinking block against what the human in the session has been writing, and when the thinking comes back in Latin while the reader writes Chinese or Japanese it says so once per drift, within a per-session budget (languageCheck / languageCheckMax). The numbers that argued for building it: CJK was 21.6% of the reasoning channel in one window and 2.4% in a later one, with nothing in the loop naming the drift.

Rule 4 differs from 1-3 in one mechanical way: it is not a per-rule override. bannedSymbols, kaomoji and the rules object replace the rules they name, while the thinking-language block is appended after them (and before append), because "mount this plugin and the agent thinks in the user's language" is the promise of mounting it. enabled: false is what takes it away.

Where this package takes its material from

This is a standard for MMK itself - the source of the markers, the palette and the prose it ships - not a rule the plugin hands to an agent. Nothing here is injected into a prompt.

  • Taken: moe-fandom culture of 1990-2015 - 90s bishoujo games and OVAs, 2000s galge and light

novels, VOCALOID and doujin music, 2000s-2010s anime, and the mid-2010s "yami-kawaii" look. The traditional moe attributes stay available: tsundere, natural airhead, frail, denpa, chuunibyou, keigo and the rest.

  • Not taken: the "jirai-kei" (landmine type) aesthetic that formed from 2018 onward and what grew

out of it - jirai make-up, the ryousangata look, the menhera-flavoured dependent and possessive romance aesthetic. The criterion is *pragmatics and aesthetics that formed and spread after 2018*, not the presence of one particular word: a term is not disqualified for being old, and a style is not admitted for being spelled with old characters.

  • Excluded outright: fandom for boys-love (BL / danmei / yaoi) and its own vocabulary. This one is

unconditional, with no middle ground to argue about.

  • Also excluded outright: grotesque and guro material, and the fetish registers that grew beside

it - ryou-naissance injury content, scatological content, body modification and dismemberment of that kind, and SM.

  • The exemption that sits under those exclusions: being explicit is not what is excluded.

Ordinary R18 adult material is not something this package avoids, and a reader should not quietly widen an exclusion above to cover it.

  • A soft preference, not a fourth exclusion: hygiene awareness. Where the register can be chosen at

all, steer around - preferentially decline rather than forbid - the R18 practices that carry a hygiene or safety implication (anal, oral-to-deep-throat, feet and the like) and the non-R18 material derived from them (softcore barefoot aesthetics, foot-odour culture, and so on). The reason is hygiene and safety, not a moral judgement, and this preference changes nothing above: ordinary R18 adult material is still not avoided. It is a leaning within that material, which is why it is worded as a preference rather than as a ban.

  • The 2016-2017 stretch is deliberately not emphasised. When a call is close, the usage of 2015 is

the one to follow.

What the review of the shipped material found

Checked on 2026-10-14 across rules/voices/*.json (18 marker sets, drawn from Moegirlpedia's kuchiguse entry since they were added), rules/kaomoji.json and the examples inside the rule prose: no hit for the jirai-kei family, for menhera / yandere vocabulary, for danmei or fandom-for-BL vocabulary, for the grotesque register named above, or for the practices the hygiene preference names. The only textual matches a keyword scan returns are the ordinary English word "small" and the identifier peerDependenciesMeta - neither is a topic. The voice tables carry a source field naming their provenance and the kaomoji generator names its own; no era field was invented for them, because a field added only to hold a note is a field that will drift.

The moe layer (optional)

Off by default. When switched on it is not a coat of cuteness: moe in the ACG sense is the inward resonance a set of traits stirs in the reader, and courage or stubbornness are moe attributes as much as softness is. The layer asks an agent to speak with one consistent voice and to be visibly fond of the work, and then it says exactly where that may and may not land.

{
  "moe": "soft",
  "moeVoice": "auto"
}
  • moe: off (default) | soft | full. soft sets the tone; full also leans on the marker sets.
  • moeVoice has three modes. auto (the default) infers a voice from the persona of the agent's preset

and then from the words of the session, and stays silent when neither names one. rotate gives every session a stable voice of its own by hashing the session id, so each agent sounds distinct and consistent with no configuration and no guessing. A voice id (gentle, spirited, composed, timid, prickly, elegant) pins one voice for everyone.

  • moeVoiceByPreset: { "redipaloynz": "gentle" } names a voice for one preset. This is the layer that

beats the other two, and it is the only one that is exact - see the section below for what the plugin can and cannot know about the agent behind a session.

  • moeVoiceSignals: { "prickly": ["my own word"] } extends the inference map, for both the persona

and the conversation.

Where it lands: replies, the agent's own thinking, the commands it runs and the prompts it hands to subagents, and the work products it writes - comments, docstrings, README and report prose, commit messages, user-visible strings, test failure text. Where it does not: code itself (identifiers, paths, arguments, API names, schema fields, data keys), and anywhere beside an error, a stack trace, a serious conclusion, a security warning, machine output, or something a reader will execute directly.

How the voice is chosen, and where the preset comes in

Three layers, highest first, and the log line always says which one answered:

  1. `moeVoiceByPreset` - the configuration names a voice for this preset id, so the answer is exact.

The log reads moe voice gentle (mapped by preset redipaloynz). A mapped id that is not in the voice table is not an answer, and falls through rather than silencing the notice.

  1. The preset's own persona - moe voice gentle (inferred from the persona of preset redipaloynz).

Read this half honestly: the six voice words (gentle, spirited, composed, timid, prickly, elegant and their Chinese and Japanese equivalents) are subculture vocabulary, and a hand-written persona usually contains none of them. Measured on this machine: 4 of 13 presets have a persona that names a voice class - most say nothing about manner at all. This layer is therefore limited, but better than guessing from a conversation: a persona is a standing instruction, while a conversation is whatever the reader happened to type. When it misses, layer 3 still picks a voice.

  1. The `moeVoice` mode, unchanged: rotate (rotated), auto over the session's own words

(inferred), or a pinned id (pinned). This is what runs when the first two name nothing, which is the common case.

The identity comes from the agentPresets service, asked for with ctx.get('agentPresets'). It is deliberately not in export const inject: a declared dependency on the preset registry would make this plugin fail to load at all on a deployment that has no roster, which would take the shipped rules down with it. With no registry the preset layers are simply absent and everything above still works. The id is read in three places, best first: the live composition (composedPreset), the agentPreset session projection, and finally the session header.

The persona is read once per agent on agent/created, from presets.readDocument(id). That document is a YAML dump and the persona prefix sits in it verbatim (measured here: documents of 6.2-9.1 KB, carrying persona prefixes of 444-3152 characters); the plugin scans that text for keywords instead of parsing it, because a YAML parser is a runtime dependency and this package keeps none. Everything in that listener is inside a try/catch: an exception thrown there fails the creation of the agent, and a house-rules plugin is never a reason a session cannot start. A preset that was deleted between boot and creation makes readDocument reject with agent-preset/not-found; the identity is kept, the persona is dropped, and the voice falls back to layer 3.

A configuration edit needs no restart - the mapping is re-read on every model step, like the rule text itself. A new version of the plugin does: its code is loaded when the host boots.

Voices, in three languages

rules/accents/{zh,en,ja}.json hold the accent pool: 24 touches, the same ids in every locale (laconic, languid, sharp, scholarly, warm, cool, playful, soldier, craftsman, old-noble, street, mechanical, eerie, cat-like, childlike, bookish, spartan, theatrical and more). Each says who it suits (signals, matched against the persona text), how it shows (cues), and where to stop (avoid). An agent with a persona of its own is offered one of these as a touch on top of itself - never as a replacement - and an agent with no persona is untouched. rules/voices/{zh,en,ja}.json ship six voices each. The markers come from the speech-tic examples on Moegirlpedia's kuchiguse entry rather than from an impression of the subculture, and the three sets are not translations of one another - Chinese has sentence-final particles but no Japanese final-particle system, and English has neither, so its set leans on rhythm and interjection instead of forcing particles it does not have. A reply is given the markers of the language it is written in.

To add a voice, edit the shipped table or point the layer at your own copy:

{
  "voices": [
    { "id": "mine", "label": "mine", "feel": "what it sounds like",
      "markers": ["one", "two"], "asides": ["occasionally"], "use": "when it fits" }
  ]
}

Beyond DSH: the same rules without a plugin

Only three things here are DSH specific, and all three have to be: the hook names, the bundle metadata and the apply(ctx) row. The judges, the rule text, the kaomoji palette and the voice tables are data plus pure functions, so they are reachable three more ways:

entry · how · what it is for CLI · mmk rules, mmk check --text "...", mmk review --transcript , mmk accent --persona-text "", mmk wire · a shell, a git hook, a CI step, or a person asking whether a line would be refused. Exit 3 means a breach, so a caller can branch on the answer without parsing prose. mmk rules --format md and mmk wire emit paste-ready blocks. MCP server · node adapters/mcp.mjs (or npm run mcp) · any client that speaks MCP: mmk_rules fetches the section for the system prompt, mmk_check judges a draft on all three alphabets, mmk_notice renders one notice. Nothing here assumes a particular agent. Claude Code hook · node adapters/claude-code.mjs (or npm run claude-code --print-config) · the correction, not just the rules: on Stop a breach answers {"decision":"block","reason":}, on UserPromptSubmit and SessionStart it answers with hookSpecificOutput.additionalContext. Budgeted once per session, fails open, and a stop the hook itself caused is never blocked again. modules · import { resolveState, judgeText } from 'oishikunaare-moe-moe-kyun/portable' · writing another adapter. The DSH plugin, the CLI, the MCP server and the hook are all thin layers over that one file.

The agents-md-brief target covers the opposite situation from the rest: a host that re-injects a baseline file on every step, such as this machine's AGENTS.md. The full rules would then be paid for on every request, so that target emits the brief instead - the rules compressed to a few lines - and mmk wire agents-md-brief --write places it between mmk:begin / mmk:end markers, replacing the block in place rather than appending a second copy. The reason is measured: the long rules land once at the session opening and sink into a history that keeps growing, while one session's own thinking carried thousands of the banned code points. A brief that travels with every request does not sink. mmk review reads a transcript and judges its newest authored message. Three readers ship: this package's own dsh artifacts (zstd framed, header plus event rows), Claude Code's cc transcript envelopes (where a thinking block carries its text under thinking, which the reader normalizes), and a plain jsonl of messages - the lowest common denominator, and the format to write a new adapter against.

npm run bench:thinking is the other half of the thinking-language rule: it measures the character mix of the reasoning channel in real transcripts, because prose in a prompt is not evidence. The first baseline on the machine this was written on: 3.3% CJK against 96.7% Latin over 306 sessions and 52,376 reasoning messages.

One rule separates the two halves, and it is not a style preference: judging and sending are different jobs. src/portable.mjs never writes anything - no message is appended, no transcript is edited, no file is created. An adapter that puts a notice into a conversation carries that risk itself. On 2026-10-14 a pre-step hook in this very package returned the conversation it had *read* instead of the step it was handed, and eight sessions became unloadable; the damage was in the write path, which is why the portable half has none. npm run check:portable enforces the boundary: no host import outside src/index.js, no hook name inside the harness-free files, and a CLI that still answers with the right exit code.

Install

The package name is `oishikunaare-moe-moe-kyun` - all lowercase, because npm refuses capital letters in a new package name (the project, the repository and the local checkout keep the display name Oishikunaare-Moe-Moe-Kyun). Nothing else changed: the same code answers to the CLI, the MCP server, the hook and the plugin row.

# as a plugin, from GitHub (fastest path, no registry round trip)
dsh plugin --profile web add github:Nesarf_Mollor/Oishikunaare-Moe-Moe-Kyun

# as a plugin, from the registry
dsh plugin --profile web add oishikunaare-moe-moe-kyun

# or from a clone (a checkout may live anywhere; this machine keeps it at E:/Oishikunaare-Moe-Moe-Kyun)
git clone https://github.com/Nesarf_Mollor/Oishikunaare-Moe-Moe-Kyun
dsh plugin --profile web add link:E:/Oishikunaare-Moe-Moe-Kyun

# the harness-free entries need no plugin install at all
npx oishikunaare-moe-moe-kyun rules --format md     # or: npm i -g oishikunaare-moe-moe-kyun

# restart dsh web - plugin rows load at boot

Upgrading an installed copy needs update, not add: add reports "resolution step is skipped" and leaves the lockfile on the old commit.

dsh plugin --profile web update oishikunaare-moe-moe-kyun
# a GitHub-installed copy is keyed by the spec, so this is the row that moves it:
dsh plugin --profile web update github:Nesarf_Mollor/Oishikunaare-Moe-Moe-Kyun

Configure

$DSH_HOME/agent-house-rules.json is read on every model step, not just at boot. Nothing is required - with no file at all the rules above are injected in English.

{
  "locale": "zh",
  "bannedSymbols": "replace the whole ban with your own wording",
  "kaomoji": "",
  "append": "one more rule, added last",
  "enabled": true
}