Skip to content

feat(runtime): add freshness-gated persisted run state gauges (issue #331) - #361

Merged
yuluo-yx merged 1 commit into
agentic-ai-java:mainfrom
konlue:feat/issue331-persisted-state-freshness-metrics
Nov 8, 2026
Merged

yuluo-yx merged 1 commit into
agentic-ai-java:mainfrom
konlue:feat/issue331-persisted-state-freshness-metrics

Conversation

@konlue

@konlue konlue commented Nov 8, 2026

Copy link
Copy Markdown
Contributor

Describe what this PR does / why we need it

Issue #331 asks for persisted-run state gauges with collection freshness: an authorized read of the persisted Run/Task stock, delivered together with the time it was collected and an explicit staleness marker. This PR adds that as a contract plus a reference implementation plus contract tests, entirely inside argi-runtime-core's new run/metrics/ package — no micrometer, no JDBC, no Redis, no table scans in the core. Storage implementations stay on the adapter side, per the persistence-ownership boundary.

Three kinds of measurement, kept apart

The package javadoc states the separation the issue requires. A process transition counter counts what this JVM did and dies with the process (issue #317's scope). An audit history is the persisted journal of every transition. The persisted state gauge delivered here is the current stock per RunState as read from the persistence — and a freshly built aggregator has no snapshot at all (current() is empty until the first successful collection), so a restarted process can never present local counters or zeros as persisted stock.

The reading contract

PersistedRunStateReader answers one PersistedRunStateQuery — a RunNamespace scope plus the states of interest — with per-state counts computed by the adapter's own storage. The contract is aggregate-only: implementations never hand out row lists for the caller to count, and the core never opens a database connection. The namespace is the aggregation scope; run and tenant identifiers are scope parameters, never metric dimensions, keeping gauge cardinality bounded per issue #318.

Freshness gating

FreshnessGatedRunStateAggregator wraps any reader. A successful refresh() advances the snapshot and its collectedAt. A failed one keeps the last known values and marks them stale since the first failure of the outage — the counts are the previous ones, never fabricated zeros, so a collection outage cannot disguise a real backlog as an empty store. A first failure with no previous snapshot propagates instead of inventing an empty one.

Multi-replica semantics

Two instances sharing one store observe the same snapshot: summing them would double every run, and the package javadoc plus a test state that a shared-store global view must not be summed. A sharded deployment instead lets each instance own its namespace, and the consumer declares the cross-namespace sum as the global aggregate — also covered by a test.

Vocabulary note

The neutral RunState has no CREATED; the issue's "CREATED → RUNNING → terminal" maps to PENDING → RUNNING → terminal, and the lifecycle test follows the real vocabulary.

Testing

PersistedRunStateReaderContractTest is abstract so durable adapters inherit the same four cases with their own reader (lifecycle stock, state-subset filtering, empty-store zeros, namespace as scope); MemoryRunStoreReaderTest runs it against the reference store. FreshnessGatedRunStateAggregatorTest adds 8 clock-driven cases covering the freshness and multi-replica semantics above. The full argi-runtime-core module is 139/139 green, checkstyle clean, and the downstream framework regression shows zero failures.

Fixes #331

…gentic-ai-java#331)

- PersistedRunStateReader: the bounded aggregate read contract — one query
  (namespace scope plus states of interest) answered with per-state counts
  computed by the adapter's own storage; the core never scans a table and never
  opens a database connection
- PersistedRunStateSnapshot: per-state stock, collectedAt advanced only by a
  successful collection, and an explicit stale marker instead of fabricated zeros
- FreshnessGatedRunStateAggregator: success advances the snapshot, failure keeps
  the last known values and marks them stale since the first outage; a fresh
  process has no snapshot at all, so a restart cannot present process counters
  or zeros as persisted stock
- MemoryRunStore doubles as the reference reader; multi-replica semantics are
  documented and tested: shared-store instances observe one snapshot (never
  summed), sharded instances own their namespace and the consumer declares the
  cross-namespace sum
- labels stay bounded: RunState values only, run/tenant identifiers are scope
  parameters per issue agentic-ai-java#318
@yuluo-yx
yuluo-yx merged commit 9b37ba4 into agentic-ai-java:main Nov 8, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Task][P2] 为持久运行提供带采集新鲜度的状态聚合指标

2 participants