Technical Spec: Honcho Mock Server

Status: Draft Author: Claude (agent-assisted) Date: 2026-03-25 Target SDK Version: v2.1


Problem Statement

Developers building with the Honcho SDK currently need a running PostgreSQL database, Redis instance, vector store, and LLM API keys to validate their integrations. This creates significant friction for:

  1. SDK developers writing and running tests against the Honcho API.
  2. Application developers validating request/response shapes without a full Honcho deployment.
  3. CI pipelines that need a lightweight, deterministic server for integration tests.
  4. Offline development where external service dependencies are unavailable.

The Honcho test suite already contains a comprehensive mock infrastructure in tests/conftest.py and a working TestServer pattern in tests/sdk_typescript/conftest.py, but these are tightly coupled to pytest and not accessible to end users.

Goals

  1. Provide a mock Honcho server that runs the real FastAPI app with mocked external dependencies (LLM, embeddings, vector store, cache).
  2. Integrate into the Honcho CLI as honcho mock so developers can start a mock server with a single command.
  3. Use in-memory SQLite (via aiosqlite) so no PostgreSQL installation is required.
  4. Validate API contract — request schemas are validated against the real Pydantic models; responses match the OpenAPI spec exactly.
  5. Support stateful CRUD — workspaces, peers, sessions, messages, and conclusions are stored in-memory and persist for the lifetime of the server process.
  6. Deterministic behavior — same input produces same output (embeddings are content hashes, dialectic returns canned responses).
  7. Support test data seeding via JSON files or programmatic API calls.
  8. Design for future extraction into a standalone honcho-mock package installable via uv tool install.

Non-Goals

  1. Realistic LLM responses — the mock server does not call any LLM. Dialectic chat returns canned/configurable strings.
  2. Background processing — the deriver, dreamer, and reconciler are disabled. No queue processing occurs.
  3. Production parity — the mock server is not intended to replicate production behavior for load testing, performance benchmarking, or concurrency testing.
  4. PostgreSQL-specific features — JSONB operators, pgvector HNSW indexes, GIN indexes, and full-text search via tsvector are not available in SQLite. Metadata filtering and vector search return simplified results.
  5. Redis/cache behavior — caching is disabled; all reads hit the in-memory database.
  6. Webhook delivery — webhook endpoints can be registered but no HTTP delivery occurs.
  7. Authentication enforcement by default — auth is disabled unless explicitly enabled via flag.

Design

Architecture Overview

The mock server reuses the real Honcho FastAPI application (src/main.py) with dependency injection overrides for:

DependencyProductionMock
DatabasePostgreSQL + psycopgIn-memory SQLite + aiosqlite
Vector storeTurbopuffer / LanceDB / pgvectorIn-memory dict (from tests/conftest.py)
EmbeddingsOpenAI / Gemini APIDeterministic SHA-256 hash (from tests/conftest.py)
LLM callsAnthropic / Google / OpenAICanned string responses
CacheRedis via cashewsDisabled (no-op)
DeriverBackground queue processingDisabled (DERIVER.ENABLED = False)
DreamerBackground consolidationDisabled (DREAM.ENABLED = False)
SentryError trackingDisabled
TelemetryCloudEvents emissionDisabled
LangfuseObservabilityNo-op decorator
+--------------------------------------------------+
|               honcho mock (CLI)                  |
|                                                  |
|  +-----------+  +------------+  +-------------+  |
|  | FastAPI   |  | SQLite     |  | Mock Vector |  |
|  | App       |  | (in-mem)   |  | Store       |  |
|  | (real     |  | via        |  | (in-mem     |  |
|  |  routers) |  | aiosqlite  |  |  dict)      |  |
|  +-----------+  +------------+  +-------------+  |
|                                                  |
|  +-----------+  +------------+  +-------------+  |
|  | Mock      |  | Mock LLM   |  | Disabled:   |  |
|  | Embeddings|  | (canned)   |  | Deriver,    |  |
|  | (SHA-256) |  |            |  | Dreamer,    |  |
|  +-----------+  +------------+  | Sentry,     |  |
|                                 | Telemetry   |  |
|                                 +-------------+  |
+--------------------------------------------------+
         |
         | HTTP :8000 (configurable)
         v
    SDK / curl / tests

Mock Behavior Table

API EndpointMock Behavior
POST /v3/workspacesCreates/returns workspace in SQLite. Fully functional.
POST /v3/workspaces/listLists workspaces with pagination. Metadata filters use simple equality (no JSONB operators).
PUT /v3/workspaces/{id}Updates workspace metadata/configuration.
DELETE /v3/workspaces/{id}Marks as deleted immediately (no background queue).
POST /v3/workspaces/{id}/peersCreates/returns peer. Fully functional.
POST /v3/workspaces/{id}/peers/listLists peers with pagination.
PUT /v3/workspaces/{id}/peers/{id}Updates peer metadata/configuration.
POST /v3/workspaces/{id}/peers/{id}/chatReturns canned response: {"content": "Mock dialectic response"}. Streaming returns 3 SSE chunks.
POST /v3/workspaces/{id}/peers/{id}/representationReturns {"representation": "No representation available (mock server)"}.
GET /v3/workspaces/{id}/peers/{id}/cardReturns stored peer card or null.
PUT /v3/workspaces/{id}/peers/{id}/cardStores and returns peer card.
GET /v3/workspaces/{id}/peers/{id}/contextReturns peer card + empty representation.
POST /v3/workspaces/{id}/sessionsCreates/returns session. Fully functional.
POST /v3/workspaces/{id}/sessions/listLists sessions with pagination.
PUT /v3/workspaces/{id}/sessions/{id}Updates session metadata/configuration.
DELETE /v3/workspaces/{id}/sessions/{id}Marks inactive immediately.
POST /v3/workspaces/{id}/sessions/{id}/cloneClones session and messages.
POST /v3/workspaces/{id}/sessions/{id}/peersAdds peers to session.
PUT /v3/workspaces/{id}/sessions/{id}/peersSets session peers.
DELETE /v3/workspaces/{id}/sessions/{id}/peersRemoves peers from session.
GET /v3/workspaces/{id}/sessions/{id}/contextReturns messages (within token limit) + summary if available. No representation.
POST /v3/workspaces/{id}/sessions/{id}/messagesCreates messages. Does NOT enqueue for deriver processing.
POST /v3/workspaces/{id}/sessions/{id}/messages/listLists messages with pagination.
GET /v3/workspaces/{id}/sessions/{id}/messages/{id}Returns single message.
PUT /v3/workspaces/{id}/sessions/{id}/messages/{id}Updates message metadata.
POST /v3/workspaces/{id}/conclusionsCreates conclusions (documents) in SQLite. Embeddings stored as deterministic hashes.
POST /v3/workspaces/{id}/conclusions/listLists conclusions with pagination.
POST /v3/workspaces/{id}/conclusions/queryReturns all conclusions for the observer/observed pair (no real vector similarity).
DELETE /v3/workspaces/{id}/conclusions/{id}Soft-deletes conclusion.
POST /v3/workspaces/{id}/searchReturns empty list (full-text search not supported in SQLite mock).
POST /v3/workspaces/{id}/peers/{id}/searchReturns empty list.
POST /v3/workspaces/{id}/sessions/{id}/searchReturns empty list.
GET /v3/workspaces/{id}/queue/statusReturns all zeros (no queue processing).
POST /v3/workspaces/{id}/schedule_dreamReturns 400 (“Dreams are not enabled”).
POST /v3/keysReturns 400 (“Auth is disabled”) unless --auth flag is used.
POST /v3/workspaces/{id}/webhooksCreates webhook endpoint. No delivery occurs.

SQLite Compatibility Layer

The production models in src/models.py use PostgreSQL-specific features that require adaptation for SQLite:

Problem areas:

  1. JSONB columns — SQLite has no native JSONB. Use JSON type (SQLAlchemy maps this to TEXT with JSON serialization).
  2. Vector(1536) columns (pgvector) — SQLite has no vector type. Store as JSON-serialized list or omit.
  3. CheckConstraint with regex (id ~ '^[A-Za-z0-9_-]+$') — SQLite does not support regex in CHECK. Remove these constraints for mock.
  4. server_default=text("'{}'::jsonb") — PostgreSQL cast syntax. Use server_default=text("'{}'").
  5. Identity() for auto-increment — SQLite uses AUTOINCREMENT. Needs adaptation.
  6. Index with postgresql_using, postgresql_ops, postgresql_with — Skip these indexes entirely.
  7. text("to_tsvector('english', content)") — Skip GIN full-text index.
  8. Partial unique indexes (postgresql_where) — SQLite supports partial indexes with different syntax but the postgresql_where kwarg won’t work. Skip these.

Approach: Create a mock_models.py module (or a function create_mock_metadata()) that clones Base.metadata and strips PostgreSQL-specific constructs before calling create_all(). Alternatively, iterate over Base.metadata.sorted_tables and remove incompatible constraints/indexes at table creation time. The cleanest approach is:

def sanitize_metadata_for_sqlite(metadata: MetaData) -> None:
    """Remove PostgreSQL-specific constructs from metadata for SQLite compatibility."""
    for table in metadata.sorted_tables:
        # Remove PostgreSQL-specific indexes
        table.indexes = {
            idx for idx in table.indexes
            if not any(
                hasattr(idx, k) for k in ['postgresql_using', 'postgresql_ops']
            )
        }
        # Remove CHECK constraints with PostgreSQL regex
        table.constraints = {
            c for c in table.constraints
            if not (isinstance(c, CheckConstraint) and '~' in str(c.sqltext))
        }

This function runs once during mock server startup before Base.metadata.create_all().

CLI Integration

The mock server is exposed as a subcommand of the Honcho CLI (as specified in the CLI spec):

# Start mock server with defaults (port 8000, no auth, no seed data)
honcho mock
 
# Custom port
honcho mock --port 9000
 
# Pre-populate with test data from JSON file
honcho mock --seed data.json
 
# Enable authentication (generates and prints an admin JWT)
honcho mock --auth
 
# Custom JWT secret (implies --auth)
honcho mock --auth --jwt-secret "my-test-secret"
 
# Verbose logging
honcho mock --log-level debug
 
# Combined
honcho mock --port 9000 --seed data.json --auth --log-level debug

CLI Output:

Honcho Mock Server v3.0.3
  Mode:     mock (in-memory SQLite, no external dependencies)
  Address:  http://127.0.0.1:8000
  Auth:     disabled
  Seed:     none

  Disabled: deriver, dreamer, sentry, telemetry, cache
  Mocked:   embeddings (SHA-256), LLM (canned), vector store (in-memory)

Press Ctrl+C to stop.

When --auth is used:

Honcho Mock Server v3.0.3
  Mode:     mock (in-memory SQLite, no external dependencies)
  Address:  http://127.0.0.1:8000
  Auth:     enabled (JWT)
  Admin JWT: eyJhbGciOiJIUzI1NiIs...
  Seed:     data.json (3 workspaces, 5 peers, 12 sessions, 48 messages)

  Disabled: deriver, dreamer, sentry, telemetry, cache
  Mocked:   embeddings (SHA-256), LLM (canned), vector store (in-memory)

Press Ctrl+C to stop.

CLI Implementation

The CLI command handler lives in the CLI package and imports mock server setup utilities from a new module:

New file: src/mock/__init__.py

Exposes: create_mock_app() -> FastAPI, run_mock_server(port, seed_file, auth, jwt_secret, log_level)

New file: src/mock/server.py

Core logic:

import threading
import uvicorn
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker
from src.main import app
from src.db import Base
from src.dependencies import get_db
from src.mock.overrides import apply_mock_overrides, sanitize_metadata_for_sqlite
 
async def create_mock_engine():
    """Create an in-memory SQLite engine with all tables."""
    engine = create_async_engine(
        "sqlite+aiosqlite:///:memory:",
        echo=False,
    )
    # Strip PG-specific constructs
    sanitize_metadata_for_sqlite(Base.metadata)
 
    async with engine.begin() as conn:
        await conn.run_sync(Base.metadata.create_all)
 
    return engine
 
def run_mock_server(
    port: int = 8000,
    seed_file: str | None = None,
    auth: bool = False,
    jwt_secret: str | None = None,
    log_level: str = "info",
):
    """Start the mock server (blocking)."""
    import asyncio
 
    engine = asyncio.run(create_mock_engine())
    session_factory = async_sessionmaker(bind=engine, expire_on_commit=False)
 
    # Apply all mock overrides to the real FastAPI app
    apply_mock_overrides(app, session_factory, auth=auth, jwt_secret=jwt_secret)
 
    # Seed data if provided
    if seed_file:
        asyncio.run(seed_from_file(engine, session_factory, seed_file))
 
    # Print banner
    print_banner(port, auth, jwt_secret, seed_file)
 
    # Run uvicorn
    uvicorn.run(app, host="127.0.0.1", port=port, log_level=log_level)

New file: src/mock/overrides.py

Centralizes all mock dependency overrides, extracted from tests/conftest.py:

def apply_mock_overrides(
    app: FastAPI,
    session_factory: async_sessionmaker,
    auth: bool = False,
    jwt_secret: str | None = None,
) -> None:
    """Apply all mock overrides to the FastAPI app."""
 
    # 1. Database override
    async def mock_get_db():
        async with session_factory() as session:
            yield session
    app.dependency_overrides[get_db] = mock_get_db
 
    # 2. tracked_db override (same pattern as tests/sdk_typescript/conftest.py)
    # Patches all tracked_db import sites
 
    # 3. Settings overrides
    settings.DERIVER.ENABLED = False
    settings.DREAM.ENABLED = False
    settings.SENTRY.ENABLED = False
    settings.CACHE.ENABLED = False
    settings.METRICS.ENABLED = False
    settings.TELEMETRY.ENABLED = False
    settings.AUTH.USE_AUTH = auth
    if auth:
        settings.AUTH.JWT_SECRET = jwt_secret or "honcho-mock-secret"
 
    # 4. Mock embeddings (reuse _content_to_embedding from tests/conftest.py)
    # 5. Mock vector store (reuse mock_vector_store from tests/conftest.py)
    # 6. Mock LLM calls (reuse mock_llm_call_functions from tests/conftest.py)
    # 7. Mock langfuse decorator
    # 8. Disable background task enqueue (make enqueue a no-op)

New file: src/mock/seed.py

Handles JSON seed file loading:

async def seed_from_file(
    engine: AsyncEngine,
    session_factory: async_sessionmaker,
    seed_file: str,
) -> SeedSummary:
    """Load seed data from a JSON file into the mock database."""
    ...

Test Data Seeding

JSON Seed Format:

{
  "workspaces": [
    {
      "id": "my-workspace",
      "metadata": {"env": "test"}
    }
  ],
  "peers": [
    {
      "workspace_id": "my-workspace",
      "id": "user-alice",
      "metadata": {"role": "user"}
    },
    {
      "workspace_id": "my-workspace",
      "id": "agent-bot",
      "metadata": {"role": "agent"}
    }
  ],
  "sessions": [
    {
      "workspace_id": "my-workspace",
      "id": "session-1",
      "peers": {
        "user-alice": {"observe": true},
        "agent-bot": {"observe": true}
      }
    }
  ],
  "messages": [
    {
      "workspace_id": "my-workspace",
      "session_id": "session-1",
      "peer_id": "user-alice",
      "content": "Hello, how are you?"
    },
    {
      "workspace_id": "my-workspace",
      "session_id": "session-1",
      "peer_id": "agent-bot",
      "content": "I'm doing well, thanks for asking!"
    }
  ],
  "conclusions": [
    {
      "workspace_id": "my-workspace",
      "observer_id": "agent-bot",
      "observed_id": "user-alice",
      "content": "Alice is a friendly user who uses greetings."
    }
  ]
}

The seeder creates resources in dependency order: workspaces peers sessions (with peer associations) messages conclusions.

TestServer Reuse Pattern

The existing TestServer class from tests/sdk_typescript/conftest.py provides the exact pattern needed. The mock server module can either:

Option A (preferred): Extract TestServer into src/mock/server.py and reuse it from both tests and CLI.

Option B: Keep TestServer in tests and have the CLI use uvicorn.run() directly (since CLI is blocking, no need for background thread).

For the CLI, Option B is simpler — uvicorn.run() blocks the main thread, which is the desired behavior. The TestServer threading pattern is only needed when embedding a server inside a test process.

For programmatic use (future standalone package), the TestServer pattern should be exposed:

from honcho.mock import MockServer
 
server = MockServer(port=8000, seed_file="data.json")
server.start()  # Non-blocking, starts in background thread
# ... run tests ...
server.stop()

Standalone Package (Future Phase)

The src/mock/ module is designed so it can be extracted into a standalone package:

honcho-mock/
  pyproject.toml
  src/
    honcho_mock/
      __init__.py       # MockServer class
      __main__.py       # CLI entry: python -m honcho_mock
      server.py         # Core server logic
      overrides.py      # Mock dependency overrides
      seed.py           # JSON seed loading

The standalone package would depend on honcho (the server package) and add only aiosqlite as an additional dependency.

Installation and usage:

uv tool install honcho-mock
honcho-mock --port 8000 --seed data.json
 
# Or as a Python module
python -m honcho_mock --port 8000

Handling the enqueue Background Task

The message creation endpoint (POST .../messages) calls background_tasks.add_task(enqueue, payloads) to trigger deriver processing. In the mock server, this must be neutralized. Two approaches:

Approach 1 (preferred): Patch enqueue to be a no-op.

async def mock_enqueue(payload: list[dict]) -> None:
    """No-op: mock server does not process the deriver queue."""
    pass
 
# In apply_mock_overrides:
patch("src.deriver.enqueue.enqueue", mock_enqueue)

This is simple and matches the existing test approach.

Approach 2: Patch enqueue_deletion and enqueue_dream as well. Since workspace/session deletion and dream scheduling also go through the queue, these also become no-ops. Deletion endpoints will return 202 but no actual background cleanup occurs (acceptable for mock).

Dialectic Chat Mock

The dialectic chat endpoint (POST /v3/workspaces/{id}/peers/{id}/chat) is the most complex mock because it involves an agentic loop. The mock replaces agentic_chat and agentic_chat_stream:

Non-streaming:

{"content": "Mock dialectic response"}

Streaming (SSE):

data: {"delta": {"content": "Mock "}, "done": false}
data: {"delta": {"content": "dialectic "}, "done": false}
data: {"delta": {"content": "response"}, "done": false}
data: {"done": true}

This matches the existing mock in tests/conftest.py (mock_llm_call_functions).

Configurable responses (stretch goal): Allow users to configure dialectic responses via a config file or environment variable:

honcho mock --dialectic-response "This is my custom mock response"

Dependencies

The mock server introduces one new dependency:

  • aiosqlite — async SQLite driver for SQLAlchemy. Already widely used, minimal footprint.

This should be added as an optional dependency in pyproject.toml:

[project.optional-dependencies]
mock = ["aiosqlite>=0.20.0"]

Implementation Phases

Phase 1: Core Mock Module (MVP)

Goal: honcho mock starts a working server that handles all CRUD operations.

Tasks:

  1. Create src/mock/__init__.py, src/mock/server.py, src/mock/overrides.py.
  2. Implement sanitize_metadata_for_sqlite() to strip PostgreSQL-specific constructs from SQLAlchemy metadata.
  3. Implement apply_mock_overrides() extracting mock logic from tests/conftest.py:
    • Database override (SQLite in-memory).
    • tracked_db patching (all 11 import sites from tests/sdk_typescript/conftest.py).
    • Embedding mock (_content_to_embedding).
    • Vector store mock (in-memory dict).
    • LLM call mocks (canned responses).
    • Langfuse no-op.
    • Deriver enqueue no-op.
    • Settings overrides (disable deriver, dreamer, sentry, cache, metrics, telemetry).
  4. Implement run_mock_server() with uvicorn.
  5. Add honcho mock CLI command with --port and --log-level flags.
  6. Add aiosqlite to optional dependencies.
  7. Verify: all CRUD operations work via curl or SDK.

Estimated effort: 3-4 days.

Phase 2: Seeding and Auth

Goal: Support --seed, --auth, and --jwt-secret flags.

Tasks:

  1. Implement src/mock/seed.py with JSON schema validation and dependency-ordered insertion.
  2. Implement --auth flag (enable JWT auth, print admin token).
  3. Implement --jwt-secret flag.
  4. Write example seed file (examples/mock-seed.json).
  5. Add banner output with server status summary.

Estimated effort: 2 days.

Phase 3: Testing and Hardening

Goal: Verify the mock server works with both Python and TypeScript SDKs.

Tasks:

  1. Write integration tests that start mock server and run SDK operations against it.
  2. Verify pagination works correctly with SQLite.
  3. Verify metadata filtering works for simple equality cases.
  4. Test seed file loading with various data shapes.
  5. Test error cases (invalid seed file, port already in use, etc.).
  6. Document known limitations (no full-text search, no vector similarity, simplified metadata filtering).

Estimated effort: 2-3 days.

Phase 4: Standalone Package (Future)

Goal: Extract src/mock/ into a standalone honcho-mock package.

Tasks:

  1. Create honcho-mock/ package with its own pyproject.toml.
  2. Add __main__.py for python -m honcho_mock usage.
  3. Add MockServer class for programmatic use.
  4. Publish to PyPI.
  5. Add uv tool install honcho-mock documentation.

Estimated effort: 1-2 days.


Files to Create

FilePurpose
src/mock/__init__.pyPackage init, exports create_mock_app, run_mock_server, MockServer
src/mock/server.pyCore server logic: engine creation, SQLite metadata sanitization, uvicorn runner
src/mock/overrides.pyAll mock dependency overrides extracted from tests/conftest.py
src/mock/seed.pyJSON seed file loading and database population
examples/mock-seed.jsonExample seed file for documentation

Files to Modify

FileChange
pyproject.tomlAdd aiosqlite to [project.optional-dependencies] under mock group
CLI entry point (per CLI spec)Add honcho mock subcommand with --port, --seed, --auth, --jwt-secret, --log-level flags
tests/conftest.pyExtract _content_to_embedding and mock factory functions into importable locations (or import from src/mock/overrides.py in tests)

Key Files Referenced

FileRelevance
tests/conftest.pySource of all mock implementations to extract
tests/sdk_typescript/conftest.pyTestServer pattern and tracked_db patching sites
src/main.pyFastAPI app definition, lifespan, middleware, routers
src/db.pyDatabase engine, Base, SessionLocal, request_context
src/dependencies.pyget_db and tracked_db — primary override targets
src/config.pyAppSettings and all nested settings to override
src/models.pyORM models with PostgreSQL-specific constructs to sanitize
src/schemas/api.pyPydantic schemas defining the API contract
src/security.pyJWT creation/verification, require_auth dependency
src/routers/*.pyAll API route handlers
src/deriver/enqueue.pyenqueue() function to no-op
src/vector_store/__init__.pyVectorStore ABC, get_external_vector_store
src/embedding_client.py_EmbeddingClient to mock

Risk Assessment

High Risk

SQLite compatibility with SQLAlchemy models. The production models use extensive PostgreSQL-specific features (JSONB, pgvector Vector type, regex CHECK constraints, partial unique indexes, Identity columns, ::jsonb casts in server_default). The sanitize_metadata_for_sqlite() function must handle all of these correctly, and edge cases will likely emerge during implementation.

Mitigation: Start with a spike to verify all tables can be created in SQLite. If too many incompatibilities arise, fall back to using a “mock-specific” metadata/model set that mirrors production schemas without PG-specific constructs. Alternatively, use DuckDB with its JSONB support as the in-memory backend instead of SQLite.

Medium Risk

tracked_db patching fragility. The mock must patch tracked_db at 11 import sites (and this list may grow). If a new module imports tracked_db and the mock doesn’t patch it, that code path will try to use the production PostgreSQL connection and fail.

Mitigation: Maintain a central list of tracked_db import sites (already done in both tests/conftest.py and tests/sdk_typescript/conftest.py). Add a startup check that verifies all known import sites are patched. Consider refactoring tracked_db to use a factory pattern that can be globally overridden in one place.

Medium Risk

Metadata filtering limitations. Production uses PostgreSQL JSONB operators for metadata filtering (@>, ?, path queries). SQLite has json_extract() but the query builder in src/crud/ likely generates PG-specific SQL for metadata filters.

Mitigation: Document that metadata filtering in the mock server only supports simple top-level key equality. Complex JSONB queries will return unfiltered results or raise an error with a clear message.

Low Risk

Pagination behavior differences. fastapi-pagination should work identically with SQLite since it operates at the SQLAlchemy query level, not the database dialect level.

Low Risk

Port conflicts. The mock server may fail to bind to the requested port.

Mitigation: Catch OSError on bind and print a clear error message suggesting an alternative port.


Verification Plan

Unit Tests

  1. SQLite metadata sanitization: Verify sanitize_metadata_for_sqlite() produces a metadata object that can create_all() without errors on an SQLite engine.
  2. Mock overrides: Verify all dependency overrides are correctly applied and the original app state is not permanently mutated.
  3. Seed loading: Verify seed files are parsed and inserted in the correct dependency order.

Integration Tests

  1. Full CRUD lifecycle: Start mock server, create workspace peer session messages conclusions, read them back, update, delete. Verify all responses match expected schemas.
  2. Python SDK compatibility: Run a subset of SDK tests against the mock server.
  3. TypeScript SDK compatibility: Run a subset of TypeScript SDK tests against the mock server.
  4. Pagination: Verify paginated endpoints return correct items, total, page, pages, size fields.
  5. Dialectic mock: Verify both streaming and non-streaming dialectic responses.
  6. Auth flow: Start with --auth, verify unauthenticated requests are rejected, authenticated requests succeed.
  7. Seed file: Start with --seed, verify seeded data is queryable via API.

Manual Smoke Tests

  1. Start honcho mock, point Python SDK at http://localhost:8000, run through the quickstart tutorial.
  2. Start honcho mock --port 9000 --auth, use the printed JWT to authenticate SDK calls.

Open Questions

  1. DuckDB vs SQLite? DuckDB supports JSONB natively and has better PostgreSQL compatibility. It would reduce the SQLite adaptation burden. However, it adds a heavier dependency. Decision: Start with SQLite (lighter weight, more widely available), upgrade to DuckDB only if SQLite compatibility becomes unmanageable.

  2. Should the mock server expose an OpenAPI spec endpoint? The real FastAPI app already serves /openapi.json and /docs. These would be automatically available in the mock server. This is a feature, not a question — just confirming it works.

  3. Should search endpoints return something useful? Currently specced to return empty lists. An alternative is to implement basic substring matching on message content using SQLite LIKE. This would make the mock more useful for testing search flows without requiring pgvector or full-text search.

  4. Should the mock persist to disk optionally? An --persist /path/to/db.sqlite flag could write the SQLite database to disk, allowing data to survive server restarts. This would be useful for longer development sessions. Low priority but worth considering.

  5. How should the mock handle file upload endpoints? The POST .../messages/upload endpoint processes files (PDF, etc.) into message content. The mock could either reject file uploads with a clear error or accept them with simplified text extraction. Decision: accept files, use the existing process_file_uploads_for_messages utility if possible, or return the raw filename as content if not.

  6. Should tests/conftest.py import from src/mock/overrides.py? This would reduce duplication but create a dependency from test infrastructure on the mock module. Alternative: keep them separate and accept the duplication, or extract shared utilities into src/mock/shared.py that both consume.