Chisel OAuth & Slack Integration¶
Overview¶
Chisel is the Guardian agent in The Smithy's AI agent fleet. This document covers two tightly coupled subsystems in the chisel repository:
- OAuth token validation — a background safety-net that monitors Claude OAuth credential expiry and alerts the team via Slack when a token has lapsed.
- Slack integration — the event-handling layer that connects Chisel to Slack via Socket Mode, routing mentions, DMs, reactions, slash commands, and approval actions to the
ChiselSlackBotorchestrator.
Repository: the-smithy1/agents/chisel
Architecture¶
Entry Point¶
src/mvp_slack_bot.py is the main entry point. It:
- Initialises a
slack_boltAsyncAppwith anAsyncWebClient(explicit SSL context). - Creates the
ChiselSlackBotorchestrator fromsrc/slack/main.py. - Registers thin event listeners that delegate to the orchestrator.
- Launches several background
asynciotasks on startup, including the OAuth check loop and the self-maintenance scheduler. - Starts an
AsyncSocketModeHandlerfor real-time Slack communication.
Slack Event Routing¶
| Event / Action | Handler | Delegates to |
|---|---|---|
app_mention |
handle_app_mention |
chisel.handle_mention |
message (DMs & thread replies) |
handle_message_events |
chisel.handle_dm / chisel.handle_mention |
reaction_added |
handle_reaction_added |
QualityFeedback rating system |
/chisel slash command |
handle_chisel_command |
chisel.handle_command |
approve_write_operation |
handle_approve_action |
chisel.handle_approval_action |
reject_write_operation |
handle_reject_action |
chisel.handle_reject_action |
Thread replies are detected by checking conversations_replies for prior Chisel messages. Duplicate handling is avoided by skipping @mention messages already caught by the app_mention handler.
Event Handler (src/slack/event_handler.py)¶
SlackEventHandler centralises mention and DM processing. Key behaviours:
- Loop prevention: Messages from sibling bot user IDs (Minh, Pearl, Arturo, Roland, Taryn) in threads are ignored to prevent inter-agent loops.
- Unified pipeline: When a
MessageProcessoris available, messages flow through emotion analysis before intent classification. The legacy pipeline is retained for backward compatibility. - Natural language commands: Phrases like "show me the status" are mapped to equivalent
/chiselslash commands. - Relationship tracking: User familiarity levels are maintained and used to personalise response tone.
OAuth Token Safety Net¶
_check_oauth_token() runs inside a background loop (_oauth_check_loop()) and acts as a safety net for the primary refresh mechanism (a local macOS launchd job). The check:
- Gates on auth mode — exits immediately unless
CHISEL_AGENT_SDK_AUTH=subscription. - Respects long-lived tokens — if
CLAUDE_CODE_OAUTH_TOKENis set (a 1-year setup token), the credentials-file expiry check is skipped entirely to avoid false alarms. - Reads credentials — parses
claudeAiOauth.expiresAtfrom<CLAUDE_CONFIG_DIR>/.credentials.json. - Alerts on expiry — posts a
:rotating_light:message to the configured Slack channel when the token has fully expired. - Throttles alerts — uses a Redis key (
oauth_token:alert_sent, 3600 s TTL) to suppress repeat alerts, with an in-memorytime.monotonic()fallback when Redis is unavailable.
Self-Maintenance Scheduler¶
_self_maintenance_scheduler() is a background asyncio task in src/mvp_slack_bot.py that drives Chisel's autonomous self-maintenance cycle (chisel#84). It processes qualifying open issues across repos listed in CHISEL_SELF_MAINTENANCE_PROJECTS (chisel + watchtower), creating draft fix MRs via SelfMaintenanceService.
Why it lives in the entrypoint, not core/scheduler.py: Prior to chisel#84, self-maintenance was scheduled as a schedule.every().day.at("03:00") cron entry inside DailyRoutineScheduler.schedule_all_routines(). That class is constructed only in core/scheduler.py's if __name__ == "__main__" test harness — the container entrypoint (mvp_slack_bot.py, per the Dockerfile CMD) never imports it. As a result, CHISEL_SELF_MAINTENANCE_ENABLED=true sat in the configmap for weeks while nothing ran. The failure was invisible: boot logs printed "disabled" for other schedulers (Books EOD, monthly-close, invoice reconcile) but emitted nothing for self-maintenance — so grepping for failures found zero lines and read as healthy. The dead cron has been removed and self-maintenance now runs as an asyncio task in the process that actually executes.
The scheduler:
- Gates on
CHISEL_SELF_MAINTENANCE_ENABLED— thestart_slack_bot()function reads this flag and logs an explicit message for both the enabled and disabled branches, making "not running" distinguishable from "not wired" in boot logs. - Boot stagger — waits 2 hours after pod start before the first cycle, so a restart doesn't immediately spend Claude SDK budget.
- Interval — configurable via
CHISEL_SELF_MAINTENANCE_INTERVAL(minutes, default1440= daily). Parsed bycore.scheduler_utils.interval_seconds(), which falls back to the default on empty/invalid values and floors at 60 seconds to prevent busy-looping. - Degrades without GitLab — if
chisel.gitlab_clientisNone, the cycle logs a warning and continues rather than raising every iteration. - Delegates to
SelfMaintenanceService— instantiated each cycle with the GitLab client, Slack client, channel, and Redis client (for run lock, daily limit, monthly usage tally, and no-change backoff; degrades to no-op when Redis is absent). - Survives exceptions — individual cycle failures are caught and logged; the loop continues on the next interval.
The _run_self_maintenance() method on DailyRoutineScheduler is retained as a manual one-shot escape hatch via run_routine("self-maintenance").
Personality Engine (src/core/personality.py)¶
PersonalityEngine drives Chisel's voice across all Slack interactions. Relevant aspects:
- Dual persona: "Knowledge Architect" in team/Slack context; "The Librarian" in game context. Context switching is handled by
ContextDetectorandPersonaManager. - LLM-driven responses: System prompts are built from identity config, personality traits, conversational style guidelines, constitution mandates, and OKRs.
- Slack formatting: Responses explicitly use Slack markdown (
*bold*,_italic_,`code`) — not standard Markdown. - Team awareness: The system prompt includes Slack user IDs for all sibling agents so Chisel can
@mentionthe right colleague when a question falls outside its domain.
Configuration¶
Required Environment Variables¶
| Variable | Description |
|---|---|
CHISEL_SLACK_BOT_TOKEN |
Slack Bot User OAuth Token |
CHISEL_SLACK_APP_TOKEN |
Slack App-Level Token (Socket Mode) |
OAuth-Related Environment Variables¶
| Variable | Default | Description |
|---|---|---|
CHISEL_AGENT_SDK_AUTH |
(none) | Auth mode. Set to subscription to enable OAuth monitoring |
CLAUDE_CODE_OAUTH_TOKEN |
(none) | 1-year setup token. When present, credentials-file expiry check is skipped |
CLAUDE_CONFIG_DIR |
~/.claude |
Directory containing .credentials.json |
CHISEL_OAUTH_CHECK_ENABLED |
true |
Enable/disable the background OAuth check loop |
CHISEL_OAUTH_CHECK_INTERVAL |
3600 |
Interval between checks, in seconds |
CHISEL_EOD_CHANNEL |
C08V2C2CFG9 |
Slack channel for OAuth expiry alerts |
AI_TIMEZONE |
America/Los_Angeles |
Timezone for formatting expiry timestamps |
Self-Maintenance Environment Variables¶
| Variable | Default | Description |
|---|---|---|
CHISEL_SELF_MAINTENANCE_ENABLED |
false |
Enable the self-maintenance asyncio scheduler. The configmap sets this to true in production |
CHISEL_SELF_MAINTENANCE_INTERVAL |
1440 |
Interval between cycles, in minutes (1440 = daily) |
CHISEL_SELF_MAINTENANCE_CHANNEL |
(empty) | Slack channel for self-maintenance Slack notifications |
CHISEL_SELF_MAINTENANCE_PROJECTS |
(see service) | Comma-separated GitLab project paths to process (chisel + watchtower) |
CHISEL_SELF_MAINTENANCE_AUTO_MERGE |
(unset) | When unset (default), the service opens DRAFT MRs only and never merges |
CHISEL_SELF_MAINTENANCE_EFFECT_VERIFY |
(unset) | When unset (default), the effect-gate is off — no effect verification before MR |
Optional Environment Variables¶
| Variable | Default | Description |
|---|---|---|
CHISEL_INTERNAL_API_TOKEN |
(none) | Enables the internal API server for inter-agent requests |
Usage¶
Starting the Bot¶
The bot connects via Socket Mode — no public URL or ingress is required.
Slash Command¶
/chisel status # Check Chisel's status
/chisel stats # View interaction statistics
/chisel help # List available commands
/chisel scan <repo> # Scan a repository
Natural Language (via @mention)¶
Users can invoke commands conversationally:
These are mapped internally to the equivalent /chisel slash commands.
Error Handling & Failure Modes¶
- OAuth token expired, refresh automation failed: The safety-net check posts an alert to the configured Slack channel. The alert message directs operators to check
taryn/logs/claude-oauth-refresh.logon the Mac running thelaunchdrefresh job. - Redis unavailable: Alert throttling falls back to an in-memory monotonic clock. Alerts are still rate-limited to one per 3600 seconds, but the throttle resets on pod restart.
- Credentials file missing: The check exits silently (debug log only) — this is expected in API-auth deployments.
- OAuth check loop exceptions: Individual iteration failures are caught and logged as warnings; the loop continues on the next interval.
- Sibling bot loops: Thread replies from known sibling agent user IDs are silently dropped to prevent cascading inter-agent conversations.
- Self-maintenance — no GitLab client: The cycle logs a warning ("no GitLab client available — skipping cycle") and continues to the next interval rather than raising every iteration.
- Self-maintenance — service exception: Individual cycle failures are caught via
logger.exception; the loop continues on the next interval. - Self-maintenance — Redis unavailable: The
SelfMaintenanceServicedegrades to no-op on run lock, daily limit, monthly usage tally, and no-change backoff when Redis is absent. - Self-maintenance — double-scheduling guard: The dead
schedule.every().day.at("03:00")cron inDailyRoutineSchedulerhas been removed (chisel#84). Re-adding it would double-schedule self-maintenance the moment that class is wired into production. Tests intest_self_maintenance_wiring.pyenforce this.
Testing¶
OAuth check tests are in tests/test_oauth_check.py. The test suite covers:
- Subscription-mode gating (no-op when auth mode is not
subscription) - Missing credentials file handling
- Valid (unexpired) token — no alert
- Expired token — Slack alert triggered
- Redis-backed throttle suppresses repeat alerts within cooldown window
- In-memory fallback throttle when Redis is unavailable
- Background loop respects the
CHISEL_OAUTH_CHECK_ENABLEDgate - Loop survives exceptions in individual check iterations
Self-maintenance wiring tests are in tests/test_self_maintenance_wiring.py (chisel#84). The test suite pins the fix against silent regression:
- Entrypoint defines the scheduler: asserts
_self_maintenance_schedulerexists as a coroutine inmvp_slack_bot - Gate and logging: verifies
CHISEL_SELF_MAINTENANCE_ENABLEDis read in the entrypoint, with an explicit "disabled" log in the else-branch (so "not running" is distinguishable from "not wired" in logs) - Dead cron stays dead: asserts
_run_self_maintenancedoes not appear inDailyRoutineScheduler.schedule_all_routines()source — re-adding would double-schedule - Manual one-shot retained: verifies
_run_self_maintenancestill exists onDailyRoutineScheduleras an escape hatch viarun_routine("self-maintenance") - Graceful degradation without GitLab: loop warns and continues when
gitlab_clientisNone - Loop survives exceptions: a raising
SelfMaintenanceService.run()does not kill the task — it retries on the next interval - Boot stagger: first sleep is 7200 seconds (2 hours)
- Interval conversion: verifies the interval env var is treated as minutes and converted to seconds
Related Components¶
src/slack/main.py(ChiselSlackBot) — orchestrator that coordinates event handling, response generation, memory, and metrics.src/slack/message_processor.py— unified message pipeline with emotion-first processing.src/core/quality_feedback.py— maps emoji reactions to quality ratings for response feedback.src/core/scheduler.py(DailyRoutineScheduler) — daily routine scheduler. Self-maintenance cron removed in chisel#84;_run_self_maintenance()retained as manual one-shot.src/core/scheduler_utils.py— defensive interval parser (interval_seconds()) used by self-maintenance and other background schedulers.src/services/self_maintenance_service.py(SelfMaintenanceService) — thin adapter over the sharedSelfMaintenanceChassisfromagents-shared. Opens DRAFT MRs for qualifying issues in chisel + watchtower repos.config/personality.json— personality configuration consumed byPersonalityEngine.taryn/scripts/refresh-claude-oauth.sh— primary OAuth token refresh automation (external to this repo).
Feature Repositories¶
chisel→ project_id:the-smithy1/agents/chisel
Code Paths to Explore¶
tests/test_oauth_check.pyinchiseltests/test_self_maintenance_wiring.pyinchiselsrc/core/personality.pyinchiselsrc/core/scheduler.pyinchiselsrc/core/scheduler_utils.pyinchiselsrc/services/self_maintenance_service.pyinchiselsrc/slack/event_handler.pyinchiselsrc/mvp_slack_bot.pyinchisel