Ring:Using Tracing logo

Ring:Using Tracing

Organization
LerianStudio
ring:using-tracing

Using lib-observability/tracing for OTEL provider lifecycle, trace-context propagation across HTTP/gRPC/queues, span error/event recording, and PII redaction, in two modes. Sweep Mode detects raw OTEL setup, hand-rolled header propagation, manual span-attribute assembly, and DIY redaction. Reference Mode catalogs Telemetry, Redactor, and propagation/span helpers. Go-only. Skip for non-Go or frontend code.

Overview

PublisherLerianStudio
Repositoryring
Skill namering:using-tracing
Stars
215
Forks
28
Bundled files
Instructions only
LicenseApache-2.0
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • Self-contained

    Everything the model needs lives in the instructions — no extra files to sync.

  • Open source

    Published by LerianStudio on GitHub. Read the source before you install it.

Installation

Install the Ring:Using Tracing AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/LerianStudio/ring.git /tmp/ring
mkdir -p .claude/skills
cp -r /tmp/ring/dev-team/skills/using-tracing .claude/skills/lerianstudio-ring-using-tracing
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Ring:Using Tracing in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Ring:Using Tracing on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Ring:Using Tracing is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

ring:using-tracing

When to use

Sweep mode:

  • "Sweep / audit tracing setup"
  • "Find raw OpenTelemetry usage we should replace"
  • "Are our HTTP/gRPC boundaries propagating trace context?"
  • "Is there DIY field redaction in spans?"
  • "Migrate this service to lib-observability/tracing"

Reference mode:

  • "How do I bootstrap Telemetry for a new service?"
  • "Which Inject/Extract helper do I use for X transport?"
  • "How does the Redactor pipeline work?"
  • "How do I record a business-error event on a span?"
  • "What does RedactingAttrBagSpanProcessor do?"

Skip when

  • Working on non-Go services
  • Working on frontend code
  • Target codebase does not depend on github.com/LerianStudio/lib-observability

Related

Parent: ring:using-lib-observability Similar: ring:using-runtime, ring:using-assert, ring:using-lib-commons

The tracing subpackage owns OTEL provider lifecycle, trace context propagation, span helpers, and the attribute-redaction pipeline (RedactingAttrBagSpanProcessor is wired automatically inside NewTelemetry). Use this skill when tracing is the primary concern. For broader lib-observability sweeps (logging, metrics, panic recovery), invoke ring:using-lib-observability.

Mode Selection

Request ShapeMode
"Sweep / audit tracing / find raw OTEL / find untraced boundaries"Sweep
"How do I bootstrap Telemetry?"Reference
"Inject/Extract helper for HTTP/gRPC/queue?"Reference
"How does Redactor / span processor work?"Reference

SWEEP MODE

5-phase sweep. Each phase has a hard gate — do not proceed until the current phase produces its artifact.

Phase 1: Version Reconnaissance   → tracing-version-report.json
Phase 2: CHANGELOG Delta Analysis → tracing-delta-report.json
Phase 3: Multi-Angle DIY Sweep    → 6 × tracing-sweep-{N}-{angle}.json
Phase 4: Consolidated Report      → tracing-sweep-report.md + tracing-sweep-tasks.json
Phase 5: Handoff                  → offer ring:running-dev-cycle dispatch

Phase 1: Version Reconnaissance

  1. Read go.mod — extract pinned version of github.com/LerianStudio/lib-observability
  2. WebFetch https://api.github.com/repos/LerianStudio/lib-observability/releases/latest — extract tag_name
  3. Classify drift: up-to-date / minor-drift / moderate-drift / major-upgrade / not-imported
  4. Emit /tmp/tracing-version-report.json: {pinned_version, latest_version, drift_classification, module_path}

Phase 2: CHANGELOG Delta Analysis

  1. WebFetch https://raw.githubusercontent.com/LerianStudio/lib-observability/main/CHANGELOG.md
  2. Filter entries affecting tracing/ (otel.go, obfuscation.go, processor.go)
  3. Emit /tmp/tracing-delta-report.json with classified entries (new-api / breaking-change / security-fix / bugfix)

Phase 3: Multi-Angle DIY Sweep

⛔ STOP-CHECK BEFORE DISPATCH

Before emitting any Task call, count the explorers you intend to launch in this turn.

  • Count MUST equal 6.
  • If count < 6 → STOP. Do not partial-dispatch. Reconcile against the 6 angles below and try again.
  • The 6 angles are the canonical sweep. No substitutions, no omissions.

⛔ MUST NOT trickle-dispatch

All 6 explorers leave in the SAME TURN, before reading any explorer output.

Forbidden sequences:

  • Dispatch explorer 1 → read result → dispatch explorer 2
  • Dispatch a subset → wait → dispatch the rest
  • Dispatch follow-up explorers conditioned on partial output
  • Loop sequentially over the angle list

If you find yourself about to dispatch an explorer in a turn AFTER any explorer has already returned a result → STOP. You violated parallel dispatch. Report the violation and mark the phase INCOMPLETE rather than completing the trickle.

Self-verify after dispatch

After the dispatch turn, verify all 6 Task calls were emitted in that single turn. If fewer than 6 went out, the phase did NOT execute correctly. Mark INCOMPLETE and surface the dispatch failure — do NOT silently continue with a partial pool.

Parallel dispatch — atomic batch

Emit all 6 Task calls in a SINGLE TURN, as one atomic batch.

If your runtime exposes a multi_tool_use.parallel wrapper, use it to dispatch the complete pool in one wrapped invocation. This is the canonical fan-out mechanism on OpenAI-style tool envelopes and on certain Anthropic SDK consumers — naming it explicitly activates parallel emission on runtimes where trickle-dispatch is the default behavior.

If your runtime emits parallel tool_use blocks natively (Claude Code with Claude models), multi_tool_use.parallel may not be needed — but naming it is harmless and serves as an enforcement anchor.

The STOP-CHECK, anti-trickle, and self-verify guards above remain binding regardless of which mechanism your runtime uses.

Dispatch all 6 explorer angles in one parallel batch. Wait for all before Phase 4.

Per-explorer dispatch (subagent_type: ring:codebase-explorer):

## Target: <absolute path to target repo root>
## Your Angle: <angle number + name from below>
## Severity / DIY Patterns / Replacement / Migration Complexity
<verbatim from angle table below>

## Output
Write to: /tmp/tracing-sweep-{N}-{angle-slug}.json
Schema: { angle_number, angle_name, severity, migration_complexity,
  findings: [{file, line, diy_pattern, replacement, evidence_snippet, notes}],
  summary }
If no findings: write file with empty findings array.

The 6 Angles

#AngleSeverityDIY PatternReplacement
1Raw OTEL TracerProvider bootstrapCRITICALsdktrace.NewTracerProvider(...) assembled by hand; otel.SetTracerProvider(...) called directly in service inittracing.NewTelemetry(cfg) + tl.ApplyGlobals()
2Hand-rolled HTTP header propagationHIGHManual req.Header.Set("traceparent", ...) / r.Header.Get("traceparent") / custom carrier typestracing.InjectHTTPContext(ctx, req.Header) / tracing.ExtractHTTPContext(ctx, c)
3Hand-rolled gRPC / queue propagationHIGHCustom metadata copy for traceparent/tracestate; AMQP headers serialized by handtracing.InjectGRPCContext / ExtractGRPCContext / PrepareQueueHeaders / ExtractTraceContextFromQueueHeaders
4Manual span attribute assembly from structsMEDIUMLooping over struct fields calling span.SetAttributes(attribute.String(...)); nested JSON flattening reinvented per servicetracing.SetSpanAttributesFromValue(span, prefix, v, redactor) / BuildAttributesFromValue
5DIY field redaction before tracingCRITICALService-local maskPassword(s) / redactToken(s) helpers; sensitive-field lists duplicated outside redaction package; hand-written regex masking inside span hot pathtracing.NewDefaultRedactor() (or NewRedactor(rules, mask)) plumbed through TelemetryConfig.Redactor
6Untraced HTTP/DB/Kafka boundariesHIGHOutbound HTTP clients, DB drivers, or queue publishers with no span around the call; missing HandleSpanError/HandleSpanBusinessErrorEvent on the error pathWrap call with tracer.Start(ctx, "op") + defer span.End(); record errors via tracing.HandleSpanError(span, msg, err)

Severity calibration

  • CRITICAL: hides production failures or leaks PII (angles 1, 5)
  • HIGH: breaks distributed traces or omits whole subsystems (angles 2, 3, 6)
  • MEDIUM: duplicates library code; loud but correctable later (angle 4)

Phase 4: Consolidated Report

Dispatch synthesizer (subagent_type: ring:codebase-explorer):

Read /tmp/tracing-version-report.json, /tmp/tracing-delta-report.json,
and /tmp/tracing-sweep-*.json (6 files).

Emit:
1. /tmp/tracing-sweep-report.md — findings grouped by severity, cross-referenced to angle table
2. /tmp/tracing-sweep-tasks.json — one task per DIY pattern cluster (same file/package = one task)

MUST NOT invent findings. MUST NOT omit explorer findings. MUST NOT reclassify severity without justification.

Phase 5: Handoff

Surface report path + task count to the caller. Offer handoff to ring:running-dev-cycle for execution.


REFERENCE MODE

Import path: github.com/LerianStudio/lib-observability/tracing

The package owns four concerns:

  1. Lifecycle — build OTEL providers and shut them down cleanly
  2. Propagation — move trace context across HTTP, gRPC, and message queues
  3. Span helpers — record errors, events, and struct-derived attributes
  4. Redaction — strip sensitive fields before they reach the collector

1. Lifecycle: Telemetry and TelemetryConfig

go
type TelemetryConfig struct {
    LibraryName               string
    ServiceName               string
    ServiceVersion            string
    DeploymentEnv             string                            // "production" | "staging" | "development" | "local"
    CollectorExporterEndpoint string                            // "otel-collector:4317" — scheme stripped automatically
    EnableTelemetry           bool
    InsecureExporter          bool                              // forbidden in production unless ALLOW_INSECURE_OTEL is set
    Logger                    log.Logger                        // required; nil returns ErrNilTelemetryLogger
    Propagator                propagation.TextMapPropagator     // defaults to TraceContext + Baggage composite
    Redactor                  *Redactor                         // defaults to NewDefaultRedactor()
}

type Telemetry struct {
    TelemetryConfig
    TracerProvider *sdktrace.TracerProvider
    MeterProvider  *sdkmetric.MeterProvider
    LoggerProvider *sdklog.LoggerProvider
    MetricsFactory *metrics.MetricsFactory
    // shutdown handlers are unexported; use ShutdownTelemetry* methods
}

func NewTelemetry(cfg TelemetryConfig) (*Telemetry, error)
func (tl *Telemetry) ApplyGlobals() error
func (tl *Telemetry) Tracer(name string) (trace.Tracer, error)
func (tl *Telemetry) Meter(name string) (metric.Meter, error)
func (tl *Telemetry) ShutdownTelemetry()
func (tl *Telemetry) ShutdownTelemetryWithContext(ctx context.Context) error

Bootstrap pattern

go
tl, err := tracing.NewTelemetry(tracing.TelemetryConfig{
    LibraryName:               "my-service",
    ServiceName:               cfg.ServiceName,
    ServiceVersion:            cfg.Version,
    DeploymentEnv:             cfg.Env,
    CollectorExporterEndpoint: cfg.OTELEndpoint,   // "otel-collector:4317" or "http://..."
    EnableTelemetry:           cfg.OTELEnabled,
    Logger:                    logger,
})
if err != nil {
    return fmt.Errorf("init telemetry: %w", err)
}
if err := tl.ApplyGlobals(); err != nil {
    return fmt.Errorf("apply globals: %w", err)
}
defer func() {
    if err := tl.ShutdownTelemetryWithContext(context.Background()); err != nil {
        logger.Log(ctx, log.LevelError, "telemetry shutdown failed", log.Err(err))
    }
}()

Sentinel errors

ErrorTrigger
ErrNilTelemetryLoggerTelemetryConfig.Logger == nil
ErrEmptyEndpointEnableTelemetry=true with empty CollectorExporterEndpoint; noop providers are installed but the call still returns this error so callers can decide whether to abort
ErrNilTelemetrymethod called on nil *Telemetry
ErrNilShutdownshutdown invoked but no shutdown function configured (corrupted state)
ErrNilProviderApplyGlobals called when TracerProvider/MeterProvider/Propagator is nil

Endpoint and security rules

  • http://host:4317 → scheme stripped, InsecureExporter forced to true
  • https://host:4317 → scheme stripped, secure exporter
  • host:4317 (no scheme) → treated as insecure (common in cluster-internal traffic)
  • InsecureExporter=true in production/prod env aborts with an error unless ALLOW_INSECURE_OTEL is set with a justification — do not bypass this lightly

Disabled / empty-endpoint fallback

When EnableTelemetry=false or the endpoint is empty, NewTelemetry installs no-op providers and ApplyGlobals ensures downstream libraries (e.g. otelfiber) do not spawn real gRPC exporters that leak goroutines. Code paths above the API stay unchanged.

2. Propagation Helpers

All helpers are nil-safe and use the globally configured TextMapPropagator.

HTTP

go
func InjectHTTPContext(ctx context.Context, headers http.Header)
func ExtractHTTPContext(ctx context.Context, c *fiber.Ctx) context.Context
func InjectTraceContext(ctx context.Context, carrier propagation.TextMapCarrier)
func ExtractTraceContext(ctx context.Context, carrier propagation.TextMapCarrier) context.Context

Outbound client:

go
req, _ := http.NewRequestWithContext(ctx, http.MethodPost, url, body)
tracing.InjectHTTPContext(ctx, req.Header)
resp, err := httpClient.Do(req)

Inbound Fiber handler:

go
func handler(c *fiber.Ctx) error {
    ctx := tracing.ExtractHTTPContext(c.UserContext(), c)
    c.SetUserContext(ctx)
    // ...
}

For non-Fiber HTTP servers, build a propagation.HeaderCarrier from r.Header and call ExtractTraceContext.

gRPC

go
func InjectGRPCContext(ctx context.Context, md metadata.MD) metadata.MD
func ExtractGRPCContext(ctx context.Context, md metadata.MD) context.Context

The gRPC helpers normalize Traceparent/Tracestate header casing — gRPC metadata is lowercase by spec, but some interceptors emit Pascal case. The helpers translate both ways. Do not reinvent this.

Message queues (AMQP / Kafka / generic string-headers)

go
func InjectQueueTraceContext(ctx context.Context) map[string]string
func ExtractQueueTraceContext(ctx context.Context, headers map[string]string) context.Context
func PrepareQueueHeaders(ctx context.Context, baseHeaders map[string]any) map[string]any
func InjectTraceHeadersIntoQueue(ctx context.Context, headers *map[string]any)
func ExtractTraceContextFromQueueHeaders(baseCtx context.Context, amqpHeaders map[string]any) context.Context

Publisher (RabbitMQ AMQP-style):

go
headers := tracing.PrepareQueueHeaders(ctx, map[string]any{"x-event-type": "user.created"})
err := channel.Publish(exchange, key, false, false, amqp.Publishing{Headers: headers, Body: payload})

Consumer:

go
ctx = tracing.ExtractTraceContextFromQueueHeaders(ctx, delivery.Headers)
ctx, span := tracer.Start(ctx, "consume.user.created")
defer span.End()

Reading IDs out of context

go
traceID := tracing.GetTraceIDFromContext(ctx)     // "" if no valid span
state   := tracing.GetTraceStateFromContext(ctx)

Use these for log correlation. Never parse traceparent headers by hand to recover the trace ID.

3. Span Helpers

go
func HandleSpanError(span trace.Span, message string, err error)
func HandleSpanBusinessErrorEvent(span trace.Span, eventName string, err error)
func HandleSpanEvent(span trace.Span, eventName string, attributes ...attribute.KeyValue)
func SetSpanAttributesFromValue(span trace.Span, prefix string, value any, r *Redactor) error
func BuildAttributesFromValue(prefix string, value any, r *Redactor) ([]attribute.KeyValue, error)
func SetSpanAttributeForParam(c *fiber.Ctx, param, value, entityName string)

All helpers are nil-safe on the span argument (untyped nil and interface-wrapped typed nil both handled). Error messages are sanitized: bearer/basic tokens stripped and the message is truncated to 1024 bytes with valid-UTF-8 enforcement.

HelperUse when
HandleSpanErrorOperation failed; mark span as failed (codes.Error) and record the error
HandleSpanBusinessErrorEventDomain rule rejected the request but the operation itself succeeded technically (e.g. balance insufficient) — adds an event without flipping the span to error
HandleSpanEventGeneric milestone event with attributes (e.g. cache.hit, retry.attempt)
SetSpanAttributesFromValueFlatten a struct/map into span attributes with redaction applied; bounded by 128 attributes and depth 32
BuildAttributesFromValueSame flattening but returns the attribute slice instead of writing to a span
SetSpanAttributeForParamFiber-specific: attach a request parameter to the context-scoped attribute bag, masking sensitive names

Idiomatic span lifecycle:

go
ctx, span := tracer.Start(ctx, "ledger.PostTransaction")
defer span.End()

if err := tracing.SetSpanAttributesFromValue(span, "request", req, tl.Redactor); err != nil {
    tracing.HandleSpanEvent(span, "attr.serialize.failed", attribute.String("error", err.Error()))
}

result, err := svc.Post(ctx, req)
if errors.Is(err, ErrInsufficientBalance) {
    tracing.HandleSpanBusinessErrorEvent(span, "business.balance_insufficient", err)
    return err
}
if err != nil {
    tracing.HandleSpanError(span, "post transaction", err)
    return err
}

4. Redaction

go
type RedactionAction string  // "mask" | "hash" | "drop"

type RedactionRule struct {
    FieldPattern string  // regex matched against field name
    PathPattern  string  // regex matched against dotted path
    Action       RedactionAction
}

type Redactor struct{ /* unexported */ }

func NewDefaultRedactor() *Redactor                            // default sensitive-field list, action = mask
func NewAlwaysMaskRedactor() *Redactor                         // fail-safe; masks every field
func NewRedactor(rules []RedactionRule, mask string) (*Redactor, error)
func ObfuscateStruct(value any, r *Redactor) (any, error)

Pipeline

  1. NewTelemetry defaults cfg.Redactor to NewDefaultRedactor() if nil
  2. The tracer provider is built with RedactingAttrBagSpanProcessor{Redactor: cfg.Redactor} — every span gets request-scoped attributes from observability.AttributesFromContext filtered through redaction
  3. SetSpanAttributesFromValue invokes ObfuscateStruct before flattening, so struct-derived attributes inherit the same rules

Custom rules

go
r, err := tracing.NewRedactor([]tracing.RedactionRule{
    {FieldPattern: `(?i)^card_number$`, Action: tracing.RedactionDrop},
    {FieldPattern: `(?i)^email$`,        Action: tracing.RedactionHash},
    {PathPattern:  `^request\.headers\.authorization$`, Action: tracing.RedactionMask},
}, "[REDACTED]")
if err != nil {
    return err
}
cfg.Redactor = r
  • mask replaces the value with the configured mask string
  • hash produces sha256:<hex> using a per-instance HMAC key (so identical inputs in different processes produce different hashes — anti-rainbow-table)
  • drop removes the field entirely

RedactingAttrBagSpanProcessor

Custom sdktrace.SpanProcessor wired automatically inside NewTelemetry. It copies observability.AttributesFromContext(ctx) onto every started span and applies redaction by attribute key. Do not register it manually unless you are bypassing NewTelemetry.

5. Common Anti-Patterns

Anti-patternFix
Calling otel.SetTracerProvider directly during initUse tl.ApplyGlobals() — it also wires the meter, logger provider, and propagator atomically
Building a propagation.TraceContext{} propagator per-callConfigure once on TelemetryConfig.Propagator; helpers read otel.GetTextMapPropagator()
Logging traceparent headers verbatimUse GetTraceIDFromContext(ctx) and log the trace ID, not the header
Shutting down providers with tp.Shutdown(ctx) directlyUse ShutdownTelemetryWithContext — it shuts down exporters and providers in the right order and joins errors
Adding a custom SpanProcessor for redactionRedactingAttrBagSpanProcessor already runs; add rules to the Redactor instead
Setting InsecureExporter=true in productionEither fix the collector to expose TLS, or set ALLOW_INSECURE_OTEL="<justification>" with a sunset date — never silently bypass

6. Cross-References

  • [[using-lib-observability]] — parent skill covering logging, metrics, panic recovery
  • [[using-runtime]] — panic observability trident; uses tracing's span events for the panic record
  • [[using-assert]] — production assertions; their AssertionError surfaces in spans via HandleSpanError
  • [[using-lib-commons]] — broader Lerian shared-library sweep; Angle "observability DIY" delegates here when tracing-specific

Frequently asked questions

What does the Ring:Using Tracing AI skill do?

Using lib-observability/tracing for OTEL provider lifecycle, trace-context propagation across HTTP/gRPC/queues, span error/event recording, and PII redaction, in two modes. Sweep Mode detects raw OTEL setup, hand-rolled header propagation, manual span-attribute assembly, and DIY redaction. Reference Mode catalogs Telemetry, Redactor, and propagation/span helpers. Go-only. Skip for non-Go or frontend code.

Why use Ring:Using Tracing on TypingMind?

Because you install it once and use it with any model. Ring:Using Tracing is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Ring:Using Tracing in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/LerianStudio/ring/tree/main/dev-team/skills/using-tracing. TypingMind reads its SKILL.md and installs it as a skill you can enable per chat.

Which AI models can use Ring:Using Tracing?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Ring:Using Tracing?

As many as you like. As long as a model supports skills, you can use Ring:Using Tracing with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Ring:Using Tracing AI skill free?

Yes. It is published on GitHub by LerianStudio under the Apache-2.0 license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇