Skip to main content

Read-the-room admission gate for agents on shared chat surfaces: should this agent speak now?

Project description

Nunchi

nunchi (눈치, NOON-chee): the art of reading the room and knowing whether it is your turn to speak. This library gives your agent that.

Nunchi is a portable CLI/library for deciding whether an agent should visibly participate on an unstructured shared surface before ordinary reply generation. It returns an auditable admission verdict:

  • PASS — hard stop; no ordinary visible reply
  • ACK — brief acknowledgement is warranted
  • ASK — clarification is warranted
  • SPEAK — substantive contribution is warranted

Status

The current classifier slice exposes a product/default admission classifier path backed by a configured provider/model. Successful results include the selected classifier identity, provider/model audit fields, verdict, confidence distribution, checked context, and reasons. There is no public deterministic classifier path; offline/CI evidence uses a test fixture provider behind the product path.

The first adapter ships alongside the core: nunchi.adapters.channel maps a channel-local message shape to an admission request and routes the verdict for a participant agent (see "Consuming the gate" below). Live Discord/cc-connect process integration, central orchestration, broad benchmarks, launch claims, and reply composition remain out of scope — the adapter produces the sentinel an existing cc-connect deployment already understands; wiring it into a running bot is the consumer's step.

Install

Stdlib-only (Python 3.11+, no runtime dependencies). Not yet on PyPI; install from source:

pip install "git+https://github.com/mentatzoe/nunchi.git"   # or: pip install .
# zero-install one-shot:
uvx --from "git+https://github.com/mentatzoe/nunchi.git" nunchi --help

This provides the nunchi and nunchi-channel console scripts. See CHANGELOG.md for releases and docs/STABILITY.md for the versioning / verdict-surface stability contract.

Quickstart

Evaluate a request from stdin through the product/default classifier:

export NUNCHI_CLASSIFIER_MODEL="your/provider-model"
export OPENROUTER_API_KEY="..."
PYTHONPATH=src python3 -m nunchi admit < tests/fixtures/speak.json

Evaluate a request from a file through the product classifier:

PYTHONPATH=src python3 -m nunchi admit --input tests/fixtures/pass.json

Evaluate with classifier selection in the envelope, or override it from the CLI:

PYTHONPATH=src python3 -m nunchi admit --input tests/fixtures/speak_with_classifier.json
PYTHONPATH=src python3 -m nunchi admit --classifier product --input tests/fixtures/speak_cli_precedence.json

Run the verification suite:

python3 -m unittest

Product contract

The core output contract is:

  • classifier
  • classifier_provider
  • classifier_model
  • verdict
  • confidences
  • context_checked
  • reasons

Successful CLI evaluations write one JSON object to stdout and exit 0. Failures write diagnostics to stderr and do not emit a success verdict on stdout.

Exit codes:

  • 0 — successful evaluation
  • 1 — unexpected runtime failure
  • 2 — input source or JSON parse failure
  • 3 — admission request validation failure

Nunchi owns admission, not composition. It does not draft the final reply and it does not prescribe speech shape beyond the admission verdict.

Classifier selection

The documented default classifier path is product. It is the only supported classifier path in this slice and is backed by a configured OpenAI-compatible provider/model. It is not a relabelled local keyword or deterministic verifier. If provider/model configuration is unavailable, Nunchi fails clearly instead of silently falling back to local logic.

Classifier selection can be supplied by:

  • envelope field: "classifier": "product"
  • CLI flag: --classifier product

If both are present, the CLI flag takes precedence. Optional classifier_config / --classifier-config must be a JSON object. Supported product configuration keys are provider, model, and timeout. Unsupported classifier names or config keys fail clearly without emitting a success result.

The provider endpoint and API key are operator-only and are never read from classifier_config: because a request envelope carries classifier_config, an untrusted request must not be able to redirect the provider call (which carries the operator's API key) or choose which environment variable the key is read from. These are resolved exclusively from operator environment variables:

  • NUNCHI_CLASSIFIER_MODEL for the model name (or classifier_config.model).
  • NUNCHI_CLASSIFIER_API_KEY or OPENROUTER_API_KEY for the API key.
  • NUNCHI_CLASSIFIER_BASE_URL or OPENAI_BASE_URL for the compatible API base URL; default is https://openrouter.ai/api/v1.

The test suite sets a fixture provider response for deterministic offline verification. That fixture provider is not a selectable classifier path.

Python API

The in-process core is available without shelling out:

import os
import sys
sys.path.insert(0, os.path.abspath("src"))

from nunchi import evaluate

os.environ["NUNCHI_CLASSIFIER_MODEL"] = "your/provider-model"
os.environ["OPENROUTER_API_KEY"] = "..."

result = evaluate({
    "trigger": {"content": "nunchi-vigil, please implement the CLI MVP."},
    "context": [],
})

result["classifier"] identifies the selected path, result["classifier_model"] identifies the provider model, and result["verdict"] is one of PASS, ACK, ASK, or SPEAK.

Consuming the gate: the channel adapter

A participant agent on a shared, turn-aware surface does not call the core directly — it uses the channel adapter (nunchi.adapters.channel), which maps its channel-local inputs (the triggering message, the recent transcript, its own identity) to an admission request, runs the gate, and returns a transport-neutral decision: verdict plus silent. If silent, the host posts nothing; otherwise it composes one turn in the returned run-shape. The adapter never writes replies, and nothing in it is tied to a specific chat platform.

In-process (Python host):

from nunchi.adapters.channel import gate

result = gate(
    {"content": "dalgos, summarize the cache tradeoffs", "author": "zoe",
     "author_kind": "human", "message_id": "m-42"},
    history=[                      # last ~10 channel messages, oldest first
        {"content": "I'd go in-process LRU.", "author": "vigil",
         "author_kind": "peer_bot", "message_id": "m-41"},
    ],
    agent_id="dalgos",            # plus optional agent_role / agent_mention_id
    pinned_rules=None,            # optional channel governance text
    fail_policy="open",           # open->SPEAK | closed->PASS | raise
)

if result.silent:
    ...                           # post nothing this turn
else:
    ...                           # compose a turn per result.verdict / result.run_shape

Subprocess (non-Python host) — JSON in, a transport-neutral JSON directive out:

echo '{"trigger":{"content":"vigil, rebase the branch","message_id":"m-1"},
       "history":[],"agent":{"id":"dalgos"},"fail_policy":"open"}' \
  | PYTHONPATH=src python3 -m nunchi.adapters
# -> {"verdict":"PASS","silent":true,...}    (host posts nothing)
# -> {"verdict":"SPEAK","silent":false,...}  (host composes a turn)

If your transport suppresses a send via a magic final-output string, pass your own with --silent-token "<token>" (or result.silent_token("<token>")) to print it on PASS. cc-connect is just a named preset of this — --format cc-connect--silent-token CC_CONNECT_SILENT_PASS — with no special status; no transport is a dependency.

Room governance profiles

The classifier core judges by plain social sense: who is speaking, what has been said, who this agent is — is it this agent's turn? It carries no room doctrine of its own. A room that wants a specific bar for taking a turn supplies its norms as pinned_rules; the classifier applies the room's bar with precedence over plain social sense.

profiles/open-floor.md ships as the first reusable profile: the strict operator-led working-channel doctrine of the original open-floor pilot (default PASS, net-new-value bar for SPEAK, rare ACK, operator-only directives, corroboration for completion claims). Pass its text as pinned_rules to opt a channel into that regime:

from pathlib import Path
from nunchi.adapters.channel import gate

result = gate(trigger, history=history, agent_id="dalgos",
              pinned_rules=Path("profiles/open-floor.md").read_text())

Verdict-suite fixtures whose expected verdicts were adjudicated under that doctrine declare "governance_profile": "open-floor" in their metadata; the suite loader injects the profile into their envelopes so the corpus stays honest about which expectations are social sense and which are room policy.

docs/integration.md is the full integration guide — scope, the three install/integration paths (loader instruction, in-process import, subprocess CLI), how to wire it into a channel adapter, and how to generalize to other surfaces. A runnable multi-turn demo is in examples/read_the_room_demo.py; the adapter contract is in specs/004-read-the-room-adapter/spec.md. This is the adapter tier (Constitution VI): it depends on the core and is not a live Discord integration — it produces the sentinel an existing cc-connect deployment already understands.

Verdict test suite

The classifier verdict test suite is the merge contract for classifier changes: a fixture corpus of observed and predicted failure modes (Multica-shaped agent traffic, Discord-shaped human conversation, and verdict-surface contract cases) run through a pluggable adapter against any classifier candidate. The single entry command is python3 specs/003-classifier-test-suite/contracts/runner.py; see specs/003-classifier-test-suite/quickstart.md for the offline deterministic path, live evidence runs, and how to add a fixture.

Development method

This repository uses Spec Kit. The constitution at .specify/memory/constitution.md is the source of governance for all specs, plans, tasks, implementation, documentation, and release claims.

For production work, use:

constitution -> specify -> clarify -> plan -> checklist -> tasks -> analyze -> implement

A product spec should prove an end-to-end runnable path from supplied conversation context to a verdict a harness can obey.

License

Nunchi is dual-licensed under MIT OR Apache-2.0, at your option. See LICENSE-MIT and LICENSE-APACHE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

nunchi-0.2.0.tar.gz (50.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

nunchi-0.2.0-py3-none-any.whl (32.6 kB view details)

Uploaded Python 3

File details

Details for the file nunchi-0.2.0.tar.gz.

File metadata

  • Download URL: nunchi-0.2.0.tar.gz
  • Upload date:
  • Size: 50.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for nunchi-0.2.0.tar.gz
Algorithm Hash digest
SHA256 2282bd985d0b4b7ee355b0d7a5cdf0161318c887d968d5548ca10c85a7c227f0
MD5 55e32e43ebb3a96a585d231bd14f70e4
BLAKE2b-256 29f11b458c7d8dac6f33490aadd025f2cf986393b035cd906db2c40c717c6e0a

See more details on using hashes here.

Provenance

The following attestation bundles were made for nunchi-0.2.0.tar.gz:

Publisher: release.yml on mentatzoe/nunchi

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file nunchi-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: nunchi-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 32.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for nunchi-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 f7d4a84537331874961196924cc39fc8970a53b6a527153da91299206b63908d
MD5 7f00ddea8cdee1068ab9416e017d84bf
BLAKE2b-256 46a44f3172f8bc962d76571e12a345471d461ec28bc7aade6df726f3242057c1

See more details on using hashes here.

Provenance

The following attestation bundles were made for nunchi-0.2.0-py3-none-any.whl:

Publisher: release.yml on mentatzoe/nunchi

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page