Agent skill

Event Tracking

by dilolabs in dilolabs/nosia

Builds event tracking, activity feeds, and webhook systems following 37signals patterns with a generic Event model and Eventable concern.

MITAuto-check passedBackend & APIs

Install Event Tracking

skills CLI
$ npx skills add dilolabs/nosia --skill event-tracking -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install dilolabs/nosia event-tracking --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/dilolabs/nosia.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.vibe/skills/event-tracking .claude/skills/event-tracking && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
event-tracking
GitHub stars
213
Used in
1 other repo
Token cost
~3.3k tokens
SKILL.md length
483 words
Files
4 (incl. references)
Skills in repo
15
Repo updated
First seen
Licence
MIT

At a glance

Builds event tracking, activity feeds, and webhook systems following 37signals patterns with a generic Event model and Eventable concern.

  • Implementing audit trails
  • SKILL.md covers Philosophy: Generic Event…, Project Knowledge, Pattern 1: Event Model and Pattern 2: Eventable Concern, plus 4 more sections
  • Calls rails
  • Event recording

What it does

Event Tracking is an agent skill from dilolabs/nosia. Builds event tracking, activity feeds, and webhook systems following 37signals patterns with a generic Event model and Eventable concern. Use when implementing audit trails, activity feeds, event recording, webhooks, or when user mentions events, tracking, webhooks, or activity logs. WHEN NOT: For state changes as records (use state-records), for background job patterns (use job-patterns), for mailer delivery (use mailer-patterns).

Its SKILL.md is about 3.3k tokens, which your agent loads only when the skill is triggered. The skill folder holds 4 other files, including reference files (for example `references/activity-feeds.md`, `references/domain-events.md` and `references/webhooks.md`). Compatibility notes: Ruby 3.3+, Rails 8.0+, Solid Queue

It sits in Backend & APIs, covering Product analytics, Webhooks and Background jobs. The repository describes itself as: Self-hosted AI RAG + MCP Platform. The licence is MIT.

When your agent uses it

  • Implementing audit trails
  • Event recording
  • User mentions events

Example prompts

  • “Use the event-tracking skill to build event tracking, activity feeds, and webhook systems following 37signals patterns with a generic Event model…”
  • “/event-tracking”

Requirements

  • Compatibility (from SKILL.md): Ruby 3.3+, Rails 8.0+, Solid Queue

What it can do on your machine

Read from SKILL.md and the folder at commit 0ef5e5d. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    Shell commands in SKILL.md call:

    • rails

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    No URLs in SKILL.md.

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

  • Compatibility

    Ruby 3.3+, Rails 8.0+, Solid Queue

    From compatibility in the SKILL.md frontmatter.

Context cost

Event Tracking loads about 3.3k tokens when it runs, and up to ~11k if it reads all its reference files. Until then it costs about 113 tokens; SKILL.md has 483 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~113
When it runs · the whole SKILL.md, loaded when a task matches
~3.3k
With references · SKILL.md plus every file in references/, read only if the agent opens them
~11k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from dilolabs/nosia at commit 0ef5e5d, republished under its MIT licence (© dilolabs). 483 words, ~3,292 tokens.

Download SKILL.mdSave it as .claude/skills/event-tracking/SKILL.md (or your agent's skills folder). This skill also uses 3 other files; get the full folder from GitHub.
name
event-tracking
description
Builds event tracking, activity feeds, and webhook systems following 37signals patterns with a generic Event model and Eventable concern. Use when implementing audit trails, activity feeds, event recording, webhooks, or when user mentions events, tracking, webhooks, or activity logs. WHEN NOT: For state changes as records (use state-records), for background job patterns (use job-patterns), for mailer delivery (use mailer-patterns).
compatibility
Ruby 3.3+, Rails 8.0+, Solid Queue
license
MIT

Event Tracking

Philosophy: Generic Event Model + Eventable Concern

  • One Event model with action string and eventable polymorphic association
  • An Eventable concern mixed into models that need tracking
  • Models call track_event("closed", particulars: {...}) in their domain methods
  • particulars JSON field stores action-specific metadata
  • Events drive activity feeds, notifications, and webhook deliveries
  • Everything is database-backed (Solid Queue for webhooks, no Redis/Kafka)

Project Knowledge

Stack: Solid Queue for background jobs, Turbo Streams for real-time activity feed updates, UUIDs for all primary keys, MySQL (SaaS) / SQLite (OSS).

Multi-tenancy: All events scoped to account via account_id. Events also scoped to board via board_id.

Commands:

bash
# Generate Event model
rails generate model Event action:string eventable:references{polymorphic} \
  board:references creator:references account:references particulars:json

# Generate Webhook models
rails generate model Webhook url:text name:string board:references \
  account:references subscribed_actions:text signing_secret:string active:boolean
rails generate model Webhook::Delivery webhook:references event:references \
  account:references state:string request:text response:text
rails generate model Webhook::DelinquencyTracker webhook:references \
  account:references consecutive_failures_count:integer first_failure_at:datetime

Pattern 1: Event Model

See @references/domain-events.md for full implementation details.

A single generic Event model records all business events:

ruby
# app/models/event.rb
class Event < ApplicationRecord
  include Notifiable, Particulars

  belongs_to :account, default: -> { board.account }
  belongs_to :board
  belongs_to :creator, class_name: "User"
  belongs_to :eventable, polymorphic: true

  has_many :webhook_deliveries, class_name: "Webhook::Delivery", dependent: :delete_all

  scope :chronologically, -> { order created_at: :asc, id: :desc }
  scope :preloaded, -> {
    includes(:creator, :board, {
      eventable: [
        :closure, :image_attachment,
        { rich_text_body: :embeds_attachments },
        { card: [ :closure, :image_attachment ] }
      ]
    })
  }

  after_create -> { eventable.event_was_created(self) }
  after_create_commit :dispatch_webhooks

  delegate :card, to: :eventable

  def action
    super.inquiry
  end

  def description_for(user)
    Event::Description.new(self, user)
  end

  private
    def dispatch_webhooks
      Event::WebhookDispatchJob.perform_later(self)
    end
end

Key design decisions:

  • action is a plain string like "card_closed", "comment_created", "card_triaged". Calling .inquiry lets you do event.action.card_closed?.
  • eventable points to the model that triggered the event (Card, Comment, etc.).
  • particulars is a JSON column for action-specific metadata (old title, new board name, assignee IDs, column name, etc.).
  • after_create (not after_create_commit) calls back into the eventable so it can create system comments or touch timestamps within the same transaction.
  • after_create_commit dispatches webhooks asynchronously.

Pattern 2: Eventable Concern

See @references/domain-events.md for the full concern hierarchy.

The base Eventable concern provides track_event to any model:

ruby
# app/models/concerns/eventable.rb
module Eventable
  extend ActiveSupport::Concern

  included do
    has_many :events, as: :eventable, dependent: :destroy
  end

  def track_event(action, creator: Current.user, board: self.board, **particulars)
    if should_track_event?
      board.events.create!(
        action: "#{eventable_prefix}_#{action}",
        creator:, board:, eventable: self, particulars:
      )
    end
  end

  def event_was_created(event)
  end

  private
    def should_track_event?
      true
    end

    def eventable_prefix
      self.class.name.demodulize.underscore
    end
end

Models override Eventable with model-specific concerns that customize behavior:

ruby
# app/models/card/eventable.rb
module Card::Eventable
  extend ActiveSupport::Concern

  include ::Eventable

  included do
    after_save :track_title_change, if: :saved_change_to_title?
  end

  def event_was_created(event)
    transaction do
      create_system_comment_for(event)
      touch_last_active_at
    end
  end

  private
    def should_track_event?
      published?
    end

    def track_title_change
      if title_before_last_save.present?
        track_event "title_changed",
          particulars: { old_title: title_before_last_save, new_title: title }
      end
    end
end

Usage in domain methods -- models call track_event directly:

ruby
# app/models/card/closeable.rb
def close(user: Current.user)
  unless closed?
    transaction do
      create_closure! user: user
      track_event :closed, creator: user
    end
  end
end

def reopen(user: Current.user)
  if closed?
    transaction do
      closure&.destroy
      track_event :reopened, creator: user
    end
  end
end

# app/models/card/assignable.rb
def assign(user)
  assignment = assignments.create assignee: user, assigner: Current.user
  if assignment.persisted?
    track_event :assigned, assignee_ids: [ user.id ]
  end
end

# app/models/card/triageable.rb
def triage_into(column)
  transaction do
    update! column: column
    track_event "triaged", particulars: { column: column.name }
  end
end

# app/models/card/postponable.rb
def postpone(user: Current.user, event_name: :postponed)
  transaction do
    create_not_now!(user: user) unless postponed?
    track_event event_name, creator: user
  end
end

Pattern 3: Particulars (Event Metadata)

The particulars JSON column stores action-specific data. Use store_accessor to provide typed access to common fields:

ruby
# app/models/event/particulars.rb
module Event::Particulars
  extend ActiveSupport::Concern

  included do
    store_accessor :particulars, :assignee_ids
  end

  def assignees
    @assignees ||= User.where id: assignee_ids
  end
end

Examples of particulars stored per action:

ActionParticulars
card_title_changed{ old_title: "...", new_title: "..." }
card_board_changed{ old_board: "...", new_board: "..." }
card_assigned{ assignee_ids: [uuid] }
card_unassigned{ assignee_ids: [uuid] }
card_triaged{ column: "In Progress" }
Show full SKILL.md (211 more words)Show less

Pattern 4: Activity Feed

See @references/activity-feeds.md for view templates and pagination.

Events ARE the activity feed. No separate Activity model needed:

ruby
# app/controllers/events_controller.rb
class EventsController < ApplicationController
  def index
    @events = Current.account.boards
      .accessible_by(Current.user)
      .events.preloaded.chronologically
  end
end

Events are rendered via partials that dispatch on action or eventable_type:

erb
<%# app/views/events/_event.html.erb %>
<% cache event do %>
  <% if lookup_context.exists?("events/event/eventable/_#{event.action}") %>
    <%= render "events/event/eventable/#{event.action}", event: event %>
  <% else %>
    <%= render "events/event/eventable/#{event.eventable_type.demodulize.underscore}",
               event: event %>
  <% end %>
<% end %>

Event descriptions are handled by a dedicated class:

ruby
# app/models/event/description.rb
class Event::Description
  def initialize(event, user)
    @event = event
    @user = user
  end

  def to_html
    # Renders "You closed \"Card title\"" or "Alice closed \"Card title\""
    # depending on whether user == creator
  end

  def to_plain_text
    # Plain text version for webhooks, emails, etc.
  end
end

Pattern 5: Webhook System

See @references/webhooks.md for full implementation details.

ruby
# app/models/webhook.rb
class Webhook < ApplicationRecord
  include Triggerable

  PERMITTED_ACTIONS = %w[
    card_assigned card_closed card_postponed card_published
    card_reopened card_triaged card_unassigned comment_created
  ].freeze

  has_secure_token :signing_secret

  has_many :deliveries, dependent: :delete_all
  has_one :delinquency_tracker, dependent: :delete

  belongs_to :account, default: -> { board.account }
  belongs_to :board

  serialize :subscribed_actions, type: Array, coder: JSON

  scope :active, -> { where(active: true) }

  normalizes :subscribed_actions,
    with: ->(value) { Array.wrap(value).map(&:to_s).uniq & PERMITTED_ACTIONS }

  validates :name, presence: true
  validate :validate_url

  def activate
    update! active: true unless active?
  end

  def deactivate
    update! active: false
  end
end

# app/models/webhook/triggerable.rb
module Webhook::Triggerable
  extend ActiveSupport::Concern

  included do
    scope :triggered_by, ->(event) {
      where(board: event.board).triggered_by_action(event.action)
    }
    scope :triggered_by_action, ->(action) {
      where("subscribed_actions LIKE ?", "%\"#{action}\"%")
    }
  end

  def trigger(event)
    deliveries.create!(event: event) unless account.cancelled?
  end
end

Webhook dispatch uses ActiveJob::Continuable for resumable processing:

ruby
# app/jobs/event/webhook_dispatch_job.rb
class Event::WebhookDispatchJob < ApplicationJob
  include ActiveJob::Continuable

  queue_as :webhooks
  discard_on ActiveJob::DeserializationError

  def perform(event)
    step :dispatch do |step|
      Webhook.active.triggered_by(event).find_each(start: step.cursor) do |webhook|
        webhook.trigger(event)
        step.advance! from: webhook.id
      end
    end
  end
end

Deliveries handle SSRF protection, format detection, and delinquency tracking:

ruby
# app/models/webhook/delivery.rb
class Webhook::Delivery < ApplicationRecord
  belongs_to :webhook
  belongs_to :event
  belongs_to :account, default: -> { webhook.account }

  store :request, coder: JSON
  store :response, coder: JSON

  enum :state, %w[ pending in_progress completed errored ].index_by(&:itself),
    default: :pending

  after_create_commit :deliver_later

  def deliver
    in_progress!
    self.request[:headers] = headers
    self.response = perform_request
    self.state = :completed
    save!
    webhook.delinquency_tracker.record_delivery_of(self)
  rescue
    errored!
    raise
  end

  private
    def headers
      { "User-Agent" => "app/1.0.0 Webhook",
        "Content-Type" => content_type,
        "X-Webhook-Signature" => signature,
        "X-Webhook-Timestamp" => event.created_at.utc.iso8601 }
    end

    def signature
      OpenSSL::HMAC.hexdigest("SHA256", webhook.signing_secret, payload)
    end
end

# app/models/webhook/delinquency_tracker.rb
class Webhook::DelinquencyTracker < ApplicationRecord
  DELINQUENCY_THRESHOLD = 10
  DELINQUENCY_DURATION = 1.hour

  belongs_to :webhook

  def record_delivery_of(delivery)
    if delivery.succeeded?
      reset
    else
      mark_first_failure_time if consecutive_failures_count.zero?
      increment!(:consecutive_failures_count, touch: true)
      webhook.deactivate if delinquent?
    end
  end

  private
    def delinquent?
      failing_for_too_long? && too_many_consecutive_failures?
    end

    def failing_for_too_long?
      first_failure_at&.before?(DELINQUENCY_DURATION.ago)
    end

    def too_many_consecutive_failures?
      consecutive_failures_count >= DELINQUENCY_THRESHOLD
    end
end

Boundaries

Always
  • Use a single generic Event model with action string and eventable polymorphic
  • Create an Eventable concern -- models call track_event in domain methods
  • Store action-specific data in particulars JSON column
  • Dispatch webhooks via background jobs (Solid Queue)
  • Include HMAC signature in webhook headers (X-Webhook-Signature)
  • Scope events to account and board
  • Use after_create_commit for webhook dispatch (async after transaction)
  • Use after_create (sync) for side effects that need the transaction (system comments)
Ask First
  • Which business events/actions to track
  • Webhook retry strategy and delinquency threshold
  • Activity feed pagination and filtering requirements
  • Whether events should create system comments on the eventable
  • SSRF protection requirements for webhook delivery
Never
  • Create separate model classes per event type (no CardMoved, CommentAdded models)
  • Use external event bus (Kafka, RabbitMQ)
  • Track boolean flags instead of event records
  • Deliver webhooks synchronously
  • Skip SSRF protection on webhook URLs
  • Store events without account + board scoping

© dilolabs, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

SKILL.md and 3 other files (references) in .vibe/skills/event-tracking of dilolabs/nosia.

  • SKILL.md
  • references/activity-feeds.md
  • references/domain-events.md
  • references/webhooks.md

Open the folder on GitHubat commit 0ef5e5d

Used in 1 other repository

We found 3 copies of this SKILL.md (exact, near-identical or edited) in other folders, from 1 other GitHub owner. This page covers the copy in dilolabs/nosia, which our catalogue first saw on October 7, 2026.

Compare with similar skills

Event Tracking next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Event Tracking compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Event Tracking this skilldilolabs/nosia2131 repos~3.3kAutomated safety check: PassMIT
Klaviyo Developerthatrebeccarae/claude-marketing162—~4.9kAutomated safety check: NotesMIT
Function Devbutterbase-ai/butterbase-skills534—~2.8kAutomated safety check: PassMIT
Upstash Qstashdavila7/claude-code-templates32k5 repos~597Automated safety check: PassMIT
Inngest Durable FunctionsAsymmetric-al/core381—~4kAutomated safety check: PassAGPL-3.0
TrackingBuilderIO/agent-native7.1k—~7.8kAutomated safety check: PassNone

Similar skills

  • Klaviyo Developer

    thatrebeccarae/claude-marketing

    Klaviyo API and developer integration expertise. An agent skill from thatrebeccarae/claude-marketing.

    162 GitHub stars~4.9k tokensUpdated 4 mo ago
    Backend & APIsAuto-check: notes
  • Function Dev

    butterbase-ai/butterbase-skills

    A skill your agent uses when developing, deploying, or debugging Butterbase serverless functions, or when the user needs to add backend logic like webhooks, scheduled jobs, or custom API endpoints

    534 GitHub stars~2.8k tokensUpdated 3 days ago
    Backend & APIsAuto-check passed
  • Upstash Qstash

    davila7/claude-code-templates

    Upstash QStash expert for serverless message queues, scheduled jobs, and reliable HTTP-based task delivery without managing infrastructure.

    32k GitHub starsUsed in 5 repos~597 tokens
    Backend & APIsAuto-check passed
  • Inngest Durable Functions

    Asymmetric-al/core

    A skill your agent uses when building functions that must survive process crashes, retry automatically on failure, run on a schedule, react to events, or maintain state across infrastructure…

    381 GitHub stars~4k tokensUpdated today
    Backend & APIsAuto-check passed
  • Tracking

    BuilderIO/agent-native

    Server-side analytics tracking with pluggable providers. An agent skill from BuilderIO/agent-native.

    7.1k GitHub stars~7.8k tokensUpdated today
    Backend & APIsAuto-check passed
  • Instantly Observability

    jeremylongshore/tons-of-skills-marketplace

    Monitor Instantly API, campaign sending, account vitals, background jobs, and webhook delivery with safe telemetry.

    2.8k GitHub stars~1.1k tokensUpdated today
    Backend & APIsAuto-check passed

More from dilolabs/nosia

All 15 skills in this repo
  • API Patterns

    dilolabs/nosia

    Builds REST APIs using respondto blocks with Jbuilder templates following the 37signals same-controllers-different-formats philosophy.

    213 GitHub starsUsed in 1 repo~2.5k tokens
    Auto-check passed
  • Auth Setup

    dilolabs/nosia

    Implements custom passwordless authentication without Devise.

    213 GitHub starsUsed in 1 repo~2.9k tokens
    Auto-check passed
  • Caching Patterns

    dilolabs/nosia

    Implements HTTP caching with ETags, fragment caching, Russian doll caching, and Solid Cache configuration.

    213 GitHub starsUsed in 1 repo~1.9k tokens
    Auto-check passed
  • Concern Patterns

    dilolabs/nosia

    Creates and refactors model and controller concerns for shared behavior.

    213 GitHub starsUsed in 1 repo~1.8k tokens
    Auto-check passed
  • Job Patterns

    dilolabs/nosia

    Implements shallow background jobs with later/now conventions using Solid Queue.

    213 GitHub starsUsed in 1 repo~1.6k tokens
    Auto-check passed
  • Mailer Patterns

    dilolabs/nosia

    Creates minimal Action Mailer classes with bundled notification patterns following 37signals conventions.

    213 GitHub starsUsed in 1 repo~2.6k tokens
    Auto-check passed

Categories

Questions about Event Tracking

What does Event Tracking do?

Builds event tracking, activity feeds, and webhook systems following 37signals patterns with a generic Event model and Eventable concern. Event Tracking is an agent skill from dilolabs/nosia. Builds event tracking, activity feeds, and webhook systems following 37signals patterns with a generic Event model and Eventable concern.

When should I use Event Tracking?

Event Tracking fits situations like: implementing audit trails; event recording; user mentions events.

How do I install Event Tracking in Claude Code?

Run `npx skills add dilolabs/nosia --skill event-tracking -a claude-code`. Or copy the skill folder (.vibe/skills/event-tracking in dilolabs/nosia) into .claude/skills/event-tracking in your project. Claude Code loads it when a task matches its description.

How do I install Event Tracking in Codex?

Run `npx skills add dilolabs/nosia --skill event-tracking -a codex`. Or copy the skill folder (.vibe/skills/event-tracking in dilolabs/nosia) into .agents/skills/event-tracking in your project. Codex loads it when a task matches its description.

Can I use Event Tracking in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add dilolabs/nosia --skill event-tracking -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/event-tracking, .gemini/skills/event-tracking, .github/skills/event-tracking and .opencode/skills/event-tracking in your project.

What does Event Tracking need to run?

Going by SKILL.md and its folder, Event Tracking needs the command-line tools its instructions call (rails). Compatibility (from SKILL.md): Ruby 3.3+, Rails 8.0+, Solid Queue.

Does Event Tracking access the network?

SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.

Is Event Tracking safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.

What licence does Event Tracking use?

Event Tracking is published under the MIT licence (declared in SKILL.md). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Event Tracking use?

About 3.3k tokens (SKILL.md is roughly 13k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full. Its references folder adds about 7.9k tokens, read only when the agent opens those files.

What are the alternatives to Event Tracking?

Skills that share tags, products or a category with Event Tracking: Klaviyo Developer (thatrebeccarae/claude-marketing, 162 stars), Function Dev (butterbase-ai/butterbase-skills, 534 stars), Upstash Qstash (davila7/claude-code-templates, 32k stars) and Inngest Durable Functions (Asymmetric-al/core, 381 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Event Tracking?

dilolabs (a GitHub organization) maintains it in dilolabs/nosia, which has 213 GitHub stars. The repository holds 15 skills in this directory. The repository was last updated on September 9, 2026.

Source: dilolabs/nosia on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.