| name | tellonce |
| description | Use when handling any user message; records and enforces user preferences with Codex-native audit/wrapper support. |
Tellonce for Codex
Codex actually exposes the same hook system as Claude Code (PreToolUse / PostToolUse / SessionStart / UserPromptSubmit / PermissionRequest). The
codex variant of tellonce installs into ~/.codex/skills/tellonce/
~/.codex/hooks.json and uses native hooks for retrieval + enforcement.
The wrapper path (tellonce_codex exec --) is still the way to enforce
on the FINAL agent text response (codex doesn't fire a Stop hook for that;
PostToolUse only sees tool inputs/outputs).
Core rules (every turn)
- Scan every user message for
preference, pitfall, friction, or none.
- Apply known preferences before responding (read
<state>/index/active_memories.json).
- Record durable evidence through
tellonce_codex scan when installed.
- Wrap any subprocess that produces user-facing output via
tellonce_codex exec -- <cmd> so its stdout is verified and audited.
Rule injection (progressive full index — default)
Each time the user submits a message, userpromptsubmit-retrieve-inject.sh injects a one-line index of the rules saved under the project memory dir as additionalContext, and I judge which apply. If the library exceeds the per-turn cap (default 50 — progressive_max in ~/.tellonce.config.json or PT_PROGRESSIVE_MAX, 0 = no cap), tier-1 rules are pinned when they fit under the cap (when tier-1 alone overflows it, the whole library rotates instead), the remaining rules rotate in across turns, and the block states how many of the total are shown. This is the default progressive backend: it just reads the saved rule files — no prompt matching, no model call, no CLI cold-start. The format looks like this — it's not external noise, it's a rule hint from the skill infra and must be respected:
### Your saved preferences — check each against this turn and apply the ones that fit:
- [fmt-pref-001] (tier1) use 4 spaces for indentation, not tabs
- [tool-pref-002] (tier2) prefer the project's own package manager / lockfile for installing dependencies | when: adding / upgrading dependencies
(These are your recorded preferences. Judge each rule against the current task; apply those that apply, skip those that do not.)
Each line carries the rule's rule_text/description and (when present) a when: applicability hint. I judge for myself whether it holds for the current turn, and skip rules that don't apply.
Legacy backends (PT_RETRIEVE_BACKEND=cli / keyword / api) instead inject only the rules matched for the current prompt, under a ### Fingerprint retrieval — ... header. The judgement I apply is the same.
audit_only ──first wrapper run──▶ wrapper ──opt-in──▶ blocking
audit_only: scan + record + advisory stderr; PostToolUse hook never blocks. Default after install.
wrapper: at least one tellonce_codex exec run has completed; same advisory behavior as audit_only.
blocking: PostToolUse hook returns exit 2 + decision:block JSON when violations detected. Opt-in only — set by editing <state_root>/mode.json (write_mode enforces monotonicity: never downgrade).
The state lives in <state_root>/mode.json. register_project only writes the default mode on first install; later CLI invocations preserve any wrapper-mode upgrade.
When to call which command
| You want to ... | Run |
|---|
| Bootstrap state for this project | tellonce_codex install --project-root . |
| Record a scan event for the latest user message | tellonce_codex scan --project-root . --message "..." |
| Audit a subprocess's stdout (the main wrapper path) | tellonce_codex exec --project-root . -- <cmd...> |
| Promote a candidate to durable memory (programmatic) | call tellonce_codex.promote.promote_candidate(state, candidate) directly |
| Health check + leak audit | tellonce_codex doctor --project-root . |
| Summary | tellonce_codex dashboard --project-root . |
| Uninstall integration (keep data) | tellonce_codex uninstall --project-root . |
| Uninstall + delete all state | tellonce_codex uninstall --project-root . --purge-state |
-- is required for exec
tellonce_codex exec --project-root /path -- claude -p "do thing"
Without --, argparse may swallow flags meant for the wrapped binary. The CLI prints an explicit error if -- is missing.
Timeout
tellonce_codex exec defaults to 600s. Override with --timeout 1200 or CODEX_PT_TIMEOUT=1200 env. Long LLM sessions need this — the prior 120s default cut every real session.
Whitelist for inline-English check
Codex's verify_output flags inline English tokens in mostly-Chinese responses (rule lang-pit-130). Both built-in verify rules are env-gated OFF by default — enable with CODEX_PT_LANG_RULE=1 (inline-English) and CODEX_PT_TMP_RULE=1 (/tmp paths); without those flags the wrapper only audits/redacts. To avoid false positives once enabled:
- A small base whitelist (programming terms like
api, json, http, model names like claude, gpt) is built in.
- Add project-specific tokens to
<state_root>/whitelist.txt (one per line, # for comments).
- Or set
CODEX_PT_WHITELIST=/path/to/file for a global file.
Doctor states (what's normal)
doctor.run_doctor() returns wrapper={PASS, NOT_USED}. NOT_USED is normal on a fresh install — it just means no tellonce_codex exec has run yet. It's not an error.
Privacy
- Subprocess stdout/stderr go through
sanitize() before disk (redacts API keys, DB URIs, JWT, SSH private-key blocks, etc.).
- Files under
<state_root> are written with mode 0o600 (user-only), and <state_root> itself is 0o700.
- The wrapped subprocess gets a filtered env: anything matching
*TOKEN* / *SECRET* / *PASSWORD* / *API_KEY* / *AUTH* etc. is dropped before the subprocess starts, except an explicit allowlist of standard LLM/dev-tool credentials (ANTHROPIC_API_KEY, OPENAI_API_KEY, GH_TOKEN, ... — see wrapper._ENV_ALLOW_NAMES) that the wrapped CLIs need to function. CODEX_PT_STRICT_ENV=1 disables that allowlist for a pure deny-list.
- See
tellonce_codex.ledger.SECRET_PATTERNS for the redaction patterns; extend via PR if your stack has a key prefix not yet covered.
Setup
bash <repo>/codex/install.sh
bash <repo>/codex/doctor.sh
PYTHONPATH=~/.codex/skills/tellonce-runtime python3 -m tellonce_codex doctor
PYTHONPATH=~/.codex/skills/tellonce python3 -m tellonce_codex doctor
One-time hook trust approval (required — hooks are silently skipped until then)
Codex trusts hooks by a hash of their exact definition. Newly installed or
changed hooks do not run — no error, no log, they are simply skipped —
until you review and approve them once in an interactive Codex session
(Codex prompts on the next session start; accept the tellonce entries).
This applies after first install AND after any upgrade that touches
hooks.json (including re-running install.sh, whose remove-then-add
re-orders the definitions). If hooks seem dead ("installed but nothing
happens"), a missing trust approval is the first thing to check. One-off
verification without trust: codex exec --dangerously-bypass-hook-trust ....
Hook flow
| Hook event | Script | Purpose |
|---|
| UserPromptSubmit | userpromptsubmit-retrieve-inject.sh | inject a one-line index of the saved rules (default progressive backend; legacy backends match the prompt via fingerprints/CLI/API instead) as additionalContext |
| UserPromptSubmit | userpromptsubmit-pending-inject.sh | warn about pending memory entries from prior session crashes |
| UserPromptSubmit | userpromptsubmit-shadow-alert-inject.sh | inject "last turn violated rule X" reminder so this turn fixes it |
| PostToolUse | posttooluse-deterministic-block.sh | regex/fingerprint scan agent's tool input (Write content / Edit / Bash); audit_only logs, blocking mode exits 2 + decision:block |
| SessionStart | sessionstart-init.sh | lazy-init project state on first codex SessionStart in a fresh project |
Mode state machine
The three modes are rank-ordered (_MODE_RANK in tellonce_codex/mode.py):
audit_only (0) → wrapper (1) → blocking (2). Per project, the persisted
mode only latches upward — write_mode raises ModeDowngradeError on any
silent downgrade (allow_downgrade=True exists for test fixtures only).
blocking is opt-in only; nothing auto-promotes into it. wrapper_seen
latches True the first time tellonce_codex exec is used and never resets,
so doctor/dashboard can tell whether wrapper enforcement has ever run here.