Tri-State Relay Service is a local macOS agent relay queue. Agents are implementation partners building a safe, quiet coordination layer for many agent sessions sharing one speaker.
- Do not estimate timelines unless the user explicitly asks for an estimate.
- Use red-green-refactor wherever practical.
- The CLI must never speak directly. App playback is owned by the Swift app; direct builds currently use Swift-launched
/usr/bin/sayfor Siri voice fidelity, App Store-safe builds use AVFoundation, andrelay-processoris legacy compatibility that must not be bundled into the macOS app. - Preserve the single-writer invariant for claiming and speaking messages.
- Focus, Ready, and Live are the playback states. Focus is the safe default, Ready releases one relay then returns to Focus, and Live keeps playing relays automatically by bounded line batches. Mute is a separate safety override.
- Reject or scrub unsafe message input rather than speaking arbitrary terminal output.
- Keep queue, policy, and validation logic testable without macOS UI or audio.
- Require human checkpoints for persistence schema changes, permissions, launch agents, Accessibility/Input Monitoring, or anything that could speak unexpectedly.
Use these local skills when their trigger matches the work:
.github/skills/improvement-loop/SKILL.mdwhen an agent makes a mistake, misses a requirement, overreaches, or after any significant task..github/skills/settings-ui-verification/SKILL.mdwhen changing 设置 UI, first-start setup, accessibility identifiers, 设置 screenshots, or UI automation.
Run the macOS test suite with set -o pipefail; xcodebuild test -project src/macos/TriStateRelayService.xcodeproj -scheme "Tri-State Relay Service" -configuration Debug -derivedDataPath dist/xcode/tests CODE_SIGNING_ALLOWED=NO | tail -40. The repository has no Makefile; do not substitute make test.
Issue #2 added a safe, app-owned 设置 opener and screenshot workflow. Use scripts/capture-settings-ui.sh for 设置 or first-start UI changes after the direct app is rebuilt and restarted. The script forces Focus mode, opens 设置 through relay debug open-settings --panel <panel>, captures screenshots under .artifacts/settings-ui/, and can run Accessibility-backed interaction checks without speaking or changing playback state. Use TSRS_SETTINGS_UI_REQUIRE_INTERACTIONS=1 scripts/capture-settings-ui.sh when a change depends on focus, copy, field, tab, or sidebar interactions. Accessibility, Screen Recording, and Input Monitoring permissions are optional local development permissions, not normal TSRS runtime requirements; ask before changing permission, launch-agent, persistence, or playback behavior.
When an implementation milestone is complete, validated, and not blocked, commit
it without waiting for another prompt. Keep unrelated changes in separate
commits, and call out any remaining uncommitted work or risks in the handoff.
Before committing, inspect git diff --cached --name-only and confirm the staged
file list matches the intended milestone.
For app-visible macOS changes, run scripts/build-macos.sh direct, replace
/Applications/Tri-State Relay Service.app with the rebuilt
dist/macos/Tri-State Relay Service.app, and run
scripts/restart-macos-app.sh. Never leave /Applications on an older version
after a successful direct build. Do not use ad hoc open commands without first
stopping the old process. The restart helper defaults to the installed app and
must report a running PID from /Applications/Tri-State Relay Service.app; use
TSRS_APP_PATH only for an intentional alternate bundle. Say the installed PID
explicitly in the handoff. Before any handoff or docs-only commit, check for
pending app-visible Swift changes; if any exist, either run the
install-and-restart gate and commit them first or explicitly say they remain
uncommitted for review.
After a direct build, compare the bundled CLI with the relay command resolved
from PATH and update the global command whenever it is missing or stale. For
local development, prefer scripts/install-dev-relay-symlink.sh so
/usr/local/bin/relay follows the CLI inside the installed
/Applications/Tri-State Relay Service.app; set TSRS_RELAY_SOURCE explicitly
to that installed bundle path. If root-owned permissions block it, report the
helper's exact sudo command instead of claiming the PATH command was updated.
Verify both the bundled CLI and the relay command resolved from PATH.
Many agents may enqueue relays, but only the app-owned playback path may speak. The CLI submits and inspects relays; it does not invoke /usr/bin/say. In the direct profile, the Swift app may launch /usr/bin/say itself to preserve Siri/say voice behavior. In the App Store-safe profile, playback uses AVFoundation and must not launch external speech commands.
The primary distribution direction is signed direct download with a standard
local relay CLI, developer customization, and future license-key Pro unlocks.
App Store builds are no longer an active product goal. Keep App Store-safe notes
as legacy hardening references only, and do not let App Store constraints block
direct-download customization such as choosing the agent used to summarize many
messages.
Start with these message states:
queued: accepted and waiting.speaking: claimed by the app-owned playback path.heard: playback completed.handled: the user acted on it.skipped: intentionally bypassed.expired: too stale to play.failed: playback or processing failed.
Heard and handled are separate storage states. In user-facing copy, prefer delivered and acknowledged.
Focus mode is safe and quiet. Incoming relays queue but do not play. Ready mode releases exactly one relay. If a relay is queued, the next eligible relay plays. If none are queued, the next incoming eligible relay may play. After one relay is spoken, return to Focus mode. Live mode keeps playing eligible relays automatically, grouped by bounded line batches so a chatty line cannot starve other lines. Mute overrides playback in every state. Muted systems should enqueue and show relays without speaking.
Keep the agent-facing command readable:
relay --line "Brain" --message "The plan is ready."
relay --line "Brain" --type complete --priority normal --message "The plan is ready."
relay list
relay ready
relay live
relay focus
relay mute
relay unmute
relay clear
relay acknowledge
relay clear-deliveredUse long flags only. --line is authoritative when provided. Future auto-detection may fill missing line labels, but must not override explicit line input.
When giving local development commands, do not point users at stale build paths
such as dist/native/relay. Prefer installed relay; if it is not installed,
use the bundled direct app CLI at
dist/macos/Tri-State Relay Service.app/Contents/MacOS/relay.
For fresh first-start app testing, prefer scripts/fresh-start-macos-app.sh
because it clears the development DB with the bundled CLI and restarts the
rebuilt direct app in one step.
Messages are intentionally authored human status updates, not command output. Cap message length, reject empty messages, reject obvious token-looking strings, avoid stdin piping for v0, and do not speak code, secrets, logs, file contents, private data, or long explanations.
Queue changes need tests for accepted message shape and defaults, rejected unsafe input, focus/ready/live/mute transitions, claiming eligible messages, inactive-line combiner behavior, and durable persistence across store instances.
Grow toward these boundaries only as features need them:
src/macos/RelayCore.swift: message validation, queue state transitions, SQLite storage, and CLI command dispatch.src/macos/RelayCli/main.swift: CLI entrypoint only.src/macos/TriStateRelayService.swift: app-owned playback, menu-bar UI, queue controls, and platform adapters.docs/: decisions, progress, and agent misses.src/macos/TriStateRelayServiceTests/: XCTest coverage for queue behavior, CLI behavior, storage, and app playback policy.
Keep queue, policy, and validation logic in Swift types that can be tested without launching the app UI or audio path. UI and platform adapters consume queue APIs and the app-owned claim/playback path rather than owning queue rules.
Use this order unless there is a strong reason to change it. After each item is completed and committed, update this section and any directly related docs so the next agent sees the current state instead of stale guidance:
- Keep trimming docs and repository cruft from the user-facing path. Remove stale LLM evaluation references and any docs that no longer describe the active direct-download product direction.
- Maintain
docs/user-guide.mdas the primary user-facing guide. Update it whenever setup, shortcuts, CLI installation, voice behavior, line behavior, or queue commands change.
Completed milestone: the command-palette shortcut selector records a pressed key combination instead of using preset-only choices, so first-start setup supports a custom shortcut.
Completed milestone: voice selection works during first-start setup and normal
设置. Direct builds list installed /usr/bin/say voices, favor natural
voices when available, and only speak a sample through explicit Preview.
Completed milestone: the CLI install panel is the first 设置 panel shown
during first-start setup, encourages /usr/local/bin/relay, preserves safe
overwrite behavior, and offers a copy button for the full bundled app-contents
CLI path.
Completed milestone: direct-download builds are arm64-only by default. Use
TSRS_MACOS_ARCHS="arm64 x86_64" only for a deliberate future universal build
path.
Completed milestone: first-start configuration opens 设置 on first launch,
stores completion in the SQLite settings table, keeps Focus mode quiet, and
reuses 设置 to guide CLI installation, shortcut selection, and voice
selection without a separate wizard. Development verification can use
relay first-start reset to retest first-start visibility without wiping queue
data, or relay first-start dev-reset-database --confirm for an explicit
destructive fresh database reset. Normal app launch paths must never wipe user
queue data.
Completed milestone: Setup includes an explicit opt-in Open at Login checkbox
backed by SMAppService.mainApp; TSRS still starts in Focus mode so login launch
does not speak automatically.
Completed milestone: TOML is now the source of truth for advanced voice command, inactive-line combiner command, and cleanup retention settings. TSRS creates config.toml once from existing SQLite settings, preserves user-edited TOML, and fails quiet without claiming speech when TOML is invalid.
Completed milestone: scripts/test-112-upgrade.sh is the release-upgrade gate from v1.1.2. It builds the tagged 1.1.2 CLI in a temporary worktree, seeds a real 1.1.2 database, and runs the current bundled CLI against that database to verify TOML creation, existing-TOML preservation, invalid-config fail-quiet behavior, and preserved runtime state.
Completed milestone: 设置 has a TOML-oriented Voice panel with Open/Reveal/Validate/Reload controls and explicit Save buttons. Use TSRS_SETTINGS_UI_ROUNDTRIP=1 TSRS_SETTINGS_UI_REQUIRE_INTERACTIONS=1 scripts/capture-settings-ui.sh for release-quality 设置 persistence checks; it modifies values through the real 设置 controller, verifies via the bundled CLI, restores the original TOML, and captures screenshots.
Completed milestone: direct builds include an optional Kokoro helper. The helper uses a user-installed Kokoro venv, keeps a same-user Unix-socket server warm, writes WAV bytes to TSRS's relay.audio path, and does not bundle Kokoro packages, model weights, voices, spaCy models, or caches. If active config is no longer provider = "kokoro", the app asks the helper to stop its local server.
Every implementation task should end with:
- The closest available validation passing.
- Behavior verified automatically or manually.
- Documentation updated when commands, state, persistence, or agent workflow changes.
- For app-visible direct-profile changes,
scripts/build-macos.sh directshould pass and the bundle should be inspected. The app must not bundlerelay-processor. - Distribution, licensing, and customization changes must preserve
docs/distribution.md. Treatdocs/app-store-profile.mdas legacy hardening reference unless the App Store direction is explicitly reopened. - Commit-ready summary with changed files and remaining risks.
When an agent miss happens:
- Capture what happened and what reality said.
- Diagnose the missing primitive: observability, instructions, tooling, guardrails, or verification.
- Choose the smallest fix.
- Encode it as a version-controlled artifact.
- Promote repeated misses into gates.
- Swift code should keep queue state and command results explicit and testable.
- Small functions over large managers.
- Shell scripts use long flags where practical and must not speak directly.
- Keep user-facing Markdown prose unwrapped: use one line per paragraph while leaving headings, lists, tables, and code blocks readable.