diff --git a/.githooks/pre-commit b/.githooks/pre-commit index 2bbeef7..fce795c 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -11,20 +11,20 @@ require_file() { semver_re='^[0-9]+\.[0-9]+\.[0-9]+$' spec_ver() { - sed -n -E 's/^Version:\s*([0-9]+\.[0-9]+\.[0-9]+).*/\1/p' documentation/protocol/spec.md | head -n1 + sed -n -E 's/^Version:[[:space:]]*([0-9]+\.[0-9]+\.[0-9]+).*/\1/p' documentation/protocol/spec.md | head -n1 } py_ver() { # Extract version under [project] - awk 'BEGIN{p=0} /^\[project\]/{p=1;next} /^\[/{p=0} p==1 && $1=="version"{gsub(/\"/ ,"",$3); print $3; exit}' pyproject.toml + awk 'BEGIN{p=0} /^\[project\]/{p=1;next} /^\[/{p=0} p==1 && $1=="version"{gsub(/"/,"",$3); print $3; exit}' pyproject.toml } js_ver() { - sed -n -E 's/\s*"version"\s*:\s*"([0-9]+\.[0-9]+\.[0-9]+)".*/\1/p' sdks/js_sdk/package.json | head -n1 + sed -n -E 's/[[:space:]]*"version"[[:space:]]*:[[:space:]]*"([0-9]+\.[0-9]+\.[0-9]+)".*/\1/p' sdks/js_sdk/package.json | head -n1 } rs_ver() { - awk 'BEGIN{p=0} /^\[package\]/{p=1;next} /^\[/{p=0} p==1 && $1=="version"{gsub(/\"/ ,"",$3); print $3; exit}' sdks/rust_sdk/Cargo.toml + awk 'BEGIN{p=0} /^\[package\]/{p=1;next} /^\[/{p=0} p==1 && $1=="version"{gsub(/"/,"",$3); print $3; exit}' sdks/rust_sdk/Cargo.toml } require_file documentation/protocol/spec.md @@ -52,10 +52,10 @@ fi if git rev-parse --verify HEAD >/dev/null 2>&1; then CHANGED=$(git diff --cached --name-only) base_ver() { git show HEAD:$1 2>/dev/null | sed -n -E "$2" | head -n1; } - BASE_SPEC=$(base_ver documentation/protocol/spec.md 's/^Version:\s*([0-9]+\.[0-9]+\.[0-9]+).*/\1/p') - BASE_PY=$(base_ver pyproject.toml '/^\[project\]/,/^\[/{s/^version\s*=\s*"([^"]+)"/\1/p}') - BASE_JS=$(base_ver sdks/js_sdk/package.json 's/\s*"version"\s*:\s*"([0-9]+\.[0-9]+\.[0-9]+)".*/\1/p') - BASE_RS=$(base_ver sdks/rust_sdk/Cargo.toml '/^\[package\]/{p=1;next} /^\[/{p=0} p==1 && $1=="version"{gsub(/\"/ ,"",$3); print $3; exit}') + BASE_SPEC=$(base_ver documentation/protocol/spec.md 's/^Version:[[:space:]]*([0-9]+\.[0-9]+\.[0-9]+).*/\1/p') + BASE_PY=$(base_ver pyproject.toml '/^\[project\]/,/^\[/{s/^version[[:space:]]*=[[:space:]]*"([^"]+)"/\1/p}') + BASE_JS=$(base_ver sdks/js_sdk/package.json 's/[[:space:]]*"version"[[:space:]]*:[[:space:]]*"([0-9]+\.[0-9]+\.[0-9]+)".*/\1/p') + BASE_RS=$(git show HEAD:sdks/rust_sdk/Cargo.toml 2>/dev/null | awk 'BEGIN{p=0} /^\[package\]/{p=1;next} /^\[/{p=0} p==1 && $1=="version"{gsub(/"/,"",$3); print $3; exit}') greater() { python - "$1" "$2" <<'PY' import sys @@ -132,5 +132,3 @@ if echo "$CHANGED" | grep -E '^sdks/rust_sdk/.*\.rs$' >/dev/null 2>&1; then fi popd >/dev/null 2>&1 fi - - diff --git a/.github/workflows/ci-python.yml b/.github/workflows/ci-python.yml index 14298f6..2aa62e2 100644 --- a/.github/workflows/ci-python.yml +++ b/.github/workflows/ci-python.yml @@ -25,7 +25,12 @@ jobs: - name: Install dev dependencies run: | - python -m pip install -e ".[dev]" + python -m pip install -e ".[dev,test]" + + - name: Install SBCL runtime + run: | + sudo apt-get update + sudo apt-get install -y sbcl - name: Run smoke test (protos + imports) run: | @@ -33,6 +38,4 @@ jobs: - name: Run Python unit tests run: | - python -m pip install pytest pytest -q sdks/py_sdk/tests - diff --git a/.gitignore b/.gitignore index 4bd3b54..6b60457 100644 --- a/.gitignore +++ b/.gitignore @@ -43,6 +43,11 @@ PROMPT.md RALPH_PROMPT.md ralph.yml +# Internal implementation status/readme artifacts +sdks/SDK_IMPLEMENTATION_PROGRESS.md +sdks/py_sdk/sw4rm/CROSS_CUTTING_README.md +sdks/py_sdk/sw4rm/METRICS_README.md + # Generated protobuf stubs sdks/py_sdk/sw4rm/protos/*.py sdks/py_sdk/sw4rm/protos/*.pyi diff --git a/docs/CONTEXT.md b/docs/CONTEXT.md index 2228a10..4fd12ca 100644 --- a/docs/CONTEXT.md +++ b/docs/CONTEXT.md @@ -35,7 +35,7 @@ This file captures the current state, decisions, and next steps for the SW4RM Ag - Advanced features: streaming support, comprehensive observability, retry logic - Type safety with compile-time guarantees - Performance optimizations and enterprise-grade error handling - - Comprehensive documentation and examples (`sdks/SDK_IMPLEMENTATION_PROGRESS.md`) + - Comprehensive documentation and examples (protocol extensions + operational docs) ### Documentation Content & Structure - Implemented hierarchical numbering system across all documentation: diff --git a/documentation/README.md b/documentation/README.md index 9353e7e..f8c9588 100644 --- a/documentation/README.md +++ b/documentation/README.md @@ -40,10 +40,10 @@ The built site will be in the `site/` directory. Cross-SDK implementation status for the inter-swarm extensions is tracked in: -- `sdks/SDK_IMPLEMENTATION_PROGRESS.md` (capability matrix by SDK) - `documentation/protocol/extensions/index.md` (extension-level status summary) +- `artifacts/verification/` (evidence snapshots for verification claims) -When updating SW4-004 or SW4-005 docs/claims, update both files together so conformance wording and SDK parity reporting stay consistent. +When updating SW4-004 or SW4-005 docs/claims, update the extension status page and evidence snapshots together so conformance wording and parity reporting stay consistent. ## Phase Status (2026-02-15) diff --git a/documentation/blog/i141-how-sw4rm-coordinates-code-writing-agents.md b/documentation/blog/i141-how-sw4rm-coordinates-code-writing-agents.md new file mode 100644 index 0000000..19e1de7 --- /dev/null +++ b/documentation/blog/i141-how-sw4rm-coordinates-code-writing-agents.md @@ -0,0 +1,60 @@ +# How SW4RM Coordinates Code-Writing Agents + +**Issue:** I141 + +## Why Coordination Is Hard + +Multi-agent coding teams rarely fail because no one is smart enough to write code. +They fail when communication, ownership, and task boundaries are unclear. + +In SW4RM, we use explicit protocol-level coordination so agents stay autonomous without +becoming chaotic: + +- each agent publishes/consumes standard envelopes, +- task ownership is explicit, +- state transitions are observable, +- and completion criteria are machine-checkable. + +## How SW4RM Helps + +SW4RM’s protocol design gives code-writing teams: + +- a shared contract for routing and negotiation, +- deterministic handoff paths between agents, +- explicit heartbeat and recovery semantics, +- and extension points for tracing and observability. + +That means a code-writing agent can hand a partial implementation to a reviewer, +where the reviewer can request targeted revision loops instead of re-deriving context. + +## The “LLM Integration” Pattern + +Across all SDKs, the LLM integration extension follows the same pattern: + +1. Keep provider specifics behind a narrow adapter interface. +2. Route all orchestration requests through protocol messages, not out-of-band side channels. +3. Record agent decisions in extension state so future turns can continue from concrete artifacts. +4. Use metrics and trace signals to catch quality regressions early (retry spikes, queue delays, nacks). + +For SW4RM today, this keeps “agent writes code” and “agent critiques code” in the same +coordinated loop with auditable inputs and repeatable exits. + +## What We Changed in This Cycle + +- Added production-capable metrics backend plumbing for Python SDK emitters (StatsD/Datadog path). +- Updated cross-SDK extension docs so the LLM Integration extension is tracked across all five SDKs. +- Continued hardening the production tranche queue so observability and operational readiness stay aligned. + +## Why This Matters + +When each SDK and agent follows the same coordination contract, we can scale code-writing effort safely. +The team gains: + +- faster iteration with fewer blind spots, +- clearer recovery when one agent diverges, +- stronger governance through logged handoffs and metrics, +- and a reusable operating model for future autonomous coding tranches. + +--- + +This post is the checkpoint write-up for I141 in the SW4RM execution stream. diff --git a/documentation/protocol/extensions/index.md b/documentation/protocol/extensions/index.md index 5a6d6fd..5ca2202 100644 --- a/documentation/protocol/extensions/index.md +++ b/documentation/protocol/extensions/index.md @@ -61,7 +61,7 @@ Implementations may claim conformance to specific extensions: - Rust: SW4-004/SW4-005 wire fields, caller redirect helper, gateway redirect-emitter helper, cancellation helper behavior, full SW4-004/SW4-005 conformance coverage, and shared vector adapters. - Common Lisp: SW4-004/SW4-005 wire fields, caller redirect helper, gateway redirect-emitter helper, cancellation helper behavior, full SW4-004/SW4-005 conformance coverage, and shared vector adapters. -See `sdks/SDK_IMPLEMENTATION_PROGRESS.md` for the authoritative cross-SDK capability matrix. +This table is the canonical public cross-SDK implementation status; supporting evidence is maintained in `artifacts/verification/`. ## Extension Lifecycle diff --git a/documentation/protocol/index.md b/documentation/protocol/index.md index 878aaee..2415bcb 100644 --- a/documentation/protocol/index.md +++ b/documentation/protocol/index.md @@ -2,7 +2,7 @@ ([Link to full RFC](https://github.com/rahulrajaram/sw4rm/blob/master/documentation/protocol/spec.md)) -**SW4RM Protocol v0.6.0** | **Status: Production Ready** | **Last Updated: 2026-02-15** +**SW4RM Protocol v0.6.0** | **Status: Production Ready** | **Last Updated: 2026-03-06** Related documents: @@ -13,7 +13,7 @@ Related documents: ??? note "Changelog" - **0.6.0 (2026-02-15)** + **0.6.0 (2026-03-06)** - Versioned extension structure: normative protocol evolution is now tracked in versioned extension release files (`extensions/v0.N.0.md`). The core diff --git a/documentation/protocol/sdk_extensions.md b/documentation/protocol/sdk_extensions.md index 75b6a0e..facb7f0 100644 --- a/documentation/protocol/sdk_extensions.md +++ b/documentation/protocol/sdk_extensions.md @@ -94,11 +94,11 @@ Agent thread spawning infrastructure (`colony/` module). Provides `Spawner` base class and thread-based agent lifecycle management. This is an application-level concern not specified in the protocol. -## 10. LLM Integration (Python only) +## 10. LLM Integration (All SDKs) -**SDK**: Python +**SDKs**: Python, JavaScript/TypeScript, Rust, Elixir, Common Lisp -Claude SDK adapter (`llm/` module) for integrating language model inference +Provider-agnostic LLM adapters and factories for integrating language model inference into agent decision-making. Protocol-agnostic; the spec does not prescribe inference engine integration. diff --git a/documentation/protocol/spec.md b/documentation/protocol/spec.md index 9c3ecde..c863c9e 100644 --- a/documentation/protocol/spec.md +++ b/documentation/protocol/spec.md @@ -1,6 +1,6 @@ # RFC: SW4RM - Interruptible, Message-Driven Agent Coordination Protocol -Version: 0.6.0 (2026-02-15) +Version: 0.6.0 (2026-03-06) ## Versioning and Changelog @@ -10,7 +10,7 @@ The versioning scope encompasses this document and the canonical protocol buffer **Changelog:** -- **0.6.0 (2026-02-15)**: Versioned extension structure. Normative protocol +- **0.6.0 (2026-03-06)**: Versioned extension structure. Normative protocol evolution is now tracked in versioned extension release files (`extensions/v0.N.0.md`). This document (`spec.md`) is frozen as a static core specification where only the version number changes. See diff --git a/mkdocs.yml b/mkdocs.yml index 4068a7a..d7b17b7 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -102,6 +102,8 @@ nav: - examples/index.md - Use Cases: examples/use-cases.md - Deployment Patterns: examples/deployment.md + - Blog: + - "I141: How SW4RM Coordinates Code-Writing Agents": blog/i141-how-sw4rm-coordinates-code-writing-agents.md - Architecture: - architecture/index.md - State Machines: architecture/state-machines.md diff --git a/pyproject.toml b/pyproject.toml index 08227fd..ada29bf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "sw4rm-sdk" -version = "0.5.0" +version = "0.6.0" description = "SW4RM Agentic Protocol — Reference Python SDK (experimental)" readme = "README.md" requires-python = ">=3.9" @@ -23,6 +23,12 @@ dev = [ "twine>=5,<6", "pkginfo>=1.10,<2", ] +test = [ + "pytest>=7.0,<9", + "pytest-asyncio", + "groq>=0.4", + "anthropic>=0.18", +] docs = [ "mkdocs>=1.5.0", "mkdocs-material>=9.4.0", diff --git a/scripts/git-hooks/pre-commit b/scripts/git-hooks/pre-commit deleted file mode 100755 index 956b92a..0000000 --- a/scripts/git-hooks/pre-commit +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# Enforce that hooks are executed from the versioned path, unless explicitly overridden. -# Set ALLOW_HOOKS_PATH_MISMATCH=1 to bypass this check (or use --no-verify). -EXPECTED_PATH="scripts/git-hooks" -CONFIGURED_PATH=$(git config --get core.hooksPath || echo "") -if [ -z "$CONFIGURED_PATH" ] || [ "$CONFIGURED_PATH" != "$EXPECTED_PATH" ]; then - if [ "${ALLOW_HOOKS_PATH_MISMATCH:-}" != "1" ]; then - echo "[pre-commit] core.hooksPath is not set to '$EXPECTED_PATH'." >&2 - echo " Run: git config core.hooksPath $EXPECTED_PATH" >&2 - echo " Or bypass once with: ALLOW_HOOKS_PATH_MISMATCH=1 git commit" >&2 - echo " (You can also use: git commit --no-verify)" >&2 - exit 1 - fi -fi - -# Run documentation style checks before commit -REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || echo ".") -python3 "$REPO_ROOT/scripts/check_docs_style.py" - -# Run TruffleHog secret scanning before commit -python3 "$REPO_ROOT/scripts/check_secrets.py" diff --git a/sdks/SDK_IMPLEMENTATION_PROGRESS.md b/sdks/SDK_IMPLEMENTATION_PROGRESS.md deleted file mode 100644 index 07d9206..0000000 --- a/sdks/SDK_IMPLEMENTATION_PROGRESS.md +++ /dev/null @@ -1,141 +0,0 @@ -# SW4RM SDK Implementation Progress (SW4-004/SW4-005) - -This document tracks cross-SDK parity for the inter-swarm extensions: - -- SW4-004: Inter-Swarm Composition -- SW4-005: Spillover Routing - -Status values: - -- `DONE`: Implemented and covered by tests in that SDK. -- `PARTIAL`: Implemented only for part of the extension surface. -- `PENDING`: Not implemented yet. - -## Cross-SDK Capability Matrix - -| Capability | Python | JS/TS | Rust | Common Lisp | Notes | -|---|---|---|---|---|---| -| SW4-004/SW4-005 handoff wire fields (`budget`, `delegation_policy`, `rejection_code`, `retry_after_ms`, `redirect_to_agent_id`) | DONE | DONE | DONE | DONE | All SDKs expose these fields on handoff request/response surfaces. | -| SW4-004 deadline validation + delegation policy normalization | DONE | DONE | DONE | DONE | Enforced in each SDK handoff client path. | -| SW4-005 caller redirect helper (`REDIRECT` follow, `OVERLOADED` retry, loop protection, redirect bounds, wall-time deduction) | DONE | DONE | DONE | DONE | Implemented in Python/JS/Rust/Common Lisp delegation helpers; JS/Rust/Common Lisp parity includes post-attempt `ACK_TIMEOUT` fail-fast and trimmed redirect-target validation ordering before redirect-bound fallback. | -| SW4-005 gateway redirect emitter + health-aware peer selection | DONE | DONE | DONE | DONE | Implemented in Python/JS/Rust/Common Lisp gateway redirect-emitter helpers with health-aware peer filtering and spillover fallback semantics. | -| SW4-004 cancellation helper behavior (cascade + grace-period handling) | DONE | DONE | DONE | DONE | Python/JS/Rust/CL now provide local cancellation helper APIs with cascade + grace-expiry semantics and forced-preemption signaling. | -| SW4-004 conformance suite (T-001..T-012) | DONE | DONE | DONE | DONE | Full suite coverage is exercised in Python/JS/Rust/Common Lisp test adapters and parity suites. | -| SW4-005 conformance suite (R-001..R-012) | DONE | DONE | DONE | DONE | Full suite coverage is exercised in Python/JS/Rust/Common Lisp test adapters and parity suites. | -| Targeted SW4-004/SW4-005 parity tests | DONE | DONE | DONE | DONE | JS/Rust/CL include targeted parity tests for implemented surfaces. | -| Shared SW4-005 gateway/delegation conformance vectors | DONE | DONE | DONE | DONE | `tests/conformance_vectors/sw4_005_delegation_vectors.json` (`v2`) is consumed by SDK-specific adapters in Python/JS/Rust/Common Lisp tests. | -| Shared SW4-004 cancellation conformance vectors | DONE | DONE | DONE | DONE | `tests/conformance_vectors/sw4_004_cancellation_vectors.json` is consumed by SDK-specific adapters in Python/JS/Rust/Common Lisp tests. | - -## Evidence (Code + Tests) - -### Python -- Handoff/gateway surfaces: `sdks/py_sdk/sw4rm/gateway.py` -- Redirect helper: `sdks/py_sdk/sw4rm/delegation.py` -- SW4-004 conformance tests: `sdks/py_sdk/tests/test_sw4_004_conformance.py` -- SW4-005 conformance tests: `sdks/py_sdk/tests/test_sw4_005_conformance.py` -- Shared vector adapter: `sdks/py_sdk/tests/test_cross_sdk_conformance_vectors.py` - -### JS/TS -- Handoff wire surface: `sdks/js_sdk/src/clients/handoff.ts` -- Redirect helper: `sdks/js_sdk/src/runtime/delegation.ts` -- Gateway redirect emitter helper: `sdks/js_sdk/src/runtime/gateway.ts` -- Cancellation helper: `sdks/js_sdk/src/runtime/cancellation.ts` -- SW4-004/SW4-005 parity tests: `sdks/js_sdk/test/handoff.test.ts`, `sdks/js_sdk/test/crossSdk.test.ts` (includes redirect-target validation ordering and post-attempt budget-exhaustion fail-fast checks) -- Gateway redirect-emitter parity tests: `sdks/js_sdk/test/gateway.test.ts` -- Cancellation parity tests: `sdks/js_sdk/test/cancellation.test.ts` (cascade propagation, grace boundary expiry, forced-preemption signaling, metadata normalization) -- Full SW4-004/SW4-005 conformance coverage (T-001..T-012, R-001..R-012) across JS parity suites: `sdks/js_sdk/test/handoff.test.ts`, `sdks/js_sdk/test/crossSdk.test.ts`, `sdks/js_sdk/test/gateway.test.ts`, `sdks/js_sdk/test/cancellation.test.ts` -- Shared vector adapter: `sdks/js_sdk/test/conformanceVectors.test.ts` - -### Rust -- Handoff wire surface: `sdks/rust_sdk/src/clients/handoff.rs` -- Redirect helper: `sdks/rust_sdk/src/clients/delegation.rs` -- Gateway redirect emitter helper: `sdks/rust_sdk/src/clients/gateway.rs` -- Cancellation helper: `sdks/rust_sdk/src/clients/cancellation.rs` -- SW4-004/SW4-005 parity tests: `sdks/rust_sdk/tests/cross_sdk_tests.rs` -- Redirect helper tests (including redirect-target validation ordering and post-attempt budget fast-fail): `sdks/rust_sdk/tests/integration_tests.rs` -- Gateway redirect-emitter parity tests: `sdks/rust_sdk/tests/gateway_tests.rs` -- Cancellation parity tests: `sdks/rust_sdk/tests/cancellation_tests.rs` (cascade propagation, grace boundary expiry, forced-preemption signaling, metadata normalization) -- Full SW4-004/SW4-005 conformance coverage (T-001..T-012, R-001..R-012) across Rust parity suites: `sdks/rust_sdk/tests/cross_sdk_tests.rs`, `sdks/rust_sdk/tests/integration_tests.rs`, `sdks/rust_sdk/tests/gateway_tests.rs`, `sdks/rust_sdk/tests/cancellation_tests.rs` -- Shared vector adapters: - - `sdks/rust_sdk/tests/integration_tests.rs` (`delegation_tests::test_delegate_to_swarm_shared_conformance_vectors`) - - `sdks/rust_sdk/tests/cancellation_tests.rs` (`shared_cancellation_conformance_vectors`) - -### Common Lisp -- Handoff wire + caller redirect helper + cancellation helper surface: `sdks/cl_sdk/src/clients/handoff.lisp` -- Gateway redirect emitter helper: `sdks/cl_sdk/src/clients/gateway.lisp` -- SW4-004/SW4-005 parity tests (including redirect/retry ordering, gateway peer health/redirect emission parity, budget exhaustion fail-fast, and cancellation metadata/cascade checks): `sdks/cl_sdk/test/suite.lisp` -- Full SW4-004/SW4-005 conformance coverage (T-001..T-012, R-001..R-012): `sdks/cl_sdk/test/suite.lisp` -- Shared vector adapters: - - `sdks/cl_sdk/test/suite.lisp` (`handoff-delegate-to-swarm-shared-conformance-vectors`) - - `sdks/cl_sdk/test/suite.lisp` (`handoff-cancellation-shared-conformance-vectors`) - -## Current Gaps - -- No remaining SW4-004/SW4-005 conformance-suite parity gaps across Python/JS/Rust/Common Lisp. - -## Recent CL Transport Closure Evidence - -- `I61` now implements previously `UNIMPLEMENTED` Common Lisp SDK method surfaces with matching gRPC transport calls and codec tests: - - Scheduler: `cancel-task`, `get-task-status` (client + codec + tests). - - Negotiation: `get-session` (client + codec + tests). - - HITL: `respond`, `get-pending`, `get-decision-status` (client + codec + tests). - - Logging: `query-logs` (client + codec + tests). - - Connector: `list-providers` (client + codec + tests). - - Activity: `deregister-activity`, `get-artifact` (client + codec + tests). - - Tool: `list-tools` (client + codec + tests). - -Evidence files: -- `sdks/cl_sdk/src/clients/scheduler.lisp` -- `sdks/cl_sdk/src/clients/negotiation.lisp` -- `sdks/cl_sdk/src/clients/hitl.lisp` -- `sdks/cl_sdk/src/clients/logging.lisp` -- `sdks/cl_sdk/src/clients/connector.lisp` -- `sdks/cl_sdk/src/clients/activity.lisp` -- `sdks/cl_sdk/src/clients/tool.lisp` -- `sdks/cl_sdk/src/transport/protobuf-codec.lisp` -- `sdks/cl_sdk/test/run-codec-tests.lisp` - -## Phase 6 Closure Summary (I49) - -- Phase 6 (`I39`-`I49`) is closed with full tranche-chain evidence recorded under `artifacts/verification/`. -- Cross-SDK parity for all tracked SW4-004/SW4-005 helper surfaces is complete after `I50` closed Common Lisp SW4-005 gateway redirect-emitter + health-aware peer selection. -- Full-matrix verification was completed in `I48` and carried forward in `I49` closure documentation. - -## Phase 7 Seed Targets - -- `I50`: Common Lisp SW4-005 gateway redirect-emitter parity closure (DONE). -- `I51`: Full SW4-004/SW4-005 conformance suite expansion for JS/TS, Rust, and Common Lisp (DONE). -- `I52`: Production transport + multi-process inter-swarm integration testbed bootstrap (DONE). - -## I52 Bootstrap Artifacts - -- Manifest-driven transport/testbed scaffold: `tests/inter_swarm_testbed/bootstrap_manifest.json` -- Multi-process dual-swarm compose template: `tests/inter_swarm_testbed/docker-compose.multi-swarm.yml` -- Bootstrap validation coverage: `sdks/py_sdk/tests/test_inter_swarm_testbed_bootstrap.py` -- Runbook: `documentation/production/inter-swarm-transport-testbed.md` - -## Phase 9 Transport Continuation - -- `I62`: CL deferred codec closure for policy/workflow deep decode completed in `sdks/cl_sdk/src/transport/protobuf-codec.lisp` (including parser-corrected deep codec forms) and client coverage in `sdks/cl_sdk/src/clients/scheduler-policy.lisp`. -- `I63`: Cross-SDK/spec drift audit and parity fixes completed, including CL transport placeholder-channel error normalization, CL endpoint/config parity updates, and refreshed parity audit evidence. -- `I64`: Full matrix verification + dispatch closure completed with two consecutive clean passes over `py-test`, `conformance`, `conformance-005`, `proto-check`, `docs-lint`, `docs-build`, `test-js`, `test-rust`, and `test-lisp`. - -- Evidence files for `I62` verification: - - `sdks/cl_sdk/src/transport/protobuf-codec.lisp` - - `sdks/cl_sdk/src/clients/scheduler-policy.lisp` - - `sdks/cl_sdk/test/run-codec-tests.lisp` - - `.yarli/evidence/i62-verify-b-codec-load-20260222T010730Z.log` - -- Evidence files for `I63` verification: - - `tests/test_reports/sdk_parity_audit.md` - - `sdks/cl_sdk/src/transport/grpc-transport.lisp` - - `sdks/cl_sdk/src/config.lisp` - -- Evidence files for `I64` verification: - - `artifacts/verification/i64-verify-a-full-matrix-20260222T011542Z.log` - - `artifacts/verification/i64-verify-b-full-matrix-20260222T011649Z.log` - - ---- - -Last updated: 2026-02-22 (I64) diff --git a/sdks/cl_sdk/README.md b/sdks/cl_sdk/README.md index 0d083b0..ce84d1e 100644 --- a/sdks/cl_sdk/README.md +++ b/sdks/cl_sdk/README.md @@ -48,7 +48,7 @@ Dependencies (resolved automatically via Quicklisp): :agent-id "echo-1" :name "EchoAgent" :description "Echoes incoming DATA messages" - :version "0.5.0" + :version "0.6.0" :capabilities '("echo" "application/json") :endpoints (make-default-endpoints) :timeout-ms 30000 diff --git a/sdks/cl_sdk/examples/advanced-agent.lisp b/sdks/cl_sdk/examples/advanced-agent.lisp index ae065f3..5811713 100644 --- a/sdks/cl_sdk/examples/advanced-agent.lisp +++ b/sdks/cl_sdk/examples/advanced-agent.lisp @@ -42,7 +42,7 @@ :agent-id "reviewer-1" :name "CodeReviewAgent" :description "Reviews code changes and provides feedback." - :version "0.5.0" + :version "0.6.0" :capabilities '("code-review" "static-analysis" "security-audit") :endpoints (make-default-endpoints) :timeout-ms 30000 diff --git a/sdks/cl_sdk/examples/echo-agent.lisp b/sdks/cl_sdk/examples/echo-agent.lisp index 0a4e259..a567cd2 100644 --- a/sdks/cl_sdk/examples/echo-agent.lisp +++ b/sdks/cl_sdk/examples/echo-agent.lisp @@ -62,7 +62,7 @@ :agent-id "echo-1" :name "EchoAgent" :description "Echoes incoming DATA messages back through the router." - :version "0.5.0" + :version "0.6.0" :capabilities '("echo" "text/plain" "application/json") :endpoints (make-default-endpoints) ; honours SW4RM_*_ADDR env vars :timeout-ms 30000 diff --git a/sdks/cl_sdk/examples/tool-streaming.lisp b/sdks/cl_sdk/examples/tool-streaming.lisp index 1e2efb5..fbac2fc 100644 --- a/sdks/cl_sdk/examples/tool-streaming.lisp +++ b/sdks/cl_sdk/examples/tool-streaming.lisp @@ -42,7 +42,7 @@ :agent-id "tool-agent-1" :name "ToolDemoAgent" :description "Demonstrates tool client and connector client usage." - :version "0.5.0" + :version "0.6.0" :capabilities '("tool-execution" "streaming") :endpoints (make-default-endpoints) :timeout-ms 30000 diff --git a/sdks/cl_sdk/src/constants.lisp b/sdks/cl_sdk/src/constants.lisp index 62f3f62..35cd53c 100644 --- a/sdks/cl_sdk/src/constants.lisp +++ b/sdks/cl_sdk/src/constants.lisp @@ -7,7 +7,7 @@ ;;; These constants mirror the enum values from protos/common.proto. ;;; All values MUST match the canonical protocol buffer definitions. ;;; -;;; SW4RM Protocol Version: 0.5.0 +;;; SW4RM Protocol Version: 0.6.0 ;;; MessageType (spec §11) ;;; Defines the type of message being sent through the routing infrastructure. diff --git a/sdks/cl_sdk/sw4rm-sdk.asd b/sdks/cl_sdk/sw4rm-sdk.asd index f0068fa..e5022f6 100644 --- a/sdks/cl_sdk/sw4rm-sdk.asd +++ b/sdks/cl_sdk/sw4rm-sdk.asd @@ -2,7 +2,7 @@ (asdf:defsystem #:sw4rm-sdk :description "SW4RM Protocol SDK for Common Lisp - Full peer implementation" - :version "0.5.0" + :version "0.6.0" :author "SW4RM Team" :license "Apache-2.0" :depends-on (#:alexandria ; Common utilities diff --git a/sdks/docs/OPERATIONAL_CONTRACTS.md b/sdks/docs/OPERATIONAL_CONTRACTS.md index 9b83e02..feed043 100644 --- a/sdks/docs/OPERATIONAL_CONTRACTS.md +++ b/sdks/docs/OPERATIONAL_CONTRACTS.md @@ -627,7 +627,6 @@ intercepted_channel = grpc.intercept_channel(channel, AuthInterceptor('my-api-ke ## 10. References - SW4RM Protocol Specification: `documentation/protocol/spec.md` -- SDK Implementation Progress: `sdks/SDK_IMPLEMENTATION_PROGRESS.md` - Python SDK README: `sdks/py_sdk/README.md` - Rust SDK README: `sdks/rust_sdk/README.md` - JavaScript SDK README: `sdks/js_sdk/README.md` diff --git a/sdks/js_sdk/package-lock.json b/sdks/js_sdk/package-lock.json index dfd4ad6..8665d1e 100644 --- a/sdks/js_sdk/package-lock.json +++ b/sdks/js_sdk/package-lock.json @@ -1,12 +1,12 @@ { "name": "@sw4rm/js-sdk", - "version": "0.5.0", + "version": "0.6.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@sw4rm/js-sdk", - "version": "0.5.0", + "version": "0.6.0", "license": "Apache-2.0", "dependencies": { "@grpc/grpc-js": "^1.10.0", diff --git a/sdks/js_sdk/package.json b/sdks/js_sdk/package.json index 3594a66..e10391c 100644 --- a/sdks/js_sdk/package.json +++ b/sdks/js_sdk/package.json @@ -1,6 +1,6 @@ { "name": "@sw4rm/js-sdk", - "version": "0.5.0", + "version": "0.6.0", "description": "SW4RM Agentic Protocol JavaScript/TypeScript SDK", "type": "module", "main": "./dist/cjs/index.cjs", diff --git a/sdks/js_sdk/src/index.ts b/sdks/js_sdk/src/index.ts index 7591144..7789356 100644 --- a/sdks/js_sdk/src/index.ts +++ b/sdks/js_sdk/src/index.ts @@ -13,7 +13,7 @@ // limitations under the License. // SW4RM JavaScript SDK -export const version = '0.5.0'; +export const version = '0.6.0'; // Clients export * from './clients/router.js'; diff --git a/sdks/py_sdk/setup.py b/sdks/py_sdk/setup.py index d09d321..5e457b7 100644 --- a/sdks/py_sdk/setup.py +++ b/sdks/py_sdk/setup.py @@ -8,7 +8,7 @@ setup( name="sw4rm-sdk", - version="0.5.0", + version="0.6.0", author="SW4RM", description="Python SDK for the SW4RM Agentic Protocol", long_description=long_description, diff --git a/sdks/py_sdk/sw4rm/CROSS_CUTTING_README.md b/sdks/py_sdk/sw4rm/CROSS_CUTTING_README.md deleted file mode 100644 index 33e6890..0000000 --- a/sdks/py_sdk/sw4rm/CROSS_CUTTING_README.md +++ /dev/null @@ -1,379 +0,0 @@ -# Cross-Cutting Concerns (Phase 3.6) - -This document describes the cross-cutting concerns implementation in the SW4RM Python SDK, including structured logging, distributed tracing, configuration management, and feature flags. - -## Overview - -Phase 3.6 implements the following cross-cutting concerns: - -1. **Structured Logging** (`sw4rm/logging.py`) - Consistent log format with correlation IDs -2. **Distributed Tracing** (`sw4rm/tracing.py`) - Trace context propagation across operations -3. **Configuration Management** (`sw4rm/config.py`) - Unified configuration from multiple sources -4. **Feature Flags** (`sw4rm/feature_flags.py`) - Runtime feature toggles - -## Structured Logging - -### Features - -- Structured log format with timestamp, level, agent_id, correlation_id, message, extra fields -- JSON output format for log aggregation systems -- Standard log levels: DEBUG, INFO, WARNING, ERROR, CRITICAL -- Integration with trace context for automatic correlation ID propagation - -### Usage - -```python -from sw4rm import logging - -# Configure global logging -logging.configure_logging(level="INFO", use_json=False) - -# Get a logger -logger = logging.get_logger(__name__, agent_id="agent-42") - -# Log messages with extra fields -logger.info("Agent started", version="1.0.0", environment="production") -logger.warning("High memory usage", usage_mb=1024) -logger.error("Failed to connect", exc_info=True, service="router") -``` - -### JSON Output - -Set `use_json=True` for structured JSON logs suitable for log aggregation: - -```python -logging.configure_logging(level="INFO", use_json=True) -``` - -Example JSON output: -```json -{ - "timestamp": "2025-12-23T20:30:00.123456+00:00", - "level": "INFO", - "logger": "sw4rm.agent", - "message": "Agent started", - "agent_id": "agent-42", - "correlation_id": "a1b2c3d4e5f6", - "extra": { - "version": "1.0.0", - "environment": "production" - } -} -``` - -## Distributed Tracing - -### Features - -- Trace context with trace_id, span_id, parent_span_id, correlation_id -- Automatic span creation and propagation -- Context-aware tracing using contextvars -- `@traced` decorator for automatic span creation -- Integration with structured logging -- Envelope-sidecar propagation for cross-service tracing continuity - -### Usage - -```python -from sw4rm import tracing - -# Create a root trace -trace = tracing.create_trace(metadata={"agent_id": "agent-42", "operation": "process"}) - -# Use trace context -with tracing.with_trace_context(trace): - # All operations in this context are part of the trace - logger.info("Processing started") # Includes correlation_id - - # Create child span - child = tracing.create_child_span( - trace, - metadata={"operation": "sub_task"} - ) - - with tracing.with_trace_context(child): - logger.info("Sub-task started") # Same correlation_id, different span - -# Use decorator for automatic tracing -@tracing.traced(operation="process_message", metadata={"component": "router"}) -def process_message(msg): - # Automatically creates a span - logger.info("Processing message") - return msg - -# Works with async functions too -@tracing.traced() -async def handle_request(request): - await process_async_operation() - -# Router client trace propagation -from sw4rm.interceptors import TracingClientInterceptor - -trace = tracing.create_trace(metadata={"operation": "send_router_message"}) -with tracing.with_trace_context(trace): - interceptor = TracingClientInterceptor() - # The interceptor injects trace headers on outbound gRPC metadata. - # RouterClient adds trace context to envelope metadata as `_trace_context` - # then strips it before protobuf serialization. -``` - -### Trace Context Structure - -```python -@dataclass -class TraceContext: - trace_id: str # Unique ID for the entire trace - span_id: str # Unique ID for this span - parent_span_id: str # ID of parent span (or None for root) - correlation_id: str # For log correlation (defaults to trace_id) - metadata: dict # Additional metadata -``` - -## Configuration Management - -### Features - -- Centralized configuration with `SW4RMConfig` dataclass -- Multiple configuration sources (priority order): - 1. Environment variables (highest priority) - 2. Configuration files (JSON/YAML) - 3. Default values (lowest priority) -- Singleton global configuration -- Type-safe configuration access - -### Configuration Fields - -```python -@dataclass -class SW4RMConfig: - router_addr: str # Router service address - registry_addr: str # Registry service address - default_timeout_ms: int # Default timeout in milliseconds - max_retries: int # Maximum retry attempts - enable_metrics: bool # Enable metrics collection - enable_tracing: bool # Enable distributed tracing - log_level: str # Log level (DEBUG, INFO, WARNING, ERROR, CRITICAL) - feature_flags: dict # Feature flag overrides -``` - -### Usage - -```python -from sw4rm import config - -# Load from environment only -cfg = config.load_config() - -# Load from JSON file with environment overrides -cfg = config.load_config("/etc/sw4rm/config.json") - -# Load from YAML file -cfg = config.load_config("/etc/sw4rm/config.yaml") - -# Set as global configuration -config.set_config(cfg) - -# Access global configuration -cfg = config.get_config() -print(f"Router: {cfg.router_addr}") -print(f"Timeout: {cfg.default_timeout_ms}ms") - -# Programmatic configuration -custom_config = config.SW4RMConfig( - router_addr="localhost:50051", - enable_metrics=False, - log_level="DEBUG" -) -config.set_config(custom_config) -``` - -### Environment Variables - -All configuration can be overridden via environment variables: - -- `SW4RM_ROUTER_ADDR` - Router service address -- `SW4RM_REGISTRY_ADDR` - Registry service address -- `SW4RM_DEFAULT_TIMEOUT_MS` - Default timeout in milliseconds -- `SW4RM_MAX_RETRIES` - Maximum retry attempts -- `SW4RM_ENABLE_METRICS` - Enable metrics (true/false) -- `SW4RM_ENABLE_TRACING` - Enable tracing (true/false) -- `SW4RM_LOG_LEVEL` - Log level (DEBUG, INFO, WARNING, ERROR, CRITICAL) - -### Configuration Files - -**JSON Example:** -```json -{ - "router_addr": "router.example.com:50051", - "registry_addr": "registry.example.com:50052", - "default_timeout_ms": 15000, - "max_retries": 5, - "enable_metrics": true, - "enable_tracing": true, - "log_level": "INFO", - "feature_flags": { - "ENABLE_WORKFLOW": true, - "ENABLE_AUDIT": false - } -} -``` - -**YAML Example:** -```yaml -router_addr: router.example.com:50051 -registry_addr: registry.example.com:50052 -default_timeout_ms: 15000 -max_retries: 5 -enable_metrics: true -enable_tracing: true -log_level: INFO -feature_flags: - ENABLE_WORKFLOW: true - ENABLE_AUDIT: false -``` - -## Feature Flags - -### Features - -- Runtime feature toggles -- Multiple flag sources (priority order): - 1. Programmatic overrides (highest priority) - 2. Environment variables (`SW4RM_FEATURE_*`) - 3. Configuration file (`feature_flags` section) - 4. Default values (lowest priority) -- Standard flags for major SDK features -- Type-safe flag access - -### Standard Feature Flags - -- `ENABLE_WORKFLOW` - Enable workflow orchestration -- `ENABLE_HANDOFF` - Enable agent handoff protocol -- `ENABLE_AUDIT` - Enable audit logging -- `ENABLE_VOTING` - Enable voting mechanisms - -### Usage - -```python -from sw4rm import feature_flags - -# Get global feature flags -flags = feature_flags.get_feature_flags() - -# Check if feature is enabled -if flags.is_enabled(feature_flags.FeatureFlags.ENABLE_WORKFLOW): - # Workflow is enabled - setup_workflow_engine() - -# Get flag value with default -max_retries = flags.get_value("MAX_RETRIES", default=3) -deployment_env = flags.get_value("DEPLOYMENT_ENV", default="development") - -# Programmatically set flags -flags.set_flag(feature_flags.FeatureFlags.ENABLE_AUDIT, True) -flags.set_flag("CUSTOM_FEATURE", "enabled") - -# Get all flags -all_flags = flags.get_all_flags() -for name, value in all_flags.items(): - print(f"{name}: {value}") -``` - -### Environment Variables - -Feature flags can be set via environment variables with the `SW4RM_FEATURE_` prefix: - -```bash -export SW4RM_FEATURE_ENABLE_WORKFLOW=true -export SW4RM_FEATURE_ENABLE_AUDIT=false -export SW4RM_FEATURE_MAX_CONNECTIONS=100 -export SW4RM_FEATURE_DEPLOYMENT_ENV=production -``` - -## Integration Example - -Here's how all cross-cutting concerns work together: - -```python -import asyncio -from sw4rm import config, feature_flags, logging, tracing - -# 1. Configure logging -logging.configure_logging(level="INFO", use_json=False) - -# 2. Load configuration -cfg = config.load_config("/etc/sw4rm/config.json") -config.set_config(cfg) - -# 3. Get logger -logger = logging.get_logger(__name__, agent_id="agent-42") - -# 4. Check feature flags -flags = feature_flags.get_feature_flags() - -@tracing.traced(operation="process_request") -async def process_request(request_id: str): - # Automatic tracing and correlation ID propagation - logger.info("Processing request", request_id=request_id) - - # Conditional logic based on feature flags - if flags.is_enabled(feature_flags.FeatureFlags.ENABLE_WORKFLOW): - await run_workflow(request_id) - else: - await run_simple_processing(request_id) - - logger.info("Request completed", request_id=request_id) - -async def main(): - # Create root trace - trace = tracing.create_trace(metadata={"operation": "main"}) - - with tracing.with_trace_context(trace): - # All logs include correlation_id - logger.info("Application started") - - # Process requests - await process_request("req-123") - await process_request("req-456") - - logger.info("Application finished") - -asyncio.run(main()) -``` - -## Testing - -Run the comprehensive test suite: - -```bash -pytest tests/test_cross_cutting.py -v -``` - -Tests cover: -- Structured logging (human-readable and JSON formats) -- Correlation ID propagation -- Trace context creation and serialization -- `@traced` decorator -- Configuration loading from files and environment -- Feature flag resolution and precedence -- Integration between all components - -## Example - -See `sw4rm/examples/cross_cutting_example.py` for a complete working example demonstrating all cross-cutting concerns. - -Run the example: -```bash -python -m sw4rm.examples.cross_cutting_example -``` - -## Implementation Files - -- `/home/rahul/Documents/sigagent/sdks/py_sdk/sw4rm/logging.py` - Structured logging -- `/home/rahul/Documents/sigagent/sdks/py_sdk/sw4rm/tracing.py` - Distributed tracing -- `/home/rahul/Documents/sigagent/sdks/py_sdk/sw4rm/interceptors.py` - Tracing client interceptor -- `/home/rahul/Documents/sigagent/sdks/py_sdk/sw4rm/clients/router.py` - Envelope trace propagation and cleanup -- `/home/rahul/Documents/sigagent/sdks/py_sdk/sw4rm/config.py` - Configuration management (enhanced) -- `/home/rahul/Documents/sigagent/sdks/py_sdk/sw4rm/feature_flags.py` - Feature flags -- `/home/rahul/Documents/sigagent/sdks/py_sdk/tests/test_cross_cutting.py` - Comprehensive tests -- `/home/rahul/Documents/sigagent/sdks/py_sdk/sw4rm/examples/cross_cutting_example.py` - Usage example diff --git a/sdks/py_sdk/sw4rm/METRICS_IMPLEMENTATION.md b/sdks/py_sdk/sw4rm/METRICS_IMPLEMENTATION.md deleted file mode 100644 index 9f59c09..0000000 --- a/sdks/py_sdk/sw4rm/METRICS_IMPLEMENTATION.md +++ /dev/null @@ -1,305 +0,0 @@ -# Metrics Interface Implementation Summary - -**Task:** Task 1.6 - Metrics Interface for SW4RM -**Location:** `/home/rahul/Documents/sigagent/sdks/py_sdk/sw4rm/metrics.py` -**Specification:** SW4RM spec §13 (Buffers and Back-Pressure) - -## Implementation Checklist - -### ✅ Required Components - -- [x] **MetricName (Enum)** - All 8 metric names from spec - - [x] `INBOUND_QUEUE_DEPTH` - - [x] `INBOUND_QUEUE_CAPACITY` - - [x] `ENQUEUE_REJECTS_TOTAL` - - [x] `NACKS_TOTAL` - - [x] `ENQUEUE_LATENCY_SECONDS` - - [x] `DEQUEUE_LATENCY_SECONDS` - - [x] `PROCESS_TIME_SECONDS` - - [x] `OLDEST_ENQUEUED_AGE_SECONDS` - -- [x] **Metric (dataclass)** - Data structure for metric observations - - [x] `name: MetricName` - - [x] `value: float` - - [x] `labels: dict[str, str]` - - [x] `timestamp: float` (unix timestamp, auto-populated) - - [x] Validation in `__post_init__` - -- [x] **MetricsCollector (Protocol)** - Interface definition - - [x] `record_gauge(name, value, labels) -> None` - - [x] `record_counter(name, increment, labels) -> None` - - [x] `record_histogram(name, value, labels) -> None` - - [x] `get_metrics() -> list[Metric]` - -- [x] **NoOpMetricsCollector** - Zero-overhead default implementation - - [x] All methods are no-ops - - [x] `get_metrics()` returns empty list - - [x] Satisfies MetricsCollector protocol - -- [x] **InMemoryMetricsCollector** - Testing/debugging implementation - - [x] Stores metrics in memory - - [x] `get_metrics()` returns all recorded metrics - - [x] `clear()` method for cleanup - - [x] `get_metric_count()` for monitoring - - [x] Satisfies MetricsCollector protocol - -### ✅ Code Quality Requirements - -- [x] **Type Hints** - Full type annotations throughout - - Modern Python style with `from __future__ import annotations` - - Protocol types for interface definition - - Optional types where appropriate - -- [x] **Docstrings** - Comprehensive documentation - - Module-level docstring with overview - - Class docstrings for all classes - - Method docstrings with Args/Returns/Examples - - Protocol documentation with usage examples - -- [x] **Standard Labels** - Support for spec-defined labels - - `agent_id`: Documented and used in examples - - `error_code`: Documented and used in examples - - `reason`: Documented and used in examples - -- [x] **Copyright Header** - Apache 2.0 license -- [x] **Module Registration** - Added to `__init__.py` - -## File Structure - -``` -sdks/py_sdk/sw4rm/ -├── metrics.py # Main implementation (11KB) -├── METRICS_README.md # User documentation (12KB) -├── METRICS_IMPLEMENTATION.md # This file -└── __init__.py # Updated to export metrics - -sdks/py_sdk/ -└── test_metrics_validation.py # Validation tests (5.3KB) -``` - -## API Surface - -### Exports - -```python -from sw4rm.metrics import ( - # Enum - MetricName, - - # Data class - Metric, - - # Protocol (for type hints) - MetricsCollector, - - # Implementations - NoOpMetricsCollector, - InMemoryMetricsCollector, -) -``` - -### Usage Example - -```python -from sw4rm.metrics import MetricName, InMemoryMetricsCollector - -# Create collector -collector = InMemoryMetricsCollector() - -# Record metrics -collector.record_gauge( - MetricName.INBOUND_QUEUE_DEPTH, - value=7, - labels={"agent_id": "agent-frontend"} -) - -collector.record_counter( - MetricName.NACKS_TOTAL, - increment=1, - labels={"agent_id": "scheduler", "error_code": "ack_timeout"} -) - -collector.record_histogram( - MetricName.PROCESS_TIME_SECONDS, - value=0.042, - labels={"agent_id": "worker"} -) - -# Retrieve metrics -metrics = collector.get_metrics() -for m in metrics: - print(f"{m.name.value} = {m.value} @ {m.timestamp}") -``` - -## Design Decisions - -### 1. Protocol vs ABC - -**Decision:** Use `Protocol` from typing module -**Rationale:** -- More Pythonic and flexible (structural subtyping) -- Allows existing classes to satisfy interface without explicit inheritance -- Better for testing (easier to create mocks) -- Matches modern Python typing best practices - -### 2. Metric Storage Format - -**Decision:** Use dataclass for Metric with automatic timestamp -**Rationale:** -- Immutable by default (safer for concurrent access) -- Automatic timestamp generation reduces boilerplate -- Validation in `__post_init__` prevents invalid metrics -- Easy to serialize for export - -### 3. Default Collector - -**Decision:** NoOpMetricsCollector as default recommendation -**Rationale:** -- Zero performance overhead for production systems -- Explicit opt-in for metrics collection -- Prevents accidental memory leaks in long-running processes -- Aligns with spec's recommendation for external metrics systems - -### 4. Label Handling - -**Decision:** Optional `dict[str, str]` with default to empty dict -**Rationale:** -- Labels are optional (not all metrics need dimensions) -- String-only values align with Prometheus/OpenMetrics standards -- Empty dict default prevents None handling complexity -- Easy to merge/filter labels in aggregation - -### 5. InMemoryCollector Design - -**Decision:** Simple list storage without aggregation -**Rationale:** -- Testing use case doesn't need complex aggregation -- Keeps implementation simple and understandable -- Raw observations useful for debugging -- Aggregation can be added by consumers if needed -- Explicit `clear()` method gives control to users - -## Testing - -Comprehensive validation tests cover: - -1. **Metric Names** - All 8 required metrics defined -2. **Metric Dataclass** - Creation, validation, timestamps -3. **NoOpCollector** - No-op behavior verified -4. **InMemoryCollector** - Storage, retrieval, clearing -5. **Protocol Compliance** - Both implementations satisfy protocol -6. **Standard Labels** - agent_id, error_code, reason - -Run tests with: -```bash -cd sdks/py_sdk -python3 test_metrics_validation.py -``` - -## Integration Points - -### Router Integration - -```python -from sw4rm.metrics import MetricName - -class MessageRouter: - def __init__(self, metrics_collector): - self.metrics = metrics_collector - - def enqueue_message(self, agent_id, message): - # Record queue depth before enqueue - self.metrics.record_gauge( - MetricName.INBOUND_QUEUE_DEPTH, - value=len(self.queue), - labels={"agent_id": agent_id} - ) - - # Check capacity and record rejection if needed - if len(self.queue) >= self.capacity: - self.metrics.record_counter( - MetricName.ENQUEUE_REJECTS_TOTAL, - labels={"agent_id": agent_id, "reason": "buffer_full"} - ) - return False - - # Enqueue and record latency - # ... -``` - -### Agent Integration - -```python -from sw4rm.metrics import MetricName -import time - -class Agent: - def __init__(self, agent_id, metrics_collector): - self.agent_id = agent_id - self.metrics = metrics_collector - - def process_message(self, message): - start_time = time.time() - - # Process message... - result = self._do_work(message) - - # Record processing time - process_time = time.time() - start_time - self.metrics.record_histogram( - MetricName.PROCESS_TIME_SECONDS, - value=process_time, - labels={"agent_id": self.agent_id} - ) - - return result -``` - -## Future Enhancements - -Potential future additions (not required by spec): - -1. **Metric Retention Policies** - - Time-based expiry - - Size-based limits - - Automatic cleanup - -2. **Advanced Aggregation** - - Percentile calculation for histograms - - Counter aggregation by label - - Gauge last-value semantics - -3. **Export Adapters** - - Prometheus exporter - - StatsD client - - OpenTelemetry integration - - DataDog agent - -4. **Performance Optimizations** - - Metric batching - - Async recording - - Lock-free data structures - -5. **Monitoring Helpers** - - Rate calculations - - Alerting thresholds - - Dashboard templates - -## Compliance - -This implementation fully satisfies SW4RM specification §13 requirements: - -✅ All 8 recommended metric names defined -✅ Support for gauge, counter, and histogram types -✅ Standard labels (agent_id, error_code, reason) documented -✅ Pluggable collector interface -✅ Production-ready no-op implementation -✅ Testing-friendly in-memory implementation -✅ Complete documentation and examples - -## References - -- [SW4RM Specification §13](../../../documentation/protocol/spec.md#13-buffers-and-back-pressure) -- [Metrics README](METRICS_README.md) - User documentation -- [Python SDK README](../README.md) - SDK overview -- [Test Validation Script](../test_metrics_validation.py) diff --git a/sdks/py_sdk/sw4rm/METRICS_README.md b/sdks/py_sdk/sw4rm/METRICS_README.md deleted file mode 100644 index 3574de6..0000000 --- a/sdks/py_sdk/sw4rm/METRICS_README.md +++ /dev/null @@ -1,417 +0,0 @@ -# SW4RM Metrics Interface - -This document describes the metrics collection interface for SW4RM, implementing specification §13 (Buffers and Back-Pressure). - -## Overview - -The metrics module provides a pluggable interface for collecting observability data from SW4RM agents and routers. It supports three metric types: - -- **Gauges**: Point-in-time measurements (e.g., current queue depth) -- **Counters**: Monotonically increasing totals (e.g., total message rejects) -- **Histograms**: Value distributions (e.g., latency measurements) - -## Quick Start - -```python -from sw4rm.metrics import ( - MetricName, - InMemoryMetricsCollector, - NoOpMetricsCollector, -) - -# For production (zero overhead) -collector = NoOpMetricsCollector() - -# For testing/debugging -collector = InMemoryMetricsCollector() - -# Record metrics -collector.record_gauge( - MetricName.INBOUND_QUEUE_DEPTH, - value=7, - labels={"agent_id": "agent-frontend"} -) - -collector.record_counter( - MetricName.NACKS_TOTAL, - increment=1, - labels={"agent_id": "scheduler", "error_code": "ack_timeout"} -) - -collector.record_histogram( - MetricName.PROCESS_TIME_SECONDS, - value=0.042, - labels={"agent_id": "worker"} -) - -# Retrieve metrics (InMemoryMetricsCollector only) -metrics = collector.get_metrics() -for metric in metrics: - print(f"{metric.name.value} = {metric.value} {metric.labels}") -``` - -## Standard Metrics - -All metric names are defined in `MetricName` enum and align with SW4RM spec §13: - -### Router Queue Metrics - -- **`INBOUND_QUEUE_DEPTH`** (gauge) - - Current number of messages in agent's inbound queue - - Labels: `agent_id` - - Example: `router.inbound_queue_depth{agent_id="agent-42"} 7` - -- **`INBOUND_QUEUE_CAPACITY`** (gauge) - - Maximum queue capacity for agent - - Labels: `agent_id` - - Example: `router.inbound_queue_capacity{agent_id="agent-42"} 10` - -### Rejection and Error Metrics - -- **`ENQUEUE_REJECTS_TOTAL`** (counter) - - Total messages rejected during enqueue - - Labels: `agent_id`, `reason` - - Reasons: `buffer_full`, `oversize_payload`, `validation_error` - - Example: `router.enqueue_rejects_total{agent_id="agent-42",reason="buffer_full"} 5` - -- **`NACKS_TOTAL`** (counter) - - Total negative acknowledgments sent - - Labels: `agent_id`, `error_code` - - Error codes: `buffer_full`, `ack_timeout`, `permission_denied`, etc. - - Example: `router.nacks_total{agent_id="scheduler",error_code="ack_timeout"} 3` - -### Latency Metrics - -- **`ENQUEUE_LATENCY_SECONDS`** (histogram) - - Time from message receive to enqueue - - Labels: `agent_id` - - Example: `router.enqueue_latency_seconds{agent_id="agent-42"} 0.001` - -- **`DEQUEUE_LATENCY_SECONDS`** (histogram) - - Time from enqueue to agent fetch - - Labels: `agent_id` - - Example: `agent.dequeue_latency_seconds{agent_id="agent-42"} 0.005` - -- **`PROCESS_TIME_SECONDS`** (histogram) - - Service time per message - - Labels: `agent_id` - - Example: `agent.process_time_seconds{agent_id="agent-42"} 0.042` - -### Age Metrics - -- **`OLDEST_ENQUEUED_AGE_SECONDS`** (gauge) - - Age of oldest message in queue - - Labels: `agent_id` - - Example: `router.oldest_enqueued_age_seconds{agent_id="agent-42"} 2.5` - -## Standard Labels - -As specified in the SW4RM spec, the following labels are commonly used: - -- **`agent_id`**: Identifier of the agent (e.g., `"agent-frontend"`, `"scheduler"`) -- **`error_code`**: Error code for failure metrics (e.g., `"buffer_full"`, `"ack_timeout"`) -- **`reason`**: Human-readable reason for rejections (e.g., `"buffer_full"`, `"oversize_payload"`) - -## Implementations - -### NoOpMetricsCollector - -The default implementation that discards all metrics. Use this in production when you don't need metrics or have external instrumentation. - -**Characteristics:** -- Zero overhead -- No memory allocation -- All operations are no-op -- `get_metrics()` always returns empty list - -**Use cases:** -- Production deployments without metrics -- Performance-critical paths -- When using external metrics systems (Prometheus, StatsD) - -```python -from sw4rm.metrics import NoOpMetricsCollector - -collector = NoOpMetricsCollector() -collector.record_gauge(MetricName.INBOUND_QUEUE_DEPTH, 10) # No-op -assert collector.get_metrics() == [] # Always empty -``` - -### InMemoryMetricsCollector - -A simple in-memory implementation for testing and debugging. - -**Characteristics:** -- Stores all metrics in memory -- No aggregation or windowing -- Thread-safe reads (shallow copy on get_metrics) -- Manual cleanup required (call `clear()`) - -**Use cases:** -- Unit testing metrics emission -- Local development and debugging -- Small-scale deployments -- Integration tests - -**Warning:** This collector does not implement retention policies. Long-running processes should periodically call `get_metrics()` and `clear()` to prevent unbounded memory growth. - -```python -from sw4rm.metrics import InMemoryMetricsCollector - -collector = InMemoryMetricsCollector() - -# Record metrics -collector.record_counter(MetricName.NACKS_TOTAL, labels={"agent_id": "test"}) -collector.record_gauge(MetricName.INBOUND_QUEUE_DEPTH, 5) - -# Retrieve and inspect -metrics = collector.get_metrics() -assert len(metrics) == 2 - -# Check memory usage -print(f"Stored metrics: {collector.get_metric_count()}") - -# Clear to prevent memory growth -collector.clear() -assert collector.get_metric_count() == 0 -``` - -## Custom Implementations - -You can create custom collectors by implementing the `MetricsCollector` protocol: - -```python -from typing import Optional -from sw4rm.metrics import MetricName, Metric, MetricsCollector - - -class PrometheusCollector: - """Example: Export metrics to Prometheus.""" - - def __init__(self, registry): - self.registry = registry - self._gauges = {} - self._counters = {} - self._histograms = {} - - def record_gauge( - self, - name: MetricName, - value: float, - labels: Optional[dict[str, str]] = None, - ) -> None: - # Get or create Prometheus gauge - gauge = self._get_or_create_gauge(name) - if labels: - gauge.labels(**labels).set(value) - else: - gauge.set(value) - - def record_counter( - self, - name: MetricName, - increment: float = 1.0, - labels: Optional[dict[str, str]] = None, - ) -> None: - counter = self._get_or_create_counter(name) - if labels: - counter.labels(**labels).inc(increment) - else: - counter.inc(increment) - - def record_histogram( - self, - name: MetricName, - value: float, - labels: Optional[dict[str, str]] = None, - ) -> None: - histogram = self._get_or_create_histogram(name) - if labels: - histogram.labels(**labels).observe(value) - else: - histogram.observe(value) - - def get_metrics(self) -> list[Metric]: - # Prometheus handles storage/export - return [] - - # Helper methods omitted for brevity... -``` - -## Integration Example - -Here's how to integrate metrics into a SW4RM router: - -```python -from sw4rm.metrics import MetricName, InMemoryMetricsCollector -import time - - -class MessageRouter: - def __init__(self, metrics_collector): - self.metrics = metrics_collector - self.queues = {} # agent_id -> queue - - def enqueue_message(self, agent_id: str, message: dict) -> bool: - start_time = time.time() - - queue = self.queues.get(agent_id) - if not queue: - return False - - # Record current queue depth - self.metrics.record_gauge( - MetricName.INBOUND_QUEUE_DEPTH, - value=len(queue), - labels={"agent_id": agent_id} - ) - - # Check capacity - capacity = 10 # from config - if len(queue) >= capacity: - # Record rejection - self.metrics.record_counter( - MetricName.ENQUEUE_REJECTS_TOTAL, - labels={"agent_id": agent_id, "reason": "buffer_full"} - ) - - # Record NACK - self.metrics.record_counter( - MetricName.NACKS_TOTAL, - labels={"agent_id": agent_id, "error_code": "buffer_full"} - ) - return False - - # Enqueue message - queue.append(message) - - # Record enqueue latency - latency = time.time() - start_time - self.metrics.record_histogram( - MetricName.ENQUEUE_LATENCY_SECONDS, - value=latency, - labels={"agent_id": agent_id} - ) - - # Update queue depth after enqueue - self.metrics.record_gauge( - MetricName.INBOUND_QUEUE_DEPTH, - value=len(queue), - labels={"agent_id": agent_id} - ) - - return True - - def process_message(self, agent_id: str) -> None: - queue = self.queues.get(agent_id, []) - if not queue: - return - - start_time = time.time() - message = queue.pop(0) - - # Calculate dequeue latency - enqueue_time = message.get("enqueue_timestamp", start_time) - dequeue_latency = start_time - enqueue_time - self.metrics.record_histogram( - MetricName.DEQUEUE_LATENCY_SECONDS, - value=dequeue_latency, - labels={"agent_id": agent_id} - ) - - # Process message... - # (processing code here) - - # Record processing time - process_time = time.time() - start_time - self.metrics.record_histogram( - MetricName.PROCESS_TIME_SECONDS, - value=process_time, - labels={"agent_id": agent_id} - ) - - # Update queue depth - self.metrics.record_gauge( - MetricName.INBOUND_QUEUE_DEPTH, - value=len(queue), - labels={"agent_id": agent_id} - ) - - -# Usage -collector = InMemoryMetricsCollector() -router = MessageRouter(collector) - -# Later, inspect metrics -metrics = collector.get_metrics() -for m in metrics: - print(f"{m.name.value} = {m.value} @ {m.timestamp}") -``` - -## Testing - -The module includes comprehensive validation tests: - -```bash -cd sdks/py_sdk -python3 test_metrics_validation.py -``` - -Example output: -``` -============================================================ -SW4RM Metrics Module Validation -============================================================ - -Testing MetricName enum... - ✓ INBOUND_QUEUE_DEPTH: router.inbound_queue_depth - ✓ INBOUND_QUEUE_CAPACITY: router.inbound_queue_capacity - ✓ ENQUEUE_REJECTS_TOTAL: router.enqueue_rejects_total - ✓ NACKS_TOTAL: router.nacks_total - ✓ ENQUEUE_LATENCY_SECONDS: router.enqueue_latency_seconds - ✓ DEQUEUE_LATENCY_SECONDS: agent.dequeue_latency_seconds - ✓ PROCESS_TIME_SECONDS: agent.process_time_seconds - ✓ OLDEST_ENQUEUED_AGE_SECONDS: router.oldest_enqueued_age_seconds - All 8 required metrics defined! - -... - -============================================================ -✓ All validation tests passed! -============================================================ -``` - -## Best Practices - -1. **Choose the right collector**: - - Use `NoOpMetricsCollector` in production unless you need metrics - - Use `InMemoryMetricsCollector` for testing and debugging - - Implement custom collectors for production observability (Prometheus, DataDog, etc.) - -2. **Label discipline**: - - Always include `agent_id` label - - Use `error_code` for error metrics - - Use `reason` for human-readable rejection reasons - - Keep label cardinality low to avoid memory issues - -3. **Memory management**: - - With `InMemoryMetricsCollector`, periodically call `clear()` - - Monitor `get_metric_count()` in long-running processes - - Consider time-based or size-based retention policies - -4. **Performance**: - - Metrics recording should be fast (< 1ms) - - Avoid expensive operations in metric recording paths - - Use batching for high-volume metrics if needed - -5. **Testing**: - - Use `InMemoryMetricsCollector` in tests to verify metrics emission - - Assert on metric counts, names, and label values - - Clear collector between test cases - -## See Also - -- [SW4RM Specification §13: Buffers and Back-Pressure](../../documentation/protocol/spec.md#13-buffers-and-back-pressure) -- [SW4RM Python SDK Documentation](../README.md) -- [Activity Buffer Module](activity_buffer.py) -- [Error Mapping Module](error_mapping.py) diff --git a/sdks/py_sdk/sw4rm/__init__.py b/sdks/py_sdk/sw4rm/__init__.py index d0a06c1..c037c76 100644 --- a/sdks/py_sdk/sw4rm/__init__.py +++ b/sdks/py_sdk/sw4rm/__init__.py @@ -52,4 +52,4 @@ "delegation", ] -__version__ = "0.5.0" +__version__ = "0.6.0" diff --git a/sdks/py_sdk/sw4rm/config.py b/sdks/py_sdk/sw4rm/config.py index 4c12714..5078f26 100644 --- a/sdks/py_sdk/sw4rm/config.py +++ b/sdks/py_sdk/sw4rm/config.py @@ -125,6 +125,12 @@ class SW4RMConfig: max_retries: Maximum number of retry attempts for failed operations enable_metrics: Whether to collect and export metrics enable_tracing: Whether to enable distributed tracing + metrics_backend: Metrics backend selector (noop|memory|statsd|datadog) + metrics_host: Hostname for statsd/dogstatsd backend + metrics_port: Port for statsd/dogstatsd backend + metrics_namespace: Optional namespace prefix for metric names + metrics_tags: Labels attached to every metric + metrics_sample_rate: Sample rate for histogram/counter emissions log_level: Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL) feature_flags: Dictionary of feature flag name to value mappings """ @@ -135,6 +141,12 @@ class SW4RMConfig: max_retries: int = 3 enable_metrics: bool = True enable_tracing: bool = True + metrics_backend: str = "noop" + metrics_host: str = "localhost" + metrics_port: int = 8125 + metrics_namespace: str = "sw4rm" + metrics_tags: list[str] = field(default_factory=list) + metrics_sample_rate: float = 1.0 log_level: str = "INFO" feature_flags: dict[str, Any] = field(default_factory=dict) @@ -157,6 +169,12 @@ def from_dict(cls, data: dict[str, Any]) -> "SW4RMConfig": "max_retries", "enable_metrics", "enable_tracing", + "metrics_backend", + "metrics_host", + "metrics_port", + "metrics_namespace", + "metrics_tags", + "metrics_sample_rate", "log_level", "feature_flags", ]: @@ -178,6 +196,12 @@ def to_dict(self) -> dict[str, Any]: "max_retries": self.max_retries, "enable_metrics": self.enable_metrics, "enable_tracing": self.enable_tracing, + "metrics_backend": self.metrics_backend, + "metrics_host": self.metrics_host, + "metrics_port": self.metrics_port, + "metrics_namespace": self.metrics_namespace, + "metrics_tags": self.metrics_tags, + "metrics_sample_rate": self.metrics_sample_rate, "log_level": self.log_level, "feature_flags": self.feature_flags, } @@ -216,6 +240,12 @@ def load_config(path: Optional[str] = None) -> SW4RMConfig: SW4RM_MAX_RETRIES: Maximum retry attempts SW4RM_ENABLE_METRICS: Enable metrics collection (true/false) SW4RM_ENABLE_TRACING: Enable tracing (true/false) + SW4RM_METRICS_BACKEND: Metrics backend (noop|memory|statsd|datadog) + SW4RM_METRICS_HOST: StatsD/DogStatsD host + SW4RM_METRICS_PORT: StatsD/DogStatsD port + SW4RM_METRICS_NAMESPACE: Optional metric namespace prefix + SW4RM_METRICS_TAGS: Comma-separated tags attached to all metrics + SW4RM_METRICS_SAMPLE_RATE: Sample rate between 0.0 and 1.0 SW4RM_LOG_LEVEL: Log level (DEBUG, INFO, WARNING, ERROR, CRITICAL) """ # Start with defaults @@ -305,6 +335,36 @@ def _load_from_env() -> dict[str, Any]: os.environ["SW4RM_ENABLE_TRACING"] ) + if "SW4RM_METRICS_BACKEND" in os.environ: + env_config["metrics_backend"] = _normalize_metrics_backend( + os.environ["SW4RM_METRICS_BACKEND"] + ) + + if "SW4RM_METRICS_HOST" in os.environ: + env_config["metrics_host"] = os.environ["SW4RM_METRICS_HOST"] + + if "SW4RM_METRICS_PORT" in os.environ: + try: + env_config["metrics_port"] = int(os.environ["SW4RM_METRICS_PORT"]) + except ValueError: + logging.warning("Invalid SW4RM_METRICS_PORT value, using default") + + if "SW4RM_METRICS_NAMESPACE" in os.environ: + env_config["metrics_namespace"] = os.environ["SW4RM_METRICS_NAMESPACE"] + + if "SW4RM_METRICS_TAGS" in os.environ: + env_config["metrics_tags"] = _parse_tags( + os.environ["SW4RM_METRICS_TAGS"] + ) + + if "SW4RM_METRICS_SAMPLE_RATE" in os.environ: + try: + env_config["metrics_sample_rate"] = float( + os.environ["SW4RM_METRICS_SAMPLE_RATE"] + ) + except ValueError: + logging.warning("Invalid SW4RM_METRICS_SAMPLE_RATE value, using default") + # Log level if "SW4RM_LOG_LEVEL" in os.environ: env_config["log_level"] = os.environ["SW4RM_LOG_LEVEL"].upper() @@ -324,6 +384,20 @@ def _parse_bool(value: str) -> bool: return value.lower() in ("true", "1", "yes", "on") +def _normalize_metrics_backend(value: str) -> str: + """Normalize metrics backend names to known aliases.""" + normalized = value.strip().lower() + if normalized in {"true", "1", "yes", "on"}: + return "statsd" + return normalized + + +def _parse_tags(raw: str) -> list[str]: + """Parse tags from a comma-separated env value.""" + parts = [part.strip() for part in raw.split(",")] + return [part for part in parts if part] + + # Global singleton config instance _global_config: Optional[SW4RMConfig] = None @@ -370,4 +444,3 @@ def set_config(config: SW4RMConfig) -> None: """ global _global_config _global_config = config - diff --git a/sdks/py_sdk/sw4rm/metrics.py b/sdks/py_sdk/sw4rm/metrics.py index 2416ada..2756e83 100644 --- a/sdks/py_sdk/sw4rm/metrics.py +++ b/sdks/py_sdk/sw4rm/metrics.py @@ -22,19 +22,39 @@ - Counters: Monotonically increasing values (e.g., total rejects) - Histograms: Distribution of values (e.g., latency measurements) -Implementations can choose between NoOpMetricsCollector (default, no overhead) -or InMemoryMetricsCollector (useful for testing and debugging). +Implementations can choose between NoOpMetricsCollector (default, no overhead), +InMemoryMetricsCollector (testing/debugging), and now StatsDMetricsCollector +for production metrics backends (StatsD/Datadog-compatible). """ from __future__ import annotations +import logging +import socket import time -from abc import ABC, abstractmethod from dataclasses import dataclass, field from enum import Enum from typing import Protocol, Optional +_DEFAULT_STATSD_HOST = "localhost" +_DEFAULT_STATSD_PORT = 8125 +_DEFAULT_METRICS_NAMESPACE = "sw4rm" + + +def _safe_tag_value(value: object) -> str: + """Return a value suitable for a Datadog tag.""" + return str(value).replace(":", "\\:").replace("|", "\\|") + + +def _normalize_namespace(namespace: str) -> str: + return namespace.strip().strip(".") + + +def _normalize_backend_name(backend: str) -> str: + return backend.strip().lower() + + class MetricName(Enum): """Standard metric names from SW4RM spec §13. @@ -83,6 +103,28 @@ def __post_init__(self) -> None: raise TypeError(f"value must be numeric, got {type(self.value)}") +def _build_statsd_tags( + labels: Optional[dict[str, str]], + *, + global_tags: Optional[list[str]] = None, +) -> str: + """Build a stable, deterministic Datadog/StatsD tag string.""" + tags: list[str] = [] + if global_tags: + tags.extend(global_tags) + if labels: + for key in sorted(labels): + tags.append(f"{_safe_tag_value(key)}:{_safe_tag_value(labels[key])}") + return f"|#{','.join(tags)}" if tags else "" + + +def _format_metric_name(name: MetricName, namespace: str = "") -> str: + """Build metric name with optional namespace.""" + metric_name = name.value + normalized_namespace = _normalize_namespace(namespace) + return f"{normalized_namespace}.{metric_name}" if normalized_namespace else metric_name + + class MetricsCollector(Protocol): """Protocol defining the interface for metrics collection. @@ -339,3 +381,169 @@ def get_metric_count(self) -> int: Useful for monitoring memory usage and testing. """ return len(self._metrics) + + +class StatsDMetricsCollector: + """Datadog/StatsD-compatible metrics collector. + + This collector streams metrics to a local or remote StatsD UDP endpoint. + It is safe by design: metric send failures are swallowed and do not interrupt + control flow. + + Example: + collector = StatsDMetricsCollector( + host="127.0.0.1", + port=8125, + namespace="sw4rm", + tags=["env:prod"], + ) + collector.record_counter(MetricName.ENQUEUE_REJECTS_TOTAL, labels={"reason": "buffer_full"}) + """ + + def __init__( + self, + host: str = _DEFAULT_STATSD_HOST, + port: int = _DEFAULT_STATSD_PORT, + namespace: str = _DEFAULT_METRICS_NAMESPACE, + tags: Optional[list[str]] = None, + sample_rate: float = 1.0, + *, + socket_factory=socket.socket, + ) -> None: + """Create a datagram-based metrics collector. + + Args: + host: StatsD host + port: StatsD port + namespace: Optional metric namespace prefix + tags: Global tags appended to every metric + sample_rate: Optional DogStatsD sample rate (0.0 < rate <= 1.0) + socket_factory: Optional injection point for testing + """ + self.host = host + self.port = port + self.namespace = _normalize_namespace(namespace) or _DEFAULT_METRICS_NAMESPACE + self.tags = tags or [] + self.sample_rate = sample_rate + + try: + self._socket = socket_factory(socket.AF_INET, socket.SOCK_DGRAM) + self._socket.settimeout(0.1) + except Exception: + logging.debug( + "Failed to create metrics socket, metrics emission will be disabled", + exc_info=True, + ) + self._socket = None + + def _emit(self, name: MetricName, value: float, metric_type: str, labels: Optional[dict[str, str]]) -> None: + """Emit one metric packet.""" + if self._socket is None: + return + + metric = _format_metric_name(name, namespace=self.namespace) + payload = f"{metric}:{value}|{metric_type}{_build_statsd_tags(labels, global_tags=self.tags)}" + if self.sample_rate and 0 < self.sample_rate < 1: + payload += f"|@{self.sample_rate}" + try: + self._socket.sendto(payload.encode("utf-8"), (self.host, self.port)) + except Exception: + logging.debug( + "Failed to send metrics packet to %s:%s", + self.host, + self.port, + exc_info=True, + ) + + def record_gauge( + self, + name: MetricName, + value: float, + labels: Optional[dict[str, str]] = None, + ) -> None: + """Emit a gauge metric.""" + self._emit(name, float(value), "g", labels) + + def record_counter( + self, + name: MetricName, + increment: float = 1.0, + labels: Optional[dict[str, str]] = None, + ) -> None: + """Emit a counter metric.""" + self._emit(name, float(increment), "c", labels) + + def record_histogram( + self, + name: MetricName, + value: float, + labels: Optional[dict[str, str]] = None, + ) -> None: + """Emit a histogram metric.""" + self._emit(name, float(value), "h", labels) + + def get_metrics(self) -> list[Metric]: + """Return empty list (StatsD stores metrics externally).""" + return [] + + def close(self) -> None: + """Close underlying socket when no longer needed.""" + if self._socket is not None: + self._socket.close() + self._socket = None + + +def build_metrics_collector( + *, + enable_metrics: bool = True, + backend: str = "noop", + host: str = _DEFAULT_STATSD_HOST, + port: int = _DEFAULT_STATSD_PORT, + namespace: str = _DEFAULT_METRICS_NAMESPACE, + tags: Optional[list[str]] = None, + sample_rate: float = 1.0, +) -> MetricsCollector: + """Build a metrics collector from explicit configuration. + + Supported backends: + - noop / off / false / disabled + - memory / inmemory + - statsd / datadog / dogstatsd + """ + if not enable_metrics: + return NoOpMetricsCollector() + + backend_name = _normalize_backend_name(backend) + if backend_name in {"noop", "off", "false", "disabled"}: + return NoOpMetricsCollector() + if backend_name in {"memory", "inmemory"}: + return InMemoryMetricsCollector() + if backend_name in {"statsd", "datadog", "dogstatsd"}: + return StatsDMetricsCollector( + host=host, + port=port, + namespace=namespace, + tags=tags, + sample_rate=sample_rate, + ) + + logging.warning("Unknown metrics backend '%s'; defaulting to noop", backend) + return NoOpMetricsCollector() + + +def build_metrics_collector_from_config(config: Optional["SW4RMConfig"] = None) -> MetricsCollector: + """Build a collector from SW4RMConfig.""" + if config is None: + from . import config as _config + + config = _config.get_config() + + return build_metrics_collector( + enable_metrics=config.enable_metrics, + backend=config.metrics_backend, + host=config.metrics_host, + port=config.metrics_port, + namespace=config.metrics_namespace, + tags=config.metrics_tags, + sample_rate=config.metrics_sample_rate, + ) diff --git a/sdks/py_sdk/tests/test_cross_cutting.py b/sdks/py_sdk/tests/test_cross_cutting.py index 2e258db..f73bcf1 100644 --- a/sdks/py_sdk/tests/test_cross_cutting.py +++ b/sdks/py_sdk/tests/test_cross_cutting.py @@ -328,6 +328,12 @@ def test_sw4rm_config_defaults(): assert cfg.max_retries == 3 assert cfg.enable_metrics is True assert cfg.enable_tracing is True + assert cfg.metrics_backend == "noop" + assert cfg.metrics_host == "localhost" + assert cfg.metrics_port == 8125 + assert cfg.metrics_namespace == "sw4rm" + assert cfg.metrics_tags == [] + assert cfg.metrics_sample_rate == 1.0 assert cfg.log_level == "INFO" assert cfg.feature_flags == {} @@ -341,6 +347,12 @@ def test_sw4rm_config_to_from_dict(): max_retries=5, enable_metrics=False, enable_tracing=False, + metrics_backend="statsd", + metrics_host="statsd.internal", + metrics_port=8126, + metrics_namespace="team.alpha", + metrics_tags=["env:ci", "service:router"], + metrics_sample_rate=0.75, log_level="DEBUG", feature_flags={"TEST_FLAG": True}, ) @@ -349,12 +361,22 @@ def test_sw4rm_config_to_from_dict(): data = original.to_dict() assert data["router_addr"] == "test-router:1234" assert data["default_timeout_ms"] == 5000 + assert data["metrics_backend"] == "statsd" + assert data["metrics_port"] == 8126 + assert data["metrics_tags"] == ["env:ci", "service:router"] + assert data["metrics_sample_rate"] == 0.75 assert data["feature_flags"]["TEST_FLAG"] is True # Reconstruct from dict reconstructed = config.SW4RMConfig.from_dict(data) assert reconstructed.router_addr == original.router_addr assert reconstructed.default_timeout_ms == original.default_timeout_ms + assert reconstructed.metrics_backend == original.metrics_backend + assert reconstructed.metrics_host == original.metrics_host + assert reconstructed.metrics_port == original.metrics_port + assert reconstructed.metrics_namespace == original.metrics_namespace + assert reconstructed.metrics_tags == original.metrics_tags + assert reconstructed.metrics_sample_rate == original.metrics_sample_rate assert reconstructed.feature_flags == original.feature_flags @@ -366,6 +388,12 @@ def test_load_config_from_env(monkeypatch): monkeypatch.setenv("SW4RM_ENABLE_METRICS", "false") monkeypatch.setenv("SW4RM_ENABLE_TRACING", "true") monkeypatch.setenv("SW4RM_LOG_LEVEL", "debug") + monkeypatch.setenv("SW4RM_METRICS_BACKEND", "statsd") + monkeypatch.setenv("SW4RM_METRICS_HOST", "statsd.internal") + monkeypatch.setenv("SW4RM_METRICS_PORT", "9999") + monkeypatch.setenv("SW4RM_METRICS_NAMESPACE", "team.alpha") + monkeypatch.setenv("SW4RM_METRICS_TAGS", "env:ci,team:alpha") + monkeypatch.setenv("SW4RM_METRICS_SAMPLE_RATE", "0.5") cfg = config.load_config() @@ -374,6 +402,12 @@ def test_load_config_from_env(monkeypatch): assert cfg.max_retries == 10 assert cfg.enable_metrics is False assert cfg.enable_tracing is True + assert cfg.metrics_backend == "statsd" + assert cfg.metrics_host == "statsd.internal" + assert cfg.metrics_port == 9999 + assert cfg.metrics_namespace == "team.alpha" + assert cfg.metrics_tags == ["env:ci", "team:alpha"] + assert cfg.metrics_sample_rate == 0.5 assert cfg.log_level == "DEBUG" diff --git a/sdks/py_sdk/tests/test_metrics.py b/sdks/py_sdk/tests/test_metrics.py new file mode 100644 index 0000000..5e092f4 --- /dev/null +++ b/sdks/py_sdk/tests/test_metrics.py @@ -0,0 +1,124 @@ +"""Tests for Python SDK metrics collector implementations.""" + +from __future__ import annotations + +from sw4rm import config as config_module +from sw4rm.metrics import ( + InMemoryMetricsCollector, + MetricName, + NoOpMetricsCollector, + StatsDMetricsCollector, + build_metrics_collector, + build_metrics_collector_from_config, +) + + +class _CapturingSocket: + def __init__(self) -> None: + self.sent: list[tuple[bytes, tuple[str, int]]] = [] + + def settimeout(self, *_args, **_kwargs) -> None: + """No-op for tests.""" + + def sendto(self, payload: bytes, destination: tuple[str, int]) -> int: + self.sent.append((payload, destination)) + return len(payload) + + def close(self) -> None: + """No-op close.""" + + +def test_build_metrics_collector_falls_back_to_noop_for_unknown_backend() -> None: + """Unknown backend values should fall back to no-op collector.""" + collector = build_metrics_collector(enable_metrics=True, backend="mystery") + assert isinstance(collector, NoOpMetricsCollector) + + +def test_build_metrics_collector_can_disable_with_flag() -> None: + """Disabling metrics should return no-op collector.""" + collector = build_metrics_collector(enable_metrics=False, backend="statsd") + assert isinstance(collector, NoOpMetricsCollector) + + +def test_statsd_collector_formats_payload_with_namespace_and_tags() -> None: + """StatsD collector should emit datagram payloads with namespace and labels.""" + fake_socket = _CapturingSocket() + + collector = StatsDMetricsCollector( + host="statsd.internal", + port=8126, + namespace="team.alpha", + tags=["global:prod"], + socket_factory=lambda *_args, **_kwargs: fake_socket, + ) + + collector.record_gauge( + MetricName.INBOUND_QUEUE_DEPTH, + value=7, + labels={"agent_id": "agent-1", "error_code": "none"}, + ) + + collector.record_counter( + MetricName.ENQUEUE_REJECTS_TOTAL, + increment=3, + labels={"reason": "buffer_full"}, + ) + + collector.record_histogram( + MetricName.DEQUEUE_LATENCY_SECONDS, + value=0.125, + labels={"agent_id": "agent-2"}, + ) + + assert len(fake_socket.sent) == 3 + assert fake_socket.sent[0][1] == ("statsd.internal", 8126) + assert fake_socket.sent[0][0].decode() == ( + "team.alpha.router.inbound_queue_depth:7.0|g|#global:prod," + "agent_id:agent-1,error_code:none" + ) + assert fake_socket.sent[1][0].decode() == ( + "team.alpha.router.enqueue_rejects_total:3.0|c|#global:prod,reason:buffer_full" + ) + assert fake_socket.sent[2][0].decode() == ( + "team.alpha.agent.dequeue_latency_seconds:0.125|h|#global:prod,agent_id:agent-2" + ) + + +def test_statsd_collector_appends_sample_rate_when_configured() -> None: + """Sample rate should be appended to emitted metrics packets.""" + fake_socket = _CapturingSocket() + + collector = StatsDMetricsCollector( + sample_rate=0.25, + socket_factory=lambda *_args, **_kwargs: fake_socket, + ) + + collector.record_counter(MetricName.NACKS_TOTAL, increment=1) + assert fake_socket.sent == [ + ( + b"sw4rm.router.nacks_total:1.0|c|@0.25", + ("localhost", 8125), + ) + ] + + +def test_build_metrics_collector_from_config_supports_memory_backend() -> None: + """Config-backed factory should honor in-memory backend.""" + cfg = config_module.SW4RMConfig( + enable_metrics=True, + metrics_backend="memory", + ) + collector = build_metrics_collector_from_config(cfg) + assert isinstance(collector, InMemoryMetricsCollector) + + +def test_memory_backend_config_still_supported() -> None: + """Memory backend should produce in-memory collector.""" + collector = build_metrics_collector( + enable_metrics=True, + backend="memory", + ) + assert isinstance(collector, InMemoryMetricsCollector) + + collector.record_gauge(MetricName.INBOUND_QUEUE_CAPACITY, value=10) + assert collector.get_metric_count() == 1 diff --git a/sdks/rust_sdk/Cargo.lock b/sdks/rust_sdk/Cargo.lock index 4734a1e..c710e57 100644 --- a/sdks/rust_sdk/Cargo.lock +++ b/sdks/rust_sdk/Cargo.lock @@ -51,9 +51,9 @@ checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] name = "anyhow" -version = "1.0.101" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "async-broadcast" @@ -79,9 +79,9 @@ dependencies = [ [[package]] name = "async-executor" -version = "1.13.3" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497c00e0fd83a72a79a39fcbd8e3e2f055d6f6c7e025f3b3d91f4f8e76527fb8" +checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" dependencies = [ "async-task", "concurrent-queue", @@ -136,7 +136,7 @@ dependencies = [ "futures-lite 2.6.1", "parking", "polling 3.11.0", - "rustix 1.1.3", + "rustix 1.1.4", "slab", "windows-sys 0.61.2", ] @@ -186,7 +186,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -201,7 +201,7 @@ dependencies = [ "cfg-if", "futures-core", "futures-io", - "rustix 1.1.3", + "rustix 1.1.4", "signal-hook-registry", "slab", "windows-sys 0.61.2", @@ -226,7 +226,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -243,7 +243,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -319,9 +319,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" [[package]] name = "block-buffer" @@ -356,9 +356,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.1" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "byteorder" @@ -389,9 +389,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.55" +version = "1.2.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" +checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" dependencies = [ "find-msvc-tools", "shlex", @@ -405,9 +405,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "chrono" -version = "0.4.43" +version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" dependencies = [ "iana-time-zone", "js-sys", @@ -456,18 +456,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.58" +version = "4.5.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63be97961acde393029492ce0be7a1af7e323e6bae9511ebfac33751be5e6806" +checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.58" +version = "4.5.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f13174bda5dfd69d7e947827e5af4b0f2f94a4a3ee92912fba07a66150f21e2" +checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" dependencies = [ "anstyle", "clap_lex", @@ -676,7 +676,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -718,7 +718,7 @@ checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -856,9 +856,9 @@ checksum = "28dd6caf6059519a65843af8fe2a3ae298b14b80179855aeb4adc2c1934ee619" [[package]] name = "futures" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" dependencies = [ "futures-channel", "futures-core", @@ -871,9 +871,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", "futures-sink", @@ -881,15 +881,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-executor" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" dependencies = [ "futures-core", "futures-task", @@ -898,9 +898,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-lite" @@ -932,32 +932,32 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-channel", "futures-core", @@ -967,7 +967,6 @@ dependencies = [ "futures-task", "memchr", "pin-project-lite", - "pin-utils", "slab", ] @@ -994,21 +993,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" -dependencies = [ - "cfg-if", - "libc", - "r-efi", - "wasip2", -] - -[[package]] -name = "getrandom" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ "cfg-if", "libc", @@ -1246,7 +1233,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.2", + "socket2 0.6.3", "system-configuration", "tokio", "tower-service", @@ -1491,9 +1478,9 @@ checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "js-sys" -version = "0.3.85" +version = "0.3.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" dependencies = [ "once_cell", "wasm-bindgen", @@ -1527,17 +1514,16 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.181" +version = "0.2.182" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "459427e2af2b9c839b132acb702a1c654d95e10f8c326bfc2ad11310e458b1c5" +checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" [[package]] name = "libredox" -version = "0.1.12" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" +checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a" dependencies = [ - "bitflags 2.10.0", "libc", ] @@ -1547,7 +1533,7 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "761e49ec5fd8a5a463f9b84e877c373d888935b71c6be78f3767fe2ae6bed18e" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "libc", ] @@ -1565,9 +1551,9 @@ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "linux-raw-sys" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" @@ -1671,7 +1657,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -1692,7 +1678,7 @@ dependencies = [ "openssl-probe", "openssl-sys", "schannel", - "security-framework 3.5.1", + "security-framework 3.7.0", "security-framework-sys", "tempfile", ] @@ -1809,7 +1795,7 @@ version = "0.10.75" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "cfg-if", "foreign-types", "libc", @@ -1826,7 +1812,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -1910,29 +1896,29 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.10" +version = "1.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.10" +version = "1.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] name = "pin-project-lite" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pin-utils" @@ -1942,9 +1928,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "piper" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" dependencies = [ "atomic-waker", "fastrand 2.3.0", @@ -2011,7 +1997,7 @@ dependencies = [ "concurrent-queue", "hermit-abi 0.5.2", "pin-project-lite", - "rustix 1.1.3", + "rustix 1.1.4", "windows-sys 0.61.2", ] @@ -2066,7 +2052,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -2114,7 +2100,7 @@ dependencies = [ "prost", "prost-types", "regex", - "syn 2.0.114", + "syn 2.0.117", "tempfile", ] @@ -2128,7 +2114,7 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -2206,18 +2192,18 @@ checksum = "95067976aca6421a523e491fce939a3e65249bac4b977adee0ee9771568e8aa3" [[package]] name = "quote" -version = "1.0.44" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] [[package]] name = "r-efi" -version = "5.3.0" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "rand" @@ -2275,7 +2261,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", ] [[package]] @@ -2314,9 +2300,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.9" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "reqwest" @@ -2395,7 +2381,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "errno", "libc", "linux-raw-sys 0.4.15", @@ -2404,22 +2390,22 @@ dependencies = [ [[package]] name = "rustix" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "errno", "libc", - "linux-raw-sys 0.11.0", + "linux-raw-sys 0.12.1", "windows-sys 0.61.2", ] [[package]] name = "rustls" -version = "0.23.36" +version = "0.23.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" +checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" dependencies = [ "log", "once_cell", @@ -2520,7 +2506,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "core-foundation 0.9.4", "core-foundation-sys", "libc", @@ -2529,11 +2515,11 @@ dependencies = [ [[package]] name = "security-framework" -version = "3.5.1" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -2542,9 +2528,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.15.0" +version = "2.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" dependencies = [ "core-foundation-sys", "libc", @@ -2583,7 +2569,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -2607,7 +2593,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -2709,12 +2695,12 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -2737,7 +2723,7 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "sw4rm-sdk" -version = "0.5.0" +version = "0.6.0" dependencies = [ "anyhow", "async-trait", @@ -2788,9 +2774,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.114" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -2814,7 +2800,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -2823,7 +2809,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -2840,14 +2826,14 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.25.0" +version = "3.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1" +checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0" dependencies = [ "fastrand 2.3.0", - "getrandom 0.4.1", + "getrandom 0.4.2", "once_cell", - "rustix 1.1.3", + "rustix 1.1.4", "windows-sys 0.61.2", ] @@ -2874,7 +2860,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -2908,9 +2894,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.49.0" +version = "1.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" dependencies = [ "bytes", "libc", @@ -2918,20 +2904,20 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.6.2", + "socket2 0.6.3", "tokio-macros", "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "2.6.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -3050,7 +3036,7 @@ dependencies = [ "prost-build", "prost-types", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -3094,7 +3080,7 @@ version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "bytes", "futures-util", "http", @@ -3138,7 +3124,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -3190,20 +3176,20 @@ checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "uds_windows" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" +checksum = "51b70b87d15e91f553711b40df3048faf27a7a04e01e0ddc0cf9309f0af7c2ca" dependencies = [ "memoffset 0.9.1", "tempfile", - "winapi", + "windows-sys 0.61.2", ] [[package]] name = "unicode-ident" -version = "1.0.23" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-xid" @@ -3237,11 +3223,11 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.20.0" +version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" +checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" dependencies = [ - "getrandom 0.3.4", + "getrandom 0.4.2", "js-sys", "serde_core", "wasm-bindgen", @@ -3316,9 +3302,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.108" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" dependencies = [ "cfg-if", "once_cell", @@ -3329,9 +3315,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.58" +version = "0.4.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" +checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8" dependencies = [ "cfg-if", "futures-util", @@ -3343,9 +3329,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.108" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3353,22 +3339,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.108" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.108" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" dependencies = [ "unicode-ident", ] @@ -3414,7 +3400,7 @@ version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "hashbrown 0.15.5", "indexmap 2.13.0", "semver", @@ -3422,9 +3408,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.85" +version = "0.3.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" +checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9" dependencies = [ "js-sys", "wasm-bindgen", @@ -3482,7 +3468,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -3493,7 +3479,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -3558,15 +3544,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.5", -] - [[package]] name = "windows-sys" version = "0.61.2" @@ -3600,30 +3577,13 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", + "windows_i686_gnullvm", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", -] - [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -3636,12 +3596,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -3654,12 +3608,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -3672,24 +3620,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -3702,12 +3638,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -3720,12 +3650,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -3738,12 +3662,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -3756,12 +3674,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - [[package]] name = "winnow" version = "0.5.40" @@ -3801,7 +3713,7 @@ dependencies = [ "heck", "indexmap 2.13.0", "prettyplease", - "syn 2.0.114", + "syn 2.0.117", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -3817,7 +3729,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -3829,7 +3741,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags 2.10.0", + "bitflags 2.11.0", "indexmap 2.13.0", "log", "serde", @@ -3894,7 +3806,7 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", "synstructure", ] @@ -3966,22 +3878,22 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.39" +version = "0.8.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" +checksum = "a789c6e490b576db9f7e6b6d661bcc9799f7c0ac8352f56ea20193b2681532e5" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.39" +version = "0.8.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" +checksum = "f65c489a7071a749c849713807783f70672b28094011623e200cb86dcb835953" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -4001,7 +3913,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", "synstructure", ] @@ -4041,14 +3953,14 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] name = "zmij" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4de98dfa5d5b7fef4ee834d0073d560c9ca7b6c46a71d058c48db7960f8cfaf7" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" [[package]] name = "zvariant" diff --git a/sdks/rust_sdk/Cargo.toml b/sdks/rust_sdk/Cargo.toml index c387abd..fe0df89 100644 --- a/sdks/rust_sdk/Cargo.toml +++ b/sdks/rust_sdk/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sw4rm-sdk" -version = "0.5.0" +version = "0.6.0" edition = "2021" description = "SW4RM Agentic Protocol - Reference Rust SDK" license = "Apache-2.0" diff --git a/sdks/rust_sdk/README.md b/sdks/rust_sdk/README.md index 05b1a62..498df9c 100644 --- a/sdks/rust_sdk/README.md +++ b/sdks/rust_sdk/README.md @@ -20,7 +20,7 @@ Add to your `Cargo.toml`: ```toml [dependencies] -sw4rm-sdk = "0.5.0" +sw4rm-sdk = "0.6.0" tokio = { version = "1.0", features = ["full"] } ``` @@ -224,7 +224,7 @@ Supported backends: **Groq** (OpenAI-compatible), **Anthropic** (Claude), and a ```toml [dependencies] -sw4rm-sdk = { version = "0.5.0", features = ["llm"] } +sw4rm-sdk = { version = "0.6.0", features = ["llm"] } tokio = { version = "1.0", features = ["full"] } ```