Skip to content

Integration Guide

This document covers how to integrate Tinman into existing AI systems, pipelines, and workflows.


Table of Contents

  1. Overview
  2. Integration Patterns
  3. Pipeline Adapter
  4. Model Clients
  5. Embedding Tinman
  6. Event System
  7. Database Integration
  8. CI/CD Integration
  9. Framework Integration
  10. Production Deployment
  11. Examples

Overview

Tinman is designed to integrate seamlessly into existing AI infrastructure. There are multiple integration approaches depending on your use case:

Integration Pattern Description Best For
Library Import Import Tinman directly in Python code Custom applications, notebooks
Pipeline Adapter Hook into existing LLM pipelines Production systems, middleware
CLI/TUI Use command-line interface Ad-hoc research, exploration
Event-Driven Subscribe to Tinman events Monitoring, alerting

Integration Architecture

┌─────────────────────────────────────────────────────────────────────┐
│                        Your Application                             │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐│
│  │ Web Service │  │  Worker     │  │  Notebook   │  │  CLI Tool   ││
│  └──────┬──────┘  └──────┬──────┘  └──────┬──────┘  └──────┬──────┘│
│         │                │                │                │        │
│         └────────────────┴────────────────┴────────────────┘        │
│                                  │                                  │
│                        ┌─────────▼─────────┐                        │
│                        │ Tinman Integration │                        │
│                        │                   │                        │
│                        │ ┌───────────────┐ │                        │
│                        │ │PipelineAdapter│ │                        │
│                        │ │ ModelClient   │ │                        │
│                        │ │ EventBus      │ │                        │
│                        │ └───────────────┘ │                        │
│                        └─────────┬─────────┘                        │
│                                  │                                  │
└──────────────────────────────────┼──────────────────────────────────┘
                          ┌────────▼────────┐
                          │    Tinman       │
                          │  Research Core  │
                          └─────────────────┘

Integration Patterns

Pattern 1: Direct Library Import

The simplest integration - import and use Tinman as a Python library.

import asyncio
from tinman import create_tinman, OperatingMode
from tinman.integrations import OpenAIClient

async def main():
    # Create model client
    client = OpenAIClient(api_key="sk-...")

    # Create Tinman instance
    tinman = await create_tinman(
        mode=OperatingMode.LAB,
        model_client=client,
        db_url="postgresql://localhost/tinman",
    )

    # Run research
    results = await tinman.research_cycle(
        focus="reasoning failures in code generation",
        max_hypotheses=5,
    )

    # Generate report
    report = await tinman.generate_report()
    print(report)

    await tinman.close()

asyncio.run(main())

Pattern 2: Pipeline Middleware

Intercept requests/responses in your existing LLM pipeline.

from tinman.integrations import PipelineAdapter, FailureDetectionHook
from tinman.config.modes import OperatingMode

# Create adapter
adapter = PipelineAdapter(mode=OperatingMode.SHADOW)
adapter.register_hook(FailureDetectionHook())

# In your existing pipeline
async def my_llm_call(messages: list, model: str):
    # Pre-request hook
    ctx = adapter.create_context(messages=messages, model=model)
    ctx = await adapter.pre_request(ctx)

    # Your existing LLM call
    response = await your_model_client.complete(messages, model)

    # Post-request hook
    ctx.response = response
    ctx = await adapter.post_request(ctx)

    # Check for detected issues
    if "detected_failures" in ctx.metadata:
        log_potential_failures(ctx.metadata["detected_failures"])

    return response

Pattern 3: Event-Driven Integration

Subscribe to Tinman events for monitoring and alerting.

from tinman import create_tinman
from tinman.core.event_bus import EventBus

# Create event bus
event_bus = EventBus()

# Subscribe to events
@event_bus.on("failure.discovered")
async def on_failure(event_data):
    await send_alert(
        channel="ai-reliability",
        message=f"New failure: {event_data['failure_class']}"
    )

@event_bus.on("intervention.proposed")
async def on_intervention(event_data):
    await create_ticket(
        title=f"Intervention proposed: {event_data['name']}",
        body=event_data['description']
    )

# Tinman will emit events as it works
tinman = await create_tinman(mode=OperatingMode.LAB)
tinman.event_bus = event_bus

Pipeline Adapter

The PipelineAdapter provides hooks at key points in LLM pipeline execution.

Hook Points

from tinman.integrations.pipeline_adapter import HookPoint

class HookPoint(str, Enum):
    PRE_REQUEST = "pre_request"      # Before model call
    POST_REQUEST = "post_request"    # After model call
    PRE_TOOL = "pre_tool"           # Before tool execution
    POST_TOOL = "post_tool"         # After tool execution
    ON_ERROR = "on_error"           # On any error
    ON_COMPLETION = "on_completion"  # On task completion

Creating Custom Hooks

from tinman.integrations.pipeline_adapter import (
    PipelineHook, HookPoint, HookResult, PipelineContext
)

class CustomValidationHook(PipelineHook):
    """Custom hook for validating model responses."""

    @property
    def name(self) -> str:
        return "custom_validation"

    @property
    def hook_points(self) -> list[HookPoint]:
        return [HookPoint.POST_REQUEST]

    async def execute(self,
                      hook_point: HookPoint,
                      context: PipelineContext) -> HookResult:
        # Validate response
        if context.response:
            content = str(context.response.get("content", ""))

            # Check for concerning patterns
            if "I cannot" in content and "error" in content.lower():
                # Log but don't block (in SHADOW mode)
                context.metadata["validation_warning"] = "Potential refusal pattern"
                return HookResult(
                    allow=True,
                    modified_context=context,
                    message="Logged potential issue"
                )

        return HookResult(allow=True)

Built-in Hooks

LoggingHook - Simple logging for debugging:

from tinman.integrations.pipeline_adapter import LoggingHook

adapter = PipelineAdapter()
adapter.register_hook(LoggingHook())
# Logs all hook points

TokenLimitHook - Enforce token limits:

from tinman.integrations.pipeline_adapter import TokenLimitHook

adapter = PipelineAdapter()
adapter.register_hook(TokenLimitHook(max_tokens=50000))
# Blocks requests exceeding token limit

FailureDetectionHook - Detect failure patterns:

from tinman.integrations.pipeline_adapter import FailureDetectionHook

adapter = PipelineAdapter()
adapter.register_hook(FailureDetectionHook(
    failure_patterns=["error", "failed", "cannot", "unable"]
))
# Detects potential failures in responses

Pipeline Context

from tinman.integrations.pipeline_adapter import PipelineContext

@dataclass
class PipelineContext:
    id: str                           # Unique request ID
    mode: OperatingMode               # Current operating mode

    # Request data
    messages: list[dict]              # Input messages
    model: str                        # Target model
    tools: list[dict]                 # Available tools

    # Response data (after request)
    response: Optional[dict]          # Model response
    error: Optional[str]              # Error if any

    # Metadata (for passing data between hooks)
    metadata: dict[str, Any]          # Custom metadata
    started_at: datetime              # Request start time

Hook Blocking Behavior

Hooks can block pipeline execution based on mode:

# In LAB/PRODUCTION mode: raises PipelineBlocked exception
# In SHADOW mode: logs warning but continues

async def execute(self, hook_point, context) -> HookResult:
    if should_block(context):
        return HookResult(
            allow=False,
            message="Request blocked: exceeds safety threshold"
        )
    return HookResult(allow=True)

Model Clients

Tinman provides a unified interface for different LLM providers.

Abstract ModelClient

from tinman.integrations.model_client import ModelClient, ModelResponse

class ModelClient(ABC):
    """Abstract base class for model clients."""

    @property
    @abstractmethod
    def provider(self) -> str:
        """Provider name (e.g., 'openai', 'anthropic')."""
        pass

    @abstractmethod
    async def complete(self,
                       messages: list[dict[str, str]],
                       model: Optional[str] = None,
                       temperature: float = 0.7,
                       max_tokens: int = 4096,
                       tools: Optional[list[dict]] = None,
                       **kwargs) -> ModelResponse:
        """Send a completion request."""
        pass

    @abstractmethod
    async def stream(self,
                     messages: list[dict[str, str]],
                     model: Optional[str] = None,
                     **kwargs):
        """Stream a completion response."""
        pass

ModelResponse

@dataclass
class ModelResponse:
    id: str                              # Response ID
    content: str                         # Generated content
    model: str                           # Model used

    # Token usage
    prompt_tokens: int                   # Input tokens
    completion_tokens: int               # Output tokens
    total_tokens: int                    # Total tokens

    # Tool calls
    tool_calls: list[dict[str, Any]]     # Tool call requests

    # Metadata
    finish_reason: str                   # Stop reason
    latency_ms: int                      # Response latency
    raw: Optional[dict[str, Any]]        # Raw response

OpenAI Client

from tinman.integrations import OpenAIClient

client = OpenAIClient(
    api_key="sk-...",                    # Or use OPENAI_API_KEY env var
    base_url="https://api.openai.com/v1",  # Optional custom endpoint
    organization="org-...",               # Optional organization
)

# Use with Tinman
tinman = await create_tinman(
    model_client=client,
    mode=OperatingMode.LAB,
)

Supported models: - GPT-4 Turbo (gpt-4-turbo-preview) - GPT-4 (gpt-4) - GPT-3.5 Turbo (gpt-3.5-turbo) - Any OpenAI-compatible endpoint

Anthropic Client

from tinman.integrations import AnthropicClient

client = AnthropicClient(
    api_key="sk-ant-...",                # Or use ANTHROPIC_API_KEY env var
    base_url="https://api.anthropic.com",  # Optional custom endpoint
)

# Use with Tinman
tinman = await create_tinman(
    model_client=client,
    mode=OperatingMode.LAB,
)

Supported models: - Claude 3.5 Sonnet (claude-3-5-sonnet-20241022) - Claude 3 Opus (claude-3-opus-20240229) - Claude 3 Sonnet (claude-3-sonnet-20240229) - Claude 3 Haiku (claude-3-haiku-20240307)


Open Model Providers

Tinman supports several providers for open/free models, making it accessible for experimentation without API costs.

Provider Comparison

Provider Free Tier Speed Models Best For
OpenRouter Many free models Good DeepSeek, Qwen, Llama, Mistral Variety, free tiers
Groq 14,400 req/day Ultra-fast Llama 3.x, Mixtral, Gemma Speed, high volume
Ollama Unlimited (local) Varies Any Ollama model Privacy, offline
Together $25 credits Good DeepSeek, Qwen, Llama Quality, credits

OpenRouter Client

Access to 100+ models including DeepSeek, Qwen, Llama, and Mistral with many free tiers.

from tinman.integrations import OpenRouterClient

client = OpenRouterClient(
    api_key="sk-or-...",  # Or set OPENROUTER_API_KEY env var
)

# Use DeepSeek (free tier available)
tinman = await create_tinman(model_client=client)

# Specify model explicitly
response = await client.complete(
    messages=[{"role": "user", "content": "Hello"}],
    model="deepseek-chat",  # Shorthand
    # or: model="deepseek/deepseek-chat"  # Full ID
)

Available model shorthands:

Shorthand Full Model ID Notes
deepseek-chat deepseek/deepseek-chat Free tier
deepseek-coder deepseek/deepseek-coder Free tier
deepseek-r1 deepseek/deepseek-r1 Reasoning model
qwen-2.5-72b qwen/qwen-2.5-72b-instruct Strong general
qwen-2.5-coder-32b qwen/qwen-2.5-coder-32b-instruct Code
llama-3.1-405b meta-llama/llama-3.1-405b-instruct Largest
llama-3.1-70b meta-llama/llama-3.1-70b-instruct Balanced
mixtral-8x22b mistralai/mixtral-8x22b-instruct MoE
phi-3-mini microsoft/phi-3-mini-128k-instruct:free Free

Get API key: https://openrouter.ai/keys


Groq Client

Ultra-fast inference with a generous free tier (14,400 requests/day for smaller models).

from tinman.integrations import GroqClient

client = GroqClient(
    api_key="gsk_...",  # Or set GROQ_API_KEY env var
)

# Use Llama 3.1 70B (very fast!)
tinman = await create_tinman(model_client=client)

# Specify model
response = await client.complete(
    messages=[{"role": "user", "content": "Hello"}],
    model="llama-3.1-70b",
)

Available models:

Shorthand Full Model ID Context
llama-3.3-70b llama-3.3-70b-versatile 128K
llama-3.1-70b llama-3.1-70b-versatile 128K
llama-3.1-8b llama-3.1-8b-instant 128K
mixtral-8x7b mixtral-8x7b-32768 32K
gemma-2-9b gemma2-9b-it 8K

Get API key: https://console.groq.com/keys


Ollama Client

Run models locally - completely free, no API keys needed, works offline.

# Install Ollama first: https://ollama.ai
ollama pull llama3.1
ollama pull qwen2.5
ollama pull deepseek-r1
from tinman.integrations import OllamaClient

# No API key needed!
client = OllamaClient(
    base_url="http://localhost:11434/v1",  # Default
)

# Use local Llama
tinman = await create_tinman(model_client=client)

# Use Qwen
response = await client.complete(
    messages=[{"role": "user", "content": "Hello"}],
    model="qwen2.5",
)

# List locally available models
models = await client.list_local_models()
print(models)  # ['llama3.1', 'qwen2.5', ...]

Popular Ollama models:

Model Command Size
Llama 3.1 ollama pull llama3.1 8B default
Qwen 2.5 ollama pull qwen2.5 7B default
DeepSeek R1 ollama pull deepseek-r1 Various
DeepSeek Coder ollama pull deepseek-coder-v2 16B
Mistral ollama pull mistral 7B
Mixtral ollama pull mixtral 8x7B
CodeLlama ollama pull codellama 7B default

Together Client

Fast inference for open models with $25 free credits for new accounts.

from tinman.integrations import TogetherClient

client = TogetherClient(
    api_key="...",  # Or set TOGETHER_API_KEY env var
)

# Use DeepSeek V3
tinman = await create_tinman(model_client=client)

response = await client.complete(
    messages=[{"role": "user", "content": "Hello"}],
    model="deepseek-v3",
)

Available models:

Shorthand Full Model ID
deepseek-v3 deepseek-ai/DeepSeek-V3
deepseek-r1 deepseek-ai/DeepSeek-R1
llama-3.1-405b meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo
llama-3.1-70b meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo
qwen-2.5-72b Qwen/Qwen2.5-72B-Instruct-Turbo
mixtral-8x22b mistralai/Mixtral-8x22B-Instruct-v0.1

Get API key: https://api.together.xyz


Custom Model Client

Implement ModelClient for any LLM provider:

from tinman.integrations.model_client import ModelClient, ModelResponse

class CustomClient(ModelClient):
    """Client for custom LLM endpoint."""

    def __init__(self, endpoint: str, api_key: str):
        super().__init__(api_key=api_key)
        self.endpoint = endpoint

    @property
    def provider(self) -> str:
        return "custom"

    async def complete(self,
                       messages: list[dict[str, str]],
                       model: Optional[str] = None,
                       temperature: float = 0.7,
                       max_tokens: int = 4096,
                       tools: Optional[list[dict]] = None,
                       **kwargs) -> ModelResponse:
        # Your implementation
        async with aiohttp.ClientSession() as session:
            response = await session.post(
                self.endpoint,
                json={
                    "messages": messages,
                    "temperature": temperature,
                    "max_tokens": max_tokens,
                },
                headers={"Authorization": f"Bearer {self.api_key}"}
            )
            data = await response.json()

        return ModelResponse(
            content=data["content"],
            model=data.get("model", "custom"),
            prompt_tokens=data.get("usage", {}).get("prompt_tokens", 0),
            completion_tokens=data.get("usage", {}).get("completion_tokens", 0),
            total_tokens=data.get("usage", {}).get("total_tokens", 0),
            tool_calls=data.get("tool_calls", []),
            finish_reason=data.get("finish_reason", ""),
            latency_ms=0,  # Calculate from timing
            raw=data,
        )

    async def stream(self, messages, **kwargs):
        # Your streaming implementation
        pass

Embedding Tinman

Full Tinman Instance

For complete research capabilities:

from tinman import Tinman, create_tinman, OperatingMode
from tinman.integrations import OpenAIClient
from tinman.core.approval_handler import ApprovalMode

# Method 1: Factory function (recommended)
tinman = await create_tinman(
    mode=OperatingMode.LAB,
    model_client=OpenAIClient(),
    db_url="postgresql://localhost/tinman",
)

# Method 2: Manual initialization
tinman = Tinman(
    model_client=OpenAIClient(),
    mode=OperatingMode.LAB,
    approval_mode=ApprovalMode.AUTO_APPROVE,  # For automated systems
    auto_approve_in_lab=True,
)
await tinman.initialize(db_url="postgresql://localhost/tinman")

Tinman Components

Use individual components for specific needs:

from tinman.agents import (
    HypothesisEngine,
    ExperimentArchitect,
    ExperimentExecutor,
    FailureDiscoveryAgent,
)
from tinman.memory import MemoryGraph
from tinman.reasoning import LLMBackbone

# Just hypothesis generation
hypothesis_engine = HypothesisEngine(
    graph=your_graph,
    llm_backbone=your_llm,
)
hypotheses = await hypothesis_engine.run(context)

# Just failure classification
failure_discovery = FailureDiscoveryAgent(
    graph=your_graph,
    llm_backbone=your_llm,
)
failures = await failure_discovery.run(context, results=experiment_results)

With Custom Approval Flow

from tinman import Tinman
from tinman.core.approval_handler import ApprovalMode

# Create with interactive approvals
tinman = Tinman(
    model_client=client,
    approval_mode=ApprovalMode.INTERACTIVE,
    auto_approve_in_lab=False,  # Require all approvals
)

# Register custom UI callback
async def my_approval_ui(context):
    """Custom approval UI."""
    # Show in your UI
    approved = await show_approval_dialog(
        title=context.action_name,
        description=context.description,
        risk_tier=context.risk_tier,
    )
    return approved

tinman.register_approval_ui(my_approval_ui)

Event System

Tinman uses an event bus for cross-component communication.

Event Bus API

from tinman.core.event_bus import EventBus

event_bus = EventBus()

# Subscribe to events
@event_bus.on("hypothesis.generated")
async def handle_hypothesis(data):
    print(f"New hypothesis: {data['target_surface']}")

# Or subscribe programmatically
def my_handler(data):
    log_event("hypothesis.generated", data)

event_bus.subscribe("hypothesis.generated", my_handler)

# Unsubscribe
event_bus.unsubscribe("hypothesis.generated", my_handler)

Event Types

Event Data Description
hypothesis.generated {id, target_surface, failure_class} New hypothesis created
experiment.designed {id, hypothesis_id, stress_type} Experiment designed
experiment.started {id, total_runs} Experiment execution started
experiment.completed {id, failures_triggered} Experiment finished
failure.discovered {id, failure_class, severity} Failure identified
intervention.proposed {id, type, risk_tier} Intervention suggested
intervention.deployed {id, status} Intervention deployed
simulation.completed {id, outcome} Simulation finished
approval.requested {id, action, risk_tier} Approval needed
approval.decided {id, approved, reason} Approval decision made
hook.blocked {hook, point, message} Pipeline hook blocked
hook.error {hook, point, error} Pipeline hook error

Integration Example

from tinman import create_tinman
from tinman.core.event_bus import EventBus

# Create shared event bus
event_bus = EventBus()

# Subscribe handlers
@event_bus.on("failure.discovered")
async def alert_on_failure(data):
    if data.get("severity") in ["S3", "S4"]:
        await pagerduty.create_incident(
            title=f"Critical AI Failure: {data['failure_class']}",
            severity="high",
            details=data,
        )

@event_bus.on("experiment.completed")
async def log_experiment(data):
    await metrics.record("tinman.experiments", 1, tags={
        "failures": data["failures_triggered"],
    })

# Use with Tinman
tinman = await create_tinman(mode=OperatingMode.LAB)
tinman.event_bus = event_bus

Database Integration

Tinman uses PostgreSQL for persistent storage and SQLite for testing.

PostgreSQL Setup

# Create database
createdb tinman

# Run migrations (handled automatically)
tinman init
from tinman import create_tinman

tinman = await create_tinman(
    db_url="postgresql://user:pass@localhost:5432/tinman",
)

Connection URL Format

postgresql://[user[:password]@][host][:port]/database[?options]

Examples:

# Local development
db_url = "postgresql://localhost/tinman"

# With credentials
db_url = "postgresql://tinman_user:secret@localhost:5432/tinman"

# Remote server with SSL
db_url = "postgresql://user:pass@prod-db.example.com:5432/tinman?sslmode=require"

# SQLite for testing
db_url = "sqlite:///tinman.db"
db_url = "sqlite:///:memory:"  # In-memory

Using DatabaseConnection Directly

from tinman.db.connection import DatabaseConnection

# Create connection
db = DatabaseConnection("postgresql://localhost/tinman")
db.create_tables()

# Use session context manager
with db.session() as session:
    # Query operations
    results = session.execute(query).fetchall()

    # The session commits on exit

Schema Migrations

Tinman handles schema migrations automatically. For manual control:

from tinman.db.connection import DatabaseConnection

db = DatabaseConnection(db_url)

# Create all tables
db.create_tables()

# The memory graph schema includes:
# - nodes table (with temporal validity)
# - edges table (with relationships)
# - indexes for common queries

CI/CD Integration

GitHub Actions Example

# .github/workflows/tinman-research.yml
name: AI Reliability Research

on:
  schedule:
    - cron: '0 0 * * *'  # Daily at midnight
  workflow_dispatch:
    inputs:
      focus:
        description: 'Research focus area'
        required: false
        default: 'general'

jobs:
  research:
    runs-on: ubuntu-latest

    services:
      postgres:
        image: postgres:15
        env:
          POSTGRES_DB: tinman
          POSTGRES_USER: postgres
          POSTGRES_PASSWORD: postgres
        ports:
          - 5432:5432
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

    steps:
      - uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.11'

      - name: Install dependencies
        run: |
          pip install tinman
          pip install pytest pytest-asyncio

      - name: Run research cycle
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
          DATABASE_URL: postgresql://postgres:postgres@localhost:5432/tinman
          TINMAN_MODE: lab
        run: |
          python -c "
          import asyncio
          from tinman import create_tinman, OperatingMode
          from tinman.integrations import OpenAIClient

          async def main():
              client = OpenAIClient()
              tinman = await create_tinman(
                  mode=OperatingMode.LAB,
                  model_client=client,
                  db_url='$DATABASE_URL',
              )

              results = await tinman.research_cycle(
                  focus='${{ github.event.inputs.focus || 'general' }}',
                  max_hypotheses=5,
              )

              # Generate report
              report = await tinman.generate_report()

              with open('research-report.md', 'w') as f:
                  f.write(report)

              await tinman.close()

              # Fail if critical issues found
              critical = [f for f in results['failures'] if f.get('severity') in ['S3', 'S4']]
              if critical:
                  print(f'Found {len(critical)} critical issues!')
                  exit(1)

          asyncio.run(main())
          "

      - name: Upload report
        uses: actions/upload-artifact@v4
        with:
          name: research-report
          path: research-report.md

      - name: Create issue on critical findings
        if: failure()
        uses: actions/github-script@v7
        with:
          script: |
            const fs = require('fs');
            const report = fs.readFileSync('research-report.md', 'utf8');

            await github.rest.issues.create({
              owner: context.repo.owner,
              repo: context.repo.repo,
              title: 'Critical AI Reliability Issues Found',
              body: report,
              labels: ['ai-reliability', 'critical']
            });

Pre-commit Hook

# .pre-commit-config.yaml
repos:
  - repo: local
    hooks:
      - id: tinman-check
        name: Tinman AI Safety Check
        entry: python -c "
          import asyncio
          from tinman import create_tinman
          from tinman.integrations import OpenAIClient

          async def check():
              tinman = await create_tinman(
                  model_client=OpenAIClient(),
                  skip_db=True,
              )
              # Quick sanity check
              results = await tinman.research_cycle(
                  max_hypotheses=2,
                  max_experiments=1,
              )
              critical = [f for f in results.get('failures', []) if f.get('severity') == 'S4']
              return len(critical) == 0

          if not asyncio.run(check()):
              exit(1)
          "
        language: python
        pass_filenames: false
        stages: [push]

Framework Integration

FastAPI Integration

from fastapi import FastAPI, BackgroundTasks
from contextlib import asynccontextmanager
from tinman import create_tinman, OperatingMode
from tinman.integrations import OpenAIClient

# Global Tinman instance
tinman = None

@asynccontextmanager
async def lifespan(app: FastAPI):
    global tinman
    tinman = await create_tinman(
        mode=OperatingMode.SHADOW,
        model_client=OpenAIClient(),
        db_url="postgresql://localhost/tinman",
    )
    yield
    await tinman.close()

app = FastAPI(lifespan=lifespan)

@app.post("/api/llm/complete")
async def complete(request: CompletionRequest, background_tasks: BackgroundTasks):
    # Your normal completion logic
    response = await your_llm_call(request)

    # Background analysis with Tinman
    background_tasks.add_task(analyze_with_tinman, request, response)

    return response

async def analyze_with_tinman(request, response):
    """Run Tinman analysis in background."""
    from tinman.integrations import record_llm_interaction
    record_llm_interaction(
        adapter=tinman.pipeline_adapter,
        messages=request.messages,
        model=request.model,
        response_text=response.content,
    )

@app.get("/api/tinman/status")
async def tinman_status():
    return tinman.get_state()

@app.get("/api/tinman/report")
async def tinman_report():
    return await tinman.generate_report(format="json")

@app.post("/api/tinman/research")
async def start_research(focus: str = None):
    results = await tinman.research_cycle(focus=focus)
    return {"status": "complete", "findings": len(results["failures"])}

LangChain Integration

from tinman.integrations import TinmanLangChainCallbackHandler

# Usage
from langchain.llms import OpenAI

llm = OpenAI(callbacks=[TinmanLangChainCallbackHandler()])
response = llm.invoke("Your prompt here")

CrewAI Integration

from tinman.integrations import TinmanCrewHook

crew_hook = TinmanCrewHook()

# Wire into CrewAI task callbacks / events:
# crew_hook.on_task_start(task)
# crew_hook.on_task_end(task, output)
# crew_hook.on_task_error(task, error)

Django Integration

# settings.py
TINMAN_CONFIG = {
    "mode": "shadow",
    "db_url": "postgresql://localhost/tinman",
}

# services/tinman_service.py
import asyncio
from django.conf import settings
from tinman import create_tinman, OperatingMode
from tinman.integrations import OpenAIClient

class TinmanService:
    _instance = None
    _tinman = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance

    async def get_tinman(self):
        if self._tinman is None:
            config = settings.TINMAN_CONFIG
            self._tinman = await create_tinman(
                mode=OperatingMode(config["mode"]),
                model_client=OpenAIClient(),
                db_url=config["db_url"],
            )
        return self._tinman

    def run_research_sync(self, focus=None):
        """Synchronous wrapper for Django views."""
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)
        try:
            tinman = loop.run_until_complete(self.get_tinman())
            return loop.run_until_complete(
                tinman.research_cycle(focus=focus)
            )
        finally:
            loop.close()

# views.py
from django.http import JsonResponse
from .services.tinman_service import TinmanService

def tinman_status(request):
    service = TinmanService()
    loop = asyncio.new_event_loop()
    tinman = loop.run_until_complete(service.get_tinman())
    state = tinman.get_state()
    loop.close()
    return JsonResponse(state)

Production Deployment

Deployment Checklist

  • Database: PostgreSQL with proper credentials and SSL
  • Secrets: API keys in secure secret store (not environment variables)
  • Mode: Start with SHADOW mode, graduate to PRODUCTION
  • Approval: Configure HITL approval with proper timeouts
  • Monitoring: Set up event handlers for alerting
  • Resources: Adequate compute for LLM calls
  • Backups: Regular database backups
  • Audit: Enable audit logging

Docker Deployment

# Dockerfile
FROM python:3.11-slim

WORKDIR /app

# Install system dependencies
RUN apt-get update && apt-get install -y \
    libpq-dev \
    && rm -rf /var/lib/apt/lists/*

# Install Python dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy application
COPY . .

# Run Tinman
CMD ["python", "-m", "tinman.cli.main", "tui"]
# docker-compose.yml
version: '3.8'

services:
  tinman:
    build: .
    environment:
      - OPENAI_API_KEY=${OPENAI_API_KEY}
      - DATABASE_URL=postgresql://postgres:postgres@db:5432/tinman
      - TINMAN_MODE=shadow
    depends_on:
      - db
    volumes:
      - ./reports:/app/reports

  db:
    image: postgres:15-alpine
    environment:
      - POSTGRES_DB=tinman
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgres
    volumes:
      - postgres_data:/var/lib/postgresql/data
    ports:
      - "5432:5432"

volumes:
  postgres_data:

Kubernetes Deployment

# k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: tinman
  labels:
    app: tinman
spec:
  replicas: 1
  selector:
    matchLabels:
      app: tinman
  template:
    metadata:
      labels:
        app: tinman
    spec:
      containers:
      - name: tinman
        image: your-registry/tinman:latest
        env:
        - name: OPENAI_API_KEY
          valueFrom:
            secretKeyRef:
              name: tinman-secrets
              key: openai-api-key
        - name: DATABASE_URL
          valueFrom:
            secretKeyRef:
              name: tinman-secrets
              key: database-url
        - name: TINMAN_MODE
          value: "shadow"
        resources:
          requests:
            memory: "512Mi"
            cpu: "500m"
          limits:
            memory: "2Gi"
            cpu: "2000m"
        volumeMounts:
        - name: reports
          mountPath: /app/reports
      volumes:
      - name: reports
        persistentVolumeClaim:
          claimName: tinman-reports-pvc

Examples

Complete Integration Example

"""
Complete example: FastAPI service with Tinman integration.
"""
import asyncio
from contextlib import asynccontextmanager
from fastapi import FastAPI, HTTPException, BackgroundTasks
from pydantic import BaseModel
from typing import Optional

from tinman import create_tinman, OperatingMode
from tinman.integrations import OpenAIClient, PipelineAdapter, FailureDetectionHook
from tinman.core.event_bus import EventBus

# Models
class CompletionRequest(BaseModel):
    messages: list[dict]
    model: str = "gpt-4"
    temperature: float = 0.7

class ResearchRequest(BaseModel):
    focus: Optional[str] = None
    max_hypotheses: int = 5

# Global state
tinman = None
event_bus = EventBus()
adapter = None

# Event handlers
@event_bus.on("failure.discovered")
async def on_failure(data):
    print(f"ALERT: New failure discovered - {data['failure_class']}")
    # In production: send to monitoring system

@asynccontextmanager
async def lifespan(app: FastAPI):
    global tinman, adapter

    # Initialize Tinman
    client = OpenAIClient()
    tinman = await create_tinman(
        mode=OperatingMode.SHADOW,
        model_client=client,
        db_url="postgresql://localhost/tinman",
    )
    tinman.event_bus = event_bus

    # Initialize pipeline adapter
    adapter = PipelineAdapter(mode=OperatingMode.SHADOW)
    adapter.register_hook(FailureDetectionHook())

    yield

    await tinman.close()

app = FastAPI(
    title="AI Service with Tinman",
    lifespan=lifespan,
)

@app.post("/v1/completions")
async def create_completion(
    request: CompletionRequest,
    background_tasks: BackgroundTasks,
):
    """LLM completion endpoint with Tinman monitoring."""
    # Create pipeline context
    ctx = adapter.create_context(
        messages=request.messages,
        model=request.model,
    )

    # Pre-request hook
    ctx = await adapter.pre_request(ctx)

    # Your actual LLM call
    try:
        response = await tinman.model_client.complete(
            messages=request.messages,
            model=request.model,
            temperature=request.temperature,
        )
    except Exception as e:
        ctx.error = str(e)
        await adapter.on_error(ctx)
        raise HTTPException(status_code=500, detail=str(e))

    # Post-request hook
    ctx.response = {"content": response.content}
    ctx = await adapter.post_request(ctx)

    # Log any detected issues
    if "detected_failures" in ctx.metadata:
        background_tasks.add_task(
            log_potential_issues,
            ctx.metadata["detected_failures"],
            ctx.id,
        )

    return {
        "id": response.id,
        "content": response.content,
        "model": response.model,
        "usage": {
            "prompt_tokens": response.prompt_tokens,
            "completion_tokens": response.completion_tokens,
            "total_tokens": response.total_tokens,
        }
    }

async def log_potential_issues(issues: list, request_id: str):
    """Background task to log potential issues."""
    for issue in issues:
        print(f"[{request_id}] Potential issue detected: {issue}")

@app.post("/v1/research")
async def start_research(request: ResearchRequest):
    """Trigger a research cycle."""
    results = await tinman.research_cycle(
        focus=request.focus,
        max_hypotheses=request.max_hypotheses,
    )

    return {
        "hypotheses": len(results["hypotheses"]),
        "experiments": len(results["experiments"]),
        "failures": len(results["failures"]),
        "interventions": len(results["interventions"]),
    }

@app.get("/v1/tinman/status")
async def get_status():
    """Get Tinman status."""
    return tinman.get_state()

@app.get("/v1/tinman/report")
async def get_report(format: str = "markdown"):
    """Get research report."""
    report = await tinman.generate_report(format=format)
    return {"report": report}

@app.get("/v1/tinman/approvals/pending")
async def get_pending_approvals():
    """Get pending approval requests."""
    return tinman.get_pending_approvals()

@app.get("/health")
async def health():
    """Health check endpoint."""
    return await tinman.health_check()

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)

Summary

Integration approaches by use case:

Use Case Pattern Key Components
Custom app Library import create_tinman(), agents
Existing pipeline Pipeline adapter PipelineAdapter, hooks
Monitoring Event-driven EventBus, event handlers
CI/CD CLI/automation CLI commands, exit codes
Web service Framework integration FastAPI/Django patterns

Key integration points:

Component Purpose
PipelineAdapter Hook into existing LLM calls
ModelClient Unified interface for LLM providers
EventBus Subscribe to Tinman events
ApprovalHandler Custom approval UI integration
MemoryGraph Query research findings

Next Steps