Skip to content

Add Claude Code channel for push-based task events - #28

Merged
bborn merged 5 commits into
mainfrom
feat/channels-integration
Jun 9, 2026
Merged

Add Claude Code channel for push-based task events#28
bborn merged 5 commits into
mainfrom
feat/channels-integration

Conversation

@bborn

@bborn bborn commented Apr 11, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds a Claude Code channel MCP server (taskyou-channel.ts) that pushes task completion/blocked events directly into the GM session — replacing the fragile background-agent-running-tail -f approach
  • Each GM gets its own channel, baked with that GM's {{SERVER_HOST}} / {{SERVER_HOME}} at setup time (same pattern as ty-remote)
  • Channel also exposes ty_command and ssh_command tools so the GM can run server commands through the channel instead of shelling out to ./bin/ty-remote
  • Updates GM alias to include --dangerously-load-development-channels server:taskyou (will simplify when channels leave research preview)
  • Updates CLAUDE.md instructions: "wait for channel events" replaces "launch background monitoring agent"

What changed

File Change
templates/channel/taskyou-channel.ts.tmpl New — the channel MCP server
templates/channel/package.json.tmpl New — MCP SDK dependency
templates/mcp.json.tmpl New — registers channel with Claude Code
setup.sh Renders channel files, installs deps, updates alias
templates/CLAUDE.md.tmpl Replaces monitoring section with channel-based approach
templates/settings.json.tmpl Auto-allows channel tools
.claude/commands/gm-babysit.md Removes background agent re-launch step

Prerequisites

  • bun installed locally (for the channel MCP server)
  • Claude Code v2.1.80+ (channels are in research preview)
  • claude.ai login (channels don't support API key auth yet)

Test plan

  • Run ./setup.sh local /tmp/test-project with a test config.env — verify channel files are rendered
  • Start a GM with the new alias — verify Claude Code spawns the channel (check /mcp status)
  • Execute a task and verify the <channel source="taskyou"> event arrives when the task completes
  • Test ty_command and ssh_command tools from the GM session
  • Test /gm-babysit still works as a manual fallback

🤖 Generated with Claude Code

bborn added a commit that referenced this pull request Jun 9, 2026
notifications.jsonl survives as the push substrate, but the channels
refactor changes three things: copy taskyou-channel.ts (bun/TS) instead
of linear-poll.mjs; reuse assigned_gm (#32 + workflow#561) for per-GM
routing (answers the shared-bot vs per-user question); adopt the
IS_LOCAL/SSH runRemote pattern + OS-aware hooks dir (#31). Adds a
dependencies section and merge order (#28 -> #31/#32 -> Slack).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
bborn and others added 5 commits June 9, 2026 07:19
Replace the pull-based monitoring approach (background agents running
ssh tail -f, 20-minute timeouts, manual /gm-babysit polling) with a
Claude Code channel that pushes task events directly into the GM session.

The channel is an MCP server (taskyou-channel.ts) that polls the server's
notifications.jsonl over SSH and emits events as <channel source="taskyou">
tags. Each GM gets its own channel, baked with that GM's server config at
setup time. Also exposes ty_command and ssh_command tools so the GM can
run server commands through the channel instead of shelling out.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Doctor Check 7 detects missing channel files, deploys them from plugin
templates using config.env, installs bun deps, checks the shell alias
for the channels flag, and verifies CLAUDE.md has the new monitoring
section. Follows the same drift-detection pattern as the nono check.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Spawns the channel server, performs the MCP handshake, and verifies
the claude/channel capability, tools capability, instructions, and
both ty_command and ssh_command tools are registered.

Run: cd channel && bun run smoke-test.ts

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…tart

Two latent bugs in the notification poller:

- Overlapping polls: a slow runRemote (up to its 30s timeout) can outlast
  the 10s interval, so two polls share lastLineCount and can double-deliver
  or skip events. Add a `polling` in-flight guard.
- Cold start: the `lastLineCount > 0` guard meant to skip the startup
  backlog also swallowed the very FIRST live event when the channel started
  from an empty notifications.jsonl (exactly what a fresh setup creates).
  Replace it with an `initialized` sentinel: skip the backlog on the first
  poll, deliver everything after — including the first new line.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- qa/run-qa.sh + qa/channel-notify-test.ts: sandboxed ($HOME) end-to-end
  harness that runs the real setup.sh, smoke-tests the channel, and drives a
  real hook through to a channel push. Never touches the real ty/GMs. Proves
  both steady-state (no backlog replay) and cold-start (first event delivered).
- .github/workflows/qa.yml: runs the harness on PRs touching setup/channel/hooks.
- setup.sh: ship smoke-test.ts alongside the channel so a GM can self-check.
- gm-doctor Check 7: add a runtime smoke-test step; fix nono cross-ref (Check 9).
- CLAUDE.md + qa/README.md: document the harness.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@bborn
bborn force-pushed the feat/channels-integration branch from 8caee9e to 188555e Compare June 9, 2026 12:35
bborn pushed a commit to kylecarbonneau/taskyou-os that referenced this pull request Jun 9, 2026
PR taskyou#28 assumes a remote Linux server reached over SSH. These additive
changes let the GM run on the same machine as the TaskYou daemon
(including macOS) with zero change to the existing remote-Linux flow:

- channel: runRemote() gains a LOCAL branch (bash -lc, no SSH) when
  SERVER_HOST is local/localhost/empty; the SSH path is unchanged.
- setup.sh: OS-detect the hooks dir to match ty's os.UserConfigDir()
  (Linux ~/.config/task/hooks vs macOS ~/Library/Application Support/
  task/hooks). The hardcoded ~/.config path silently misfired on macOS.
- setup.sh: a local-server provisioning path that skips SSH/systemd
  when the daemon runs on this same box; remote-Linux/systemd intact.
- config.example.env: document SERVER_HOST=local.

Both paths are auto-detected; no existing behavior is removed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@bborn
bborn merged commit dfe1ffd into main Jun 9, 2026
1 check failed
bborn pushed a commit to kylecarbonneau/taskyou-os that referenced this pull request Jun 9, 2026
PR taskyou#28 assumes a remote Linux server reached over SSH. These additive
changes let the GM run on the same machine as the TaskYou daemon
(including macOS) with zero change to the existing remote-Linux flow:

- channel: runRemote() gains a LOCAL branch (bash -lc, no SSH) when
  SERVER_HOST is local/localhost/empty; the SSH path is unchanged.
- setup.sh: OS-detect the hooks dir to match ty's os.UserConfigDir()
  (Linux ~/.config/task/hooks vs macOS ~/Library/Application Support/
  task/hooks). The hardcoded ~/.config path silently misfired on macOS.
- setup.sh: a local-server provisioning path that skips SSH/systemd
  when the daemon runs on this same box; remote-Linux/systemd intact.
- config.example.env: document SERVER_HOST=local.

Both paths are auto-detected; no existing behavior is removed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
bborn added a commit that referenced this pull request Jun 9, 2026
#28 (channels) and #31 (local/macOS) are merged to main, so the design
is concrete: a Slack module is taskyou-channel.ts with its two ends
re-pointed — reuse pollNotifications() + runRemote() verbatim; net-new is
just a Slack adapter + LLM classifier. Ship outbound (hook -> Slack
webhook) first, inbound control second. Removes the assigned_gm / per-GM
scoping dependency (out of scope: single bot, single operator).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
bborn added a commit that referenced this pull request Jun 9, 2026
notifications.jsonl survives as the push substrate, but the channels
refactor changes three things: copy taskyou-channel.ts (bun/TS) instead
of linear-poll.mjs; reuse assigned_gm (#32 + workflow#561) for per-GM
routing (answers the shared-bot vs per-user question); adopt the
IS_LOCAL/SSH runRemote pattern + OS-aware hooks dir (#31). Adds a
dependencies section and merge order (#28 -> #31/#32 -> Slack).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
bborn added a commit that referenced this pull request Jun 9, 2026
#28 (channels) and #31 (local/macOS) are merged to main, so the design
is concrete: a Slack module is taskyou-channel.ts with its two ends
re-pointed — reuse pollNotifications() + runRemote() verbatim; net-new is
just a Slack adapter + LLM classifier. Ship outbound (hook -> Slack
webhook) first, inbound control second. Removes the assigned_gm / per-GM
scoping dependency (out of scope: single bot, single operator).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
bborn added a commit that referenced this pull request Jun 9, 2026
* docs: add Slack module design (manage TaskYou from Slack)

From TaskYou task #3723. Proposes modules/slack/, modeled on
modules/linear/linear-poll.mjs: watch a Slack channel/DM/@mention,
classify intent, drive ty, post replies, and tail notifications.jsonl
to push task.blocked/completed back to the channel. Enabled via
SLACK_ENABLED. Separates the chat-control bridge (recommended) from the
harder hosted/remote-MCP path (deferred to the credential-proxy work).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs: reconcile Slack design with in-flight channels work (#28/#31/#32)

notifications.jsonl survives as the push substrate, but the channels
refactor changes three things: copy taskyou-channel.ts (bun/TS) instead
of linear-poll.mjs; reuse assigned_gm (#32 + workflow#561) for per-GM
routing (answers the shared-bot vs per-user question); adopt the
IS_LOCAL/SSH runRemote pattern + OS-aware hooks dir (#31). Adds a
dependencies section and merge order (#28 -> #31/#32 -> Slack).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs: land Slack design on merged channel work; drop per-GM scoping

#28 (channels) and #31 (local/macOS) are merged to main, so the design
is concrete: a Slack module is taskyou-channel.ts with its two ends
re-pointed — reuse pollNotifications() + runRemote() verbatim; net-new is
just a Slack adapter + LLM classifier. Ship outbound (hook -> Slack
webhook) first, inbound control second. Removes the assigned_gm / per-GM
scoping dependency (out of scope: single bot, single operator).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(slack): implement two-way Slack module (modules/slack)

Builds the Slack integration end to end (outbound + inbound), per the
design in docs/plans/2026-06-08-slack-module-design.md.

modules/slack/slack-bridge.mjs — zero-dependency Node daemon (raw fetch +
global WebSocket, like the Linear poller). Runs on the agents server next
to ty + notifications.jsonl:
  • Outbound: tails notifications.jsonl → chat.postMessage (blocked/
    completed/failed/started). Slack-originated tasks answer in-thread;
    everything else goes to SLACK_NOTIFY_CHANNEL.
  • Inbound: Slack Socket Mode (no public URL) → allowlist check →
    intent classification (Anthropic API, with a keyword-heuristic
    fallback when no key) → ty create/execute/input/list. Replies in-thread.

Also: unit tests for the pure logic (node --test, 11 cases), a
README, the ty-slack systemd service template, and setup.sh wiring
(server + exe.dev install the service; local mode renders files + .env)
plus SLACK_* in config.example.env and the README modules list. The
module is gated behind SLACK_ENABLED=true.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(slack): default classifier to on-box `claude` CLI + runaway guards

Classifier no longer requires a separate ANTHROPIC_API_KEY: it prefers the
already-authenticated on-box `claude` CLI (claude -p — the same primitive the
GM/executors use), falls back to the Anthropic API if a key is set, then a
keyword heuristic. Override via SLACK_CLASSIFIER.

Loop / token-burn hardening (per review):
- claude -p sandboxed per call: --strict-mcp-config --mcp-config {} +
  --disallowedTools … keep it to a single turn (no agentic spiral);
  --max-budget-usd (default $0.05) hard-caps cost; timeout + maxBuffer; no retry.
- Self-loop guards: ignore own/bot/edited messages (bot_id/subtype/BOT_USER_ID).
- Concurrency cap (SLACK_MAX_CONCURRENT, default 3) bounds in-flight
  classifications; excess messages declined, not queued.
- Socket generation guard: only the newest connection's handlers stay live, so
  a reconnect race can't double-deliver events.
- Outbound poller: per-tick cap (SLACK_MAX_NOTIFS_PER_POLL, default 25) with
  byte-accurate gradual drain; only complete lines consumed (partial hook line
  held); lost/corrupt state skips backlog instead of replaying.

Tests: +2 (buildClassifierContext, readNewChunk partial-line/rotation) → 13/13.
Docs: README "Runaway / cost protection", config knobs, design-doc note.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
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