Skip to content

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:

  1. OAuth token validation — a background safety-net that monitors Claude OAuth credential expiry and alerts the team via Slack when a token has lapsed.
  2. 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 ChiselSlackBot orchestrator.

Repository: the-smithy1/agents/chisel

Architecture

Entry Point

src/mvp_slack_bot.py is the main entry point. It:

  • Initialises a slack_bolt AsyncApp with an AsyncWebClient (explicit SSL context).
  • Creates the ChiselSlackBot orchestrator from src/slack/main.py.
  • Registers thin event listeners that delegate to the orchestrator.
  • Launches several background asyncio tasks on startup, including the OAuth check loop and the self-maintenance scheduler.
  • Starts an AsyncSocketModeHandler for 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 MessageProcessor is 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 /chisel slash 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:

  1. Gates on auth mode — exits immediately unless CHISEL_AGENT_SDK_AUTH=subscription.
  2. Respects long-lived tokens — if CLAUDE_CODE_OAUTH_TOKEN is set (a 1-year setup token), the credentials-file expiry check is skipped entirely to avoid false alarms.
  3. Reads credentials — parses claudeAiOauth.expiresAt from <CLAUDE_CONFIG_DIR>/.credentials.json.
  4. Alerts on expiry — posts a :rotating_light: message to the configured Slack channel when the token has fully expired.
  5. Throttles alerts — uses a Redis key (oauth_token:alert_sent, 3600 s TTL) to suppress repeat alerts, with an in-memory time.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:

  1. Gates on CHISEL_SELF_MAINTENANCE_ENABLED — the start_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.
  2. Boot stagger — waits 2 hours after pod start before the first cycle, so a restart doesn't immediately spend Claude SDK budget.
  3. Interval — configurable via CHISEL_SELF_MAINTENANCE_INTERVAL (minutes, default 1440 = daily). Parsed by core.scheduler_utils.interval_seconds(), which falls back to the default on empty/invalid values and floors at 60 seconds to prevent busy-looping.
  4. Degrades without GitLab — if chisel.gitlab_client is None, the cycle logs a warning and continues rather than raising every iteration.
  5. 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).
  6. 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 ContextDetector and PersonaManager.
  • 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 @mention the 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)
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

python src/mvp_slack_bot.py

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:

@Chisel show me the status
@Chisel check health for my-repo
@Chisel find broken links

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.log on the Mac running the launchd refresh 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 SelfMaintenanceService degrades 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 in DailyRoutineScheduler has been removed (chisel#84). Re-adding it would double-schedule self-maintenance the moment that class is wired into production. Tests in test_self_maintenance_wiring.py enforce 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_ENABLED gate
  • Loop survives exceptions in individual check iterations
# Run OAuth-specific tests
pytest tests/test_oauth_check.py -v

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_scheduler exists as a coroutine in mvp_slack_bot
  • Gate and logging: verifies CHISEL_SELF_MAINTENANCE_ENABLED is 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_maintenance does not appear in DailyRoutineScheduler.schedule_all_routines() source — re-adding would double-schedule
  • Manual one-shot retained: verifies _run_self_maintenance still exists on DailyRoutineScheduler as an escape hatch via run_routine("self-maintenance")
  • Graceful degradation without GitLab: loop warns and continues when gitlab_client is None
  • 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
# Run self-maintenance wiring tests
pytest tests/test_self_maintenance_wiring.py -v
  • 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 shared SelfMaintenanceChassis from agents-shared. Opens DRAFT MRs for qualifying issues in chisel + watchtower repos.
  • config/personality.json — personality configuration consumed by PersonalityEngine.
  • 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.py in chisel
  • tests/test_self_maintenance_wiring.py in chisel
  • src/core/personality.py in chisel
  • src/core/scheduler.py in chisel
  • src/core/scheduler_utils.py in chisel
  • src/services/self_maintenance_service.py in chisel
  • src/slack/event_handler.py in chisel
  • src/mvp_slack_bot.py in chisel