Skald Ruby SDK

Ruby client library for Skald - an API platform that allows you to easily push context/knowledge via our API (in the form of memos) and get access to chat, document generation, and semantic search out-of-the-box.

Installation

Add this line to your application's Gemfile:

gem 'skald'

And then execute:

bundle install

Or install it yourself as:

gem install skald

Requirements

  • Ruby 3.0.0 or higher

Quick Start

require 'skald'

# Initialize the client
client = Skald.new('your-api-key')

# Create a memo
client.create_memo(
  title: "Meeting Notes",
  content: "Discussed project timeline..."
)

# Search your memos
results = client.search(
  query: "project timeline",
  search_method: "chunk_vector_search"
)

# Chat with your memos
response = client.chat(query: "What were the main discussion points?")
puts response[:response]

Initialization

# Default base URL (https://api.useskald.com)
client = Skald.new('your-api-key')

# Custom base URL (for self-hosted or testing)
client = Skald.new('your-api-key', 'https://custom.api.com')

Memo Management

Create Memo

Create a new memo with content and optional metadata.

result = client.create_memo(
  title: "Project Planning Meeting",
  content: "Discussed Q1 goals, resource allocation, and timeline...",
  metadata: { priority: "high", department: "engineering" },
  tags: ["meeting", "planning"],
  source: "notion",
  reference_id: "notion-page-123",
  expiration_date: "2025-12-31T23:59:59Z"
)
# => { ok: true }

Parameters:

  • title (String, required): Memo title (max 255 characters)
  • content (String, required): Memo content
  • metadata (Hash, optional): Custom JSON metadata
  • tags (Array, optional): Array of tags
  • source (String, optional): Source system (e.g., "notion", "slack", max 255 chars)
  • reference_id (String, optional): External reference ID (max 255 chars)
  • expiration_date (String, optional): ISO 8601 expiration timestamp

Response:

{ ok: true }

Get Memo

Retrieve a memo by UUID or reference ID.

# Get by UUID
memo = client.get_memo("550e8400-e29b-41d4-a716-446655440000")

# Get by reference ID
memo = client.get_memo("notion-page-123", "reference_id")

Parameters:

  • memo_id (String, required): Memo UUID or reference ID
  • id_type (String, optional): "memo_uuid" (default) or "reference_id"

Response:

{
  uuid: "550e8400-e29b-41d4-a716-446655440000",
  created_at: "2025-01-15T10:30:00Z",
  updated_at: "2025-01-15T10:30:00Z",
  title: "Project Planning Meeting",
  content: "Discussed Q1 goals...",
  summary: "AI-generated summary of the memo",
  content_length: 150,
  metadata: { priority: "high" },
  client_reference_id: "notion-page-123",
  source: "notion",
  type: "memo",
  expiration_date: nil,
  archived: false,
  pending: false,
  tags: [
    { uuid: "tag-uuid", tag: "meeting" }
  ],
  chunks: [
    { uuid: "chunk-uuid", chunk_content: "...", chunk_index: 0 }
  ]
}

List Memos

List all memos with pagination.

# Default pagination (page 1, 20 items)
response = client.list_memos

# Custom pagination
response = client.list_memos(page: 2, page_size: 50)

Parameters:

  • page (Integer, optional): Page number (default: 1)
  • page_size (Integer, optional): Results per page (default: 20, max: 100)

Response:

{
  count: 150,
  next: "https://api.useskald.com/api/v1/memo?page=2",
  previous: nil,
  results: [
    {
      uuid: "...",
      title: "Memo 1",
      summary: "...",
      # ... other memo fields
    }
  ]
}

Update Memo

Update an existing memo. Updating content triggers reprocessing.

# Update by UUID
client.update_memo(
  "550e8400-e29b-41d4-a716-446655440000",
  {
    title: "Updated Title",
    metadata: { priority: "medium", status: "reviewed" }
  }
)

# Update by reference ID
client.update_memo(
  "notion-page-123",
  { content: "New content..." },
  "reference_id"
)

Parameters:

  • memo_id (String, required): Memo UUID or reference ID
  • update_data (Hash, required): Fields to update
    • title (String, optional): New title
    • content (String, optional): New content (triggers reprocessing)
    • metadata (Hash, optional): New metadata
    • client_reference_id (String, optional): New reference ID
    • source (String, optional): New source
    • expiration_date (String, optional): New expiration date
  • id_type (String, optional): "memo_uuid" (default) or "reference_id"

Response:

{ ok: true }

Delete Memo

Permanently delete a memo and all associated data.

# Delete by UUID
client.delete_memo("550e8400-e29b-41d4-a716-446655440000")

# Delete by reference ID
client.delete_memo("notion-page-123", "reference_id")

Parameters:

  • memo_id (String, required): Memo UUID or reference ID
  • id_type (String, optional): "memo_uuid" (default) or "reference_id"

Response:

nil

Search your memos using semantic search or title matching.

Search Methods

  1. chunk_vector_search: Semantic/vector search on memo content chunks
  2. title_contains: Case-insensitive substring match on titles
  3. title_startswith: Case-insensitive prefix match on titles
results = client.search(
  query: "project timeline and milestones",
  search_method: "chunk_vector_search",
  limit: 20
)
# Contains
results = client.search(
  query: "meeting",
  search_method: "title_contains"
)

# Starts with
results = client.search(
  query: "Project",
  search_method: "title_startswith"
)

Parameters:

  • query (String, required): Search query
  • search_method (String, required): One of "chunk_vector_search", "title_contains", or "title_startswith"
  • limit (Integer, optional): Maximum results (1-50, default: 10)
  • filters (Array, optional): Filters to apply (see Filters)

Response:

{
  results: [
    {
      uuid: "550e8400-...",
      title: "Project Planning Meeting",
      summary: "Discussed Q1 goals...",
      content_snippet: "...relevant excerpt from the content...",
      distance: 0.45  # For vector search: 0-2, lower is better. nil for title searches
    }
  ]
}

Chat

Ask questions and get answers based on your memos with inline citations.

Non-Streaming Chat

response = client.chat(
  query: "What are the main project goals for Q1?"
)

puts response[:response]
# => "The main goals are: 1) Launch MVP [[1]], 2) Hire team [[2]]..."

Parameters:

  • query (String, required): Question to ask
  • filters (Array, optional): Filters to apply (see Filters)

Response:

{
  ok: true,
  response: "Answer with inline citations [[1]], [[2]], etc.",
  intermediate_steps: []  # For debugging
}

Streaming Chat

print "Answer: "
client.streamed_chat(
  query: "Summarize the meeting notes from last week"
).each do |event|
  if event[:type] == "token"
    print event[:content]
  elsif event[:type] == "done"
    puts "\nDone!"
  end
end

Parameters:

  • Same as non-streaming chat

Yields:

{ type: "token", content: "Each" }
{ type: "token", content: " word" }
{ type: "done" }

Document Generation

Generate documents based on your memos with optional style rules.

Non-Streaming Generation

response = client.generate_doc(
  prompt: "Create a comprehensive project status report",
  rules: "Use bullet points and maintain a professional tone"
)

puts response[:response]
# => "# Project Status Report\n\n## Overview\nThe project is on track [[1]]..."

Parameters:

  • prompt (String, required): What document to generate
  • rules (String, optional): Style/format rules
  • filters (Array, optional): Filters to apply (see Filters)

Response:

{
  ok: true,
  response: "Generated document with citations [[1]], [[2]]...",
  intermediate_steps: []
}

Streaming Generation

puts "Generated Document:"
client.streamed_generate_doc(
  prompt: "Write a technical architecture document",
  rules: "Include diagrams descriptions and code examples"
).each do |event|
  if event[:type] == "token"
    print event[:content]
  elsif event[:type] == "done"
    puts "\nDone!"
  end
end

Parameters:

  • Same as non-streaming generation

Yields:

{ type: "token", content: "Each" }
{ type: "token", content: " word" }
{ type: "done" }

Filters

Filters allow you to narrow down which memos are used for search, chat, and document generation. Multiple filters use AND logic (all must match).

Filter Structure

{
  field: "field_name",
  operator: "eq",
  value: "value",
  filter_type: "native_field"
}

Filter Types

  1. native_field: Built-in memo properties

    • title: Memo title
    • source: Source system
    • client_reference_id: Your reference ID
    • tags: Memo tags
  2. custom_metadata: User-defined metadata fields

Filter Operators

Operator Description Value Type
eq Exact match String
neq Not equals String
contains Substring match (case-insensitive) String
startswith Prefix match (case-insensitive) String
endswith Suffix match (case-insensitive) String
in Value is in array Array
not_in Value not in array Array

Examples

Filter by Native Field

results = client.search(
  query: "project update",
  search_method: "chunk_vector_search",
  filters: [
    {
      field: "source",
      operator: "eq",
      value: "notion",
      filter_type: "native_field"
    }
  ]
)

Filter by Tags

results = client.search(
  query: "status",
  search_method: "chunk_vector_search",
  filters: [
    {
      field: "tags",
      operator: "in",
      value: ["project", "important"],
      filter_type: "native_field"
    }
  ]
)

Filter by Custom Metadata

results = client.search(
  query: "urgent tasks",
  search_method: "chunk_vector_search",
  filters: [
    {
      field: "priority",
      operator: "eq",
      value: "high",
      filter_type: "custom_metadata"
    }
  ]
)

Multiple Filters

results = client.search(
  query: "engineering work",
  search_method: "chunk_vector_search",
  filters: [
    {
      field: "source",
      operator: "eq",
      value: "jira",
      filter_type: "native_field"
    },
    {
      field: "tags",
      operator: "in",
      value: ["backend", "api"],
      filter_type: "native_field"
    },
    {
      field: "priority",
      operator: "eq",
      value: "high",
      filter_type: "custom_metadata"
    }
  ]
)

Filters in Chat

response = client.chat(
  query: "What are the high priority backend tasks?",
  filters: [
    {
      field: "department",
      operator: "eq",
      value: "engineering",
      filter_type: "custom_metadata"
    }
  ]
)

Filters in Document Generation

response = client.generate_doc(
  prompt: "Create a security audit summary",
  rules: "Focus on key findings",
  filters: [
    {
      field: "tags",
      operator: "in",
      value: ["security", "audit"],
      filter_type: "native_field"
    }
  ]
)

Error Handling

The SDK raises exceptions for API errors. Wrap your calls in begin/rescue blocks:

begin
  memo = client.get_memo("invalid-id")
rescue => e
  puts "Error: #{e.message}"
  # => "Skald API error (404): Not Found"
end

All methods may raise RuntimeError with the format:

Skald API error (STATUS_CODE): ERROR_MESSAGE

Examples

See the examples directory for complete working examples:

Development

# Install dependencies
bundle install

# Run tests
bundle exec rspec

# Run tests with coverage
bundle exec rspec --format documentation

# Run linter
bundle exec rubocop

Testing

The SDK includes comprehensive tests with 100% method coverage:

bundle exec rspec
# => 54 examples, 0 failures

Tests use WebMock to mock HTTP requests, so no actual API calls are made during testing.

API Reference

For complete API documentation, visit the Skald API Documentation.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/skald-org/skald-ruby.

License

The gem is available as open source under the terms of the MIT License.

Support