diff --git a/CHANGELOG.md b/CHANGELOG.md index feb7b21..c6b7c28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,29 @@ All notable changes to selectools will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.19.3] - 2026-03-31 + +### Added + +#### Stability Markers Applied to All Public APIs + +Every public class and function exported from `selectools` now carries a stability marker: + +**`@stable`** (API frozen — breaking changes require a major version bump): +`Role`, `Message`, `ToolCall`, `AgentResult`, `StepType`, `TraceStep`, `AgentTrace`, `Agent`, `AgentConfig`, `AgentObserver`, `AsyncAgentObserver`, `LoggingObserver`, `ConversationMemory`, `Tool`, `ToolParameter`, `ToolRegistry`, `tool`, `ToolPolicy`, `PolicyDecision`, `PolicyResult`, `OpenAIProvider`, `AnthropicProvider`, `GeminiProvider`, `OllamaProvider`, `FallbackProvider`, `TestCase`, `CaseResult`, `CaseVerdict`, `EvalSuite`, `EvalReport`, `AuditLogger`, `PrivacyLevel`, `CancellationToken`, `InMemoryCache`, `Cache`, `CacheKeyBuilder`, `CacheStats`, `SessionStore`, `JsonFileSessionStore`, `SQLiteSessionStore`, `KnowledgeMemory`, `KnowledgeEntry`, `KnowledgeStore`, `FileKnowledgeStore`, `SQLiteKnowledgeStore`, `Guardrail`, `GuardrailAction`, `GuardrailResult`, `GuardrailsPipeline`, `PIIGuardrail`, `ToxicityGuardrail`, `TopicGuardrail`, `FormatGuardrail`, `LengthGuardrail`, `TokenEstimate`, `estimate_tokens`, `estimate_run_tokens`, `AgentUsage`, `UsageStats`, `AgentAnalytics`, `Entity`, `EntityMemory`, `KnowledgeGraphMemory` + +**`@beta`** (API may change in a minor release): +`trace_to_html`, `SimpleStepObserver`, `LocalProvider`, `RedisSessionStore`, `AgentGraph`, `SupervisorAgent`, `GraphState`, `MergePolicy`, `ContextMode`, `InterruptRequest`, `Scatter`, `CheckpointStore`, `InMemoryCheckpointStore`, `FileCheckpointStore`, `SQLiteCheckpointStore`, `PostgresCheckpointStore`, `Pipeline`, `Step`, `StepResult`, `step`, `parallel`, `branch`, `retry`, `cache_step`, `PlanAndExecuteAgent`, `ReflectiveAgent`, `DebateAgent`, `TeamLeadAgent`, `compose` + +Introspect programmatically: +```python +from selectools import Agent, AgentGraph +print(Agent.__stability__) # "stable" +print(AgentGraph.__stability__) # "beta" +``` + +**Tests:** 3135 | **Examples:** 75 | **Models:** 152 + ## [0.19.2] - 2026-03-31 ### Added diff --git a/README.md b/README.md index 83dfc55..0c79197 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,22 @@ result = AgentGraph.chain(planner, writer, reviewer).run("Write a blog post") ## What's New in v0.19 +### v0.19.3 — Stability Markers Applied to All Public APIs + +Every public class and function exported from `selectools` now carries a stability marker: + +```python +from selectools import Agent, AgentGraph, PlanAndExecuteAgent + +print(Agent.__stability__) # "stable" +print(AgentGraph.__stability__) # "beta" +print(PlanAndExecuteAgent.__stability__) # "beta" +``` + +**`@stable`** — 60+ core symbols (Agent, AgentConfig, providers, memory, tools, evals, guardrails, sessions, knowledge, cache, cancellation) + +**`@beta`** — 30+ newer symbols (AgentGraph, SupervisorAgent, Pipeline, @step, parallel, branch, all four patterns, compose) + ### v0.19.2 — Enterprise Hardening ```python diff --git a/ROADMAP.md b/ROADMAP.md index 79ba944..d0c7f4f 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -55,6 +55,10 @@ Security audit → Stability markers (@stable/@beta/@deprecated) → Deprecation → Property-based tests (Hypothesis) → Concurrency smoke suite → 5 production simulations → 3135 tests, 75 examples +v0.19.3 ✅ Stability Markers Applied to All Public APIs +@stable on 60+ core symbols → @beta on 30+ orchestration/pipeline/patterns symbols +→ Full stability introspection via .__stability__ on every exported class and function + v0.20.0 🟡 Visual Agent Builder Zero-install web UI → Drag-drop graph builder → YAML/Python export → Live test execution @@ -184,6 +188,29 @@ Focus: Production readiness and developer trust signals before the Visual Agent | **Production simulations** (5 new) | ✅ | High | Medium | +--- + +## v0.19.3: Stability Markers Applied ✅ + +Focus: Apply `@stable` and `@beta` markers to every public symbol in the library, completing the stability annotation work started in v0.19.2. + +### Stable APIs (60+ symbols) + +Core types, providers, agent, memory, tools, evals, guardrails, sessions, knowledge, cache, cancellation, token estimation, analytics, audit — all marked `@stable`. Breaking changes to these require a major version bump. + +### Beta APIs (30+ symbols) + +Orchestration (`AgentGraph`, `SupervisorAgent`), pipelines (`Pipeline`, `@step`, `parallel`, `branch`), patterns (`PlanAndExecuteAgent`, `ReflectiveAgent`, `DebateAgent`, `TeamLeadAgent`), and composition (`compose`) — marked `@beta`. These may change in a minor release. + +### Introspection + +```python +from selectools import Agent, AgentGraph, PlanAndExecuteAgent +print(Agent.__stability__) # "stable" +print(AgentGraph.__stability__) # "beta" +print(PlanAndExecuteAgent.__stability__) # "beta" +``` + --- ## v0.20.0: Visual Agent Builder 🟡 diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index feb7b21..c6b7c28 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -5,6 +5,29 @@ All notable changes to selectools will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.19.3] - 2026-03-31 + +### Added + +#### Stability Markers Applied to All Public APIs + +Every public class and function exported from `selectools` now carries a stability marker: + +**`@stable`** (API frozen — breaking changes require a major version bump): +`Role`, `Message`, `ToolCall`, `AgentResult`, `StepType`, `TraceStep`, `AgentTrace`, `Agent`, `AgentConfig`, `AgentObserver`, `AsyncAgentObserver`, `LoggingObserver`, `ConversationMemory`, `Tool`, `ToolParameter`, `ToolRegistry`, `tool`, `ToolPolicy`, `PolicyDecision`, `PolicyResult`, `OpenAIProvider`, `AnthropicProvider`, `GeminiProvider`, `OllamaProvider`, `FallbackProvider`, `TestCase`, `CaseResult`, `CaseVerdict`, `EvalSuite`, `EvalReport`, `AuditLogger`, `PrivacyLevel`, `CancellationToken`, `InMemoryCache`, `Cache`, `CacheKeyBuilder`, `CacheStats`, `SessionStore`, `JsonFileSessionStore`, `SQLiteSessionStore`, `KnowledgeMemory`, `KnowledgeEntry`, `KnowledgeStore`, `FileKnowledgeStore`, `SQLiteKnowledgeStore`, `Guardrail`, `GuardrailAction`, `GuardrailResult`, `GuardrailsPipeline`, `PIIGuardrail`, `ToxicityGuardrail`, `TopicGuardrail`, `FormatGuardrail`, `LengthGuardrail`, `TokenEstimate`, `estimate_tokens`, `estimate_run_tokens`, `AgentUsage`, `UsageStats`, `AgentAnalytics`, `Entity`, `EntityMemory`, `KnowledgeGraphMemory` + +**`@beta`** (API may change in a minor release): +`trace_to_html`, `SimpleStepObserver`, `LocalProvider`, `RedisSessionStore`, `AgentGraph`, `SupervisorAgent`, `GraphState`, `MergePolicy`, `ContextMode`, `InterruptRequest`, `Scatter`, `CheckpointStore`, `InMemoryCheckpointStore`, `FileCheckpointStore`, `SQLiteCheckpointStore`, `PostgresCheckpointStore`, `Pipeline`, `Step`, `StepResult`, `step`, `parallel`, `branch`, `retry`, `cache_step`, `PlanAndExecuteAgent`, `ReflectiveAgent`, `DebateAgent`, `TeamLeadAgent`, `compose` + +Introspect programmatically: +```python +from selectools import Agent, AgentGraph +print(Agent.__stability__) # "stable" +print(AgentGraph.__stability__) # "beta" +``` + +**Tests:** 3135 | **Examples:** 75 | **Models:** 152 + ## [0.19.2] - 2026-03-31 ### Added diff --git a/pyproject.toml b/pyproject.toml index 3061b7c..cc2ccf9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "selectools" -version = "0.19.2" +version = "0.19.3" description = "Production-ready AI agents with tool calling, structured output, execution traces, and RAG. Provider-agnostic (OpenAI, Anthropic, Gemini, Ollama) with fallback chains, batch processing, tool policies, streaming, caching, and cost tracking." readme = "README.md" requires-python = ">=3.9" diff --git a/src/selectools/__init__.py b/src/selectools/__init__.py index d2ad20e..3e73690 100644 --- a/src/selectools/__init__.py +++ b/src/selectools/__init__.py @@ -1,6 +1,6 @@ """Public exports for the selectools package.""" -__version__ = "0.19.2" +__version__ = "0.19.3" # Import submodules (lazy loading for optional dependencies) from . import embeddings, evals, guardrails, models, patterns, rag, toolbox diff --git a/src/selectools/agent/config.py b/src/selectools/agent/config.py index 29224c6..82589c6 100644 --- a/src/selectools/agent/config.py +++ b/src/selectools/agent/config.py @@ -7,6 +7,8 @@ from dataclasses import dataclass, field from typing import TYPE_CHECKING, Any, Callable, Coroutine, Dict, List, Optional, Union +from ..stability import stable + if TYPE_CHECKING: from ..cache import Cache from ..cancellation import CancellationToken @@ -30,6 +32,7 @@ ] +@stable @dataclass class AgentConfig: """ diff --git a/src/selectools/agent/core.py b/src/selectools/agent/core.py index 90f966b..8334b37 100644 --- a/src/selectools/agent/core.py +++ b/src/selectools/agent/core.py @@ -18,6 +18,7 @@ from ..prompt import PromptBuilder from ..providers.base import Provider, ProviderError from ..providers.openai_provider import OpenAIProvider +from ..stability import stable from ..structured import ( ResponseFormat, build_schema_instruction, @@ -57,6 +58,7 @@ class _RunContext: terminal_tool_result: Optional[str] = None +@stable class Agent(_ToolExecutorMixin, _ProviderCallerMixin, _LifecycleMixin, _MemoryManagerMixin): """ Provider-agnostic AI agent that iteratively calls tools to accomplish tasks. diff --git a/src/selectools/analytics.py b/src/selectools/analytics.py index 74aad23..48ffb1b 100644 --- a/src/selectools/analytics.py +++ b/src/selectools/analytics.py @@ -10,6 +10,8 @@ from pathlib import Path from typing import Any, Dict +from .stability import stable + @dataclass class ToolMetrics: @@ -81,6 +83,7 @@ def to_dict(self) -> dict: } +@stable class AgentAnalytics: """ Analytics tracker for agent tool usage. diff --git a/src/selectools/audit.py b/src/selectools/audit.py index 35a68d8..e0bce57 100644 --- a/src/selectools/audit.py +++ b/src/selectools/audit.py @@ -24,10 +24,12 @@ from typing import Any, Dict, List, Optional from .observer import AgentObserver +from .stability import stable from .types import AgentResult, Message from .usage import UsageStats +@stable class PrivacyLevel(str, Enum): """Controls how tool arguments are recorded in audit logs.""" @@ -37,6 +39,7 @@ class PrivacyLevel(str, Enum): NONE = "none" +@stable class AuditLogger(AgentObserver): """JSONL audit logger that implements the AgentObserver protocol. diff --git a/src/selectools/cache.py b/src/selectools/cache.py index 6d928ce..764b269 100644 --- a/src/selectools/cache.py +++ b/src/selectools/cache.py @@ -20,12 +20,14 @@ from .tools.base import Tool from .types import Message +from .stability import stable # --------------------------------------------------------------------------- # CacheStats # --------------------------------------------------------------------------- +@stable @dataclass class CacheStats: """Tracks cache performance metrics.""" @@ -69,6 +71,7 @@ class _CacheEntry: # --------------------------------------------------------------------------- +@stable @runtime_checkable class Cache(Protocol): """ @@ -109,6 +112,7 @@ def stats(self) -> CacheStats: # --------------------------------------------------------------------------- +@stable class InMemoryCache: """ Thread-safe in-memory LRU cache with per-entry TTL. @@ -216,6 +220,7 @@ def __repr__(self) -> str: # --------------------------------------------------------------------------- +@stable class CacheKeyBuilder: """ Builds deterministic cache keys from LLM request parameters. diff --git a/src/selectools/cancellation.py b/src/selectools/cancellation.py index 5656ae8..0e320ab 100644 --- a/src/selectools/cancellation.py +++ b/src/selectools/cancellation.py @@ -10,8 +10,10 @@ import threading from .exceptions import CancellationError +from .stability import stable +@stable class CancellationToken: """Thread-safe signal for cancelling an agent run. diff --git a/src/selectools/compose.py b/src/selectools/compose.py index ed3fc03..63d847c 100644 --- a/src/selectools/compose.py +++ b/src/selectools/compose.py @@ -23,10 +23,12 @@ def extract(data: dict, field: str) -> str: ... from typing import Any, Callable, List, Optional, Sequence +from .stability import beta from .tools.base import Tool from .tools.decorators import tool as tool_decorator +@beta def compose( *tools_or_fns: Any, name: Optional[str] = None, diff --git a/src/selectools/entity_memory.py b/src/selectools/entity_memory.py index 7cc58be..c41776a 100644 --- a/src/selectools/entity_memory.py +++ b/src/selectools/entity_memory.py @@ -14,9 +14,11 @@ from dataclasses import dataclass, field from typing import Any, Dict, List, Optional +from .stability import stable from .types import Message, Role +@stable @dataclass class Entity: """A named entity extracted from conversation. @@ -69,6 +71,7 @@ def from_dict(cls, data: Dict[str, Any]) -> "Entity": ) +@stable class EntityMemory: """Maintains a registry of entities mentioned in conversation. diff --git a/src/selectools/evals/report.py b/src/selectools/evals/report.py index 4deb6db..c8a2bc9 100644 --- a/src/selectools/evals/report.py +++ b/src/selectools/evals/report.py @@ -9,9 +9,11 @@ from pathlib import Path from typing import Any, Dict, List, Optional, Union +from ..stability import stable from .types import CaseResult, CaseVerdict, EvalMetadata +@stable @dataclass class EvalReport: """Aggregated evaluation results with statistics.""" diff --git a/src/selectools/evals/suite.py b/src/selectools/evals/suite.py index d25b8fd..616a696 100644 --- a/src/selectools/evals/suite.py +++ b/src/selectools/evals/suite.py @@ -11,11 +11,13 @@ from .. import __version__ from ..agent import Agent +from ..stability import stable from .evaluators import DEFAULT_EVALUATORS from .report import EvalReport from .types import CaseResult, CaseVerdict, EvalFailure, EvalMetadata, TestCase +@stable class EvalSuite: """Evaluate an agent against a list of test cases. diff --git a/src/selectools/evals/types.py b/src/selectools/evals/types.py index c05b0cc..6e6a33e 100644 --- a/src/selectools/evals/types.py +++ b/src/selectools/evals/types.py @@ -6,7 +6,10 @@ from enum import Enum from typing import Any, Callable, Dict, List, Optional +from selectools.stability import stable + +@stable class CaseVerdict(str, Enum): """Verdict for a single evaluated test case.""" @@ -16,6 +19,7 @@ class CaseVerdict(str, Enum): SKIP = "skip" +@stable @dataclass class TestCase: """A single test case for agent evaluation. @@ -125,6 +129,7 @@ class EvalFailure: message: str +@stable @dataclass class CaseResult: """Result of evaluating a single TestCase.""" diff --git a/src/selectools/guardrails/base.py b/src/selectools/guardrails/base.py index fe8051d..7138063 100644 --- a/src/selectools/guardrails/base.py +++ b/src/selectools/guardrails/base.py @@ -12,7 +12,10 @@ from enum import Enum from typing import Optional +from selectools.stability import stable + +@stable class GuardrailAction(str, Enum): """Action to take when a guardrail check fails.""" @@ -21,6 +24,7 @@ class GuardrailAction(str, Enum): WARN = "warn" +@stable @dataclass class GuardrailResult: """Result of a single guardrail check. @@ -40,6 +44,7 @@ class GuardrailResult: guardrail_name: Optional[str] = None +@stable class Guardrail: """Base class for all guardrails. diff --git a/src/selectools/guardrails/format.py b/src/selectools/guardrails/format.py index 2ef987c..2276ccf 100644 --- a/src/selectools/guardrails/format.py +++ b/src/selectools/guardrails/format.py @@ -10,9 +10,12 @@ import json from typing import List, Optional +from selectools.stability import stable + from .base import Guardrail, GuardrailAction, GuardrailResult +@stable class FormatGuardrail(Guardrail): """Validate that content matches expected format constraints. diff --git a/src/selectools/guardrails/length.py b/src/selectools/guardrails/length.py index e5a770c..1cdca1e 100644 --- a/src/selectools/guardrails/length.py +++ b/src/selectools/guardrails/length.py @@ -9,9 +9,12 @@ import re from typing import Optional +from selectools.stability import stable + from .base import Guardrail, GuardrailAction, GuardrailResult +@stable class LengthGuardrail(Guardrail): """Enforce content length constraints. diff --git a/src/selectools/guardrails/pii.py b/src/selectools/guardrails/pii.py index 17b2ba5..15f6be8 100644 --- a/src/selectools/guardrails/pii.py +++ b/src/selectools/guardrails/pii.py @@ -11,6 +11,8 @@ from dataclasses import dataclass from typing import Dict, List, Optional +from selectools.stability import stable + from .base import Guardrail, GuardrailAction, GuardrailResult _BUILTIN_PATTERNS: Dict[str, re.Pattern[str]] = { @@ -34,6 +36,7 @@ class PIIMatch: end: int +@stable class PIIGuardrail(Guardrail): """Detect (and optionally redact) PII in content. diff --git a/src/selectools/guardrails/pipeline.py b/src/selectools/guardrails/pipeline.py index 5abdefe..377178c 100644 --- a/src/selectools/guardrails/pipeline.py +++ b/src/selectools/guardrails/pipeline.py @@ -12,11 +12,14 @@ from dataclasses import dataclass, field from typing import List, Optional +from selectools.stability import stable + from .base import Guardrail, GuardrailAction, GuardrailError, GuardrailResult logger = logging.getLogger("selectools.guardrails") +@stable @dataclass class GuardrailsPipeline: """Ordered pipeline of input and output guardrails. diff --git a/src/selectools/guardrails/topic.py b/src/selectools/guardrails/topic.py index 7cbb870..b6ad6a1 100644 --- a/src/selectools/guardrails/topic.py +++ b/src/selectools/guardrails/topic.py @@ -11,9 +11,12 @@ import unicodedata from typing import List, Optional +from selectools.stability import stable + from .base import Guardrail, GuardrailAction, GuardrailResult +@stable class TopicGuardrail(Guardrail): """Reject content that mentions denied topics. diff --git a/src/selectools/guardrails/toxicity.py b/src/selectools/guardrails/toxicity.py index 0ceac61..0262768 100644 --- a/src/selectools/guardrails/toxicity.py +++ b/src/selectools/guardrails/toxicity.py @@ -11,6 +11,8 @@ import re from typing import List, Optional, Set +from selectools.stability import stable + from .base import Guardrail, GuardrailAction, GuardrailResult _DEFAULT_BLOCKLIST: Set[str] = { @@ -33,6 +35,7 @@ } +@stable class ToxicityGuardrail(Guardrail): """Block content that exceeds a toxicity threshold. diff --git a/src/selectools/knowledge.py b/src/selectools/knowledge.py index 6bba340..eb02589 100644 --- a/src/selectools/knowledge.py +++ b/src/selectools/knowledge.py @@ -19,11 +19,14 @@ from datetime import datetime, timedelta, timezone from typing import Any, Dict, List, Optional, Protocol, runtime_checkable +from .stability import stable + # ====================================================================== # KnowledgeEntry — structured entry for the new store-based API # ====================================================================== +@stable @dataclass class KnowledgeEntry: """A single piece of knowledge stored by the agent. @@ -66,6 +69,7 @@ def is_expired(self) -> bool: # ====================================================================== +@stable @runtime_checkable class KnowledgeStore(Protocol): """Protocol for knowledge storage backends. @@ -113,6 +117,7 @@ def prune( # ====================================================================== +@stable class FileKnowledgeStore: """File-based knowledge store (backward-compatible with the original KnowledgeMemory). @@ -279,6 +284,7 @@ def prune( # ====================================================================== +@stable class SQLiteKnowledgeStore: """SQLite-backed knowledge store for production single-process use. @@ -442,6 +448,7 @@ def prune( # ====================================================================== +@stable class KnowledgeMemory: """Maintains cross-session knowledge with daily logs and persistent facts. diff --git a/src/selectools/knowledge_graph.py b/src/selectools/knowledge_graph.py index 38120ff..537e6ff 100644 --- a/src/selectools/knowledge_graph.py +++ b/src/selectools/knowledge_graph.py @@ -14,6 +14,7 @@ from dataclasses import dataclass, field from typing import Any, Dict, List, Optional, Protocol, runtime_checkable +from .stability import stable from .types import Message, Role @@ -301,6 +302,7 @@ def _prune(self, conn: sqlite3.Connection) -> None: ) +@stable class KnowledgeGraphMemory: """Maintains a knowledge graph of relationship triples from conversation. diff --git a/src/selectools/memory.py b/src/selectools/memory.py index 6b65f12..dfb822a 100644 --- a/src/selectools/memory.py +++ b/src/selectools/memory.py @@ -8,9 +8,11 @@ from dataclasses import replace from typing import Any, Dict, List, Optional +from .stability import stable from .types import Message, Role +@stable class ConversationMemory: """ Maintains conversation history with configurable limits. diff --git a/src/selectools/observer.py b/src/selectools/observer.py index e50a3da..52b45e0 100644 --- a/src/selectools/observer.py +++ b/src/selectools/observer.py @@ -35,10 +35,12 @@ def on_tool_end(self, run_id, call_id, tool_name, result, duration_ms): import time from typing import Any, Callable, Dict, List, Optional +from .stability import beta, stable from .types import AgentResult, Message from .usage import UsageStats +@stable class AgentObserver: """Base class for agent lifecycle observers. @@ -602,6 +604,7 @@ def on_supervisor_replan( # ====================================================================== +@stable class LoggingObserver(AgentObserver): """Observer that writes structured JSON events to Python's logging module. @@ -1021,6 +1024,7 @@ def on_eval_end( ) +@stable class AsyncAgentObserver(AgentObserver): """Base class for async agent lifecycle observers. @@ -1420,6 +1424,7 @@ async def a_on_supervisor_replan(self, run_id: str, stall_count: int, new_plan: # ====================================================================== +@beta class SimpleStepObserver(AgentObserver): """Observer that routes all lifecycle events to a single callback. diff --git a/src/selectools/orchestration/checkpoint.py b/src/selectools/orchestration/checkpoint.py index d611a56..41aa3e8 100644 --- a/src/selectools/orchestration/checkpoint.py +++ b/src/selectools/orchestration/checkpoint.py @@ -32,6 +32,7 @@ from datetime import datetime, timezone from typing import Dict, List, Optional, Protocol, Tuple, runtime_checkable +from ..stability import beta from .state import GraphState @@ -56,6 +57,7 @@ class CheckpointMetadata: created_at: datetime +@beta @runtime_checkable class CheckpointStore(Protocol): """Protocol for checkpoint backends. @@ -117,6 +119,7 @@ def _deserialize_checkpoint(data: Dict) -> Tuple[GraphState, int]: # ------------------------------------------------------------------ +@beta class InMemoryCheckpointStore: """Thread-safe in-memory checkpoint store. @@ -172,6 +175,7 @@ def delete(self, checkpoint_id: str) -> bool: # ------------------------------------------------------------------ +@beta class FileCheckpointStore: """File-based checkpoint store. @@ -290,6 +294,7 @@ def delete(self, checkpoint_id: str) -> bool: """ +@beta class SQLiteCheckpointStore: """SQLite-backed checkpoint store with WAL mode for concurrent access. diff --git a/src/selectools/orchestration/graph.py b/src/selectools/orchestration/graph.py index ad89d22..0692b22 100644 --- a/src/selectools/orchestration/graph.py +++ b/src/selectools/orchestration/graph.py @@ -37,6 +37,7 @@ ) from ..exceptions import GraphExecutionError +from ..stability import beta from ..trace import AgentTrace, StepType, TraceStep from ..types import AgentResult, Message, Role from ..usage import UsageStats @@ -164,6 +165,7 @@ def _merge_usage(base: UsageStats, added: Any) -> UsageStats: ) +@beta class AgentGraph: """Directed graph of agent nodes with routing, parallelism, and HITL support. diff --git a/src/selectools/orchestration/state.py b/src/selectools/orchestration/state.py index 054bf97..b70d1d1 100644 --- a/src/selectools/orchestration/state.py +++ b/src/selectools/orchestration/state.py @@ -17,10 +17,12 @@ if TYPE_CHECKING: from ..types import AgentResult, Message +from ..stability import beta STATE_KEY_LAST_OUTPUT: str = "__last_output__" +@beta class MergePolicy(str, Enum): """Policy for merging parallel branch states. @@ -34,6 +36,7 @@ class MergePolicy(str, Enum): APPEND = "append" +@beta class ContextMode(str, Enum): """Controls what conversation history is forwarded to a node's agent. @@ -51,6 +54,7 @@ class ContextMode(str, Enum): CUSTOM = "custom" +@beta @dataclass class GraphState: """Shared context passed between nodes in an AgentGraph. @@ -153,6 +157,7 @@ def from_prompt(cls, prompt: str) -> "GraphState": return cls(messages=[Message(role=Role.USER, content=prompt)]) +@beta @dataclass class InterruptRequest: """Yielded from generator nodes to pause execution for human input. @@ -173,6 +178,7 @@ class InterruptRequest: interrupt_key: str = "" +@beta @dataclass class Scatter: """Returned from routing functions to create dynamic parallel branches. diff --git a/src/selectools/orchestration/supervisor.py b/src/selectools/orchestration/supervisor.py index c8f5655..b813252 100644 --- a/src/selectools/orchestration/supervisor.py +++ b/src/selectools/orchestration/supervisor.py @@ -45,6 +45,7 @@ from ..types import AgentResult from .checkpoint import CheckpointStore +from ..stability import beta from ..types import Message, Role from ..usage import UsageStats from .graph import AgentGraph, ErrorPolicy, GraphResult, _merge_usage @@ -168,6 +169,7 @@ def _safe_json_parse(text: str, default: Any = None) -> Any: return default +@beta class SupervisorAgent: """High-level multi-agent coordinator. diff --git a/src/selectools/patterns/debate.py b/src/selectools/patterns/debate.py index 3929811..4ccbd0e 100644 --- a/src/selectools/patterns/debate.py +++ b/src/selectools/patterns/debate.py @@ -16,6 +16,7 @@ from ..cancellation import CancellationToken from ..observer import AgentObserver +from ..stability import beta from ..types import Message, Role @@ -39,6 +40,7 @@ def total_rounds(self) -> int: return len(self.rounds) +@beta class DebateAgent: """Multi-agent debate: agents argue → judge synthesizes conclusion. diff --git a/src/selectools/patterns/plan_and_execute.py b/src/selectools/patterns/plan_and_execute.py index 3b5d708..5c3c008 100644 --- a/src/selectools/patterns/plan_and_execute.py +++ b/src/selectools/patterns/plan_and_execute.py @@ -22,6 +22,7 @@ from ..orchestration.graph import GraphResult from ..orchestration.state import GraphState from ..orchestration.supervisor import _safe_json_parse +from ..stability import beta from ..trace import AgentTrace from ..types import Message, Role from ..usage import UsageStats @@ -63,6 +64,7 @@ class PlanStep: """ +@beta class PlanAndExecuteAgent: """Planner generates a structured plan; executor agents handle each step. diff --git a/src/selectools/patterns/reflective.py b/src/selectools/patterns/reflective.py index 129aab7..868a4bb 100644 --- a/src/selectools/patterns/reflective.py +++ b/src/selectools/patterns/reflective.py @@ -17,6 +17,7 @@ from ..cancellation import CancellationToken from ..observer import AgentObserver +from ..stability import beta from ..types import Message, Role @@ -43,6 +44,7 @@ def total_rounds(self) -> int: return len(self.rounds) +@beta class ReflectiveAgent: """Actor-Critic loop: actor drafts, critic evaluates, actor revises. diff --git a/src/selectools/patterns/team_lead.py b/src/selectools/patterns/team_lead.py index 4dfff8a..a0ad531 100644 --- a/src/selectools/patterns/team_lead.py +++ b/src/selectools/patterns/team_lead.py @@ -24,6 +24,7 @@ from ..orchestration.graph import AgentGraph from ..orchestration.state import ContextMode, GraphState from ..orchestration.supervisor import _safe_json_parse +from ..stability import beta from ..types import Message, Role @@ -79,6 +80,7 @@ def total_assignments(self) -> int: """ +@beta class TeamLeadAgent: """Team lead delegates subtasks to agents and coordinates results. diff --git a/src/selectools/pipeline.py b/src/selectools/pipeline.py index 2effcf9..ca1a9b5 100644 --- a/src/selectools/pipeline.py +++ b/src/selectools/pipeline.py @@ -36,6 +36,8 @@ def translate(text: str, lang: str = "es") -> str: from functools import wraps from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union +from selectools.stability import beta + def _filter_kwargs(fn: Callable, kwargs: Dict[str, Any]) -> Dict[str, Any]: """Return only the kwargs that *fn* accepts. @@ -94,6 +96,7 @@ def _get_type_hints(fn: Callable) -> Dict[str, Any]: # --------------------------------------------------------------------------- +@beta @dataclass class StepResult: """Result of a single step or pipeline execution. @@ -109,6 +112,7 @@ class StepResult: steps_run: int = 0 +@beta class Step: """A callable wrapper around a plain function with composition support. @@ -167,6 +171,7 @@ def __repr__(self) -> str: # noqa: D105 return f"Step({self.name!r})" +@beta def step( fn: Optional[Callable] = None, *, @@ -215,6 +220,7 @@ def _ensure_step(fn: Any) -> Step: # --------------------------------------------------------------------------- +@beta class Pipeline: """A sequence of steps executed in order. @@ -522,6 +528,7 @@ def __call__(self, state: Any) -> Any: # --------------------------------------------------------------------------- +@beta def parallel(*steps_or_fns: Union[Step, Callable]) -> Step: """Run multiple steps in parallel on the same input, return dict of results. @@ -571,6 +578,7 @@ async def _run(s: Step) -> Tuple[str, Any]: # --------------------------------------------------------------------------- +@beta def branch( router: Optional[Callable] = None, **named_steps: Union[Step, Callable], @@ -629,6 +637,7 @@ def _branch_fn(input: Any, **kwargs: Any) -> Any: # --------------------------------------------------------------------------- +@beta def retry(step_or_fn: Union[Step, Callable], attempts: int = 3) -> Step: """Wrap a step with retry logic. @@ -644,6 +653,7 @@ def retry(step_or_fn: Union[Step, Callable], attempts: int = 3) -> Step: return Step(wrapped.fn, name=wrapped.name, retry=attempts, on_error="raise") +@beta def cache_step( step_or_fn: Union[Step, Callable], ttl: int = 300, diff --git a/src/selectools/policy.py b/src/selectools/policy.py index b5f64f0..1d700b2 100644 --- a/src/selectools/policy.py +++ b/src/selectools/policy.py @@ -13,13 +13,17 @@ from pathlib import Path from typing import Any, Dict, List, Optional, Union +from selectools.stability import stable + +@stable class PolicyDecision(str, Enum): ALLOW = "allow" REVIEW = "review" DENY = "deny" +@stable @dataclass class PolicyResult: """Outcome of evaluating a tool call against the policy.""" @@ -29,6 +33,7 @@ class PolicyResult: matched_rule: str = "" +@stable @dataclass class ToolPolicy: """Declarative allow / review / deny rules for tool execution. diff --git a/src/selectools/providers/anthropic_provider.py b/src/selectools/providers/anthropic_provider.py index ecc024f..ad75ea9 100644 --- a/src/selectools/providers/anthropic_provider.py +++ b/src/selectools/providers/anthropic_provider.py @@ -17,11 +17,13 @@ from ..exceptions import ProviderConfigurationError from ..models import Anthropic as AnthropicModels from ..pricing import calculate_cost +from ..stability import stable from ..types import Message, Role, ToolCall from ..usage import UsageStats from .base import Provider, ProviderError +@stable class AnthropicProvider(Provider): """Anthropic Messages API adapter.""" diff --git a/src/selectools/providers/fallback.py b/src/selectools/providers/fallback.py index f9dd66e..69ad8ba 100644 --- a/src/selectools/providers/fallback.py +++ b/src/selectools/providers/fallback.py @@ -24,6 +24,7 @@ cast, ) +from ..stability import stable from ..types import Message, ToolCall from .base import Provider, ProviderError @@ -43,6 +44,7 @@ def _is_retriable(exc: Exception) -> bool: return any(s in msg for s in _RETRIABLE_SUBSTRINGS) or bool(_RETRIABLE_STATUS_CODES.search(msg)) +@stable class FallbackProvider: """Wraps multiple providers with automatic failover. diff --git a/src/selectools/providers/gemini_provider.py b/src/selectools/providers/gemini_provider.py index 0e815ca..eb92583 100644 --- a/src/selectools/providers/gemini_provider.py +++ b/src/selectools/providers/gemini_provider.py @@ -19,11 +19,13 @@ from ..exceptions import ProviderConfigurationError from ..models import Gemini as GeminiModels from ..pricing import calculate_cost +from ..stability import stable from ..types import Message, Role, ToolCall from ..usage import UsageStats from .base import Provider, ProviderError +@stable class GeminiProvider(Provider): """ Google Gemini adapter using the new google-genai SDK. diff --git a/src/selectools/providers/ollama_provider.py b/src/selectools/providers/ollama_provider.py index 962289f..c15765a 100644 --- a/src/selectools/providers/ollama_provider.py +++ b/src/selectools/providers/ollama_provider.py @@ -9,11 +9,13 @@ from typing import Any, Dict from ..models import Ollama as OllamaModels +from ..stability import stable from ..types import ToolCall from ._openai_compat import _OpenAICompatibleBase from .base import ProviderError +@stable class OllamaProvider(_OpenAICompatibleBase): """ Adapter for Ollama local models using OpenAI-compatible API. diff --git a/src/selectools/providers/openai_provider.py b/src/selectools/providers/openai_provider.py index e501d66..85028fd 100644 --- a/src/selectools/providers/openai_provider.py +++ b/src/selectools/providers/openai_provider.py @@ -15,6 +15,7 @@ from ..exceptions import ProviderConfigurationError from ..models import OpenAI as OpenAIModels from ..pricing import calculate_cost +from ..stability import stable from ._openai_compat import _OpenAICompatibleBase from .base import ProviderError @@ -33,6 +34,7 @@ def _uses_max_completion_tokens(model: str) -> bool: return any(model.startswith(p) for p in _MAX_COMPLETION_TOKENS_PREFIXES) +@stable class OpenAIProvider(_OpenAICompatibleBase): """Adapter that speaks to OpenAI's Chat Completions API.""" diff --git a/src/selectools/providers/stubs.py b/src/selectools/providers/stubs.py index c5eaa6a..fbb2c67 100644 --- a/src/selectools/providers/stubs.py +++ b/src/selectools/providers/stubs.py @@ -8,11 +8,13 @@ from typing import Any, AsyncIterable, Iterable, List, Union +from ..stability import beta from ..types import Message, Role, ToolCall from ..usage import UsageStats from .base import Provider +@beta class LocalProvider(Provider): """ Local fallback provider. diff --git a/src/selectools/sessions.py b/src/selectools/sessions.py index 0e18e45..0883f94 100644 --- a/src/selectools/sessions.py +++ b/src/selectools/sessions.py @@ -15,6 +15,7 @@ from typing import Any, Dict, List, Optional, Protocol from .memory import ConversationMemory +from .stability import beta, stable @dataclass @@ -34,6 +35,7 @@ class SessionMetadata: updated_at: float +@stable class SessionStore(Protocol): """Protocol for persistent session backends.""" @@ -74,6 +76,7 @@ def branch(self, source_id: str, new_id: str) -> None: # ====================================================================== +@stable class JsonFileSessionStore: """File-based session store using one JSON file per session. @@ -215,6 +218,7 @@ def branch(self, source_id: str, new_id: str) -> None: # ====================================================================== +@stable class SQLiteSessionStore: """SQLite-based session store. @@ -366,6 +370,7 @@ def branch(self, source_id: str, new_id: str) -> None: # ====================================================================== +@beta class RedisSessionStore: """Redis-backed session store. diff --git a/src/selectools/stability.py b/src/selectools/stability.py index d234a19..a47dd3b 100644 --- a/src/selectools/stability.py +++ b/src/selectools/stability.py @@ -33,18 +33,40 @@ def old_function(): ... _C = TypeVar("_C", bound=type) +@overload +def stable(obj: _C) -> _C: ... + + +@overload +def stable(obj: _F) -> _F: ... + + def stable(obj: Union[_F, _C]) -> Union[_F, _C]: """Set stability marker to 'stable' (API is frozen).""" obj.__stability__ = "stable" # type: ignore[union-attr] return obj +stable.__stability__ = "stable" # type: ignore[attr-defined] + + +@overload +def beta(obj: _C) -> _C: ... + + +@overload +def beta(obj: _F) -> _F: ... + + def beta(obj: Union[_F, _C]) -> Union[_F, _C]: """Set stability marker to 'beta' (API may change in minor releases).""" obj.__stability__ = "beta" # type: ignore[union-attr] return obj +beta.__stability__ = "stable" # type: ignore[attr-defined] + + def deprecated( since: str, replacement: Optional[str] = None, @@ -108,4 +130,6 @@ def _wrapper(*args: Any, **kwargs: Any) -> Any: return decorator # type: ignore[return-value] +deprecated.__stability__ = "stable" # type: ignore[attr-defined] + __all__ = ["stable", "beta", "deprecated"] diff --git a/src/selectools/token_estimation.py b/src/selectools/token_estimation.py index c7eff59..13840a4 100644 --- a/src/selectools/token_estimation.py +++ b/src/selectools/token_estimation.py @@ -16,7 +16,10 @@ from .tools.base import Tool from .types import Message +from .stability import stable + +@stable @dataclass class TokenEstimate: """Breakdown of estimated token usage for the first iteration of an agent run. @@ -64,6 +67,7 @@ def _tiktoken_count(text: str, model: str) -> Optional[int]: return len(enc.encode(text)) +@stable def estimate_tokens(text: str, model: str = "gpt-4o") -> int: """Estimate the token count for a string. @@ -85,6 +89,7 @@ def estimate_tokens(text: str, model: str = "gpt-4o") -> int: return _heuristic_count(text) +@stable def estimate_run_tokens( messages: List[Message], tools: List[Tool], diff --git a/src/selectools/tools/base.py b/src/selectools/tools/base.py index 55cc237..69ac2ea 100644 --- a/src/selectools/tools/base.py +++ b/src/selectools/tools/base.py @@ -16,6 +16,7 @@ from typing import Any, Callable, Dict, List, Optional, Union from ..exceptions import ToolExecutionError, ToolValidationError +from ..stability import stable JsonSchema = Dict[str, Any] @@ -53,6 +54,7 @@ def _python_type_to_json(param_type: type) -> str: return type_map.get(param_type, "string") +@stable @dataclass class ToolParameter: """ @@ -110,6 +112,7 @@ def to_schema(self) -> JsonSchema: return schema +@stable class Tool: """ Encapsulates a callable tool with validation and schema generation. diff --git a/src/selectools/tools/decorators.py b/src/selectools/tools/decorators.py index 665a60e..c258998 100644 --- a/src/selectools/tools/decorators.py +++ b/src/selectools/tools/decorators.py @@ -8,6 +8,7 @@ import sys from typing import Any, Callable, Dict, List, Optional, Union, get_args, get_origin, get_type_hints +from ..stability import stable from .base import ParamMetadata, Tool, ToolParameter @@ -111,6 +112,7 @@ def _infer_parameters_from_callable( return parameters +@stable def tool( *, name: Optional[str] = None, diff --git a/src/selectools/tools/registry.py b/src/selectools/tools/registry.py index 7896122..eaa888c 100644 --- a/src/selectools/tools/registry.py +++ b/src/selectools/tools/registry.py @@ -7,10 +7,12 @@ import warnings from typing import Any, Callable, Dict, List, Optional +from ..stability import stable from .base import ParamMetadata, Tool from .decorators import tool +@stable class ToolRegistry: """ Central registry for managing a collection of tools. diff --git a/src/selectools/trace.py b/src/selectools/trace.py index 853daf9..4bd4ef4 100644 --- a/src/selectools/trace.py +++ b/src/selectools/trace.py @@ -14,7 +14,10 @@ from enum import Enum from typing import Any, Dict, Iterator, List, Optional +from selectools.stability import beta, stable + +@stable class StepType(str, Enum): """Enumeration of trace step types. @@ -51,6 +54,7 @@ class StepType(str, Enum): GRAPH_LOOP_DETECTED = "graph_loop_detected" +@stable @dataclass class TraceStep: """A single step in the agent execution timeline.""" @@ -91,6 +95,7 @@ def _new_run_id() -> str: return uuid.uuid4().hex +@stable @dataclass class AgentTrace: """Ordered list of trace steps from a single agent run. @@ -327,6 +332,7 @@ def _span_id() -> str: _DEFAULT_COLOR = "#64748b" +@beta def trace_to_html(trace: "AgentTrace") -> str: """Render an AgentTrace as a standalone HTML waterfall timeline. diff --git a/src/selectools/types.py b/src/selectools/types.py index b5affb3..8427ff0 100644 --- a/src/selectools/types.py +++ b/src/selectools/types.py @@ -13,7 +13,10 @@ from pathlib import Path from typing import Any, Dict, List, Optional, Protocol +from selectools.stability import stable + +@stable class Role(str, Enum): """ Enumeration of conversation roles for messages. @@ -41,6 +44,7 @@ def _encode_image(image_path: str) -> str: return base64.b64encode(data).decode("utf-8") +@stable @dataclass class Message: """ @@ -153,6 +157,7 @@ def from_dict(cls, data: Dict[str, Any]) -> "Message": return msg +@stable @dataclass class ToolCall: """ @@ -179,6 +184,7 @@ class ToolCall: thought_signature: Optional[str] = None +@stable @dataclass class AgentResult: """ diff --git a/src/selectools/usage.py b/src/selectools/usage.py index 2e75b17..fde454b 100644 --- a/src/selectools/usage.py +++ b/src/selectools/usage.py @@ -7,7 +7,10 @@ from dataclasses import dataclass, field from typing import Any, Dict, List, Optional +from .stability import stable + +@stable @dataclass class UsageStats: """ @@ -39,6 +42,7 @@ def __post_init__(self) -> None: self.total_tokens = self.prompt_tokens + self.completion_tokens +@stable @dataclass class AgentUsage: """