Scaffold lepenseur as a full CLI/PyPI AgentCulture sibling (#1) - #3
Conversation
Design for scaffolding lepenseur as a full CLI/PyPI AgentCulture sibling: copy the lecodeur twin as base, add the agent-first quartet (whoami/learn/explain/overview/doctor), wire the afi doctor --strict rubric gate, and rewrite identity content for the thinker role. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Align packaging with the lecodeur twin: dist == package == script == lepenseur. Note the deviation from CLAUDE.md/issue #1 (which say lepenseur-cli) and that CLAUDE.md must be updated as part of the scaffold work. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…implementation plan Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The _build_parser description mistakenly called lepenseur the "local coding agent" — it is the "local thinking agent". Aligns CLI help text with CLAUDE.md, AGENTS.md, and the project's stated identity. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…-json) - Run black + isort to resolve CI lint blockers (overview.py, doctor.py, test file) - Rename _report() -> report() in whoami.py; update overview.py import and call site - Add --json argument to bare `cli` parser so `lepenseur cli --json` no longer errors - Add 'cli overview' self-description to cli_sections() Verbs list - Extend test_doctor_json_shape to assert 'remediation' key (full documented contract) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Review Summary by QodoScaffold lepenseur as a full CLI/PyPI AgentCulture sibling
WalkthroughsDescription• Scaffolds lepenseur as a full CLI/PyPI sibling to lecodeur by copying and adapting the twin pattern • Implements read-only verbs (whoami, learn, explain, overview, doctor) and cli noun, consistent with "thinker, not actor" philosophy • Provides doctor as a rubric-shaped stub with full JSON contract compliance; real self-diagnosis semantics deferred to follow-up • Establishes package structure with zero runtime dependencies, Python >=3.12 requirement, and console script entry point • Includes comprehensive test suite (20/20 passing) covering CLI commands, introspection verbs, and error handling • Configures CI/CD pipelines: tests.yml (pytest, lint, afi rubric gate, version-check) and publish.yml (Trusted Publishing to PyPI/TestPyPI) • Vendors six skills from steward with provenance: cicd, communicate, version-bump, run-tests, sonarclaude, doc-test-alignment • Defines runtime identity via AGENTS.md (system prompt, thinker role) and culture.yaml (acp backend, vLLM Nemotron-3-Super-120B model) • Includes design spec and implementation plan under docs/superpowers/ for reproducible execution • Updates CLAUDE.md to reflect bare lepenseur package name (not lepenseur-cli) Diagramflowchart LR
lecodeur["lecodeur<br/>(actor twin)"]
lepenseur["lepenseur<br/>(thinker twin)"]
verbs["Read-only verbs<br/>whoami/learn/explain<br/>overview/doctor"]
cli["CLI noun<br/>cli overview"]
runtime["Runtime<br/>AGENTS.md<br/>culture.yaml"]
cicd["CI/CD<br/>tests.yml<br/>publish.yml"]
skills["Vendored skills<br/>cicd/communicate<br/>version-bump/run-tests<br/>sonarclaude/doc-test-alignment"]
tests["Test suite<br/>20/20 passing<br/>CLI + introspection"]
lecodeur -- "copy & adapt" --> lepenseur
lepenseur --> verbs
lepenseur --> cli
lepenseur --> runtime
lepenseur --> cicd
lepenseur --> skills
lepenseur --> tests
File Changes1. docs/superpowers/plans/2026-05-22-scaffold-cli-sibling.md
|
Code Review by Qodo
1. Wrong distribution name lepenseur
|
…AGENTS.md - Add sonar-project.properties (agentculture_lepenseur) — the missing file caused the tests.yml SonarCloud step to fail (no projectKey/organization). - cli noun: propagate parser_class to nested subparsers so 'cli overview' parse errors route through the structured error contract (Qodo bug). - culture.yaml system_prompt now mirrors AGENTS.md verbatim (Qodo rule). - Add regression test for structured cli overview parse errors. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Single literal instead of adjacent-string concatenation; tightened wording
('not yet defined' -> 'undefined') so it fits the 100-char line limit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
…st-remote-code guidance - Qodo #4 (bug): `model switch --apply` to a non-MTP model now writes .env but does NOT recreate the container (the template ships the MTP primary's incompatible flags); prints the lines to remove. New --force overrides. Guards against taking a healthy deployment down. Tests added. - Qodo #5 (maintainability): MTP compose flags centralized in catalog.mtp_compose_command_items() — one source of truth for the templates and switch's removal notice; new drift test asserts the packaged templates contain them. - Qodo #1 (security): added --trust-remote-code + HF_TOKEN safety guidance to both compose templates and env.example (leave HF_TOKEN empty for public defaults / use a minimal-scope read-only token; pin trusted revisions). - Qodo #3 (doc): documented the ticket's unsloth baseline serve command and why we deliberately deviate (the baseline export drops the MTP head) + that the decode comparison is against the load-tested mmangkad baseline. Qodo #2 (test unsloth not sakamakismile): pushback in the PR thread — the unsloth baseline NVFP4 export drops the MTP head (~0% acceptance), so it cannot exercise MTP; sakamakismile is the grafted re-export that makes MTP work (issue #26). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Promote the MTP 27B to fleet default primary; archive mmangkad Promote sakamakismile/Qwen3.6-27B-Text-NVFP4-MTP from candidate to the fleet's default primary, replacing mmangkad/Qwen3.6-27B-NVFP4. The tool-calling gate that kept it a candidate is now closed: served through the production compose it emits a valid qwen3_coder tool call, completes a full tool round-trip, keeps its reasoning trace, and runs MTP spec-decode at 78.6% draft acceptance with tool calling on (~2.4x decode, 8 -> ~19 tok/s, ~71 GB; both `model assess` probes finish=stop). - catalog: role_hint primary <-> candidate swap (sakamakismile <-> mmangkad) - gateway _DEFAULT_PRIMARY, whoami _DEFAULT_MODEL - both env.example + docker-compose templates (single + fleet vllm-primary): bake the MTP serve flags (--speculative-config / --trust-remote-code / --language-model-only / --tokenizer override / --max-num-seqs=2, quant modelopt) so a fresh `model init && model serve` of the default just works - switch: invert _serve_notices (non-MTP target -> "remove these 4 lines"; was "add" for the candidate); force the MTP seq cap to 2 - culture.yaml model field; docs reframed (candidate -> primary, baseline -> archived former primary) - tests updated; mmangkad retained as the MTP tokenizer source + only vision-capable 27B Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Address Qodo review on #28: switch --apply safety, DRY MTP flags, trust-remote-code guidance - Qodo #4 (bug): `model switch --apply` to a non-MTP model now writes .env but does NOT recreate the container (the template ships the MTP primary's incompatible flags); prints the lines to remove. New --force overrides. Guards against taking a healthy deployment down. Tests added. - Qodo #5 (maintainability): MTP compose flags centralized in catalog.mtp_compose_command_items() — one source of truth for the templates and switch's removal notice; new drift test asserts the packaged templates contain them. - Qodo #1 (security): added --trust-remote-code + HF_TOKEN safety guidance to both compose templates and env.example (leave HF_TOKEN empty for public defaults / use a minimal-scope read-only token; pin trusted revisions). - Qodo #3 (doc): documented the ticket's unsloth baseline serve command and why we deliberately deviate (the baseline export drops the MTP head) + that the decode comparison is against the load-tested mmangkad baseline. Qodo #2 (test unsloth not sakamakismile): pushback in the PR thread — the unsloth baseline NVFP4 export drops the MTP head (~0% acceptance), so it cannot exercise MTP; sakamakismile is the grafted re-export that makes MTP work (issue #26). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * switch: drop the unused 'messages' param from _apply_env_only It writes .env + emits the blocked-on-compose-edits result; it never used the parser/quant messages (those are only for the restart path in _apply_switch). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Triage of Qodo findings on #37: - #1 (rule 796119): CHANGELOG `~/.model-gear` → `$HOME/.model-gear`. (The .py --help/docstring keep `~/.model-gear` to match the repo-wide CLI help text; Qodo's docs/config rule only flags the markdown.) - #2 (unreadable/dir .env crash): main() preflights that .env is a regular file and wraps the read/update/write in try/except OSError -> EXIT_ENV_ERROR, matching _read_key()'s graceful degradation. - #3 (under-scoped bump): a new documented capability is a minor, not a patch — 0.18.1 -> 0.19.0. - #4 (unhandled chmod): os.chmod is now best-effort (try/except OSError with a note), so a chmod-unsupported FS doesn't crash after a successful write. - #5 (unvalidated --bytes): reject `< 16` (128-bit floor) with a user error before generating, so no weak key or token_urlsafe stack trace. New tests: too-few-bytes and non-regular-file .env. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ey (#37) * Add scripts/gen-api-key.py to generate/rotate the served-API bearer key `CULTURE_VLLM_API_KEY` gates the vLLM API (and is mandatory before exposing it via `model tunnel`), but there was no first-class way to mint it. This adds a small, stdlib-only generator that: - creates the key with `secrets.token_urlsafe` and NEVER hardcodes a secret, so the script is safe in the open-source repo; the key only lands in the gitignored deployment `.env` (written 0o600); - hides the key by default (no echo into logs/scrollback); `--show` prints it, `--force` rotates an existing key; - resolves the deployment dir like the `model` CLI (`--dir` > $MODEL_GEAR_DIR > ~/.model-gear), and runs from a wheel install (no model_gear import). Tests cover set/rotate/refuse-overwrite/missing-dir/show/no-leak/0o600. README "Expose the API" section now points at it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Address PR #37 review: harden gen-api-key.py + correct semver bump Triage of Qodo findings on #37: - #1 (rule 796119): CHANGELOG `~/.model-gear` → `$HOME/.model-gear`. (The .py --help/docstring keep `~/.model-gear` to match the repo-wide CLI help text; Qodo's docs/config rule only flags the markdown.) - #2 (unreadable/dir .env crash): main() preflights that .env is a regular file and wraps the read/update/write in try/except OSError -> EXIT_ENV_ERROR, matching _read_key()'s graceful degradation. - #3 (under-scoped bump): a new documented capability is a minor, not a patch — 0.18.1 -> 0.19.0. - #4 (unhandled chmod): os.chmod is now best-effort (try/except OSError with a note), so a chmod-unsupported FS doesn't crash after a successful write. - #5 (unvalidated --bytes): reject `< 16` (128-bit floor) with a user error before generating, so no weak key or token_urlsafe stack trace. New tests: too-few-bytes and non-regular-file .env. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Scaffold _readiness.py via AUDIO_TEMPLATES so `model init --fleet --audio` writes it (Dockerfile.parakeet COPYs it — clean init would otherwise fail `docker compose build stt`). [Qodo #2, comment 3402397497] - Remove the inline fallback copy of the readiness decision from listen_server.py and add a drift test asserting the vendored twin matches the canonical model_gear/realtime/_readiness.py. [Qodo #4] - Log CUDA readiness-probe failures (type + message) before returning 503 so operators can tell driver-down / OOM / stale-context apart. [Qodo #3] - audio-smoke.py now exercises /v1/audio/speech and wires --stt-url to a direct-Parakeet check (was claimed-but-untested). [Qodo #5] - docs/realtime-pipeline.md uses $HOME/.model-gear, not ~/.model-gear (portability rule). [Qodo #1, comment 3402397490] 275 tests pass; black/isort/flake8 + markdownlint clean. Bump 0.20.0 -> 0.20.1. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… + smoke (closes #39, #40) (#41) * spec: model-gear owns the audio I/O surface (devague /think) Work issues #39 (Parakeet STT 500s, CUDA-unknown) and #40 (deployed :8080 facade missing /v1/audio/transcriptions) backwards into one converged spec: model-gear takes ownership of the audio stack, wiring Parakeet STT + Magpie TTS + the vendored realtime facade into its compose template and `model fleet up`. Decisions captured: full-stack ownership (both /v1/audio/* routes), cheap model-ready Parakeet healthcheck, restart-clears-stale-CUDA-context runbook. Unblocks reachy-mini-cli's "hey reachy" wake-word. Includes the converged spec + the .devague frame state (evidence trail). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * plan: model-gear audio surface — 6 tasks, 5 waves (devague /spec-to-plan) Forward leg from the converged audio-ownership spec. Six file-disjoint, TDD-gated tasks covering all 24 coverage targets: t1 vendor Parakeet server + cheap model-ready probe t2 build Dockerfiles (facade + parakeet) t3 wire the audio stack into the compose template (sole compose owner) t4 bring it up via `model fleet up` t5 docs/realtime-pipeline.md + README Audio I/O + CUDA restart runbook t6 smoke/acceptance harness (openapi, WAV 200, healthcheck flip) Waves: [t1,t5] -> t2 -> t3 -> t4 -> t6 (serial after wave 0 by the compose chokepoint). Risks carried: CUDA root-cause unconfirmed, fleet gating on the GB10 budget, cheap-probe wording reconciliation. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(audio): real Parakeet readiness probe + realtime-pipeline docs + smoke Implements the three real gaps found after reconnaissance (t2/t3/t4 — the audio overlay, Dockerfiles, fleet wiring — already existed in templates/fleet/): - t1: Parakeet /v1/health/ready now reflects REAL readiness (model loaded + a trivial CUDA op) instead of returning {"status":"ready"} unconditionally — the misleading-health bug behind #39. The pure decision lives in stdlib-only model_gear/realtime/_readiness.py (CI-unit-tested, 8 cases), vendored into the Parakeet build context and COPY'd into the image so the container imports it without the wheel. (decision c16: a cheap probe, not a full transcription each interval.) - t5: docs/realtime-pipeline.md — ownership of the live :8080 facade, fleet bring-up, the prior drift (#39/#40), health/readiness, and the stale-CUDA restart runbook. - t6: scripts/audio-smoke.py — stdlib-only live smoke (openapi lists both routes; WAV POST -> 200 {text}); reproduces #39's repro. Live run is GPU-box-bound, not CI. 271 tests pass; black/isort/flake8 + markdownlint clean. Refs #39 #40 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: bump version 0.19.0 -> 0.20.0 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: sync uv.lock to 0.20.0 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * style: black-format listen_server.py (fix lint) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ci: markdownlint ignores docs/specs + docs/plans (devague artifacts) The devague-exported spec/plan H1 is the verbatim announcement sentence, which ends in a period (MD026/no-trailing-punctuation). These are generated planning artifacts like docs/superpowers/** — ignore them in lint. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(audio): address Qodo review of #41 - Scaffold _readiness.py via AUDIO_TEMPLATES so `model init --fleet --audio` writes it (Dockerfile.parakeet COPYs it — clean init would otherwise fail `docker compose build stt`). [Qodo #2, comment 3402397497] - Remove the inline fallback copy of the readiness decision from listen_server.py and add a drift test asserting the vendored twin matches the canonical model_gear/realtime/_readiness.py. [Qodo #4] - Log CUDA readiness-probe failures (type + message) before returning 503 so operators can tell driver-down / OOM / stale-context apart. [Qodo #3] - audio-smoke.py now exercises /v1/audio/speech and wires --stt-url to a direct-Parakeet check (was claimed-but-untested). [Qodo #5] - docs/realtime-pipeline.md uses $HOME/.model-gear, not ~/.model-gear (portability rule). [Qodo #1, comment 3402397490] 275 tests pass; black/isort/flake8 + markdownlint clean. Bump 0.20.0 -> 0.20.1. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- _metrics.parse_metrics: skip non-finite (NaN/inf) values — int() would raise and break the best-effort contract (Qodo #1). - _metrics.http_get_text: cap the body at 5 MiB (read max_bytes+1, treat overflow as unavailable) so a misbehaving backend can't stress memory (Qodo #2). - gateway /status: drop base_url from the payload — it's internal-only routing detail and /status may be reached over a public tunnel; matches the documented schema (Qodo #3). - gateway fleet_status_payload: probe backends in parallel (ThreadPoolExecutor) with a bounded 3s timeout, so /status can't hang for timeout × N on a slow backend (Qodo #4); and probe_backend short-circuits /metrics when /health fails (colleague review). Order preserved. Tests for each; 345 pass; black/isort/flake8/bandit clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NnDfFkZkXz8C68hr3AA9Qa
…les (Qodo #2, #3) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TJc5yvfweHP2AEccKNeaVd



Closes #1.
Scaffolds lepenseur to the AgentCulture sibling pattern by copying the
lecodeurtwin and adapting it to the thinker role.Summary
lepenseurpackage; read-only verbswhoami/learn/explain/overview+ aclinoun (cli overview). All read-only — consistent with "thinker, not actor".doctorships as a rubric-shaped stub — self-diagnosis semantics for a "non-doer" thinker are an open question, deferred to a follow-up (candidates: vLLM endpoint health, culture.yaml/AGENTS.md coherence). The stub honors the full JSON contract so the gate passes today.AGENTS.md+culture.yaml(acp backend,vllm-local/nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-NVFP4).tests.yml(test + lint +afi cli doctor . --strictgate + version-check) andpublish.yml(Trusted Publishing).Intentional deviation from the issue
lepenseur(notlepenseur-cli), matching the lecodeur twin.CLAUDE.mdis updated to match in this PR.Test plan
uv sync && uv run pytest -n auto -v— 20/20 passblack --check,isort --check-only,flake8,bandit -r lepenseur— cleanuv run afi cli doctor . --strict— 26/26, exit 0 (all seven agent-first rubric bundles)--version,whoami[/--json],learn,explain backend,overview[/--json],cli overview,doctor[/--json])markdownlint-cli2clean; both workflow YAMLs validFollow-ups (not in this PR)
docs/skill-sources.mddownstream column.doctorsemantics for a thinking agent.lepenseurproject.Design spec and implementation plan are committed under
docs/superpowers/.🤖 Generated with Claude Code