Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
af882f9
Load older saved chat history when scrolling upward
mostlydev Sep 16, 2026
c8d4e4a
Render chat inline so terminals keep scrollback and selection
mostlydev Sep 16, 2026
633785b
Use the shared editor for inline chat input
mostlydev Sep 16, 2026
da3137f
Preserve inline draft position and restore terminal modes
mostlydev Sep 16, 2026
4aa9ec2
Restore live chat controls with native terminal scrollback
mostlydev Sep 16, 2026
0aef4c8
Erase the inline panel with the geometry it was drawn at
mostlydev Sep 16, 2026
c30cd93
Place the room bar directly above the chat prompt
mostlydev Sep 16, 2026
0aac55d
Keep chat alive during SQLite contention and shorten cleanup locks
mostlydev Sep 17, 2026
8709183
Update chat delivery status without adding transcript lines
mostlydev Sep 17, 2026
b986b56
Deliver native room events with exact acknowledgement
mostlydev Sep 17, 2026
77277f5
Record native event delivery verification
mostlydev Sep 17, 2026
a5062c0
Guard Grok stops and identify Grok sessions by env marker
mostlydev Sep 17, 2026
4e8392d
Require an explicit end_turn reason for Grok stops
mostlydev Sep 17, 2026
668f750
Deliver room events through active Grok hooks
mostlydev Sep 17, 2026
cd97912
Record live Grok hook delivery and fix README list punctuation
mostlydev Sep 17, 2026
107acb1
Keep the harness display name while a guardian holds the turn
mostlydev Sep 17, 2026
54f8bf7
Keep CLI tests hermetic when the suite runs inside Grok
mostlydev Sep 17, 2026
d947d14
Fall back to plain chat under TERM=dumb and pin TERM in tests
mostlydev Sep 17, 2026
dc1e63a
Clarify manual standby delivery and plan guarded Herdr wake
mostlydev Sep 17, 2026
23448ac
Attach delivery receipts to transcript messages and compact the prompt
mostlydev Sep 17, 2026
637f3e3
Deliver operator room messages to every agent and compact envelopes
mostlydev Sep 17, 2026
d539547
Steer Claude on operator messages and honor recipient scopes
mostlydev Sep 17, 2026
329a524
Show delivery as marks beside each recipient in the message header
mostlydev Sep 17, 2026
7fa3c20
Verify delivery marks across wrapped message headers
mostlydev Sep 17, 2026
d6eceae
Reconcile release notes and delivery docs with shipped behavior
mostlydev Sep 17, 2026
150b41e
Prepare 0.19.0 release
mostlydev Sep 17, 2026
6afbcfe
Match the remaining wake notes to the shipped envelope and marks
mostlydev Sep 17, 2026
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
32 changes: 32 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,37 @@ changes will be called out under **Breaking changes**.

## Unreleased

## [0.19.0] — 2026-09-17

Full notes: [`docs/releases/0.19.0.md`](docs/releases/0.19.0.md).

This release changes how messages reach agents. An operator's chat now reaches every agent in the room, the message content itself is delivered into the agent's context (no `tt wait` needed), and delivery is shown per recipient in the chat. `/invite` for agents that have not joined, and a guarded herdr wake for idle Grok sessions, are not included.

### Added

- **Native event delivery.** Claude Code and Codex wakes carry the room events themselves, so an agent answers without running `tt wait`. `tt ack <token> --json` durably acknowledges exactly those events without fetching them again or claiming the turn. A batch left unacknowledged for five minutes is retried when new directed work arrives; quiet rooms never retry on a timer. Oversized payloads and cmux keep the body-free pull notification.
- **Operator room messages reach every agent.** A room message from a `human:*` sender goes to every agent that is a member when it is sent, standby included, as one event; later joiners do not inherit it. Room messages from agents still wake nobody, and an agent's room interrupt still reaches only the owner.
- **Steering for busy agents.** Operator messages reach a busy Claude Code session at its next tool boundary without cancelling the running tool. Agent-to-agent messages keep default delivery. Codex receives messages after its current turn.
- **Grok Build integration.** `tt install grok` adds a stop guard (`~/.grok/hooks/talking-stick-stop.json`) and active-turn delivery hooks (`~/.grok/hooks/talking-stick-inbox.json`), so a working Grok session receives room events after a tool call or at a normal turn end and acknowledges them like Claude and Codex. `GROK_AGENT=1` now identifies a Grok session and carries `GROK_SESSION_ID` as its session anchor. Idle Grok sessions still need a live `tt wait` or cmux.
- **Chat scopes.** A plain chat line or `@everyone` is one room message; `@name` narrows it; several `@names` share one message that lists them. Named scopes are honoured: agents not named do not see the message in their own wait.
- **Delivery marks in chat.** Each message header lists its recipients with a one-character mark: `…` not delivered yet, `✓` delivered, `!` failed. Marks update in place while the message is on screen and are restored from durable receipts in saved history.
- **Saved history.** Fullscreen scrolling fetches earlier room events; normal-screen chat offers `/older`.

### Changed

- **Compact envelopes.** Native envelopes are attributed plain text instead of JSON: a short header with the room path and ack command, a `#seq sender → you|room` line per event with its content indented, and a closing boundary. A short chat message costs about 190 characters instead of about 600.
- **Chat renders inline by default**, in the terminal's normal screen, so the terminal or multiplexer keeps scrollback, wheel scrolling, selection, and copy. `tt chat --fullscreen` keeps the pinned layout.
- The room path sits in a compact bar directly above the prompt, and the prompt no longer reserves empty suggestion rows.

### Fixed

- A stale wake batch could silently swallow every later message to an agent; new directed work now retries it.
- Resizing the chat redraws the visible screen instead of guessing the old panel position, so no stale copy of the draft is left behind, and never clears native scrollback.
- `tt chat` falls back to plain line mode under `TERM=dumb`, where Node's readline cannot edit a draft.
- The per-turn guardian no longer renames an agent to its full id while it holds the stick, which broke short mentions such as `@claude`.
- Chat stays open and keeps the draft during transient SQLite contention.
- Envelope handoffs render plain string artifacts as paths instead of `undefined`, and chat consoles no longer accumulate native receipts they never consume.

## [0.18.3] — 2026-09-16

Full notes: [`docs/releases/0.18.3.md`](docs/releases/0.18.3.md).
Expand Down Expand Up @@ -578,6 +609,7 @@ Initial alpha. Core room protocol, SQLite-backed persistence, multi-process
contention coverage, MCP smoke coverage, human guardian flow, harness
installers, and the portable `talking-stick` skill.

[0.19.0]: https://github.com/mostlydev/talking-stick/releases/tag/v0.19.0
[0.18.3]: https://github.com/mostlydev/talking-stick/releases/tag/v0.18.3
[0.18.2]: https://github.com/mostlydev/talking-stick/releases/tag/v0.18.2
[0.18.1]: https://github.com/mostlydev/talking-stick/releases/tag/v0.18.1
Expand Down
51 changes: 35 additions & 16 deletions README.md

Large diffs are not rendered by default.

58 changes: 58 additions & 0 deletions docs/plans/2026-09-17-grok-hook-delivery.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Grok hook delivery

The operator requested the same room integration for a newly joined Grok session.
Grok 1.0.34 exposes active-turn hooks but no verified external inbox for waking
an existing idle session. Its installed hook documentation and live payload
probes establish PostToolUse, PostToolUseFailure and normal Stop feedback.

## Implementation

- Keep the lifecycle identity recorder and shared Claude-compatible Stop guard.
- Install a separate `talking-stick-inbox.json` for the three feedback events.
`--no-guard` disables ownership guarding, not message delivery.
- Only an existing, joined Grok member with matching session and local host can
receive events. Ambiguous membership fails open with a diagnostic.
- Open existing state without creating or migrating it. Quiet tool calls use
reads only; reserve pending deliveries atomically when work exists.
- Deliver the same attributed native-event envelope and exact-event ack token.
Acknowledgement records receipt, never grants writer ownership.
- Bound envelopes to 8 KB, below Grok's documented 10,000-character clipping.
Page complete events after acknowledgement; an oversized single event gets a
bounded pull notice and remains unread. Never truncate a body or ack token.
- Reserve each hook batch for 60 seconds to avoid repeating feedback on every
tool. A later hook retries unaccepted work after that interval. Duplicate
tokens can cover the same IDs; event-ID dedup and idempotent acknowledgements
deliberately handle this. A normal wait also consumes pending receipts.
- Record urgent receipts when the event is written so hook delivery does not
depend on a successful external wake or envelope formatting attempt.
- Ignore shutdown, recursive Stop and subagent feedback. All hook errors fail
open. No new native idle transport or `can_self_wake` claim is introduced.

## Verification

- Full Vitest suite: 622 passed, 1 skipped.
- Typecheck and build passed.
- `tt install grok --link` installed the new inbox file and preserved the
existing lifecycle and guard files.
- Regression coverage: exact bodies, acknowledgement/replay, no ownership,
pending tails, expiry retry, normal wait recovery, size paging and oversize
fallback, malformed input, foreign session/host, ambiguous membership,
repeated hooks, database failure, urgent delivery, installer idempotence,
independent uninstall and `--no-guard` delivery retention.
- Live Grok, after the operator reloaded hooks from the Hooks tab: event 18662
(marker `GROK-HOOK-PROOF-9c42`) reached the Grok model complete through
`global/talking-stick-inbox: post_tool_use[0].hooks[0]`, with no `tt wait` and
no lease. `tt ack 2bdebc3c...` acknowledged 18662 only; a following tool call
did not replay it. The setup message 18659 was also delivered by hook and
acknowledged. The chat console still labelled the first send `unreachable`,
because no idle wake endpoint exists; the acknowledgement is the proof of
delivery, and nothing here is an idle wake.
- Independent review (Claude) at 668f750: full suite 622 passed, 1 skipped, in
20 seconds; typecheck clean. An earlier run hung for ten minutes while a second
suite ran concurrently in the same checkout; rerun alone it passed normally.
Findings raised on the first draft were all addressed: quiet tool calls read
before taking a write lock and open the database without migrating it, the
oversized notice points at plain `tt wait`, ambiguous membership reports a
diagnostic, and retry duplication is documented as deliberate.

No merge or publication performed.
88 changes: 88 additions & 0 deletions docs/plans/2026-09-17-herdr-room-wake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Herdr room wake and invitations

The operator authorized Herdr support after discovering that Grok's active-turn
hooks do not wake an idle session. Wake must only reach agents that previously
joined the exact room. The operator also wants easy room startup across harnesses
without typing individually in each pane, while keeping unrelated panes separate.

## User workflow

1. Opening `tt chat` opens or resumes its room and shows membership/delivery state.
Opening alone does not submit prompts to agents or enroll nearby processes.
2. Ordinary directed messages and `@everyone` address joined members. Existing
native transports remain preferred; Herdr is an idle-wake fallback when it can
establish the intended session safely. Merely sharing a folder is not consent
to receive the room's subsequent messages.
3. A separate explicit human invitation action can discover unjoined agents in
the exact canonical folder/room and ask them to join. Default shape pending
operator preference: `/invite` displays eligible targets; `/invite @everyone`
invites those candidates. Agent messages never implicitly invite processes.
An invitation is not enrollment: the receiving agent must run `tt join`.
4. An empty room may show an invitation hint so startup remains one chat entry
point. No background broadcast on launch, reconnect, resize or history replay.

## Membership and target boundaries

- Require a current member of this room, local host, matching harness kind and
exact harness session ID. An agent that left is not a wake target.
- Discover Herdr session/pane identity from the member's own trusted registration;
do not rely on focus, display-name matches or arbitrary operator message text.
- Never use a directory-prefix match for invitation eligibility. Nested rooms,
independent repos, sibling worktrees, and deliberately unrelated panes must not
be swept into a parent room. Canonicalize paths and resolve exact room scope.
- Session continuation can retain membership only when the harness session still
matches. A new occupant of an old pane must never receive the previous session's
messages. A stale endpoint must fail closed.

## Blocking transport requirements

Before enabling Herdr delivery, verify its server/API supports:

- Send-time expected-session validation, not just list-then-send checks.
- Preserving or rejecting an unsent user draft; never appending to or submitting
the operator's draft. Idle state alone is not evidence of an empty composer.
- Refusing blocked/unknown states without sending input or answering dialogs.
- Unambiguous failure versus possible submission. An uncertain timeout must not
cause a duplicate send through another transport.

These are not proven by `herdr agent list` exposing session IDs. Installed CLI
`agent prompt` currently accepts a pane/name target rather than an expected
session parameter; Claude is inspecting server semantics before code enables it.
No pane prompt has been sent during this investigation.

## Delivery semantics

Durable room write comes first. Submission is not model receipt: retain exact-event
acknowledgement and the existing delivered receipt. When no verified transport or
live listener exists, show that the message waits for the agent to resume, rather
than implying it has entered the harness. Prefer native delivery when available;
never inject into a busy composer merely because an urgent message arrived.

## Acceptance cases

Joined correct-room idle agent wakes and acknowledges once; active hooks continue
to deliver without a second wake; absent/left/wrong-room/new-session targets do
not receive prompts; nested worktrees do not match invitation scope; unsent drafts
and approval dialogs remain unchanged; ambiguous timeout does not double-submit;
blocked or manual-only status is honest; reopening chat does not send invitations.
Live testing uses consenting test sessions and checks both receipt and UI state.

No release until the operator accepts the resulting chat behavior.

## Research outcome and implementation split

Existing Herdr prompt is insufficient: it writes into the composer and schedules
Enter later, validates only harness kind, and exposes no draft model. A last-second
session lookup, a body-free prompt, or skipping focused panes cannot establish the
required guarantees. Do not enable an adapter against that interface.

A guarded server path is being investigated in an isolated Herdr checkout. It
must validate session, room path and process incarnation at send time, preserve
input boundaries, and refuse any uncertain/dirty composer. Input-source tracking
and a verified empty-composer predicate may both be needed; unknown capability
means unavailable, never optimistic fallback. Do not replace the operator's live
Herdr server to test this.

Talking Stick interim fix: manual standby renders “waiting for resume”; the chat
integration test confirms receipt later changes it to “delivered” without moving
history or altering the unsent draft. Focused chat suite: 55 tests passed.
52 changes: 52 additions & 0 deletions docs/plans/2026-09-17-native-event-delivery.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Native event delivery (#81)

## Workflow and invariants

An operator sends a message in chat. The durable room event exists before any wake is dispatched. A live receiver continues to receive through its existing wait. Without a live receiver, supported native endpoints receive an attributed JSON envelope containing the event itself. The recipient can answer from that envelope; no fetch is required.

Transport submission is not recipient acceptance: Claude can refuse inbound content after a successful socket write. Therefore the recipient acknowledges the envelope with `tt ack <token> --json`, a body-free, idempotent operation that never acquires a lease. Queued remains queued until acknowledgement or normal receiver delivery. Acknowledgement covers exact event identities, not a high-water cursor that could skip other messages.

States: durable event -> pending native batch -> transport queued/ambiguous (still unread) -> acknowledged. Definite transport failure permits fallback. Unknown outcomes remain recoverable through normal wait. An old or repeated envelope is deduplicated by event ID; acknowledgement is safe to repeat. A handoff describes work but still requires normal wait/claim and a live guardian before edits.

Messages arriving during an outstanding batch remain pending. Acknowledging that normal batch rearms delivery of its remainder. Interrupt acknowledgement does not clear an unrelated outstanding normal batch. If new directed work arrives after a batch has been unaccepted for five minutes, it rearms the stale batch with the same event IDs; silence never triggers periodic retries. Interrupt envelopes carry the urgent event and steer existing work; no forced cancellation. A live wait may race an interrupt, so event-ID deduplication remains necessary.

Payloads have a byte/event-count ceiling; oversized batches retain the complete durable events and use the fixed pull notification instead of truncating content. cmux retains its body-free fallback. No terminal shell interpolation of room content.

## Verification

Cover exact attribution and hostile delimiter content; normal and urgent delivery; queued/refused/ambiguous/failed outcomes; repeated ack; wrong recipient/session; later wait excluding only accepted events; unrelated unread events; messages arriving in flight; recipient restart; handoffs without ownership; bounded payload fallback. Exercise a real local chat sender and native harness delivery where available. Keep isolated test rooms separate from operator work.

## Scope

This change reuses registered native endpoints. Automatically joining previously unregistered harnesses from presence hooks is a separate installation/lifecycle concern; do not silently enroll unrelated sessions. Existing join/wait/standby endpoint registration remains supported.

## Review and live evidence

Claude independently reviewed the design and confirmed exact-event acceptance, idempotence and session binding. Review reduced envelope repetition and exposed a stale outstanding-batch problem; new directed work now rearms an unaccepted batch after five minutes. Interrupt acknowledgements deliberately leave unrelated normal batches intact, covered by a regression test.

Local validation: 596 tests passed, one skipped; typecheck and build passed. Live Claude events 18463 and 18467 arrived as full attributed bodies without a fetch, and durable receipt records confirm both acknowledgements. The Codex idle test was queued from a disposable real `tt chat` PTY in an isolated room (marker `7f21`); recipient acceptance is still pending until this active turn ends. This is not yet release acceptance.

## Verification record (2026-09-17)

Codex, live: an isolated chat event (18466) reached the Codex model as a queued
native envelope with the complete body, without `tt wait`; `tt ack` returned
`acknowledged` for that exact event; the sender's chat surface moved
`queued -> delivered` in place; a zero-duration self read starting before 18466
returned no events and `replayed: false`.

Claude, live: envelopes for events 18463, 18467, 18475, 18479, 18488 and the
18489/18491 batch arrived with full bodies and were acknowledged by token; each
`tt ack` returned `acknowledged` once and never a lease or a body. Compacted
envelope overhead measured at 576 fixed characters (header plus JSON scaffolding)
against 1,136 total for a 237-character message before compaction.

Claude, read-only on the live database at commit b986b56: every receipt for an
active member is consumed, acknowledged receipts are excluded from self waits
only, and the stale-batch retry heals rows written before the fix. The endpoint
for `claude:49512d87` has been stuck at `awaiting_wait = 1` since
2026-09-15T21:50 with no `batch_started_at`; the retry clause falls back to
`last_attempt_at`, so the next directed message to that member clears the batch
and redelivers instead of coalescing silently. That was the failure that
silently swallowed two operator messages on 2026-09-16.

Suite at b986b56: 596 passed, 1 skipped; typecheck and build clean.
Loading
Loading