The 21 the automatic pass could not make on its own. `ruff check` and `ruff format --check` are both clean now; typecheck is still red and is next. `in_reasoning` in chat/service.py was a complete state machine that nothing read: initialised False, set True when a reasoning delta arrived, set False when the summary ended — three assignments, zero reads. Ruff reported one at a time, and removing each revealed the next, so what looked like a single stray variable took three passes to bottom out. The branches themselves do real work and are untouched; only the flag is gone. Four `raise HTTPException` inside `except` blocks now chain with `from e`. Until now a failure while handling an error was indistinguishable from the error, which matters most in exactly the situation where the traceback is all you have. In biographer/tools.py the binding was unused but the call is not: MemoryType() is called for the ValueError it raises on an invalid name. The binding is gone and the call and its comment stay, because dropping the line would have removed the validation. The rest are unused bindings in tests where the assertions are on something else (call_args, mostly), plus three unused loop variables and an isinstance tuple. One correction to my own work: removing a dead comprehension in test_error_handling.py left an `if` block with nothing but comments in it, which is a SyntaxError. Ruff caught it immediately. The block now says what the test actually pins — that the stream parses without crashing, which reaching that line demonstrates — rather than computing a list nobody asserts on. `make test` is intermittent here, and it is not this change. test_tatlock_tool_call_logging_calculator failed in two of five full runs across both HEAD and this branch, and passes in the other three; it also fails in isolation at HEAD while passing in isolation here. Order- or timing-dependent. Recorded rather than chased, since tests are not gated in this repo yet. Co-Authored-By: Claude <noreply@anthropic.com>
End-to-End API Tests
These tests make real HTTP requests to the running Tatlock API server to verify the complete stack works correctly.
Prerequisites
- Server must be running on
http://localhost:8777(usemake run) - Ollama must be running with the
gemma4:e2bmodel - Redis must be running (for benchmarking)
- Qdrant must be running on
http://localhost:6333(for memory tests)
Running the Tests
Start the server first:
# Terminal 1: Start the server (auto-reload enabled)
make run
# Logs are written to build/logs/server.log - tail them in another terminal:
tail -f logs/server.log
Run the E2E tests:
# Run all E2E tests
pytest tests/e2e/ -v -m e2e
# Run orchestration tests specifically
pytest tests/e2e/test_orchestration_e2e.py -v
# Run API endpoint tests
pytest tests/e2e/test_api_endpoints.py -v
Run specific test categories:
# Memory system tests
pytest tests/e2e/test_orchestration_e2e.py::TestMemoryStorage -v
pytest tests/e2e/test_orchestration_e2e.py::TestMemoryRecall -v
# Steward delegation tests
pytest tests/e2e/test_orchestration_e2e.py::TestStewardDelegation -v
# Direct delegation bypass tests (new feature)
pytest tests/e2e/test_orchestration_e2e.py::TestDirectDelegationBypass -v
# User isolation tests
pytest tests/e2e/test_orchestration_e2e.py::TestUserContextIsolation -v
# Orchestration scenario tests
pytest tests/e2e/test_orchestration_e2e.py::TestScenario1WeatherWithMemory -v
pytest tests/e2e/test_orchestration_e2e.py::TestScenario4SimpleExpertDelegation -v
pytest tests/e2e/test_orchestration_e2e.py::TestScenario6WikiCreation -v
# Generate evaluation report
pytest tests/e2e/test_orchestration_e2e.py::TestEvaluationReport -v -s
Test Organization
test_api_endpoints.py - Core API Tests
- Chat Completions endpoint (
/v1/chat/completions) - Responses API endpoint (
/v1/responses) - Streaming responses
- Error handling
- OpenAI format compliance
test_orchestration_e2e.py - Orchestration Scenario Tests
Based on ORCHESTRATION_SCENARIOS.md:
| Class | Scenario | What it Tests |
|---|---|---|
TestMemoryStorage |
Memory storage | Store -> Qdrant verification |
TestMemoryRecall |
Memory recall | Store -> Recall flow |
TestStewardDelegation |
Steward routing | Capability recommendations |
TestDirectDelegation |
Direct bypass | Pure memory/librarian requests |
TestScenario1WeatherWithMemory |
Weather check | Multi-step with memory lookup |
TestScenario4SimpleExpertDelegation |
Calculator/datetime | Simple tool use |
TestScenario6WikiCreation |
Wiki operations | Librarian delegation |
TestScenario8MultiExpertCoordination |
Complex requests | Multiple capabilities |
TestUserContextIsolation |
User isolation | llm_tester vs production |
TestDataVerification |
Data presence | Qdrant structure verification |
TestIntegrationHealth |
System health | API/Qdrant reachability |
TestEvaluationReport |
Diagnostic | Generates behavior reports |
User Isolation
Tests use the llm_tester user (development environment default) to isolate test data from production:
- Test memories:
memories_llm_tester(Qdrant collection) - Production memories:
memories_jpmschweitzer(never modified by tests)
Handling LLM Non-Determinism
LLM outputs are non-deterministic. Tests handle this by:
- Flexible assertions - Check for behavior patterns, not exact text
assert_llm_behavior()- Helper for pattern matching with confidence levels- Soft failures (
pytest.xfail) - Some tests may fail due to LLM variance without failing the suite - Evaluation reports - Generate diagnostic reports for human review
Example:
result = assert_llm_behavior(
message_text,
expected_patterns=[r"(remember|noted|stored)", r"purple"],
min_matches=1,
)
if not result.passed:
pytest.xfail(f"LLM response unclear: {result.evidence}")
Data Verification
Tests verify data presence in Qdrant:
# QdrantVerifier helper
qdrant = QdrantVerifier()
points = await qdrant.scroll_points("memories_llm_tester")
memory = await qdrant.find_memory_by_key("memories_llm_tester", "favorite_color")
Troubleshooting
Tests fail with connection error
Make sure the server is running:
make run
curl http://localhost:8777/health # Should return 200
Tests timeout
- Check Ollama is running:
curl http://localhost:11434/api/tags - Increase timeout if needed (default: 120s for LLM calls)
Memory tests fail
- Check Qdrant is running:
curl http://localhost:6333/collections - Verify
memories_llm_testercollection exists
Inconsistent results
- LLM responses vary - this is expected
- Check the evaluation report for detailed diagnostics:
pytest tests/e2e/test_orchestration_e2e.py::TestEvaluationReport -v -s
Tests pollute production data
- This shouldn't happen - tests use
llm_testeruser - If it does, check
ENVIRONMENTis set todevelopmentin.env
Adding New Tests
- Use existing fixtures (
client,qdrant,clean_test_memories) - Use
assert_llm_behavior()for flexible LLM output checking - Add
@pytest.mark.e2edecorator - Consider adding soft failures for non-deterministic checks
- Add test keys to
clean_test_memoriesfixture if storing new memories
Example:
@pytest.mark.e2e
@pytest.mark.asyncio
class TestNewScenario:
async def test_something(
self,
client: httpx.AsyncClient,
qdrant: QdrantVerifier,
clean_test_memories,
):
response = await client.post("/v1/responses", json={...})
# Use assert_llm_behavior for flexible checking
result = assert_llm_behavior(response_text, expected_patterns=[...])