Skip to main content

Squillify development is paused indefinitely. Read why, and discover what this experiment achieved. Read the story

MicroSkills Reference

Incident Timeline Builder

observability.build_incident_timeline

ProductionBounded custom (Class B)Deterministicstrictfastv1.0.0
No network

Overview

You send the events with their timestamps and we return them in one defensible order, with the interval since the previous row, the interval since the start, and a list of the silences longer than the threshold you set. The order is instant, then your sequence, then input position, and never any text you sent, because sorting on text makes the order depend on the server's locale. A repeated identifier is refused, but two events at the same instant with no identifier are concurrent rather than duplicated and both appear.

Assembling a timeline by hand goes wrong in the same three places every time: two events at the same second order differently on each run, a local timestamp gets read in the reader's zone, and the silences nobody noticed never make it into the review.

Reliability

Fully deterministic and locale-independent: the order never depends on text, so a Turkish or Japanese default locale produces the identical timeline. Descriptions are echoed truncated at 200 code points and are not redacted, so send them through a redaction skill first when they may carry secrets. Failures, none worth retrying unchanged: OBS_TIMELINE_EVENTS_INVALID means events was absent or empty, or an entry had the wrong shape or an over-long field. OBS_TIMELINE_EVENT_LIMIT_EXCEEDED means more than 10000 events arrived, so split the incident by phase. OBS_TIMELINE_TIMESTAMP_INVALID means a timestamp was absent or unparseable. OBS_TIMELINE_TIMESTAMP_LOCAL_NOT_ALLOWED means a timestamp carried no offset, and the answer names its index; add Z or an explicit offset. OBS_TIMELINE_OFFSET_MIXED means the events mixed offsets under REQUIRE_UNIFORM or carried a non-UTC one under REQUIRE_UTC, with both positions named. OBS_TIMELINE_EVENT_ID_DUPLICATE means an identifier repeated, and the answer names both indices; drop the identifier or make it unique. OBS_TIMELINE_KIND_UNSUPPORTED means a kind is not one of the nine. OBS_TIMELINE_GAP_THRESHOLD_INVALID means gapThresholdSeconds was absent or outside 1 to 604800. OBS_TIMELINE_WINDOW_INVALID means the window end was not after its start or the span fell outside 1 millisecond to 366 days. OBS_TIMELINE_OPTION_UNSUPPORTED means a property or an offset policy is not one we accept. OBS_TIMELINE_OUTPUT_TOO_LARGE means the rows would not fit the inline response ceiling, so set includeDescriptions false or send fewer events.

Context footprint

Echoes one row per event with a description of at most 200 code points; turn descriptions off for a compact ordering. A successful call costs 2 execution credits.

When to use it

When you hold incident events from several sources and need one order everybody can reproduce, plus the silences worth asking about.

When you want the events grouped by error class, summarised statistically, or redacted.

Scope

Note
Order incident events by instant, sequence and input position, and report the intervals that exceed a caller-set silence threshold.

Non-goals

  • Does not decide what caused what, or write a narrative
  • Does not redact a description — a token you send comes back
  • Does not merge, deduplicate or collapse repeated events
  • Does not invent boundaries: edge gaps appear only when you supply a window
  • Does not read a clock or fetch anything

Properties

IdempotentRetry safeStreaming supportedFile input supportedRequires external service

Capabilities

analysis

Cost & billing

Flat rate per invocation

MetercallsBillable onsuccessCost per call2 credits

File handling

Accepts file_refProduces file_refMax input1 MB

How we handle your data

Sensitive output possible
What this means

This skill may receive sensitive content and may return submitted, transformed, extracted, or reformatted content.

Squillify guarantees
  • Runs inside Squillify
  • This skill runs inside Squillify.
  • Payloads are not sent to an external service by this skill.
  • Squillify does not permanently store submitted payloads.
  • Telemetry records metadata only, not submitted content.
  • Submitted payloads are not used for training.
  • Submitted payloads are not reviewed by humans.
  • This skill does not target sensitive data.
  • The response may contain submitted or transformed content.
Your responsibility

Squillify does not determine whether submitted content is sensitive. Your application is responsible for deciding what data it is allowed to submit. Your application is responsible for securing the response.

Read the Microskill Data Handling Policy

Input schema

FieldTypeRequiredDescription
eventsarrayYesIncident events, 1 to 10000 closed objects each carrying at plus optional kind, source, id, sequence and description.
gapThresholdSecondsintegerYesSilence worth reporting, from 1 through 604800 seconds.
windowobjectNoOptional half-open window [start, end) as ISO-8601 instants with explicit offsets; omit it and only gaps between events are reported.
includeDescriptionsbooleanNoWhether descriptions are echoed; omit it for true.
offsetPolicystringNoOffset mixing rule; omit it for REQUIRE_UNIFORM.

Output schema

FieldTypeRequiredDescription
timelinearrayYesOne closed row per event in published order, 1 to 10000 entries.
gapsarrayYesUp to 1000 silences, each a closed {fromPosition, toPosition, start, end, durationSeconds, edge} object.
summaryobjectYesClosed summary with eventCount, firstAt, lastAt, spanSeconds, gapCount, gapsTruncated, longestGapSeconds, gapThresholdSeconds, offsetPolicy and windowApplied.

API contract

POST /v1/execute
curl
curl -sS -X POST "$BASE/v1/execute" -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' -d '{"skillKey":"observability.build_incident_timeline","input":{"events":[{"at":"2026-07-29T10:00:00Z","kind":"ALERT"}],"gapThresholdSeconds":600}}'
Integrations
TypeScript SDK
import { Squillify } from "@squillify/sdk";

const sq = new Squillify({ apiKey: process.env.SQUILLIFY_API_KEY });

const { output } = await sq.execute("observability.build_incident_timeline", {"gapThresholdSeconds":600,"events":[{"at":"2026-07-29T10:40:00Z","description":"rolled back","kind":"MITIGATION"},{"at":"2026-07-29T10:00:00Z","description":"error rate above threshold","kind":"ALERT"},{"at":"2026-07-29T10:02:00Z","description":"release 4.2 rolled out","kind":"DEPLOY"}]});

Errors

CodeHTTPRetryableDescription
VALIDATION_ERROR400NoThe request payload failed schema validation.
UNAUTHORIZED401NoMissing or invalid API key.
SKILL_NOT_ACCESSIBLE403NoThe API key is not granted access to this skill.
SKILL_NOT_AVAILABLE_FOR_PLAN403NoThis skill is not included in your current plan.
PLAN_ELIGIBILITY_UNAVAILABLE503YesPlan eligibility could not be decided. Retry after backoff.
SKILL_NOT_FOUND404NoThe requested skill or version does not exist.
TENANT_RATE_LIMITED429YesRequest rate limit exceeded. Retry after backoff.
QUOTA_EXCEEDED429YesPlan quota exhausted. Retryable once the quota period resets.
EXECUTION_TIMEOUT504YesExecution exceeded the allowed time budget.
OBS_TIMELINE_EVENTS_INVALID400NoThe request payload failed this skill's input validation.
OBS_TIMELINE_EVENT_LIMIT_EXCEEDED429NoA size, quota, rate, or time limit was exceeded.
OBS_TIMELINE_TIMESTAMP_INVALID400NoThe request payload failed this skill's input validation.
OBS_TIMELINE_TIMESTAMP_LOCAL_NOT_ALLOWED400NoThe request payload failed this skill's input validation.
OBS_TIMELINE_OFFSET_MIXED400NoThe request payload failed this skill's input validation.
OBS_TIMELINE_EVENT_ID_DUPLICATE400NoThe request payload failed this skill's input validation.
OBS_TIMELINE_KIND_UNSUPPORTED400NoThe skill does not support the requested variant.
OBS_TIMELINE_GAP_THRESHOLD_INVALID400NoThe request payload failed this skill's input validation.
OBS_TIMELINE_WINDOW_INVALID400NoThe request payload failed this skill's input validation.
OBS_TIMELINE_OPTION_UNSUPPORTED400NoThe skill does not support the requested variant.
OBS_TIMELINE_OUTPUT_TOO_LARGE429NoA size, quota, rate, or time limit was exceeded.

Other limits

LimitValue
Recommended inline bytes32 KiB

Limitations

  • Does not decide what caused what, or write a narrative
  • Does not redact a description — a token you send comes back
  • Does not merge, deduplicate or collapse repeated events
  • Does not invent boundaries: edge gaps appear only when you supply a window
  • Does not read a clock or fetch anything
  • Grouping repeated errors by class, which observability.extract_error_events answers
  • Removing secrets from event descriptions before sharing the timeline

How to call

POST /v1/execute (requires authentication)

Request bodyjson
{
  "skillKey": "observability.build_incident_timeline",
  "input": {
    "gapThresholdSeconds": 600,
    "events": [
      {
        "at": "2026-07-29T10:40:00Z",
        "description": "rolled back",
        "kind": "MITIGATION"
      },
      {
        "at": "2026-07-29T10:00:00Z",
        "description": "error rate above threshold",
        "kind": "ALERT"
      },
      {
        "at": "2026-07-29T10:02:00Z",
        "description": "release 4.2 rolled out",
        "kind": "DEPLOY"
      }
    ]
  }
}

Examples

Three shuffled events ordered, with the silence between two of them

Inputjson
{
  "gapThresholdSeconds": 600,
  "events": [
    {
      "at": "2026-07-29T10:40:00Z",
      "description": "rolled back",
      "kind": "MITIGATION"
    },
    {
      "at": "2026-07-29T10:00:00Z",
      "description": "error rate above threshold",
      "kind": "ALERT"
    },
    {
      "at": "2026-07-29T10:02:00Z",
      "description": "release 4.2 rolled out",
      "kind": "DEPLOY"
    }
  ]
}
Expected outputjson
{
  "summary": {
    "eventCount": 3,
    "firstAt": "2026-07-29T10:00:00Z",
    "lastAt": "2026-07-29T10:40:00Z",
    "spanSeconds": 2400,
    "gapCount": 1,
    "gapsTruncated": false,
    "longestGapSeconds": 2280,
    "gapThresholdSeconds": 600,
    "offsetPolicy": "REQUIRE_UNIFORM",
    "windowApplied": false
  },
  "timeline": [
    {
      "position": 1,
      "at": "2026-07-29T10:00:00Z",
      "kind": "ALERT",
      "description": "error rate above threshold",
      "descriptionTruncated": false,
      "secondsSincePrevious": null,
      "secondsSinceStart": 0
    },
    {
      "position": 2,
      "at": "2026-07-29T10:02:00Z",
      "kind": "DEPLOY",
      "description": "release 4.2 rolled out",
      "descriptionTruncated": false,
      "secondsSincePrevious": 120,
      "secondsSinceStart": 120
    },
    {
      "position": 3,
      "at": "2026-07-29T10:40:00Z",
      "kind": "MITIGATION",
      "description": "rolled back",
      "descriptionTruncated": false,
      "secondsSincePrevious": 2280,
      "secondsSinceStart": 2400
    }
  ],
  "gaps": [
    {
      "fromPosition": 2,
      "toPosition": 3,
      "start": "2026-07-29T10:02:00Z",
      "end": "2026-07-29T10:40:00Z",
      "durationSeconds": 2280,
      "edge": false
    }
  ]
}
Tip
The thirty-eight minute silence between the deploy and the mitigation clears the ten-minute threshold and the two-minute one does not.

Two events at the same instant ordered by sequence

Inputjson
{
  "events": [
    {
      "at": "2026-07-29T10:00:00Z",
      "sequence": 2,
      "kind": "COMMUNICATION"
    },
    {
      "at": "2026-07-29T10:00:00Z",
      "sequence": 1,
      "kind": "INCIDENT_DECLARED"
    }
  ],
  "includeDescriptions": false,
  "gapThresholdSeconds": 60
}
Expected outputjson
{
  "summary": {
    "eventCount": 2,
    "firstAt": "2026-07-29T10:00:00Z",
    "lastAt": "2026-07-29T10:00:00Z",
    "spanSeconds": 0,
    "gapCount": 0,
    "gapsTruncated": false,
    "longestGapSeconds": 0,
    "gapThresholdSeconds": 60,
    "offsetPolicy": "REQUIRE_UNIFORM",
    "windowApplied": false
  },
  "timeline": [
    {
      "position": 1,
      "at": "2026-07-29T10:00:00Z",
      "kind": "INCIDENT_DECLARED",
      "descriptionTruncated": false,
      "secondsSincePrevious": null,
      "secondsSinceStart": 0
    },
    {
      "position": 2,
      "at": "2026-07-29T10:00:00Z",
      "kind": "COMMUNICATION",
      "descriptionTruncated": false,
      "secondsSincePrevious": 0,
      "secondsSinceStart": 0
    }
  ],
  "gaps": []
}
Tip
Sharing an instant makes two events concurrent rather than duplicated, and the sequence you supplied decides which reads first.
  • Assemble a postmortem timeline from alerts, deploys and human actions
  • Find the silences in an incident where nobody recorded anything

Avoid for

  • Grouping repeated errors by class, which observability.extract_error_events answers
  • Removing secrets from event descriptions before sharing the timeline

Composable with

Safety & privacy

  • Runs without calling any external service.
  • Your payloads are never stored permanently.
  • Telemetry captures metadata only — never your payload contents.
  • We never train models on your payloads.
  • No human reviews your payloads.

Tags

analysisobservabilityincidenttimeline