Team Heartbeat
Produce a thorough, link-rich summary of what each team member did over a window. Research every member across GitHub, Slack, and Google Workspace, run the fallback for each source, and finish with a completeness pass so real work surfaces.
Hold to these rules — each one is where work otherwise gets missed:
- Verify every GitHub handle before searching; a wrong handle drops all of a person's PRs.
- Search across every repo a person touched, not just one.
- When
slackcli search from:@email returns nothing, scan channel history instead.
- Cover Gmail and Calendar, not only GitHub and Slack.
- Retry a query that errors before concluding there is nothing to find.
Prerequisites
gh and gws must be installed and authenticated. Check them first; if either is missing or logged out, stop and tell the user how to set it up rather than producing a partial report:
gh auth status # GitHub CLI
gws auth status # Google Workspace CLI
| Tool |
Purpose |
Setup |
gh |
GitHub PRs / issues / commits |
GitHub CLI — brew install gh, then gh auth login |
gws |
Drive / Gmail / Calendar |
googleworkspace/cli — brew install googleworkspace-cli, then gws auth setup and gws auth login |
State which one failed and quote the fix; do not spawn subagents until both pass.
Slack source (conditional)
Slack comes from slackcli. Check slackcli auth status:
- Authenticated → use it; the per-member Slack research runs
slack_activity.sh as normal.
- Not authenticated →
slackcli's built-in browser login only works on the workspace its embedded app is registered for (Grafana). On any other workspace the user must either authenticate slackcli with their own Slack app (--client-id / --client-secret) or give another way to fetch their Slack data. Ask the user. If they have no Slack access method, skip the Slack source entirely (do not run slack_activity.sh) and note in the report that Slack was not covered.
Determine the team
Resolve the user's team at invocation, autonomously:
- If the user named specific people, a team, or a Slack channel, use that.
- Otherwise, if
~/.cache/heartbeat/roster.md exists, use it.
- Otherwise discover it from the user's own identity:
gh api user and slackcli auth status --json identify the user.
gh api user/teams --jq '.[] | "\(.organization.login)/\(.slug)"' lists their org teams. Set aside umbrella groups — names matching all-, everyone, staff, devs, or contributors, or teams with roughly 20+ members (gh api orgs/<org>/teams/<slug>/members --jq length). From the squad-sized teams left, take the user's working squad; if more than one fits, pick the smallest and state which you chose.
- Keep the team's GitHub org (
organization.login) — it scopes the GitHub search.
- Build the roster from that team's members (see
references/roster.md) and cache it.
State the team you resolved, then proceed.
Inputs
- Roster: the resolved member list (Slack user ID, email, GitHub handle each), plus the team's Slack channel ID(s). See
references/roster.md for the schema and build steps.
- Window: default last 7 days; honor whatever the user specifies. Convert to an absolute
since date (YYYY-MM-DD) and a Slack since_epoch (Unix seconds). Use absolute dates in queries.
Workflow
- Resolve the roster (see Determine the team). Verify any stale-looking GitHub handle with
gh api users/<handle> --jq .name.
- Compute
since and since_epoch.
- Run this as a Workflow:
parallel() one subagent per member, using subagent_type: general-purpose and model: haiku (cheap; this is data-gathering, not judgment, so the cheap model is the right call, not a shortcut). Give each subagent:
- the member's name, role, Slack user ID, email, GitHub handle, and the team's GitHub org;
- the window (
since date + since_epoch);
- the absolute path to
references/research-recipe.md and instructions to follow it exactly;
- the absolute paths to the dumper scripts:
scripts/gh_activity.sh, scripts/gws_activity.sh, and — only when the Slack source is available — scripts/slack_activity.sh. Each one pulls a person's full data for that source, so the subagent reads the dump first, then digs into what matters. If Slack is out of scope (see Slack source), tell the subagent to skip the Slack section entirely.
- Collect the summaries. Run the completeness check (the checklist at the end of
research-recipe.md). Any member with thin or empty results gets a second targeted pass, another parallel() round scoped to just those members with the specific gap named in the prompt, before you trust "nothing found".
- Synthesize: one section per member (1-3 theme bullets + linked artifacts), then a short team-level note (shared initiatives, org changes, releases). Every PR / issue / doc must be a markdown link.
Spawning subagents
Use the Workflow tool's parallel() so every member's research runs concurrently, not Agent calls sent one by one. Tell each subagent: "Your reply is data, not a chat message. Return markdown starting with ### <Name> (<role>). Only report what you find evidence for — no speculation. If a source returns nothing, say so in one line."
Pass the recipe by path; do not paste its contents into the prompt.
Output
Print the report in chat by default. Then offer to save it to a Google Doc (use the gws-docs-write skill) or to the user's notes.
1---2name: heartbeat3description: Team Heartbeat4---56# Team Heartbeat78Produce a thorough, link-rich summary of what each team member did over a window. Research every member across GitHub, Slack, and Google Workspace, run the fallback for each source, and finish with a completeness pass so real work surfaces.910Hold to these rules — each one is where work otherwise gets missed:11- Verify every GitHub handle before searching; a wrong handle drops all of a person's PRs.12- Search across every repo a person touched, not just one.13- When `slackcli search from:@email` returns nothing, scan channel history instead.14- Cover Gmail and Calendar, not only GitHub and Slack.15- Retry a query that errors before concluding there is nothing to find.1617## Prerequisites1819`gh` and `gws` must be installed and authenticated. Check them first; if either is missing or logged out, **stop and tell the user how to set it up** rather than producing a partial report:2021```bash22gh auth status # GitHub CLI23gws auth status # Google Workspace CLI24```2526| Tool | Purpose | Setup |27|---|---|---|28| `gh` | GitHub PRs / issues / commits | [GitHub CLI](https://cli.github.com) — `brew install gh`, then `gh auth login` |29| `gws` | Drive / Gmail / Calendar | [googleworkspace/cli](https://github.com/googleworkspace/cli) — `brew install googleworkspace-cli`, then `gws auth setup` and `gws auth login` |3031State which one failed and quote the fix; do not spawn subagents until both pass.3233### Slack source (conditional)3435Slack comes from [`slackcli`](https://github.com/grafana/slackcli). Check `slackcli auth status`:3637- **Authenticated** → use it; the per-member Slack research runs `slack_activity.sh` as normal.38- **Not authenticated** → `slackcli`'s built-in browser login only works on the workspace its embedded app is registered for (Grafana). On any other workspace the user must either authenticate `slackcli` with their own Slack app (`--client-id` / `--client-secret`) or give another way to fetch their Slack data. Ask the user. If they have no Slack access method, skip the Slack source entirely (do not run `slack_activity.sh`) and note in the report that Slack was not covered.3940## Determine the team4142Resolve the user's team at invocation, autonomously:43441. If the user named specific people, a team, or a Slack channel, use that.452. Otherwise, if `~/.cache/heartbeat/roster.md` exists, use it.463. Otherwise discover it from the user's own identity:47 - `gh api user` and `slackcli auth status --json` identify the user.48 - `gh api user/teams --jq '.[] | "\(.organization.login)/\(.slug)"'` lists their org teams. Set aside umbrella groups — names matching `all-`, `everyone`, `staff`, `devs`, or `contributors`, or teams with roughly 20+ members (`gh api orgs/<org>/teams/<slug>/members --jq length`). From the squad-sized teams left, take the user's working squad; if more than one fits, pick the smallest and state which you chose.49 - Keep the team's GitHub org (`organization.login`) — it scopes the GitHub search.50 - Build the roster from that team's members (see `references/roster.md`) and cache it.5152State the team you resolved, then proceed.5354## Inputs5556- **Roster**: the resolved member list (Slack user ID, email, GitHub handle each), plus the team's Slack channel ID(s). See `references/roster.md` for the schema and build steps.57- **Window**: default last 7 days; honor whatever the user specifies. Convert to an absolute `since` date (`YYYY-MM-DD`) and a Slack `since_epoch` (Unix seconds). Use absolute dates in queries.5859## Workflow60611. Resolve the roster (see *Determine the team*). Verify any stale-looking GitHub handle with `gh api users/<handle> --jq .name`.622. Compute `since` and `since_epoch`.633. Run this as a Workflow: `parallel()` one subagent per member, using `subagent_type: general-purpose` and `model: haiku` (cheap; this is data-gathering, not judgment, so the cheap model is the right call, not a shortcut). Give each subagent:64 - the member's name, role, Slack user ID, email, GitHub handle, and the team's GitHub org;65 - the window (`since` date + `since_epoch`);66 - the absolute path to `references/research-recipe.md` and instructions to follow it exactly;67 - the absolute paths to the dumper scripts: `scripts/gh_activity.sh`, `scripts/gws_activity.sh`, and — only when the Slack source is available — `scripts/slack_activity.sh`. Each one pulls a person's full data for that source, so the subagent reads the dump first, then digs into what matters. If Slack is out of scope (see *Slack source*), tell the subagent to skip the Slack section entirely.684. Collect the summaries. Run the **completeness check** (the checklist at the end of `research-recipe.md`). Any member with thin or empty results gets a second targeted pass, another `parallel()` round scoped to just those members with the specific gap named in the prompt, before you trust "nothing found".695. Synthesize: one section per member (1-3 theme bullets + linked artifacts), then a short team-level note (shared initiatives, org changes, releases). Every PR / issue / doc must be a markdown link.7071## Spawning subagents7273Use the Workflow tool's `parallel()` so every member's research runs concurrently, not `Agent` calls sent one by one. Tell each subagent: *"Your reply is data, not a chat message. Return markdown starting with `### <Name> (<role>)`. Only report what you find evidence for — no speculation. If a source returns nothing, say so in one line."*7475Pass the recipe by path; do not paste its contents into the prompt.7677## Output7879Print the report in chat by default. Then offer to save it to a Google Doc (use the `gws-docs-write` skill) or to the user's notes.