Krahnie — System Overview
A high-level map of how Krahnie is put together: the Slack-native delivery-ops assistant for Krahnborn's Salesforce work.
1 · What Krahnie is
Krahnie is a Slack-native assistant for Krahnborn's Salesforce delivery work. The team talks to it in Slack (DMs, or by @-mention in channels); it routes each message to one of ~30 tools that read/write Salesforce and reply with Block Kit cards. It also runs proactive work (digests, reminders), captures feedback, hands engineering cards off to coding agents, and cross-syncs with clients' own ticketing systems.
It runs as a single Python service connected to Slack over Socket Mode and to Salesforce over the REST API.
2 · Architecture
Four layers, each with a clear seam:
- slack_gateway.py — the transport. Receives Slack events/interactions, fast-acks them (Slack's 3s rule), and routes: chat → orchestrator; button/modal/reaction → dedicated handlers. Holds the channel/DM gating and easter eggs.
- agent.py — the orchestrator: a thin Pydantic-AI agent (Claude Haiku) whose job is to recognize intent and call the right tool. The routing directive lives in prompts.py.
- tools/*.py — the verbs. Each is an
@toolfunction that does the work and posts a Block Kit card. - salesforce.py / clients.py / writes.py — the data layer (multi-org REST + the client registry + write helpers).
Why Pydantic-AI + Haiku? The orchestrator is a cheap, fast router. Heavier reasoning (card enrichment, summaries) is delegated to Claude Sonnet inside specific tools, so most turns cost a fraction of a cent.
3 · Request lifecycle
A chat message
A button / modal / reaction
Interactivity never goes through the LLM — it's wired directly: buttons → @app.action, modal submits → @app.view, searchable selects → @app.options, slash commands → @app.command, reactions → @app.event("reaction_added"). The pattern is always ack() first, then run the work in the background.
4 · Core building blocks
RequestContext context.py
A frozen dataclass stamped per turn and read by tools: who (slack_user_id), where (channel, thread_ts), what (text), a posted flag (a tool appends to it when it posts a card → the gateway then stays silent), and a cost sink.
The tool contract tools/__init__.py
Every tool is an @tool(name, description, input_schema) function. Its description is the routing signal the model reads; prompts.py carries the explicit "if the user says X → call tool Y" directive. Adding a tool = write the function, register it in build_tools, add a routing line.
Confirm-before-commit pending.py
Risky writes never fire on the first message. The tool posts a Confirm/Edit/Cancel card and stashes the proposed payload under a nonce; the button handler loads it and commits. This is the spine of every "are you sure?" flow.
Block Kit blockkit.py
Pure render functions returning (blocks, fallback_text); all action ids are constants here. slackio.py is the thin Slack Web API wrapper.
5 · Salesforce & the client registry
salesforce.py talks to Salesforce over the REST API across multiple orgs, caching auth per org. clients.py reads a per-client registry (clients.yaml, refreshed per call, zero model-context cost) that resolves nicknames to accounts and holds per-client routing/defaults (channel, board, org aliases, repo, ticketing config). The model never sees this file — resolution happens deterministically in tool code.
6 · Feedback capture
The team reports what they were trying to do, what happened, and pastes the chat — so tools get hardened. NL "log feedback" or /krahnie-feedback opens a modal whose submission writes a Krahnie_Feedback__c record. feedback.py + tools/feedback.py.
7 · Coyote voice
The brand persona is a literal contract: prompts.py ends with a VOICE block — the Coyote (terse, clever-not-cute, confident, proactive). Voice lives in acks, nudges, and help; data stays neutral.
8 · Easter eggs
Handled before the agent (free + instant). Message-based: "hello friend", "ship it", team legends, :linux:×3, the konami code, "am I cooked", "coffee". Reaction-based: :linux: → a Linux fact in the thread; :man-playing-handball: → a warm fuzzy (§9).
9 · Warm fuzzies
tools/warmfuzzy.py. "@krahnie we got a warm fuzzy! shout out the SDI team" — or a :man-playing-handball: reaction — posts a celebratory shout-out to the warm-fuzzies channel, quoting the praise and crediting the team/person.
10 · Card create
Flow: tools/createcard.py → a confirm card → writes.create_card. The model extracts a description; the confirm card offers two buttons — Create (plain) and ✨ Create + AI insights (opt-in Sonnet enrichment: story points, next steps, follow-ups) — and the created card carries an ✏️ Edit button into the edit modal.
11 · Channel behavior
- Mention-gating — in a channel Krahnie replies only when @-mentioned; DMs/assistant respond to everything.
- Channel → client — used in a client's channel, card-create defaults the client (and its default board) from the channel, so you don't name it every time.
12 · Agent handoff — deep link Mode B
"Hand off CARD-1234 to an agent" → a Claude Code deep link the dev clicks to open Claude Code locally, pre-loaded with the card brief (title, description, triage notes). agentwork.py assembles the brief and builds the link; the human drives from there.
13 · Agent handoff — Codex first pass Mode A
An autonomous attempt that opens a draft PR for review. From the handoff card, a "⚡ First pass" button opens a confirm modal (review/edit the repo, the target sandbox, and the branch), then Krahnie runs Codex against the card brief in an isolated container, scoped to a chosen sandbox org, and opens a draft PR. Human-in-the-loop by design: it proposes, a person reviews and merges. codexwork.py + agentwork.py.
14 · Ticket sync (cross-org)
Krahnie-mediated sync between a client's own Salesforce ticket object and Krahnborn's Card__c — no middleware. ticketsync.py.
- Field ownership — the client owns the request; Krahnborn owns delivery status. Inbound never touches delivery fields.
- Status crosswalk — per-client maps between their statuses and Requests/Develop/Test/Done.
- Idempotent linking — a unique external-id key prevents duplicate cards on re-sync.
15 · Data model
| Object | Role |
|---|---|
Card__c | the unit of delivery work (status, due, estimate, assignee, AI insights, sync cross-refs) |
Work_Log__c | time entries |
Board__c / Project__c / Account | structure: board → project → account/client |
Krahnie_Feedback__c | team feedback on Krahnie |
Custom metadata is source-controlled as an SFDX project. clients.yaml holds the per-client registry (name, aliases, channel, board, org aliases, repo, ticketing) plus a dev-sandboxes list.
16 · Module map
| File | Purpose |
|---|---|
| slack_gateway.py | Socket Mode transport: events, interactivity, mention-gating, easter eggs, handlers |
| agent.py | Pydantic-AI orchestrator (Haiku), tool adaptation, per-convo history, prompt caching |
| prompts.py | System prompt: routing directive + the Coyote voice |
| salesforce.py | Multi-org REST: query / create / patch / upsert, per-org token cache |
| clients.py | Client registry: resolve, repo, orgs, channel→client, dev sandboxes, ticketing |
| writes.py | Write layer: Work_Log + Card inserts/updates, modal builders, renderers |
| blockkit.py | Pure Block Kit renderers + action-id constants |
| enrich.py / refresh.py / pm.py | Sonnet reasoning · week digest · team rollups & metrics |
| agentwork.py / codexwork.py | Handoff core (brief, deep link, modal) · Codex first pass |
| ticketsync.py | Cross-org ticket sync (pull/push, crosswalk) |
| scheduler.py / state.py / usage.py | Durable scheduler · snooze state · cost/usage log |
| tools/*.py | ~30 tools — week, cards, logtime, createcard, editcard, cost, metrics, pm, research, reminders, leaderboard, help, feedback, handoff, warmfuzzy… |
17 · How to extend
Add a tool
- New
tools/foo.py:make_foo_tool(get_ctx)returning an@tool(...)function that posts viaslackioand marksctx.posted. - Register it in
tools/__init__.py. - Add a routing line in
prompts.py.
Add a confirm-card flow
- Render the card in
blockkit.pywith new action-id constants; stash the payload viapending.save. - Register the action handler in
slack_gateway.pythat loads the nonce and commits.
Krahnie — Krahnborn's Salesforce delivery-ops assistant. Generated overview.