Skip to content

release: v0.16.5 — design patterns, terminal actions, async observers#7

Merged
johnnichev merged 3 commits intomainfrom
release/v0.16.5
Mar 16, 2026
Merged

release: v0.16.5 — design patterns, terminal actions, async observers#7
johnnichev merged 3 commits intomainfrom
release/v0.16.5

Conversation

@johnnichev
Copy link
Copy Markdown
Owner

Summary

Major structural refactoring release — design patterns, code quality enforcement, and two community-requested features (FR-001 + FR-002).

New Features

  • Terminal actions (FR-001): @tool(terminal=True) + AgentConfig.stop_condition — stops the agent loop after specific tools fire
  • Async observers (FR-002): AsyncAgentObserver with blocking flag — async lifecycle hooks for DB writes, webhooks
  • Gemini 3.x thought signatures (FR-003): ToolCall.thought_signature field for function call signature preservation
  • StepType + ModelType enums: str, Enum for type safety (backward compatible)
  • Namespace exports: from selectools.providers import OpenAIProvider now works

Refactoring

  • Agent decomposed into 4 mixins: core.py 3128 → 1448 lines (-54%)
  • OpenAI/Ollama share base class: _OpenAICompatibleBase Template Method (-75% duplication)
  • Hooks deprecated: Wrapped via _HooksAdapter, single observer pipeline
  • Tool execution extracted: _execute_single_tool/_aexecute_single_tool replace 540 lines of duplication
  • astream() parity: Non-streaming fallback delegates to _acall_provider()

Quality

  • 163 new tests (total: 1640), 53 architecture fitness tests
  • Shared test fixtures in conftest.py
  • 6 Architecture Decision Records in docs/decisions/
  • Full documentation update (15+ files, notebook, example)

Test plan

  • pytest tests/ -x -q — 1586 passed, 54 skipped
  • black --check — clean
  • flake8 — clean
  • mypy — clean (mixin attr-defined suppressed via pyproject.toml)
  • mkdocs build — clean
  • All pre-commit hooks pass

🤖 Generated with Claude Code

Major structural refactoring to prevent the class of bugs found in
v0.16.0-v0.16.4 and prepare a clean foundation for v0.17.0 multi-agent
orchestration.

Features:
- Terminal action support: @tool(terminal=True) + stop_condition callback
- AsyncAgentObserver: async lifecycle hooks with blocking/non-blocking modes
- Gemini 3.x thought signature support (ToolCall.thought_signature)
- StepType + ModelType converted to str Enums (backward compatible)
- Namespace exports (from selectools.providers import OpenAIProvider)

Refactoring:
- Agent decomposed into 4 mixins (core.py 3128->1448 lines, -54%)
- OpenAI/Ollama share _OpenAICompatibleBase (Template Method, -75% duplication)
- Hooks deprecated via _HooksAdapter, single observer pipeline
- Tool execution extracted into _execute_single_tool/_aexecute_single_tool
- astream() non-streaming fallback delegates to _acall_provider()

Quality:
- 163 new tests (total: 1640), 53 architecture fitness tests
- Shared test fixtures in conftest.py
- 6 Architecture Decision Records in docs/decisions/
- Full documentation update across 15+ files
str(Enum) and format(Enum) behavior differs between Python versions:
- 3.9-3.10: str() returns "StepType.LLM_CALL", format() returns "llm_call"
- 3.11+: str() returns "llm_call", format() returns "StepType.LLM_CALL"

Use .value consistently for string output in trace timeline and OTel spans.
Fix test to assert on .value instead of str()/format().
- CI workflows: 3.11 -> 3.13
- mypy python_version: 3.9 -> 3.13
- CLAUDE.md: document Python 3.13+ target
@johnnichev johnnichev merged commit 725ad2f into main Mar 16, 2026
4 checks passed
johnnichev added a commit that referenced this pull request Mar 24, 2026
Security:
- Path traversal in JsonFileSessionStore — validate session_id (#9)
- Unicode homoglyph bypass in injection screening — NFKD + zero-width
  strip + homoglyph map (#13)

Data integrity:
- FileKnowledgeStore._save_all() atomic write via tmp + os.replace (#10)
- JsonFileSessionStore.save() atomic write (#31)

Agent core:
- astream() uses self._effective_model (was self.config.model) (#1)
- Sync _check_policy rejects async confirm_action with clear error (#2)
- Sync _streaming_call isinstance(chunk, str) guard (#18)

Providers:
- FallbackProvider stream()/astream() record success after consumption,
  not before — circuit breaker now works for streaming (#3)
- Gemini response.text ValueError catch for tool-call-only responses (#4)

Tools:
- aexecute() uses run_in_executor(None) shared executor (#5)
- execute() awaits coroutines from async tools via asyncio.run (#6)

RAG:
- Hybrid search O(n²) → O(1) via text_to_key dict lookup (#7)
- SQLiteVectorStore thread safety + WAL mode (#8)

Evals:
- OutputEvaluator catches re.error on invalid regex (#11)
- JsonValidityEvaluator respects expect_json=False (#12)

16 new regression tests. Full suite: 2000 passed.
@johnnichev johnnichev deleted the release/v0.16.5 branch March 24, 2026 02:55
johnnichev added a commit that referenced this pull request Mar 29, 2026
Critical:
- astream(): guard response_msg.content with `or ""` (pitfall #7)
- FileCheckpointStore.save(): atomic write via temp file + os.replace()
- llm_evaluators: fence all user-controlled content in judge prompts to prevent injection
- evals/regression.py, snapshot.py: atomic baseline/snapshot writes; sanitise suite_name path
- tools/base.py: shared module-level executor instead of per-call ThreadPoolExecutor
- rag/stores/memory.py: threading.Lock() on InMemoryVectorStore add/search/delete/clear

High:
- fallback.py: threading.Lock() on circuit breaker _failures/_circuit_open_until dicts
- memory.py: ConversationMemory.branch() deep-copies tool_calls to prevent shared mutation
- evals/report.py: fix p95/p99 off-by-one (use math.ceil, not int)
- tools/registry.py: warn on silent tool name collision instead of silently overwriting
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant