Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
ed84b4c
feat: version discovery, User-Agent header, health_check_detailed
saurabhjain1592 Mar 1, 2026
cc6e7d9
docs: add v3.8.0 changelog entry
saurabhjain1592 Mar 1, 2026
99e8d96
fix: use semantic version comparison for mismatch warning
saurabhjain1592 Mar 1, 2026
2be4781
feat: add trace_id, ToolContext, and per-tool adapter methods
saurabhjain1592 Mar 1, 2026
a21caec
fix: resolve lint issues in version mismatch warning
saurabhjain1592 Mar 1, 2026
d85135b
fix: resolve mypy union-attr error in version check
saurabhjain1592 Mar 1, 2026
386794a
Merge branch 'feat/trace-id-tool-context' into feat/version-discovery
saurabhjain1592 Mar 1, 2026
c60c424
fix: ruff format warning message string
saurabhjain1592 Mar 1, 2026
41fef7a
fix: wire trace_id and tool_context in workflow client methods
saurabhjain1592 Mar 1, 2026
bed535e
fix: sanitize step_id to avoid slashes in URL path
saurabhjain1592 Mar 1, 2026
c49101f
fix: address code review findings
saurabhjain1592 Mar 1, 2026
c83e44a
docs: add trace_id, ToolContext, and per-tool adapter entries to v3.8…
saurabhjain1592 Mar 2, 2026
c57bd44
feat: add anonymous runtime telemetry
saurabhjain1592 Mar 2, 2026
165f165
fix: resolve ruff lint issues in telemetry module
saurabhjain1592 Mar 2, 2026
fa84c30
fix: ruff format for test files
saurabhjain1592 Mar 2, 2026
9dde1af
fix: add mypy type parameters to dict annotations in telemetry
saurabhjain1592 Mar 2, 2026
08bdfdd
fix: telemetry defaults OFF for self-hosted (no credentials)
saurabhjain1592 Mar 3, 2026
eacd4af
fix(telemetry): fix docs URL to docs.getaxonflow.com
saurabhjain1592 Mar 3, 2026
4aa0cb9
feat(telemetry): default ON for all modes except sandbox
saurabhjain1592 Mar 3, 2026
3ffefc4
fix: set v3.8.0 release date, add telemetry first-run notice
saurabhjain1592 Mar 3, 2026
5395e66
fix: suppress ruff ARG001 for has_credentials (kept for API compat)
saurabhjain1592 Mar 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,31 @@ All notable changes to the AxonFlow Python SDK 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).

## [3.8.0] - 2026-03-03

### Added

- `health_check_detailed()` method (async + sync) returning `HealthResponse` with platform version, capabilities, and SDK compatibility info
- `has_capability(name)` method on `HealthResponse` to check if platform supports a specific feature
- User-Agent header (`axonflow-sdk-python/{version}`) sent on all HTTP requests
- Version mismatch warning logged when SDK version is below platform's `min_sdk_version`
- `PlatformCapability`, `SDKCompatibility`, `HealthResponse` dataclasses
- `trace_id` field on `CreateWorkflowRequest`, `CreateWorkflowResponse`, `WorkflowStatusResponse`, and `ListWorkflowsOptions` for distributed tracing correlation
- `ToolContext` dataclass for per-tool governance within workflow steps
- `tool_context` field on `StepGateRequest` for tool-level policy enforcement
- `check_tool_gate()` method on LangGraph adapter for per-tool governance gate checks
- `tool_completed()` method on LangGraph adapter for per-tool step completion
- `list_workflows()` now supports `trace_id` filter parameter
- Anonymous runtime telemetry for version adoption tracking and feature usage signals
- `TelemetryEnabled` / `telemetry` configuration option to explicitly control telemetry
- `AXONFLOW_TELEMETRY=off` and `DO_NOT_TRACK=1` environment variable opt-out support

### Fixed

- `__version__` corrected from `3.6.0` to `3.8.0`

---

## [3.7.0] - 2026-02-28

### Added
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,12 @@ If you are evaluating AxonFlow in a company setting and cannot open a public iss

No email required. Optional contact if you want a response.

## Telemetry

This SDK sends anonymous usage telemetry (SDK version, OS, enabled features) to help improve AxonFlow.
No prompts, payloads, or PII are ever collected. Opt out: `AXONFLOW_TELEMETRY=off` or `DO_NOT_TRACK=1`.
See [Telemetry Documentation](https://docs.getaxonflow.com/docs/telemetry) for full details.

## License

MIT - See [LICENSE](LICENSE) for details.
Expand Down
18 changes: 16 additions & 2 deletions axonflow/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,14 @@
>>> result = client.proxy_llm_call("user-token", "What is AI?", "chat")
"""

from axonflow.client import AxonFlow, SyncAxonFlow
from axonflow._version import __version__
from axonflow.client import (
AxonFlow,
HealthResponse,
PlatformCapability,
SDKCompatibility,
SyncAxonFlow,
)
from axonflow.code_governance import (
CodeFile,
CodeGovernanceMetrics,
Expand Down Expand Up @@ -207,17 +214,23 @@
StepGateRequest,
StepGateResponse,
StepType,
ToolContext,
WorkflowSource,
WorkflowStatus,
WorkflowStatusResponse,
WorkflowStepInfo,
)

__version__ = "3.6.0"
__all__ = [
# Version
"__version__",
# Main client
"AxonFlow",
"SyncAxonFlow",
# Version discovery types
"PlatformCapability",
"SDKCompatibility",
"HealthResponse",
# Configuration
"Mode",
"RetryConfig",
Expand Down Expand Up @@ -368,6 +381,7 @@
"MarkStepCompletedRequest",
"AbortWorkflowRequest",
"PolicyMatch",
"ToolContext",
# WCP Approval types (Feature 5)
"ApproveStepResponse",
"RejectStepResponse",
Expand Down
3 changes: 3 additions & 0 deletions axonflow/_version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""Single source of truth for the AxonFlow SDK version."""

__version__ = "3.8.0"
126 changes: 123 additions & 3 deletions axonflow/adapters/langgraph.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
MarkStepCompletedRequest,
StepGateRequest,
StepType,
ToolContext,
WorkflowSource,
)

Expand Down Expand Up @@ -132,6 +133,7 @@ async def start_workflow(
self,
total_steps: int | None = None,
metadata: dict[str, Any] | None = None,
trace_id: str | None = None,
) -> str:
"""Register the workflow with AxonFlow.

Expand All @@ -140,21 +142,24 @@ async def start_workflow(
Args:
total_steps: Total number of steps (if known)
metadata: Additional workflow metadata
trace_id: External trace ID for correlation (Langsmith, Datadog, OTel)

Returns:
The assigned workflow ID

Example:
>>> workflow_id = await adapter.start_workflow(
... total_steps=5,
... metadata={"customer_id": "cust-123"}
... metadata={"customer_id": "cust-123"},
... trace_id="langsmith-run-abc123",
... )
"""
request = CreateWorkflowRequest(
workflow_name=self.workflow_name,
source=self.source,
total_steps=total_steps,
metadata=metadata or {},
trace_id=trace_id,
)

response = await self.client.create_workflow(request)
Expand All @@ -170,6 +175,7 @@ async def check_gate(
step_input: dict[str, Any] | None = None,
model: str | None = None,
provider: str | None = None,
tool_context: ToolContext | None = None,
) -> bool:
"""Check if a step is allowed to proceed.

Expand All @@ -182,6 +188,7 @@ async def check_gate(
step_input: Input data for the step (for policy evaluation)
model: LLM model being used
provider: LLM provider being used
tool_context: Tool-level context for per-tool governance (tool_call steps)

Returns:
True if step is allowed, False if blocked (when auto_block=False)
Expand All @@ -206,14 +213,16 @@ async def check_gate(
# Generate step ID if not provided
if step_id is None:
self._step_counter += 1
step_id = f"step-{self._step_counter}-{step_name.lower().replace(' ', '-')}"
safe_name = step_name.lower().replace(" ", "-").replace("/", "-")
step_id = f"step-{self._step_counter}-{safe_name}"

request = StepGateRequest(
step_name=step_name,
step_type=step_type,
step_input=step_input or {},
model=model,
provider=provider,
tool_context=tool_context,
)

response = await self.client.step_gate(self.workflow_id, step_id, request)
Expand Down Expand Up @@ -247,6 +256,9 @@ async def step_completed(
step_id: str | None = None,
output: dict[str, Any] | None = None,
metadata: dict[str, Any] | None = None,
tokens_in: int | None = None,
tokens_out: int | None = None,
cost_usd: float | None = None,
) -> None:
"""Mark a step as completed.

Expand All @@ -257,6 +269,9 @@ async def step_completed(
step_id: Optional step ID (must match the one used in check_gate)
output: Output data from the step
metadata: Additional metadata
tokens_in: Input tokens consumed
tokens_out: Output tokens produced
cost_usd: Cost in USD

Example:
>>> await adapter.step_completed("generate", output={"code": result})
Expand All @@ -267,15 +282,120 @@ async def step_completed(

# Generate step ID if not provided (must match check_gate)
if step_id is None:
step_id = f"step-{self._step_counter}-{step_name.lower().replace(' ', '-')}"
safe_name = step_name.lower().replace(" ", "-").replace("/", "-")
step_id = f"step-{self._step_counter}-{safe_name}"

request = MarkStepCompletedRequest(
output=output or {},
metadata=metadata or {},
tokens_in=tokens_in,
tokens_out=tokens_out,
cost_usd=cost_usd,
)

await self.client.mark_step_completed(self.workflow_id, step_id, request)

async def check_tool_gate(
self,
tool_name: str,
tool_type: str | None = None,
*,
step_name: str | None = None,
step_id: str | None = None,
tool_input: dict[str, Any] | None = None,
model: str | None = None,
provider: str | None = None,
) -> bool:
"""Check if a specific tool invocation is allowed.

Convenience wrapper around check_gate() that sets step_type=TOOL_CALL
and includes ToolContext for per-tool governance.

Args:
tool_name: Name of the tool being invoked
tool_type: Tool type (function, mcp, api)
step_name: Step name (defaults to "tools/{tool_name}")
step_id: Optional step ID (auto-generated if not provided)
tool_input: Input arguments for the tool
model: LLM model being used
provider: LLM provider being used

Returns:
True if tool invocation is allowed, False if blocked (when auto_block=False)

Raises:
WorkflowBlockedError: If tool is blocked and auto_block=True
WorkflowApprovalRequiredError: If tool requires approval
ValueError: If workflow not started

Example:
>>> if await adapter.check_tool_gate("web_search", "function",
... tool_input={"query": "latest news"}):
... result = await web_search(query="latest news")
... await adapter.tool_completed("web_search", output={"results": result})
"""
if step_name is None:
step_name = f"tools/{tool_name}"

tool_context = ToolContext(
tool_name=tool_name,
tool_type=tool_type,
tool_input=tool_input or {},
)

return await self.check_gate(
step_name=step_name,
step_type=StepType.TOOL_CALL,
step_id=step_id,
model=model,
provider=provider,
tool_context=tool_context,
)

async def tool_completed(
self,
tool_name: str,
*,
step_name: str | None = None,
step_id: str | None = None,
output: dict[str, Any] | None = None,
tokens_in: int | None = None,
tokens_out: int | None = None,
cost_usd: float | None = None,
) -> None:
"""Mark a tool invocation as completed.

Convenience wrapper around step_completed() for tool-level tracking.

Args:
tool_name: Name of the tool that was invoked
step_name: Step name (defaults to "tools/{tool_name}")
step_id: Optional step ID (must match the one used in check_tool_gate)
output: Output data from the tool
tokens_in: Input tokens consumed
tokens_out: Output tokens produced
cost_usd: Cost in USD

Example:
>>> await adapter.tool_completed("web_search",
... output={"results": search_results},
... tokens_in=150,
... tokens_out=500,
... cost_usd=0.002,
... )
"""
if step_name is None:
step_name = f"tools/{tool_name}"

await self.step_completed(
step_name=step_name,
step_id=step_id,
output=output,
tokens_in=tokens_in,
tokens_out=tokens_out,
cost_usd=cost_usd,
)

async def complete_workflow(self) -> None:
"""Mark the workflow as completed.

Expand Down
Loading