Skip to content

Releases: JieNEUer/ActionLens

v1.5.4

Choose a tag to compare

@JieNEUer JieNEUer released this 01 Oct 17:10

ActionLens v1.5.4

Fixed

  • Harden approval, idempotency, tenant isolation and cancellation handling
  • Secure artifact access and enforce output limits
  • Improve durable audit delivery and evidence verification
  • Add regression coverage and strengthen CI checks

v1.5.3

Choose a tag to compare

@JieNEUer JieNEUer released this 06 Aug 09:47

ActionLens v1.5.3

v1.5.3 is a reliability release focused on cross-platform artifact storage,
run-scoped resource lifecycle, and concurrent observability behavior.

Fixed

  • Fixed a Windows race when concurrent workers persist identical
    content-addressed artifacts. put() and put_stream() now treat a
    destination conflict as successful deduplication only after confirming that
    the peer-created target is visible; unrelated permission failures still
    propagate.
  • Serialized ordinary artifact sidecar initialization with the existing
    metadata lease. Concurrent deduplicated writes no longer race to replace the
    same sidecar or overwrite first-writer metadata.
  • Added FileArtifactStore.reset_run(run_id) to release completed run byte
    accounting.
  • Added ActionLens.reset_run(run_id), which resets artifact accounting and
    built-in BudgetPolicy state together after the host has definitively
    completed a durable run.
  • Disabled unnecessary run accounting when max_bytes_per_run is not enabled,
    preventing default long-running stores from retaining unused run entries.
  • Made OpenTelemetrySink.error_count thread-safe.

Compatibility and Upgrade Notes

  • Python support remains Python 3.10+.
  • For direct FileArtifactStore use with max_bytes_per_run, writes must now
    include a non-empty metadata["run_id"].
  • Do not call reset_run() for a workflow that may still resume; run budgets
    are intentionally cumulative for the active durable run.
  • OutputPolicy settings, except trajectory-only
    include_raw_in_trajectory, remain part of tool schema conflict detection.
    Deployments that reuse idempotency keys must keep these settings aligned.

CI and Validation

  • Added a Windows + Python 3.12 artifact CI job covering durable-media and
    streaming artifact paths.
  • Added deterministic regression coverage for Windows-style destination
    conflicts, concurrent sidecar initialization, streaming persistence, run
    budget lifecycle, and concurrent OpenTelemetry failures.
  • Verified with Ruff, compileall, full pytest (180 passed, 9 skipped),
    package build, wheel import, CLI smoke test, benchmark smoke, and soak smoke.

v1.5.2

Choose a tag to compare

@JieNEUer JieNEUer released this 31 Jul 10:23

v1.5.2 — 2026-07-31

Fixes

  • invoke_many now preserves session context for parallel SAFE read tools.
    The caller's ToolCallContext (session_id, run_id, budget identity) is explicitly passed through __al_ctx to ThreadPoolExecutor workers. Previously, parallel tools fell back to session_id="generated", breaking trajectory correlation and budget tracking.

  • Old ledger paths (MemoryLedger, SQLiteLedger) now detect idempotency conflicts.
    begin() accepts optional args_hash/tool_schema_hash parameters and returns "conflict" when a non‑empty hash differs from the existing record. The runtime passes these fields on the legacy path and handles the conflict response. The SQLite schema is extended via ALTER TABLE migration.

  • OpenTelemetrySink is now thread‑safe.
    Added a threading.Lock around the internal _spans dictionary to prevent span loss or leaks under concurrent emit() calls.

  • WebhookSink caches DNS resolution between emits.
    A TTL‑backed cache (dns_cache_ttl_sec, default 60s) avoids a socket.getaddrinfo call on every request, reducing latency during outbox batch delivery. The cache is refreshed on expiry to mitigate DNS rebinding.

Chores

  • Renamed BudgetPolicy._evict_completed_runs → _evict_oldest_runs to accurately reflect the method's behavior (LRU eviction of the oldest run, not completion‑aware eviction).

  • Updated all three examples (temporal_integration_example.py, dbos_integration_example.py, provider_reconciliation_cookbook.md) to showcase v1.5 features: approval_resolver, child_context(), CACHE_READ, and close() lifecycle.

Tests

173 passed, 9 skipped (14 new regression tests covering the fixes above).

v1.5.1

Choose a tag to compare

@JieNEUer JieNEUer released this 28 Jul 04:44

ActionLens v1.5.1

Release date: 2026-07-28


Overview

v1.5 is a major diagnostic-driven release addressing all P0 and most P1 issues identified in the v1.4 diagnostic audit. The core semantics around timeouts, resource management, output policy, and type safety have been hardened. Three strategic capabilities that were previously missing — MCP governance, artifact navigation, and streaming tools — are now available.

150 tests pass (up from 129 in v1.4.1), 9 skipped.


Breaking Changes

OutputPolicy fields rename

  • artifact_threshold_bytes is deprecated and must match max_inline_bytes when both are supplied. Supply max_inline_bytes alone instead.
  • IdempotencyPolicy.CACHE_READ now requires risk=READ; a validator rejects misconfigured specs at registration time. The field is no longer a silent alias for AUTO_HASH.

Sequence type change

TrajectoryEvent.sequence changed from int to str | int. New events use a process-unique lexical string key (<hostname>-<pid>-<uid>-<counter>). Integer values from v1.x event files remain readable. The export and summary CLIs handle both formats transparently.

RemoteToolRunner ownership transfers

RemoteToolRunner and related types (RemoteJobRef, CancelResult, etc.) are no longer re-exported from actionlens.remote — use actionlens directly.


New Features

MCP Governance Proxy (integrations/mcp.py)

MCPGovernanceProxy lets ActionLens govern any MCP server's tools/call without owning MCP transport. Every registered remote tool becomes a normal governed tool — idempotency, approval, leases, output policy, and trajectory capture all apply before the request reaches the MCP server.

from actionlens import ActionLens, MCPGovernanceProxy
from mcp import ClientSession

lens = ActionLens(project="my-project")
session = ClientSession(...)
proxy = MCPGovernanceProxy(lens, session)
proxy.register_tool("fetch", risk="EXTERNAL_IO", approval_required=True)

RemoteToolAdapter (integrations/remote.py)

RemoteToolAdapter bridges the existing RemoteToolRunner SPI into the governed tool path. This closes a long-standing gap where the SPI was defined but never wired into ToolRuntime.

Artifact Navigation

Two new host-callable methods enable model-visible pagination and search over artifacts:

  • ActionLens.read_artifact_page(ref, offset, limit) — bounded UTF-8 page reader (max 64 KB per call).
  • ActionLens.grep_artifact(ref, needle, ...) — literal search over a bounded artifact window with automatic line extraction and inline secret redaction.

Streaming Tool Support

Sync Generator and async AsyncGenerator functions decorated with @lens.tool are now supported. The generator's output is collected into a chunked artifact; the model sees the last streaming_tail_lines of output (configurable via OutputPolicy.streaming_tail_lines).

@lens.tool(risk="EXTERNAL_IO", output=OutputPolicy(streaming_tail_lines=20))
def run_build():
    yield "Building..."
    ...
    yield "Build complete."

Synchronous Approval Resolver

ActionLens(approval_resolver=...) accepts a callable (ApprovalTicket, ToolCallContext) → ApprovalResolution that runs synchronously in the governance hot path. The resolver returns APPROVE, DENY, or PENDING — enabling lightweight in-session approval without the 24-hour ticket TTL model.

Child Context API

ActionLens.child_context(parent, ...) and ActionLens.child_session(parent, ...) create properly linked ToolCallContext instances with parent_call_id tracking. This gives sub-agent orchestration a way to build call trees without ActionLens owning the scheduling logic.


Bug Fixes (P0)

Timeout → UNCERTAIN for mutation/destructive tools

TimeoutError is now treated as UNCERTAIN (not FAILED_RETRYABLE) when the tool's risk level is MUTATION or DESTRUCTIVE. This closes the most critical semantic gap: a payment POST that times out will no longer trigger automatic retry. The fix lives in _handle_exception (runtime.py) and respects spec.risk — READ tools still get automatic retry on timeout.

BudgetPolicy LRU eviction

BudgetPolicy._run_counts and _tool_counts are now OrderedDict with configurable max_tracked_runs (default 10,000). The oldest runs are evicted when the limit is exceeded. A reset_run(project, run_id) API is also available for explicit cleanup.

OutputPolicy dead configs eliminated

  • max_inline_items now bounds top-level collection items independently of max_inline_bytes.
  • summary_fields now controls which dict keys appear in StructuredToolOutput.result_summary.
  • _preview_value now accepts limit and max_items from the configured policy instead of a hardcoded 240 characters.
  • _summary() respects ArtifactPolicy.raw_mode and a new summary_includes_content toggle — when raw_mode="deny" or encryption is active, only shape/hash metadata is shown.

UTF-8 byte-aware truncation

All inline previews and summaries now truncate by UTF-8 byte count (via _truncate_utf8) rather than character count, matching the max_inline_bytes unit.

ValidationError classification fixed

ValidationError is now checked before ValueError in classify_exception. Previously the Pydantic-specific branch was unreachable (both Python and Pydantic confirm ValidationError is a ValueError subclass).

Tool name conflict detection

ActionLens.wrap() now raises ValueError when a tool name is already registered, instead of silently overwriting.

PEP 561 py.typed marker

A py.typed sentinel file is now included in both sdist and wheel, so downstream mypy/pyright can use the package's type annotations.

Missing guard: run_sync_in_thread + MUTATION warning

When timeout_sec and run_sync_in_thread are combined with a MUTATION/DESTRUCTIVE risk tool, a UserWarning is emitted at decoration time noting that thread-mode timeouts cannot stop the running business function.


Bug Fixes (P1)

  • Lease heartbeat wired — _LeaseHeartbeat daemon thread now refreshes the lease during long-running tool execution. Previously the Repository.heartbeat() interface was defined but never invoked at runtime, meaning long tool executions would inevitably expire their lease.
  • Cross-process sequence keys — Event sequence numbers now use <hostname>-<pid>-<uid>-<counter> instead of a bare int. The export command's _event_sort_key handles both legacy and v1.5 formats transparently, so mixed-trajectory exports remain deterministic.
  • ActionLens._tickets LRU — The ticket-id-to-key cache now has a configurable ticket_cache_max_entries (default 10,000) with LRU eviction.
  • ActionLens.close() — Graceful shutdown now cancels the sync thread pool and stops the outbox dispatcher.

CI & Packaging

  • py.typed sentinel added (MANIFEST.in + pyproject.toml package-data)
  • Smoke-test version check reads from pyproject.toml dynamically instead of hardcoding
  • setuptools version constraint relaxed back to >=68 for CI isolated build environment compatibility
  • Version bumped to 1.5.1

Full Changelog

Commit Description
6b3dbbb v1.5.0: All diagnostic P0/P1 fixes (setuptools>=77 broke CI)
41050dc Fix CI build: revert setuptools>=77 to >=68
eaed1bd Fix CI: smoke-test version from pyproject.toml dynamically
b38c5dc v1.5.1 bump