Repository navigation
Releases: JieNEUer/ActionLens
Release list
v1.5.4
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
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()andput_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-inBudgetPolicystate together after the host has definitively
completed a durable run. - Disabled unnecessary run accounting when
max_bytes_per_runis not enabled,
preventing default long-running stores from retaining unused run entries. - Made
OpenTelemetrySink.error_countthread-safe.
Compatibility and Upgrade Notes
- Python support remains Python 3.10+.
- For direct
FileArtifactStoreuse withmax_bytes_per_run, writes must now
include a non-emptymetadata["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
v1.5.2 — 2026-07-31
Fixes
-
invoke_manynow preserves session context for parallel SAFE read tools.
The caller'sToolCallContext(session_id, run_id, budget identity) is explicitly passed through__al_ctxtoThreadPoolExecutorworkers. Previously, parallel tools fell back tosession_id="generated", breaking trajectory correlation and budget tracking. -
Old ledger paths (
MemoryLedger,SQLiteLedger) now detect idempotency conflicts.
begin()accepts optionalargs_hash/tool_schema_hashparameters 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 viaALTER TABLEmigration. -
OpenTelemetrySinkis now thread‑safe.
Added athreading.Lockaround the internal_spansdictionary to prevent span loss or leaks under concurrentemit()calls. -
WebhookSinkcaches DNS resolution between emits.
A TTL‑backed cache (dns_cache_ttl_sec, default 60s) avoids asocket.getaddrinfocall 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_runsto 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, andclose()lifecycle.
Tests
173 passed, 9 skipped (14 new regression tests covering the fixes above).
v1.5.1
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_bytesis deprecated and must matchmax_inline_byteswhen both are supplied. Supplymax_inline_bytesalone instead.IdempotencyPolicy.CACHE_READnow requiresrisk=READ; a validator rejects misconfigured specs at registration time. The field is no longer a silent alias forAUTO_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_itemsnow bounds top-level collection items independently ofmax_inline_bytes.summary_fieldsnow controls which dict keys appear inStructuredToolOutput.result_summary._preview_valuenow acceptslimitandmax_itemsfrom the configured policy instead of a hardcoded 240 characters._summary()respectsArtifactPolicy.raw_modeand a newsummary_includes_contenttoggle — whenraw_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 —
_LeaseHeartbeatdaemon thread now refreshes the lease during long-running tool execution. Previously theRepository.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 bareint. Theexportcommand's_event_sort_keyhandles both legacy and v1.5 formats transparently, so mixed-trajectory exports remain deterministic. ActionLens._ticketsLRU — The ticket-id-to-key cache now has a configurableticket_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.typedsentinel added (MANIFEST.in+pyproject.tomlpackage-data)- Smoke-test version check reads from
pyproject.tomldynamically instead of hardcoding setuptoolsversion constraint relaxed back to>=68for 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 |