Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions .claude/skills/talk/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
---
name: talk
type: command
description: Make the agent actually TALK out loud in its own non-speech harmonic voice, using the `harmonics` CLI (say/play). Renders a sentence or explicit axes to audio and plays it through a system player — so you hear it even without the optional [audio] extra that `--play` needs. Use when the user says "talk", "talk to me", "say something", "speak", "say it out loud", "tell me something", or wants to hear the agent's voice rather than read text. First-party to harmonics-cli (not vendored); slated to be surfaced in `harmonics learn`.
---

# Talk

This is the agent's own **voice** — not TTS, not words. harmonics turns meaning
(*intent, confidence, urgency, state, identity*) into short, pleasant sonic
gestures. "Talking" here means rendering a sentence or a set of axes to notes
and **sounding them out loud**.

Two pathways, both from the `harmonics` CLI:

- **`say "<sentence>"`** — infer the voice from a natural sentence. Emphasize a
word with `*asterisks*` or ALL-CAPS. This is the default for "talk to me".
- **`play --intent … --confidence … --urgency … --state …`** — drive the voice
from explicit axes when you know exactly what you want to express.

Both are **dry-run by default** (they print the note sequence). To actually
*hear* it you need audio out. The built-in `--play` flag needs the optional
`harmonics-cli[audio]` extra (`sounddevice`); in a dev checkout that extra is
deliberately **not** installed (it would break the no-backend test path), so
`--play` fails with a friendly hint. The reliable path everywhere is to render a
WAV (`--wav FILE`, pure-Python, no device or extra needed) and pipe it to a
system player. `scripts/talk.sh` does exactly that.

## Usage

```bash
# Say a sentence out loud (renders + plays; also prints the note sequence)
bash .claude/skills/talk/scripts/talk.sh "done, tests all green"

# Emphasis shapes the voice
bash .claude/skills/talk/scripts/talk.sh "that is *wonderful*, Ori"

# Shape the voice: identity, articulation, deterministic variation
bash .claude/skills/talk/scripts/talk.sh "handing off now" --as harmonics-cli --articulation smooth --seq 7

# Explicit axes instead of a sentence
bash .claude/skills/talk/scripts/talk.sh --axes --intent success --confidence high --urgency calm --state done

# Keep the rendered WAV instead of a throwaway temp file
bash .claude/skills/talk/scripts/talk.sh "welcome back" --keep /tmp/hello.wav
```

The script prints the dry-run note sequence to stderr (so you can see what was
"said"), renders a WAV, and plays it through the first available of `pw-play`,
`paplay`, `aplay`, `ffplay`, or `afplay`. If none exists, it prints the WAV path
so you can play it yourself.

## The axes (the design spine)

| Axis | Values | What it shades |
|------|--------|----------------|
| **intent** | ack, question, success, failure, thinking, handoff | timbre / motif family |
| **confidence** | low → high | consonance, resolved vs. suspended cadence |
| **urgency** | calm → urgent | tempo, attack sharpness, repetition |
| **state** | idle, working, blocked, done | sustained pad vs. discrete events |
| **identity** (`--as`) | which agent | signature motif / key / instrument |

`say` infers these from the sentence; `play` takes them as flags. Run
`harmonics play --help` / `harmonics say --help` for the exact accepted values.

## Direct CLI (what the script wraps)

```bash
harmonics say "done, tests all green" # dry-run: print notes
harmonics say "done, tests all green" --wav out.wav # render audio (no extra)
harmonics say "done, tests all green" --play # live (needs [audio] extra)
harmonics play --intent success --confidence high # explicit axes
```

Use `uv run harmonics …` from inside the checkout if `harmonics` is not on PATH.

## Notes

- **No sound stack required to render.** `--wav` uses the stdlib `wave` module;
it does not pull in `numpy`/`sounddevice`. Only live `--play` needs the extra.
- **Keep it pleasant.** This voice plays repeatedly next to a human — prefer
`calm`/`low`-urgency renders for routine chatter; save sharp/urgent for real
alerts.
- **Roadmap:** this capability is slated to be taught directly by
`harmonics learn` (its command map already grows toward `say`/`play`). Until
then, this skill is the front door for "talk to me".
134 changes: 134 additions & 0 deletions .claude/skills/talk/scripts/talk.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
#!/usr/bin/env bash
# talk.sh — make the agent actually TALK in its own non-speech harmonic voice.
#
# Renders a sentence (via `harmonics say`) or explicit axes (via `harmonics
# play`, with --axes) to a WAV and plays it through a system audio player. This
# is the reliable "hear it" path: it needs no live-audio backend, because it
# renders to a file (`--wav`, pure-Python) and hands that file to the OS player,
# rather than relying on `harmonics … --play` (which needs the optional
# harmonics-cli[audio] extra, absent in a dev checkout).
#
# Usage:
# talk.sh "<sentence>" [harmonics say flags...] # default: `say`
# talk.sh --axes [harmonics play flags...] # explicit axes: `play`
# talk.sh "..." --keep FILE # keep the WAV
# talk.sh "..." --dry-run # notes only, no render/play
#
# Our own flags: --axes (use `play` instead of `say`), --keep FILE (save WAV),
# --dry-run/--notes-only (print the note sequence only; skip render and
# playback entirely).
# --play, --wav, --out, and --midi are NOT accepted here — this wrapper owns
# audio output end-to-end; use --keep FILE to save the WAV instead.
# Everything else is passed straight through to the harmonics verb, so you can
# use --as, --seq, --articulation, --intent, --confidence, --urgency, --state,
# etc. The dry-run note sequence is always printed to stderr; on success the
# WAV path is echoed to stdout ONLY when the file persists (i.e. --keep, or
# the no-player fallback below).

set -euo pipefail

MODE="say"
KEEP=""
DRY_RUN=""
ARGS=()

while [[ $# -gt 0 ]]; do
case "$1" in
--axes) MODE="play"; shift ;;
--keep) KEEP="${2:?--keep needs a FILE}"; shift 2 ;;
--dry-run|--notes-only)
DRY_RUN=1; shift ;;
--play|--wav|--out|--midi)
echo "talk: '$1' is not supported here — this wrapper owns audio output." >&2
echo "talk: use --keep FILE to save the WAV, or --dry-run for notes only." >&2
exit 1 ;;
--help|-h)
sed -n '2,26p' "$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
Comment thread
OriNachum marked this conversation as resolved.

if [[ "$MODE" == "play" ]]; then
has_intent=""
if [[ ${#ARGS[@]} -gt 0 ]]; then
for a in "${ARGS[@]}"; do
if [[ "$a" == "--intent" ]]; then
has_intent=1
break
fi
done
fi
if [[ -z "$has_intent" ]]; then
echo "talk: --axes needs --intent (harmonics play requires it)." >&2
echo "talk: e.g. talk.sh --axes --intent success --confidence high" >&2
exit 1
fi
fi

# Locate the harmonics command: prefer an installed console script (may carry
# the [audio] extra); fall back to `uv run` inside a checkout.
if command -v harmonics >/dev/null 2>&1; then
HARMONICS=(harmonics)
elif command -v uv >/dev/null 2>&1; then
HARMONICS=(uv run harmonics)
else
echo "talk: need either 'harmonics' or 'uv' on PATH" >&2
exit 2
fi

# 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
}

if [[ -n "$DRY_RUN" ]]; then
exit 0
fi

# 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

Comment thread
OriNachum marked this conversation as resolved.
# 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
[[ -n "$KEEP" ]] && echo "$WAV"
else
Comment thread
OriNachum marked this conversation as resolved.
rc=$?
if [[ $rc -eq 127 ]]; then
# No player found — keep the file and tell the user where it is.
CLEANUP=""
echo "talk: no audio player found (tried pw-play/paplay/aplay/ffplay/afplay)." >&2
echo "talk: WAV written to ${WAV} — play it yourself." >&2
echo "$WAV"
exit 0
fi
echo "talk: playback failed (player exit ${rc}); WAV at ${WAV}" >&2
CLEANUP=""
exit "$rc"
fi
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file.
Format follows [Keep a Changelog](https://keepachangelog.com/). This project
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.8.0] - 2026-07-08

### Added

- harmonics say/play now taught in `harmonics learn` — the command map lists play/say/demo, plus a "Talking out loud" section and a step-by-step "Build a talk skill" recipe (text + structured JSON `voice`/`talk_skill` keys) so any agent can learn to speak.
- New first-party `.claude/skills/talk/` skill: renders a sentence (`say`) or explicit axes (`play`) to a WAV and plays it through a system player (pw-play/paplay/aplay/ffplay/afplay), so the agent's non-speech voice is audible without the optional `[audio]` extra that `--play` needs.

### Changed

- `docs/skill-sources.md` now records `talk` as a first-party (non-vendored) skill so a guildmaster re-sync never clobbers it.

## [0.7.0] - 2026-07-08

### Added
Expand Down
11 changes: 11 additions & 0 deletions docs/skill-sources.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,17 @@ is load-bearing, even where guildmaster's upstream copy omits it.
| `assign-to-workforce` | `../guildmaster/.claude/skills/assign-to-workforce/` | **devague** (re-broadcast via guildmaster) | plan→parallel-implementation leg of the devague workflow chain. Verbatim (already carried `type: command`). | 2026-05-26 (guildmaster 0.6.0) |
| `ask-colleague` | `../colleague/.claude/skills/ask-colleague/` | **colleague** (renamed from convertible; vendored directly — guildmaster re-broadcast pending) | The first-party front door to the `colleague` CLI: hand a scoped task to a *different* engine/mind via `explore` / `review` / `write`, grade a finished work item via `feedback` (the ROI loop), and reap stale/corrupt `colleague/*` branches a crashed run left behind via `clean`. Every verb takes `--json` (result JSON on stdout, diagnostics on stderr). `explore`/`review` run isolated in a throwaway `git worktree`; `write` **previews by default** (throwaway worktree, no side effects) and refuses a dirty tree only when applying (`--apply` / `--pr`). Verbatim except one consumer-identifying clause in the Provenance paragraph (`colleague vendors from guildmaster` → `harmonics-cli vendors from guildmaster`); already carried `type: command`. Optional runtime dep: **`colleague`** on PATH. | 2026-06-12 (colleague 1.7.0, direct) |

## First-party skills (not vendored)

These originate **in harmonics-cli** and are not pulled from any upstream. A
re-sync must never overwrite or remove them; they carry `type: command` like the
vendored ones so the `colleague`/`claude` backend's `core.skill_loader` picks
them up.

| Skill | Origin | Notes |
|-------|--------|-------|
| `talk` | harmonics-cli (first-party) | The front door for "talk to me": renders a sentence (`harmonics say`) or explicit axes (`harmonics play`) to a WAV and plays it through a system player (`pw-play`/`paplay`/`aplay`/…), so the agent's non-speech voice is audible even without the optional `harmonics-cli[audio]` extra that `--play` needs. `scripts/talk.sh` is pure passthrough to the harmonics verbs. Slated to be surfaced in `harmonics learn`. |

## Re-sync procedure

```bash
Expand Down
55 changes: 55 additions & 0 deletions harmonics/cli/_commands/learn.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,36 @@
harmonics explain <path>... Markdown docs for any noun/verb path.
harmonics overview Descriptive snapshot of the agent.
harmonics doctor Check the agent-identity invariants.
harmonics play <axes> Render explicit axes to notes (dry-run).
harmonics say "<sentence>" Render a sentence in the agent's voice (dry-run).
harmonics demo Tour the whole voice (play / gallery / stream).
harmonics cli overview Describe the CLI surface itself.

Talking out loud
----------------
`say` and `play` are the voice. Both are DRY-RUN by default — they print a note
sequence (`start dur pitch velocity timbre`), assert-able offline with no audio
device. To actually sound it:
harmonics say "done, tests all green" --wav out.wav # render a file (no extra)
harmonics say "done, tests all green" --play # live (needs [audio] extra)
Emphasize a word with *asterisks* or ALL-CAPS. `play` takes the axes as flags
(--intent/--confidence/--urgency/--state/--as); `say` infers them from the
sentence.

Build a "talk" skill (so any agent can speak)
---------------------------------------------
Wrap the voice in a skill under `.claude/skills/talk/` so "talk to me" just works:
1. SKILL.md — frontmatter `type: command` (load-bearing for the skill loader)
plus a description with trigger words ("talk", "say something"). Document
the two pathways (`say` a sentence, `play` explicit axes) and the axes.
2. scripts/talk.sh — the "hear it" path that needs no audio backend:
a. print the dry-run notes (harmonics say "$s"),
b. render a WAV (harmonics say "$s" --wav "$f") # stdlib, no extra,
c. play "$f" through the first of pw-play/paplay/aplay/ffplay/afplay.
Prefer WAV+system-player over `--play`: `--play` needs the optional
harmonics-cli[audio] extra, which a dev checkout deliberately omits. Keep the
render offline and the palette pleasant — it plays next to a human.

Machine-readable output
-----------------------
Every command supports --json. Errors in JSON mode emit
Expand Down Expand Up @@ -66,8 +94,35 @@ def _as_json_payload() -> dict[str, object]:
{"path": ["explain"], "summary": "Markdown docs by path."},
{"path": ["overview"], "summary": "Descriptive snapshot of the agent."},
{"path": ["doctor"], "summary": "Check the agent-identity invariants."},
{"path": ["play"], "summary": "Render explicit axes to notes (dry-run)."},
{"path": ["say"], "summary": "Render a sentence in the agent's voice (dry-run)."},
{"path": ["demo"], "summary": "Tour the whole voice (play / gallery / stream)."},
{"path": ["cli", "overview"], "summary": "Describe the CLI surface."},
],
"voice": {
"verbs": ["say", "play"],
"dry_run_default": True,
"axes": ["intent", "confidence", "urgency", "state", "identity"],
"emphasis": "*asterisks* or ALL-CAPS raise a word",
"render_offline": "harmonics say '<sentence>' --wav out.wav",
"play_live": "harmonics say '<sentence>' --play # needs harmonics-cli[audio]",
},
"talk_skill": {
"purpose": "Wrap the voice so any agent can talk out loud.",
"location": ".claude/skills/talk/",
"files": {
"SKILL.md": (
"frontmatter type: command + trigger words + the two "
"pathways (say/play) and the axes"
),
"scripts/talk.sh": "print notes, render --wav, then play via a system player",
},
"why_wav_not_play": (
"--play needs the optional harmonics-cli[audio] extra; a dev "
"checkout omits it, so render --wav (stdlib, offline) and pipe "
"the file to a system player instead."
),
},
"exit_codes": {
"0": "success",
"1": "user-input error",
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "harmonics-cli"
version = "0.7.0"
version = "0.8.0"
description = "An agent + CLI giving agents and robots non-TTS (non-speech) audio: express meaning through pleasant sonic signatures — chimes, flutes, pulses, and tonal motifs mapped to intent, confidence, urgency, state, and identity. Turns text/sentences into notes and audio (text-to-notes / text-to-audio)."
readme = "README.md"
license = "Apache-2.0"
Expand Down
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading