agentleFS
WaniWani-AIWaniWani-AI/sdk/skills/instrument-tracking/SKILL.md

instrument-tracking

Skill18 starsChanged 5 months agoMITView on GitHub

Auto-instrument Waniwani funnel events across a createFlow app. Analyzes every flow in the project, decides where each tracking event belongs (lead_qualified, price_shown, prices_compared, option_selected, converted, identify), inserts the calls with the right metadata from flow state, and verifies the result. Trigger when the user wants to add tracking to a flow, track qualified leads, instrument a funnel, measure conversions, or right after scaffolding a new flow with @waniwani/sdk.

  • Reads credentials

The file

We split this skill into the rules it gives an agent

5 rules. About 0 are common, 1 is distinctive, and 4 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈1

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 4

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Do not add a source: leadQualified has no acquisition-source property.

    line 39
  2. Very specific:

    Not every flow has every stage.

    line 79
  3. Very specific:

    Do not await tracking calls inside hot paths unless the runtime is serverless.

    line 101
  4. Very specific:

    Every call is guarded (waniwani?.), and no call passes sessionId manually.

    line 107
  5. Distinctive:

    No invented event names; only taxonomy events.

    line 108

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Instrument tracking on a createFlow app

Read the project's flows, map each node to the Waniwani event taxonomy, insert the tracking calls with metadata sourced from flow state, and verify. The output is a flow where every funnel stage emits exactly the right event, without the user writing a single track call.

Hosted reference: docs.waniwani.ai/sdk/tracking/instrumentation

The taxonomy (closed set)

Events are typed and first-class. Never invent a custom event name; model every funnel step with one of these.

EventHelperWhen it firesRequired data
tool.calledautomatic via withWaniwani(server)Every tool invocationnone
lead_qualifiedwaniwani?.track.leadQualified({ externalId?, email?, name? })The conversation produced a concrete handoff to your businessidentity only
price_shownwaniwani?.track.priceShown({ amount, currency, itemId?, label? })You showed one priceamount, currency
prices_comparedwaniwani?.track.pricesCompared({ options: [{ id, amount, currency }] })You showed 2+ options side by sideoptions[]
option_selectedwaniwani?.track.optionSelected({ id, amount, currency })The user picked one optionid, amount, currency
convertedwaniwani?.track.converted({ amount, currency, occurredAt? })The user became payingamount, currency
identitywaniwani?.identify(userId, properties?)A stable external id becomes knownuserId

page.viewed, chat.*, and widget_render are emitted automatically by the chat widget and the widget hook. Never send them from server code.

Placement rules

Work these out per flow, in order:

  1. lead_qualified fires at the handoff, not at flow entry. A lead is qualified when the conversation produces a concrete handoff toward the business: the user is redirected to the pricing page or the business's own website, books a call, is sent an email, or meets an explicit qualification bar (finished the qualifying questions, requested a demo, matched the target profile). Place it where the handoff happens: the node that pushes the lead to a CRM or sends the email, or the widget callback right before the external redirect. Emit it exactly once per flow run. A user merely starting the flow is not a qualified lead; tool.called already covers activity. Latest definition: docs.waniwani.ai/sdk/tracking/events.
  2. lead_qualified metadata comes from flow state. Fill every property you can:

    • externalId: the strongest field. Use the record id your CRM or lead API returns (place the event after that push so the id exists).
    • email and name: map from the state fields that hold them, whatever they are called (email, workEmail, contactName, ...).

    Do not add a source: leadQualified has no acquisition-source property. The origin channel is set automatically on the event envelope.

  3. identify as soon as a stable id exists. The first node where an email, phone number, name, or user id is present in state gets waniwani?.identify(state.email). This is the join key for off-platform conversions. Sharing an email, phone number, or first and last name is identify, not lead_qualified; identified is not qualified yet.
  4. Price events go where the numbers are. The node that computes or returns a single price gets priceShown. A node that presents multiple plans (usually right before or inside the node feeding a comparison widget) gets pricesCompared. The node that runs after the user picked (the selected id is now in state) gets optionSelected.
  5. converted only on real conversion. A booking confirmed, a purchase completed, a signup finished inside the flow. If conversion happens later on the customer's own site, do not emit it from the flow; instead make sure identify ran, and add a snippet for their backend that calls client.track.converted({ amount, currency, externalUserId }).
  6. Emit from node handlers, never from validate callbacks. Action nodes are the natural home. For data collected by an interrupt, emit in the next node that runs after the answer landed in state.
  7. Always guard with waniwani?.. The scoped client is undefined when withWaniwani(server) was not applied, and tracking throws without WANIWANI_API_KEY. Optional chaining keeps keyless and OSS runs working.
  8. Never pass sessionId manually inside a flow. The scoped client (context.waniwani) carries session identity automatically.

Procedure

Step 1: Inventory

# Find every flow
grep -rln "createFlow(" src/ server/ lib/ app/ 2>/dev/null

# Is the server wrapped? (required for context.waniwani + tool.called auto-capture)
grep -rn "withWaniwani(" src/ server/ lib/ app/ 2>/dev/null

# Is the key configured? (tracking is free tier)
grep -l "WANIWANI_API_KEY" .env .env.local .env.example 2>/dev/null

If withWaniwani is missing, add it where the server is created: withWaniwani(server); (import from @waniwani/sdk/mcp). It is safe without an API key. If no key is configured anywhere, tell the user tracking needs WANIWANI_API_KEY (free at app.waniwani.ai) and continue; the guarded calls you add will no-op safely once shipped with a key.

Step 2: Map each flow

For every flow, read the full node graph and build a table: node id, node kind (interrupt / action / widget), which state fields are populated when it runs, and what the node does for the business (asks, computes, presents, confirms). Then assign events using the placement rules above. A typical mapping:

START -> welcome (interrupt: email)        -> [identify after answer]
      -> qualify (interrupt: role, size)
      -> push_to_crm (action, returns id)  -> lead_qualified { externalId, email, name }
      -> compute_quote (action)            -> price_shown { amount, currency }
      -> show_plans (widget: 3 options)    -> prices_compared { options }
      -> confirm_plan (action)             -> option_selected { id, amount, currency }
      -> book (action)                     -> converted { amount, currency }
      -> END

Not every flow has every stage. A lead-gen flow may end at lead_qualified; a support flow may only get identify. Never force events onto nodes that do not represent that stage.

Step 3: Apply

Insert the calls. waniwani comes from the node handler context, next to state and interrupt:

.addNode({
  id: "push_to_crm",
  label: "Push to CRM",
  run: async ({ state, waniwani }) => {
    const lead = await crm.createLead({ email: state.email, name: state.name });
    waniwani?.track.leadQualified({
      externalId: lead.id,
      email: state.email,
      name: state.name,
    });
    return { leadId: lead.id };
  },
})

Do not await tracking calls inside hot paths unless the runtime is serverless; the transport batches in the background. In serverless runtimes, pass flushAfterToolCall: true to withWaniwani() instead of flushing by hand.

Step 4: Verify

  • bun run typecheck (or the project's equivalent) passes.
  • Exactly one leadQualified call per flow, placed at the qualification bar.
  • Every call is guarded (waniwani?.), and no call passes sessionId manually.
  • No invented event names; only taxonomy events.
  • withWaniwani(server) wraps the server.
  • Report the final mapping (node -> event -> metadata) back to the user as a table.

Running as a subagent

Scaffolding playbooks (for example the Waniwani initialize playbook) invoke this skill as a follow-up step once a flow exists. Spawn a subagent with this prompt, adjusted for the project root:

Follow the instrument-tracking skill (skills/instrument-tracking/SKILL.md, or the
installed instrument-tracking skill) to instrument Waniwani funnel events across
every createFlow app in this project. Inventory the flows, map nodes to the event
taxonomy, insert guarded track calls with metadata from flow state (lead_qualified
with externalId/email/name where the handoff to the business happens), ensure
withWaniwani(server) wraps the server, run typecheck, and report the node -> event
mapping you applied.

The subagent needs write access to the project and nothing else; tracking keys are not required to apply the instrumentation.

Common mistakes

  • lead_qualified at flow entry. Entering a funnel is not qualifying. Use the node where your bar is met.
  • Emitting from validate callbacks. Validation can run multiple times per answer; you get duplicate events.
  • Unguarded calls. waniwani.track... without ?. crashes keyless runs; inside a node handler that throw fails the whole tool call.
  • Passing sessionId inside a flow. The scoped client already carries it; a manual value can mis-attribute the event.
  • Custom event names. The taxonomy is closed. track({ event: "my_step" }) is rejected by the types.
  • converted for "reached the last node". Finishing a conversation is not revenue. Only emit on actual purchase/booking, or from the customer's backend with externalUserId.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.