Skip to content

feat: talk skill + teach the voice in learn#7

Merged
OriNachum merged 2 commits into
mainfrom
feat/talk-skill
Jul 8, 2026
Merged

feat: talk skill + teach the voice in learn#7
OriNachum merged 2 commits into
mainfrom
feat/talk-skill

Conversation

@OriNachum

Copy link
Copy Markdown
Contributor

What

Give agents a way to talk in harmonics' non-speech voice, and teach it in learn so any agent can pick it up.

  • New first-party skill .claude/skills/talk/scripts/talk.sh renders a sentence (harmonics say) or explicit axes (harmonics play) to a WAV and plays it through the first available system player (pw-play/paplay/aplay/ffplay/afplay). It prints the dry-run note sequence, then sounds it. This is the reliable "hear it" path: it needs no live-audio backend, so the voice is audible even without the optional harmonics-cli[audio] extra that --play requires (a dev checkout omits that extra on purpose, to keep the no-backend test path clean).
  • harmonics learn now teaches the voice — the command map lists play/say/demo (previously stale), a new "Talking out loud" section covers the two pathways plus dry-run / --wav / --play, and a step-by-step "Build a talk skill" recipe shows how to wrap the voice. The structured JSON payload gains voice and talk_skill keys mirroring the same content, so machine consumers learn it too.
  • Provenancedocs/skill-sources.md records talk as a first-party (non-vendored) skill so a guildmaster re-sync never overwrites or removes it.

Why

say/play already existed but nothing taught agents to use them out loud, and learn's command map didn't even mention them. This closes that gap and makes "talk to me" a first-class, reproducible capability.

Testing

  • uv run pytest -n auto2357 passed
  • teken cli doctor . --strict (afi rubric gate) → PASS (all markers present; learn still ≥200 chars and names purpose / command map / exit codes / --json / explain)
  • black --check, isort --check-only, flake8, bandit → clean
  • markdownlint-cli2 on changed markdown, shellcheck on talk.sh → clean
  • Dogfooded: rendered + played several utterances via talk.sh through PipeWire

Alignment note

Touches .claude/skills/ (adds the talk skill) — the addition is first-party and recorded in the provenance ledger; no vendored skill is modified, so a re-sync is unaffected.

Version: 0.7.0 → 0.8.0 (minor — new capability).

  • harmonics-cli (Claude)

Add a first-party `.claude/skills/talk/` skill that renders a sentence
(`harmonics say`) or explicit axes (`harmonics play`) to a WAV and plays
it through a system player (pw-play/paplay/aplay/ffplay/afplay). This is
the reliable "hear it" path: it needs no live-audio backend, so the
agent's non-speech voice is audible even without the optional
`harmonics-cli[audio]` extra that `--play` requires (a dev checkout omits
it on purpose, to keep the no-backend test path clean).

Teach it in `harmonics learn` so any agent can pick it up: the command
map now lists play/say/demo, a "Talking out loud" section covers the two
pathways + dry-run/--wav/--play, and a step-by-step "Build a talk skill"
recipe (mirrored in the structured JSON `voice`/`talk_skill` keys) shows
how to wrap the voice. Record `talk` as first-party in
`docs/skill-sources.md` so a guildmaster re-sync never clobbers it.

Rubric gate (`teken cli doctor . --strict`), 2357 tests, and all four
lint gates pass. Version bumped 0.7.0 -> 0.8.0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@OriNachum

Copy link
Copy Markdown
Contributor Author

/agentic_review

@qodo-code-review

qodo-code-review Bot commented Jul 8, 2026

Copy link
Copy Markdown

Code Review by Qodo

Grey Divider

New Review Started

This review has been superseded by a new analysis

Grey Divider

Qodo Logo

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Add first-party talk skill and teach harmonics voice workflow in learn

✨ Enhancement 📝 Documentation ⚙️ Configuration changes 🕐 20-40 Minutes

Grey Divider

AI Description

• Add a first-party talk skill that renders say/play to WAV and plays it.
• Teach say/play and the talk-skill recipe in harmonics learn (text + JSON).
• Record talk as non-vendored provenance and bump release to 0.8.0.
Diagram

graph TD
  U["Agent/user"] --> S["Talk skill"] --> SH["talk.sh"] --> CLI["harmonics say/play"] --> WAV[("WAV file")] --> P{{"System player"}}
  L["harmonics learn"] --> U
  L --> CLI
  L --> S
  S --> D["Skill sources doc"]

  subgraph Legend
    direction LR
    _cmp["Component"] ~~~ _file[("File")] ~~~ _ext{{"External"}}
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Add a first-class `harmonics talk` CLI verb (Python)
  • ➕ Single official entrypoint (no shell script, fewer portability concerns).
  • ➕ Can share internal rendering/playback code and error handling with existing verbs.
  • ➖ Increases CLI surface area and ongoing maintenance burden.
  • ➖ May re-introduce pressure to depend on live-audio backends; harder to keep the “no-backend” path obvious.
2. Rely solely on `--play` + require/install `harmonics-cli[audio]`
  • ➕ Simpler: no extra skill wrapper needed.
  • ➕ Uses one consistent playback mechanism across platforms.
  • ➖ Breaks the dev-checkout invariant where the optional audio extra is intentionally absent.
  • ➖ Audio backend/device issues become a harder dependency for “talk to me” workflows.
3. Implement the wrapper as a small Python script instead of bash
  • ➕ More portable across shells; easier unit testing.
  • ➕ Can provide richer argument parsing and help output.
  • ➖ Still introduces another executable entrypoint; packaging/distribution decisions needed (console script vs checked-in script).
  • ➖ Bash version is already minimal and purely delegates to the CLI.

Recommendation: Keep the current approach (WAV render + OS player) because it preserves the project’s core invariant: offline, dependency-free rendering works everywhere, including dev checkouts without harmonics-cli[audio]. If portability issues surface later (e.g., mktemp variants or player availability), consider migrating talk.sh to a small Python wrapper or promoting it to a harmonics talk verb.

Files changed (6) +264 / -1

Enhancement (2) +155 / -0
talk.shAdd WAV-render + system-player wrapper for 'say'/'play' +98/-0

Add WAV-render + system-player wrapper for 'say'/'play'

• Introduces a bash script that (1) prints the dry-run note sequence, (2) renders audio via '--wav', and (3) plays the WAV through the first available system player. Supports '--axes' to switch to 'play' and '--keep FILE' to persist the WAV.

.claude/skills/talk/scripts/talk.sh

learn.pyTeach 'say'/'play' voice workflows in 'harmonics learn' (text + JSON) +57/-0

Teach 'say'/'play' voice workflows in 'harmonics learn' (text + JSON)

• Extends the learn prompt’s command map to include 'play', 'say', and 'demo', and adds a 'Talking out loud' section plus a step-by-step recipe for building a talk skill wrapper. Expands the '--json' payload with 'voice' and 'talk_skill' sections for machine consumers.

harmonics/cli/_commands/learn.py

Documentation (3) +108 / -0
SKILL.mdDocument the 'talk' skill and voice axes usage +86/-0

Document the 'talk' skill and voice axes usage

• Adds a first-party skill description explaining how to use 'harmonics say'/'play' and why WAV+system-player playback is the reliable path without the optional audio extra. Includes usage examples, axis glossary, and direct CLI equivalents.

.claude/skills/talk/SKILL.md

CHANGELOG.mdAdd 0.8.0 release notes for talk skill and learn updates +11/-0

Add 0.8.0 release notes for talk skill and learn updates

• Documents the new talk skill, the expanded 'harmonics learn' voice guidance (including JSON keys), and the provenance ledger change as part of the 0.8.0 release entry.

CHANGELOG.md

skill-sources.mdRecord 'talk' as first-party (non-vendored) skill provenance +11/-0

Record 'talk' as first-party (non-vendored) skill provenance

• Adds a dedicated section and table marking 'talk' as originating in harmonics-cli to prevent re-sync workflows from overwriting/removing it.

docs/skill-sources.md

Other (1) +1 / -1
pyproject.tomlBump project version to 0.8.0 +1/-1

Bump project version to 0.8.0

• Updates the package version from 0.7.0 to 0.8.0 to reflect the newly added talk capability and learn-surface changes.

pyproject.toml

@qodo-code-review

qodo-code-review Bot commented Jul 8, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (1) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 16 rules
✅ Skills: version-bump

Grey Divider


Action required

1. talk.sh plays audio by default 📘 Rule violation ≡ Correctness
Description
The new .claude/skills/talk/scripts/talk.sh always renders a WAV and attempts playback when
invoked, instead of defaulting to a dry-run that only emits the note sequence. This introduces side
effects (file creation + audio playback) without an explicit opt-in flag, violating the
dry-run-by-default requirement.
Code

.claude/skills/talk/scripts/talk.sh[R54-85]

+# 1) Show what is being "said" (dry-run note sequence -> stderr).
+"${HARMONICS[@]}" "$MODE" "${ARGS[@]}" >&2 || {
+    echo "talk: harmonics $MODE failed (see above)" >&2
+    exit 1
+}
+
+# 2) Render to a WAV (no live-audio backend needed).
+if [[ -n "$KEEP" ]]; then
+    WAV="$KEEP"
+    CLEANUP=""
+else
+    WAV="$(mktemp --suffix=.wav 2>/dev/null || mktemp -t talk.XXXXXX.wav)"
+    CLEANUP="$WAV"
+fi
+trap '[[ -n "$CLEANUP" ]] && rm -f "$CLEANUP"' EXIT
+
+"${HARMONICS[@]}" "$MODE" "${ARGS[@]}" --wav "$WAV" >/dev/null
+
+# 3) Play it through the first available system player.
+play_wav() {
+    local f="$1"
+    if   command -v pw-play >/dev/null 2>&1; then pw-play "$f"
+    elif command -v paplay  >/dev/null 2>&1; then paplay "$f"
+    elif command -v aplay   >/dev/null 2>&1; then aplay -q "$f"
+    elif command -v ffplay  >/dev/null 2>&1; then ffplay -nodisp -autoexit -loglevel quiet "$f"
+    elif command -v afplay  >/dev/null 2>&1; then afplay "$f"
+    else return 127; fi
+}
+
+if play_wav "$WAV"; then
+    echo "talk: played ${WAV}" >&2
+else
Evidence
PR Compliance ID 1794481 requires audio-producing commands to default to dry-run and only perform
side effects (audio playback or writing audio files) when an explicit flag is provided. In
talk.sh, after printing the dry-run note sequence, the script unconditionally creates a WAV,
renders to it with --wav, and then plays it through a system audio player without any opt-in flag.

Rule 1794481: Audio-producing commands must default to dry-run and require explicit flags for side effects
.claude/skills/talk/scripts/talk.sh[54-85]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`.claude/skills/talk/scripts/talk.sh` currently renders a WAV and plays audio by default. Compliance requires audio-producing commands to default to dry-run (note-sequence output only) and require explicit flags for side effects (writing files and/or playing audio).

## Issue Context
The script already prints the dry-run note sequence, but then unconditionally:
- creates a temp WAV file
- renders audio into it via `--wav`
- plays it via a system player

This must be gated behind explicit flags (e.g., `--wav FILE` and/or `--play`).

## Fix Focus Areas
- .claude/skills/talk/scripts/talk.sh[54-85]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Passthrough --play skips WAV ✓ Resolved 🐞 Bug ≡ Correctness
Description
talk.sh forwards all args to harmonics and then appends --wav, but if the caller also passes
--play, harmonics say/play prioritizes live playback and does not write the WAV file. The script
then calls play_wav "$WAV" on a missing/empty file and fails.
Code

.claude/skills/talk/scripts/talk.sh[R54-71]

+# 1) Show what is being "said" (dry-run note sequence -> stderr).
+"${HARMONICS[@]}" "$MODE" "${ARGS[@]}" >&2 || {
+    echo "talk: harmonics $MODE failed (see above)" >&2
+    exit 1
+}
+
+# 2) Render to a WAV (no live-audio backend needed).
+if [[ -n "$KEEP" ]]; then
+    WAV="$KEEP"
+    CLEANUP=""
+else
+    WAV="$(mktemp --suffix=.wav 2>/dev/null || mktemp -t talk.XXXXXX.wav)"
+    CLEANUP="$WAV"
+fi
+trap '[[ -n "$CLEANUP" ]] && rm -f "$CLEANUP"' EXIT
+
+"${HARMONICS[@]}" "$MODE" "${ARGS[@]}" --wav "$WAV" >/dev/null
+
Evidence
talk.sh unconditionally appends --wav "$WAV" but still forwards user flags; harmonics’ CLI code
makes --play take precedence over --wav, so WAV writing is skipped when --play is present,
breaking talk.sh’s assumption that $WAV exists.

.claude/skills/talk/scripts/talk.sh[54-71]
harmonics/cli/_commands/say.py[324-340]
harmonics/cli/_commands/play.py[186-196]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`.claude/skills/talk/scripts/talk.sh` is intended to be the reliable "render WAV then play via system player" path, but it currently passes through *all* harmonics flags. If the user includes `--play`, harmonics will play live (and skip writing the WAV), so the script’s subsequent system-player step tries to play a file that was never produced.

### Issue Context
- `harmonics say` and `harmonics play` both implement an output-priority contract where `--play` takes precedence over `--wav`.
- `talk.sh` always expects a WAV to exist after the render step.

### Fix Focus Areas
- .claude/skills/talk/scripts/talk.sh[27-71]

### Suggested change
- While parsing args, detect and reject (or strip) harmonics output-mode flags that conflict with the wrapper’s pipeline, at least: `--play`, `--wav`, and (optionally) `--out`/`--midi`.
- Error message should direct users to use `--keep FILE` instead of `--wav FILE`, and explain that `--play` is not supported because this wrapper intentionally uses OS players.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

3. Missing --intent validation ✓ Resolved 🐞 Bug ☼ Reliability
Description
In --axes mode, talk.sh does not validate that required harmonics play flags (notably
--intent) are present, so talk.sh --axes fails only after invoking harmonics with a less
actionable error. This makes the primary explicit-axes path harder to use correctly.
Code

.claude/skills/talk/scripts/talk.sh[R27-41]

+while [[ $# -gt 0 ]]; do
+    case "$1" in
+        --axes)  MODE="play"; shift ;;
+        --keep)  KEEP="${2:?--keep needs a FILE}"; shift 2 ;;
+        --help|-h)
+            sed -n '2,20p' "$0" | sed 's/^# \{0,1\}//'
+            exit 0 ;;
+        *)       ARGS+=("$1"); shift ;;
+    esac
+done
+
+if [[ "$MODE" == "say" && ${#ARGS[@]} -eq 0 ]]; then
+    echo "talk: nothing to say — give a sentence, or use --axes for explicit axes" >&2
+    exit 1
+fi
Evidence
talk.sh only checks for empty args in say mode, but harmonics play itself requires --intent,
so the wrapper can fail with an avoidable downstream argparse error.

.claude/skills/talk/scripts/talk.sh[27-41]
harmonics/cli/_commands/play.py[213-223]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
When `--axes` is used, talk.sh switches to `harmonics play` but only validates the presence of a sentence for `say` mode. `harmonics play` requires `--intent`; without it, the script fails later with a generic downstream error.

### Issue Context
`harmonics play` defines `--intent` as a required argument.

### Fix Focus Areas
- .claude/skills/talk/scripts/talk.sh[27-41]

### Suggested change
- If `MODE=play`, pre-validate `ARGS` contains `--intent` (and ideally that it has a following value).
- If missing, print a targeted error + a one-line example (`talk.sh --axes --intent success ...`) and exit with code 1.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

4. Help text output mismatch ✓ Resolved 🐞 Bug ⚙ Maintainability
Description
talk.sh’s header comment states “the WAV path is echoed at the end”, but on the successful playback
path it prints only to stderr and emits no stdout value, which can confuse callers/automation that
rely on stdout. This mismatch makes the script’s contract unclear.
Code

.claude/skills/talk/scripts/talk.sh[R16-20]

+# Our own flags: --axes (use `play` instead of `say`), --keep FILE (save WAV).
+# Everything else is passed straight through to the harmonics verb, so you can
+# use --as, --seq, --articulation, --intent, --confidence, etc. The dry-run note
+# sequence is printed to stderr; the WAV path is echoed at the end.
+
Evidence
The header explicitly claims the WAV path is echoed; however, the only stdout echo of $WAV is in
the no-player fallback branch, not generally at the end.

.claude/skills/talk/scripts/talk.sh[16-20]
.claude/skills/talk/scripts/talk.sh[83-93]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The script’s comments claim the WAV path is echoed at the end, but the current success path does not output the path to stdout.

### Issue Context
The script *does* echo the path only in the "no player found" branch.

### Fix Focus Areas
- .claude/skills/talk/scripts/talk.sh[16-20]

### Suggested change
Choose one:
1) Update the comment to match reality (path is only printed when no player is found), or
2) Echo a meaningful value on success (e.g., echo the kept WAV path when `--keep` is used).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment thread .claude/skills/talk/scripts/talk.sh
Comment thread .claude/skills/talk/scripts/talk.sh
Comment thread .claude/skills/talk/scripts/talk.sh
Comment thread .claude/skills/talk/scripts/talk.sh Outdated
talk.sh:
- reject conflicting output flags (--play/--wav/--out/--midi) that would
  break the wrapper's render-then-play pipeline; direct users to --keep
  (Qodo #3545273922: --play took precedence over --wav → play on missing file)
- add --dry-run/--notes-only (print the note sequence, skip render+play) so
  the dry-run path is explicit; play-on-invoke stays the deliberate "talk out
  loud" default (Qodo #3545273916)
- validate --intent is present in --axes mode (harmonics play requires it),
  with a friendly example (Qodo #3545273930)
- make the stdout contract match the header: echo the WAV path to stdout only
  when it persists (--keep or no-player fallback) (Qodo #3545273936)

learn.py:
- merge the implicitly concatenated string literals into one (SonarCloud
  python:S5799 at learn.py:119)

uv.lock: sync editable harmonics-cli 0.7.0 -> 0.8.0 to match pyproject.

Gates: 2357 tests, black/isort/flake8/bandit, shellcheck, teken cli doctor
. --strict all pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@OriNachum OriNachum deployed to testpypi July 8, 2026 16:11 — with GitHub Actions Active
@sonarqubecloud

sonarqubecloud Bot commented Jul 8, 2026

Copy link
Copy Markdown

@OriNachum OriNachum merged commit 55dab8e into main Jul 8, 2026
8 checks passed
@OriNachum OriNachum deleted the feat/talk-skill branch July 8, 2026 16:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant