sibylline-skald


Namesibylline-skald JSON
Version 0.2.0 PyPI version JSON
download
home_pageNone
SummaryUniversal Execution Monitoring & Feedback Library for Python applications, supporting MCP servers, functions, shell commands, and more
upload_time2025-09-02 05:07:11
maintainerNone
docs_urlNone
authorNone
requires_python>=3.9
licenseMIT
keywords agent ai feedback mcp tools
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # Skald - MCP Feedback Adapter

[![PyPI version](https://badge.fury.io/py/skald.svg)](https://badge.fury.io/py/skald)
[![Python versions](https://img.shields.io/pypi/pyversions/skald.svg)](https://pypi.org/project/skald/)
[![Tests](https://github.com/sibyllinesoft/skald/workflows/CI%2FCD%20Pipeline/badge.svg)](https://github.com/sibyllinesoft/skald/actions)
[![Coverage](https://codecov.io/gh/sibyllinesoft/skald/branch/main/graph/badge.svg)](https://codecov.io/gh/sibyllinesoft/skald)
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)

> *"A storyteller for your tools' performance, letting them speak back through AI agents when they shine or stumble."*

Skald is an in-process adapter library for Python MCP servers that enables structured feedback collection on tool usefulness, clarity, and fit from AI agents.

## Features

- **Transparent Tool Wrapping**: Drop-in wrapper that intercepts MCP tool calls and adds trace IDs with metrics collection
- **Structured Feedback Collection**: New `feedback.report` tool with strict JSON schema validation
- **Smart Feedback Invitations**: Automatically invites feedback based on errors, latency, and output size
- **Opt-out Controls**: Decorator and context manager for suppressing feedback collection
- **Multiple Storage Backends**: SQLite default with optional Parquet export and data redaction
- **Multiple Transports**: Support for stdio, TCP, and Unix socket transports

## Installation

```bash
pip install skald
```

## Quick Start

### Basic Usage

```python
import skald
from your_mcp_server import YourMCPServer

# Wrap your existing MCP server
upstream = YourMCPServer()
survey = skald.SurveyingProxy(
    upstream=upstream,
    store="sqlite:///feedback.db",
    invite_policy={"error": True, "p95_ms": 5000, "large_output_kb": 256},
    sample_neutral=0.10,
    ttl_hours=24
)

# Your server now collects feedback transparently
```

### Using Opt-out Controls

```python
# Opt out specific functions
@survey.opt_out(reason="contains sensitive data")
def run_sensitive_query(sql: str) -> dict:
    return execute_query(sql)

# Suppress collection for a block of code
with survey.suppressed(reason="benchmark"):
    for _ in range(1000):
        call_some_tool()
```

### Running a Server

```python
from skald.transport import TCPTransport

# Create transport and serve
transport = TCPTransport(survey)
await transport.serve(host="0.0.0.0", port=8765)
```

## CLI Usage

Skald includes a CLI for running demo servers and querying feedback data:

```bash
# Run over TCP
skald tcp --host localhost --port 8765

# Run over stdio  
skald stdio

# Query stored feedback
skald query --store sqlite:///feedback.db --limit 10

# Clean up expired data
skald cleanup --store sqlite:///feedback.db --ttl-hours 24
```

## Agent Integration

To collect feedback from AI agents, add this line to your system prompt:

```
When a tool provides a trace_id in its response metadata and invite_feedback=true, you may optionally use feedback.report to provide structured feedback on the tool's helpfulness (1-5), fit (1-5), clarity (1-5), confidence (0.0-1.0), and up to 3 short suggestions for improvement.
```

## Feedback Schema

The `feedback.report` tool accepts this JSON schema:

```json
{
  "trace_id": "550e8400-e29b-41d4-a716-446655440000",
  "helpfulness": 4,
  "fit": 3, 
  "clarity": 5,
  "confidence": 0.8,
  "better_alternative": "different_tool",
  "suggestions": ["Try tool X", "Use parameter Y"],
  "notes": "This worked well overall"
}
```

### Fields

- `trace_id` (required): UUID from the tool response metadata
- `helpfulness` (required): How helpful was the tool (1-5)
- `fit` (required): How well did the tool fit the task (1-5)  
- `clarity` (required): How clear was the tool output (1-5)
- `confidence` (required): Confidence in this feedback (0.0-1.0)
- `better_alternative` (optional): Enum suggesting better alternatives
- `suggestions` (optional): Up to 3 suggestions, each ≤100 characters
- `notes` (optional): Additional notes

## Configuration

### Invite Policy

Control when feedback is invited:

```python
invite_policy = {
    "error": True,           # Invite on errors
    "timeout": True,         # Invite on timeouts  
    "p95_ms": 5000.0,       # Latency threshold (ms)
    "large_output_kb": 256.0 # Output size threshold (KB)
}
```

### Storage Configuration

```python
# SQLite (default)
store = "sqlite:///path/to/feedback.db"

# Custom storage backend
from skald.storage.sqlite import SQLiteStorage
storage = SQLiteStorage("/custom/path.db")
survey = SurveyingProxy(upstream, store=storage)
```

### Data Redaction

Configure custom data redaction:

```python
def custom_redactor(args: dict) -> dict:
    """Remove sensitive data from tool arguments."""
    redacted = args.copy()
    if 'password' in redacted:
        redacted['password'] = '[REDACTED]'
    return redacted

survey = SurveyingProxy(
    upstream=upstream,
    redactor=custom_redactor
)
```

## Storage Schema

Skald uses two main tables:

### tool_runs
- `trace_id` (PK): UUID trace identifier
- `timestamp`: Execution timestamp
- `agent_id`: ID of the calling agent
- `tool_name`: Name of the tool called
- `status`: success/error/timeout
- `latency_ms`: Execution latency in milliseconds
- `output_bytes`: Size of output in bytes
- `invite_feedback`: Whether feedback was invited
- `opt_out`: Whether collection was opted out
- `args_redacted`: Redacted tool arguments (JSON)

### tool_feedback
- `trace_id` (FK): Reference to tool_runs
- `agent_id`: ID of the agent providing feedback
- `helpfulness`: Helpfulness rating (1-5)
- `fit`: Fit rating (1-5)
- `clarity`: Clarity rating (1-5)
- `confidence`: Confidence score (0.0-1.0)
- `better_alternative`: Better alternative suggestion
- `suggestions`: List of suggestions (JSON)
- `notes`: Additional notes
- `valid`: Whether feedback passed validation
- `raw_json`: Raw feedback JSON
- `timestamp`: Feedback timestamp

## Architecture

Skald follows a clean architecture with these components:

- **Core**: `SurveyingProxy` - Main proxy class that wraps MCP servers
- **Schema**: Pydantic models for data validation
- **Storage**: Pluggable storage backends (SQLite default)
- **Transport**: Communication protocols (stdio, TCP, Unix sockets)
- **Decorators**: Opt-out mechanisms (`@opt_out`, `with suppressed()`)

## Development

### Setup

```bash
git clone https://github.com/sibyllinesoft/skald.git
cd skald
uv venv
source .venv/bin/activate  # On Windows: .venv\Scripts\activate
uv pip install -e ".[dev,test]"
```

### Testing

```bash
pytest
pytest --cov=skald --cov-report=html
```

### Code Quality

```bash
black skald tests
isort skald tests  
mypy skald
ruff skald tests
```

## License

MIT License - see LICENSE file for details.

## Contributing

Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "sibylline-skald",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.9",
    "maintainer_email": null,
    "keywords": "agent, ai, feedback, mcp, tools",
    "author": null,
    "author_email": "Nathan Rice <nathan.alexander.rice@gmail.com>",
    "download_url": "https://files.pythonhosted.org/packages/00/c7/1a33195a4f97afe5b5e2e6f42b3aadffac73b45b1600f7e3cce48aeb65c0/sibylline_skald-0.2.0.tar.gz",
    "platform": null,
    "description": "# Skald - MCP Feedback Adapter\n\n[![PyPI version](https://badge.fury.io/py/skald.svg)](https://badge.fury.io/py/skald)\n[![Python versions](https://img.shields.io/pypi/pyversions/skald.svg)](https://pypi.org/project/skald/)\n[![Tests](https://github.com/sibyllinesoft/skald/workflows/CI%2FCD%20Pipeline/badge.svg)](https://github.com/sibyllinesoft/skald/actions)\n[![Coverage](https://codecov.io/gh/sibyllinesoft/skald/branch/main/graph/badge.svg)](https://codecov.io/gh/sibyllinesoft/skald)\n[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)\n[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)\n\n> *\"A storyteller for your tools' performance, letting them speak back through AI agents when they shine or stumble.\"*\n\nSkald is an in-process adapter library for Python MCP servers that enables structured feedback collection on tool usefulness, clarity, and fit from AI agents.\n\n## Features\n\n- **Transparent Tool Wrapping**: Drop-in wrapper that intercepts MCP tool calls and adds trace IDs with metrics collection\n- **Structured Feedback Collection**: New `feedback.report` tool with strict JSON schema validation\n- **Smart Feedback Invitations**: Automatically invites feedback based on errors, latency, and output size\n- **Opt-out Controls**: Decorator and context manager for suppressing feedback collection\n- **Multiple Storage Backends**: SQLite default with optional Parquet export and data redaction\n- **Multiple Transports**: Support for stdio, TCP, and Unix socket transports\n\n## Installation\n\n```bash\npip install skald\n```\n\n## Quick Start\n\n### Basic Usage\n\n```python\nimport skald\nfrom your_mcp_server import YourMCPServer\n\n# Wrap your existing MCP server\nupstream = YourMCPServer()\nsurvey = skald.SurveyingProxy(\n    upstream=upstream,\n    store=\"sqlite:///feedback.db\",\n    invite_policy={\"error\": True, \"p95_ms\": 5000, \"large_output_kb\": 256},\n    sample_neutral=0.10,\n    ttl_hours=24\n)\n\n# Your server now collects feedback transparently\n```\n\n### Using Opt-out Controls\n\n```python\n# Opt out specific functions\n@survey.opt_out(reason=\"contains sensitive data\")\ndef run_sensitive_query(sql: str) -> dict:\n    return execute_query(sql)\n\n# Suppress collection for a block of code\nwith survey.suppressed(reason=\"benchmark\"):\n    for _ in range(1000):\n        call_some_tool()\n```\n\n### Running a Server\n\n```python\nfrom skald.transport import TCPTransport\n\n# Create transport and serve\ntransport = TCPTransport(survey)\nawait transport.serve(host=\"0.0.0.0\", port=8765)\n```\n\n## CLI Usage\n\nSkald includes a CLI for running demo servers and querying feedback data:\n\n```bash\n# Run over TCP\nskald tcp --host localhost --port 8765\n\n# Run over stdio  \nskald stdio\n\n# Query stored feedback\nskald query --store sqlite:///feedback.db --limit 10\n\n# Clean up expired data\nskald cleanup --store sqlite:///feedback.db --ttl-hours 24\n```\n\n## Agent Integration\n\nTo collect feedback from AI agents, add this line to your system prompt:\n\n```\nWhen a tool provides a trace_id in its response metadata and invite_feedback=true, you may optionally use feedback.report to provide structured feedback on the tool's helpfulness (1-5), fit (1-5), clarity (1-5), confidence (0.0-1.0), and up to 3 short suggestions for improvement.\n```\n\n## Feedback Schema\n\nThe `feedback.report` tool accepts this JSON schema:\n\n```json\n{\n  \"trace_id\": \"550e8400-e29b-41d4-a716-446655440000\",\n  \"helpfulness\": 4,\n  \"fit\": 3, \n  \"clarity\": 5,\n  \"confidence\": 0.8,\n  \"better_alternative\": \"different_tool\",\n  \"suggestions\": [\"Try tool X\", \"Use parameter Y\"],\n  \"notes\": \"This worked well overall\"\n}\n```\n\n### Fields\n\n- `trace_id` (required): UUID from the tool response metadata\n- `helpfulness` (required): How helpful was the tool (1-5)\n- `fit` (required): How well did the tool fit the task (1-5)  \n- `clarity` (required): How clear was the tool output (1-5)\n- `confidence` (required): Confidence in this feedback (0.0-1.0)\n- `better_alternative` (optional): Enum suggesting better alternatives\n- `suggestions` (optional): Up to 3 suggestions, each \u2264100 characters\n- `notes` (optional): Additional notes\n\n## Configuration\n\n### Invite Policy\n\nControl when feedback is invited:\n\n```python\ninvite_policy = {\n    \"error\": True,           # Invite on errors\n    \"timeout\": True,         # Invite on timeouts  \n    \"p95_ms\": 5000.0,       # Latency threshold (ms)\n    \"large_output_kb\": 256.0 # Output size threshold (KB)\n}\n```\n\n### Storage Configuration\n\n```python\n# SQLite (default)\nstore = \"sqlite:///path/to/feedback.db\"\n\n# Custom storage backend\nfrom skald.storage.sqlite import SQLiteStorage\nstorage = SQLiteStorage(\"/custom/path.db\")\nsurvey = SurveyingProxy(upstream, store=storage)\n```\n\n### Data Redaction\n\nConfigure custom data redaction:\n\n```python\ndef custom_redactor(args: dict) -> dict:\n    \"\"\"Remove sensitive data from tool arguments.\"\"\"\n    redacted = args.copy()\n    if 'password' in redacted:\n        redacted['password'] = '[REDACTED]'\n    return redacted\n\nsurvey = SurveyingProxy(\n    upstream=upstream,\n    redactor=custom_redactor\n)\n```\n\n## Storage Schema\n\nSkald uses two main tables:\n\n### tool_runs\n- `trace_id` (PK): UUID trace identifier\n- `timestamp`: Execution timestamp\n- `agent_id`: ID of the calling agent\n- `tool_name`: Name of the tool called\n- `status`: success/error/timeout\n- `latency_ms`: Execution latency in milliseconds\n- `output_bytes`: Size of output in bytes\n- `invite_feedback`: Whether feedback was invited\n- `opt_out`: Whether collection was opted out\n- `args_redacted`: Redacted tool arguments (JSON)\n\n### tool_feedback\n- `trace_id` (FK): Reference to tool_runs\n- `agent_id`: ID of the agent providing feedback\n- `helpfulness`: Helpfulness rating (1-5)\n- `fit`: Fit rating (1-5)\n- `clarity`: Clarity rating (1-5)\n- `confidence`: Confidence score (0.0-1.0)\n- `better_alternative`: Better alternative suggestion\n- `suggestions`: List of suggestions (JSON)\n- `notes`: Additional notes\n- `valid`: Whether feedback passed validation\n- `raw_json`: Raw feedback JSON\n- `timestamp`: Feedback timestamp\n\n## Architecture\n\nSkald follows a clean architecture with these components:\n\n- **Core**: `SurveyingProxy` - Main proxy class that wraps MCP servers\n- **Schema**: Pydantic models for data validation\n- **Storage**: Pluggable storage backends (SQLite default)\n- **Transport**: Communication protocols (stdio, TCP, Unix sockets)\n- **Decorators**: Opt-out mechanisms (`@opt_out`, `with suppressed()`)\n\n## Development\n\n### Setup\n\n```bash\ngit clone https://github.com/sibyllinesoft/skald.git\ncd skald\nuv venv\nsource .venv/bin/activate  # On Windows: .venv\\Scripts\\activate\nuv pip install -e \".[dev,test]\"\n```\n\n### Testing\n\n```bash\npytest\npytest --cov=skald --cov-report=html\n```\n\n### Code Quality\n\n```bash\nblack skald tests\nisort skald tests  \nmypy skald\nruff skald tests\n```\n\n## License\n\nMIT License - see LICENSE file for details.\n\n## Contributing\n\nContributions are welcome! Please see CONTRIBUTING.md for guidelines.",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Universal Execution Monitoring & Feedback Library for Python applications, supporting MCP servers, functions, shell commands, and more",
    "version": "0.2.0",
    "project_urls": {
        "Documentation": "https://github.com/sibyllinesoft/skald#readme",
        "Homepage": "https://github.com/sibyllinesoft/skald",
        "Issues": "https://github.com/sibyllinesoft/skald/issues",
        "Repository": "https://github.com/sibyllinesoft/skald.git"
    },
    "split_keywords": [
        "agent",
        " ai",
        " feedback",
        " mcp",
        " tools"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "aa18be53dcf807ccd51244b39a9b2e0ecbcfa05501e302d274c84de4ef02ea1e",
                "md5": "f8902d52af4dd7646481a2d11748de80",
                "sha256": "eca2fb12d7224fe4c95ca03b16db6657cb9511fe6f2572b6dc3f7783e286734d"
            },
            "downloads": -1,
            "filename": "sibylline_skald-0.2.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "f8902d52af4dd7646481a2d11748de80",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.9",
            "size": 55806,
            "upload_time": "2025-09-02T05:07:10",
            "upload_time_iso_8601": "2025-09-02T05:07:10.247280Z",
            "url": "https://files.pythonhosted.org/packages/aa/18/be53dcf807ccd51244b39a9b2e0ecbcfa05501e302d274c84de4ef02ea1e/sibylline_skald-0.2.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "00c71a33195a4f97afe5b5e2e6f42b3aadffac73b45b1600f7e3cce48aeb65c0",
                "md5": "da4867b93a42709eb090250babde0f45",
                "sha256": "e48c38af899cd11f8abe34bed1d6f6cf17179dfc26417f1f50696715ebc95e41"
            },
            "downloads": -1,
            "filename": "sibylline_skald-0.2.0.tar.gz",
            "has_sig": false,
            "md5_digest": "da4867b93a42709eb090250babde0f45",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.9",
            "size": 50090,
            "upload_time": "2025-09-02T05:07:11",
            "upload_time_iso_8601": "2025-09-02T05:07:11.203280Z",
            "url": "https://files.pythonhosted.org/packages/00/c7/1a33195a4f97afe5b5e2e6f42b3aadffac73b45b1600f7e3cce48aeb65c0/sibylline_skald-0.2.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-09-02 05:07:11",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "sibyllinesoft",
    "github_project": "skald#readme",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "sibylline-skald"
}
        
Elapsed time: 0.54028s