Skip to Content
GuidesSlack

Slack

karani pushes to Slack and takes commands back. It is the surface that makes the hourly hunt useful, because a pack nobody sees is a pack nobody sends.

karani notify --kind digest # push the shortlist karani notify --kind actions # push the worklist karani slack # the two-way listener

Two halves, two dependencies

The push half is httpx only and works from cron with nothing extra. That is deliberate: delivery is the part the scheduler depends on, so it has the fewest moving pieces.

The pull half — the listener, over Socket Mode — needs an extra:

uv sync --extra slack karani slack

Review cards

The hourly pass drafts packs for new top-fit roles and posts each as a card: the summary, the cover letter, the voice score from the humanizer, presigned links to the rendered documents, and four buttons.

ButtonWhat it does
Approve packMarks it ready and links the posting
Skip roleRecords a skip verdict; feeds the taste calibration
I applied (warm)Status applied, warm path
I applied (cold)Status applied, cold path

Approve, then open the portal and submit it yourself, then press I applied. Every click records a verdict, feeds the memory ledger, and updates the Notion board.

There is no button that submits (ADR 0012).

Quiet by design

An hour with no new high-fit roles posts nothing. Summary pushes — the digest and the worklist — stay on a twice-daily schedule so the channel is not a feed.

That is the difference between a tool you keep and a tool you mute.

The verbs

In the channel or a DM, the same verbs the CLI has:

actions digest verdict 1482 apply status 1482 applied draft 1482 prep 1482 followup 1482 intel Northwind Robotics warm Northwind Robotics remember <fact> recall <query> sync help

commands.py maps these onto the same storage, runner and memory calls the CLI and the MCP server use — a third thin adapter, same rule (ADR 0010).

Setup

A Slack app with:

  • Socket Mode on → SLACK_APP_TOKEN (xapp-...)
  • Bot scopes chat:write and im:history → SLACK_BOT_TOKEN (xoxb-...)
  • Event subscription: message.im
  • Interactivity: on (for the buttons)

Then in .env:

SLACK_BOT_TOKEN=xoxb-... SLACK_APP_TOKEN=xapp-... SLACK_CHANNEL=C01234567

And in karani.toml:

[slack] channel = "C01234567" allowed_users = [] # empty = everyone

Delivery never sinks a pass

Every delivery step is best-effort. An unconfigured channel, an expired token, a brief Slack outage — each logs and the pass continues. A pipeline run that fetched 1,600 postings and qualified twenty should not be recorded as a failure because a webhook timed out.

The corollary: a Slack push failing is not loud. If cards stop arriving, check the log rather than waiting for an alert.

Last updated on