Repository navigation
v0.28.0
[0.28.0] - 2026-10-30
Fixed
- Every long-running service in the MetricsBench stack runs with
nofile
524288 soft and hard (issue #2821). That is Docker's hard default, which
the Go engines already raise their soft limit to themselves; ravel-server
and the object store do not, and would otherwise run at the 1,024 soft
default. - A cold
/api/v1/serieslisting no longer runs the host out of memory
(issue #3434). The metrics discovery reads (/api/v1/series,
/api/v1/labels,/api/v1/label/{name}/values) kept every segment's
matched series until the last segment was fetched, so a series appeared once
per object it wrote to. A tenant with 1,000,000 series in about 15,000
objects drew the server down to its host memory floor and was refused there.
Each segment's series are now folded into one copy per series as its fetch
completes, and the retained series are charged to the memory budget, so a
match[]selector whose series do not fit is refused against the budget's
limit. The merge of several selectors' series is not charged yet.
Added
- MetricsBench classifies every engine answer into one of seven statuses
(issue #938).ravel_bench::metrics_correctness::classifymaps a Ravel,
Prometheus, VictoriaMetrics or Mimir query outcome took,incorrect,
partial,timeout,error,unsupported_constructorrefused
(reasonbudgetorpartial_coverage), comparing data against the oracle
through the difftest comparator. Engine query limits are matched against a
per-engine budget table that cites a source for every row, a 503 is never
mapped by its status code alone, and warnings never change a status. A
failed oracle, or an empty oracle answer on an entry not marked
intended-empty, is an error of the experiment, not a status. The
difftest'sQueryOutcome::Transportnow carries the response's HTTP status
when headers arrived before the failure. - MetricsBench report schema version 3 and
metricsbench_report validate
(issues #938, #2821). A version-3 report keys each row by engine, query id
and query kind, records every pass's status (the row's status is the first
non-okpass), carries latency and per-pass medians onokrows only, a
refusal_reasonon exactly therefusedrows, and per-engine corpus,
correctness, timing and coverage denominators that the validator recomputes
from the rows. The rows must cover exactly the expected cases, Prometheus
rows are flagged as the oracle,in-processendpoints are refused, and
rel_mad_pctover the pass medians is required with five or more passes.
metricsbench_report validate <path>exits 0 only when a report stands and
otherwise names the failing rule. Version 1 and 2 reports still validate
under their own rules. - MetricsBench runs the PromQL corpus over HTTP against every engine
(issue #938). The newmetricsbench_querybinary binds to a
metricsbench_ingestreport (anchor, steps, workload digest and seed,
stream digest, Ravel's per-shard commit tokens), refuses a run whose
portable ingest rows fail band 6 or their accounting, queries Prometheus as
the oracle and Ravel, VictoriaMetrics and Mimir against it with one range
request per range entry, marks native-histogram entries
unsupported_constructon an engine whose ingest row declares the class
unsupported, and writes a validated version-3 report in which onlyok
rows carry latency.
PrometheusClientgainswith_bearer_tokenandwith_extra_params; a
bearer token is read from an environment variable and is not written to
the report. metricsbench_queryretries an unavailable oracle answer before voiding
the run (issue #3370). When Prometheus' answer for an entry is lost to a
timeout or a transport failure,metricsbench_queryre-sends that oracle
query up to--oracle-retriesmore times (default 2), one second apart,
and names each retry on stderr. A 502, 503 or 504 is retried too. Any
other received answer (another HTTP status, a body over the cap, a body
that is not JSON) is not. Prometheus' own row keeps the first attempt's
outcome, so a retry cannot make it faster. The provenance config records
oracle_retries,oracle_retries_usedandoracle_retry_entries. A run
whose retries are exhausted is void.metricsbench_query --diagnosticswrites a sidecar with ULP gaps and
oracle retries (issues #938, #3370). For every entry and every engine
other than Prometheus it records the largest ULP distance to the oracle's
answer over every step, repetition and timed pass (null when no answer
aligned), and it lists every oracle retry with its entry, pass, repetition
and attempt, beside the run's profile and corpus digest. The version-3
report and its validation are unchanged.ravel-promql-difftest's
comparator gains a publiculp_distance.- The MetricsBench ingest lane sends every payload type, and drives Ravel
over HTTP (issue #2821). Staleness markers go on the wire as the
Prometheus stale NaN and native histograms as Remote Write 1.0
Histogrammessages, soexcluded_stale_markerand
excluded_native_histogramare zero and each row reports native
histograms as their own class (offered_histograms,accepted_histograms,
rejected_histograms,dropped_histograms).offered_digestcovers stale
markers by bit pattern and histograms by every encoded field.
metricsbench_ingest --ravel-endpoint <url> --ravel-bearer-token-env <VAR>
adds a portable Ravel row (system: ravel,endpoint: <url>) that records
thex-ravel-commit-tokenanswers (see the band 6 entry below); the bearer
token is read from the named environment variable and never written to
the report. The in-process row stays, withendpoint: in-process.
--osn-endpointis renamed--mimir-endpointand its row'ssystemis
mimir. metricsbench_ingestreads each receiver's accepted set back and
enforces band 6 (issues #2821, #3330). Each HTTP row reads its receiver's
response by that receiver's rule (a Prometheus 400 rejects the request, a
Mimir 400 rejects only the samples it names, a VictoriaMetrics 2xx stores
everything but native histograms, Ravel is confirmed by read-back) and
recordsacceptance_basis,unsupported_classesandtenant. After each
replay the bin reads the data back over the receiver's query API, records
readback, and fails on a mismatch; every HTTP endpoint now needs its
--<system>-query-url, and--ravel-endpointneeds--ravel-tenant,
equal to--tenantwhen the in-process row is built. Band 6 compares
per-class accepted and expected digests on every row and the expected
digests across rows, and band 4 counts unsupported points. The portable
Ravel row keeps the highest(epoch, seq)commit token per shard in
commit_tokens_by_shardand joins them aslast_commit_token. Drops
ended on a retryable status are keyed by it in
retry_exhausted_by_status, and a zero scrape interval exits 65. Reports
written before these fields existed still load.
A read-back whose figures do not match what was sent is repeated every
500 ms for up to--readback-settle-secs(default 60, the same for every
system), since VictoriaMetrics acknowledges a write before it is
queryable;readbackrecordsattemptsandsettled_after_ms. Each
count_over_timeis summed over consecutive windows of at most one day,
since Prometheus refuses one query over a 30-day replay with a 422, and
readback.query_windowsrecords the window count per query. The
/api/v1/seriesanswer is counted as it streams, so a 1,000,000-series
answer fits, and one whosestatusis notsuccessis refused.
Read-back queries take--readback-timeout-secs(default 30), separate
from the write timeout--http-timeout-secs, andreadback.query_ms
records each query's wall time;deploy/metricsbench/run.shpasses the
read-back timeout per profile (ci 30, cardinality 900, history 300, churn
900). A failed read-back exits non-zero after printing the report for the
rows replayed so far, the failed row carrying its mismatchingreadback
or its query error inreadback_error, soingest.jsonkeeps the ingest
figures for diagnosis.metricsbench_queryrefuses such a report as a
run result.metricsbench_ingest --no-in-process-rowruns the portable rows only
(issue #2821). The run builds noravel-in-processrow, opens no store and
runs no post-ingest query, so thecardinalityandhistoryprofiles can
run beside the engine containers without a memory store. The report records
in_process_row: "skipped"("present"otherwise). Band 4, band 6, the
read-back and the cross-row expected-digest check still run on every
portable row; only the comparison with the in-process scalar digest is
skipped. The flag with no portable endpoint exits 64./metricsshows what the catalog's decoded-output caches hold (issue
#3244). New series: the gauge
ravel_catalog_decoded_cache_reserved_bytes{cache="part"|"postings"}, the
bytes the cached decoded snapshot parts and name postings reserve across
every tenant, and the countersravel_catalog_decode_reserve_retries_total
andravel_catalog_decoded_cache_memory_evictions_total, which move only
when the process memory budget refuses a catalog decode, so they read 0
where that budget is unlimited (gatewaymode, or an unknown host memory
size with no--memory-budget-bytes). Exporting them changes no read,
cache or eviction behaviour.ravel-cli load --signal logswarns about a--chunk-ordername the
mapping does not carry (issue #3276). Before any row is read, each name no
[[attribute]]or[[resource_attribute]]key of the mapping names prints
warning: --chunk-order names <name>, which no mapped attribute carriesto
stderr, one line per name in the order given. The RLOG writer still skips
such a name, as before; the warning makes a misspelled name visible instead
of silent. A name the mapping carries only as a string or bytes resource
attribute gets a column only when it is one of the tenant's indexed fields,
which the load cannot know before it flushes, so it prints
warning: --chunk-order names <name>, a string resource attribute, which gets a column only if it is an indexed field(ora bytes resource attribute). A numeric resource key and a key also mapped as an
[[attribute]]get a column and are not warned about.--max-seriesand--max-samplesset the PromQL per-query ceilings
(issue #3306). Both were fixed at 10,000 series and 10,000,000 samples; the
two ravel-server flags now set them process-wide, with the same defaults when
unset, and a0is refused. Startup logs both values and their sources, and
the selector-cache cap derived from--max-samples. The refusal text and its
422executionmapping are unchanged.- Opt-in decoded column-block cache for the SQL
logsscan
(--decoded-cache-max-bytes, ADR-3326, issue #3448): it serves decoded
columns to the columnar path, charges them to the process memory budget,
and is off by default. An interactive statement admits a column on its
first decode and any other statement on its second touch; a touch whose
reservation the memory budget refuses keeps that second-touch credit. - Ravel Agents contracts land as
ravel-agents-core(issue #3331). The
newproto/ravel/agents.protodefines the per-agentAgentHeadroot and
eight immutable object types, each sealed. The objects are at
format_version1; HEAD is written at version 2, which keeps the open tail
of each indexed list inline and the rest in sealed index pages (see the
index-page entry below), and is read at version 1 or 2. The crate holds
thet/<tenant_hash>/ag/<agent_id>/key layout, agent and minted-id
hashing, the HEAD codecs, typed commands
and errors, a pure transition kernel in which a turn commit publishes the
new state, the consumed input, its result, events and effect intents in
one root, and the HTTP wire types, with JSON fixtures under
sdk/fixtures/. Every*_unix_nswire field is a decimal string, since
a JSON number loses precision past 2^53.ravel-agents-store,
ravel-agents-runtime,ravel-agents-clientand the dev-only
ravel-agents-simbuild on it (entries below). ravel-servergains an off-by-defaultagentsfeature and--agents
(issue #3331). With the feature,--agentsmounts
GET /api/v1/agents/healthon the public and the mTLS listener of an
allorqueryprocess; it answers the mode and the settings of
--agents-activation-concurrency,--agents-poll-concurrency,
--agents-memory-budgetand the five per-agent and per-tenant quota flags,
which/metricsalso reports asravel_agents_*gauges.
--agents-max-mailbox,--agents-max-receiptsand--agents-max-timers
are enforced;--agents-memory-budget,--agents-max-agentsand
--agents-max-state-bytesare parsed and reported only. A build without
the feature refuses--agents, and the published image is built without
it, so agents are served only by a ravel-server built with theagents
cargo feature.--agentsundergatewayormaintainis refused.
Static tokens acceptagents,agents_workerandagents_adminafter
;, and--oidc-agents-claim,--oidc-agents-worker-claimand
--oidc-agents-admin-claimgrant the same capabilities from OIDC; every
agents route buthealthchecks the one it needs.ravel-agents-storepublishes agent commands over the object store
(issue #3331, task #3338).AgentStoreruns every command through one
skeleton: read HEAD, let the kernel resolve the request against the
receipt index and validate it, upload the referenced objects with
CreateIfAbsent(reading back and hash-checking an object that already
exists), then CAS HEAD. A refused CAS re-reads and revalidates, at most 8
times; a lost answer to the HEAD write returnsOutcomeUnknownwith the
operation handle, whichresolvesettles through the receipt index. A
HEAD of a newer format version is reported asUnsupportedFormatand
never written. Every store call is counted per phase (resolve, build,
store, publish).ravel-agents-runtimecoordinates agent turns for workers
(issue #3331, task #3343).CoordinatordrivesAgentStorefor workers,
and a poll needs no registration on the coordinator that serves it: a
long poll (capped at 20 s) offers the agent whose
next input was accepted earliest, taking ownership over with one
TakeoverCAS when the agent is unowned, its owner holds no active turn,
or its lease and grace have run out (so with several workers on one agent
each turn boundary costs one such CAS), and binds the oldest input with
BeginTurn, so one agent's turns run
inaccept_seqorder. A commit or failure is fenced by the owner and epoch
the worker's own lease carries, never by a re-read HEAD, so a worker paused
past a takeover is refusedOwnershipLostwithout a write. Failed
attempts retry up tomax_turn_attemptsand then dead-letter the input;
OutcomeUnknownis settled throughresolve(or, for a takeover or turn
start, by re-reading HEAD) before anything is retried, so a lost
acknowledgement never commits twice. Heartbeats renew the lease in the
receiving coordinator's memory without writing HEAD (another coordinator
still sees the takeover's lease hint, so a turn longer than the lease plus
grace can be taken over through it), idle agents are unloaded without
writing anything,
and a restarted coordinator resumes an active turn from the object store
alone: HEAD, plus the inbox page holding the turn's input once that input
is sealed. Counters are read throughAgentsMetricsSnapshot.--agentsnow serves the agents client and worker HTTP API (issue
#3345). Under/api/v1/agents, on the public and the mTLS listener,
client routes (create,send,call,operation,result,resolve,
cancel,state,events) need theagentscapability and worker
routes (worker/register,worker/poll,worker/commit,
worker/heartbeat) needagents_worker; a missing capability is 403.
Long polls are capped at 20 s,eventspages after the committed event
sequence, refusals are typedErrorBodyresponses, and the router has its
own body limit and admission lanes and never takes a query permit.
--agents-activation-concurrencyand--agents-poll-concurrencynow
bound that admission, and/metricsadds the coordinator's
ravel_agents_*counters beside the settings gauges.ravel-agents-simruns the production agents store and runtime under
the step scheduler (issue #3331, task #3346). Named schedules cover
competing senders, duplicate and conflicting sends, a lost CAS response,
workers killed before and after each publication boundary, a suspended
worker after a takeover, enqueue and cancel racing a completion, a cold
restart on a second coordinator, and typed errors. Each runs a fixed
script and a batch of 64 seeds, and checks every history against the
reference machine with exact counts. Test-only; no request path changes.ravel-agents-runtimejournals tool steps and their effects (issue
#3331).Coordinator::run_steprecords a step, commits its intent and
authorizes an attempt before handing it to anEffectDispatcher, and
replays a completed step without a write. After a lost attempt,
provider_idempotencyre-dispatches the same effect id,at_least_once
authorizes a new attempt, andmanualblocksEffectInDoubtuntil
reconcile_attachorreconcile_authorizesettles it.
reconcile_authorizedispatches nothing: it hands the new attempt to the
owner's nextrun_stepfor that step. Each reconciliation is recorded
once as an audit event in the turn's event stream. A result for a
superseded attempt or a cancelled input is kept as evidence. The server's
step, effect and reconcile routes call it (entry below).- Agent timers fire from HEAD, and a bounded sweep reconciles wake hints
(issue #3348).Coordinator::schedule_timerandcancel_timersettle an
unknown CAS outcome by re-reading HEAD, andCoordinator::sweep_onceruns
one reconciliation pass for the coordinator's tenant: it lists_wake
hints up to the current minute bucket, then walks the agent directory,
reads each agent's HEAD, fires every timer HEAD holds as scheduled and
due, and deletes a hint only once that HEAD read shows no timer still
armed in its bucket. A hint is never trusted on its own, so a premature,
duplicated or stale hint fires nothing extra, and a lost hint is found
by the directory half. A create now writes the agent's_direntry
before its HEAD and fails withStoreUnavailable, writing no HEAD, when
the entry cannot be written, so the directory lists every agent that has
a HEAD; a pass that reaches an agent through a hint writes a missing
entry back (ADR-3331, the directory amendment to decision 8). A listing
page of keys the store cannot address still moves the pass's cursor. A
pass is capped bySweepConfig(default 8 list pages and 64 HEAD GETs,
resumed from a cursor next pass). An idle worker poll starts at most one
pass per 5 s interval, and a poll with a ready turn starts none.
Coordinator::sweep_metricsreturns a flat set of counters (passes, list
pages, HEAD GETs, hints read and deleted, timers fired, agents loaded,
_direntries repaired and directory cycles), whichravel-server
renders on/metricsasravel_agents_sweep_*. The gateway and query IAM
templates grants3:DeleteObjectont/*/ag/_wake/*for the hint delete.
ravel-serverschedules the sweep per tenant (issue #3353, below). --agentsnow serves the step, effect and reconcile routes (issue
#3349), in a server built with theagentsfeature, which the published
image is not. Workers journal each tool call withworker/stepand
report its attempt withworker/effects/completeor
worker/effects/fail; a replacement worker's replay follows the step's
recovery policy, a result for a superseded attempt answerslate: true
and is kept as evidence, and amanualeffect in doubt blocks the turn
with 409effect_in_doubt. A failed tool call is kept as a typed failure: the
step stays open, its replay andGET effects/{effect}carryfailure,
and a non-terminal failed turn replays it on the next turn. Clients list
and inspect effects withGET effectsandGET effects/{effect}, and an
agents_admincaller settles an effect in doubt withreconcile, which
writes oneagents_reconcileaudit record per change through the audit
pipeline. Aretryhands the new attempt to the nextworker/stepfor
that step, from whichever worker holds the turn by then, instead of
dispatching it from the reconcile request; the hand-over lives in the
server's memory, so after a restart, or on another server process, the
step is in doubt again and needs a secondreconcile. Anevents
request withlimit0 is 422.- Agents simulation covers steps and their effects (issue #3352). The
reference machine models step journaling, attempt dispatch under each
recovery policy, effect results and evidence, and operator reconcile, and
ravel-agents-simdrives the production runtime through kill points
around a tool call, late and conflicting results, a cancel with an open
attempt and a handler version change, each with a 64-seed batch and
pinned totals. A takeover re-applied after a lost CAS that fences a live
turn is pinned as an ignored suspected defect. - Agents timers, sweep, event resume and inspector routes (issue
#3353). Under/api/v1/agents,timers,timers/cancelandGET timersschedule, cancel and list an agent's timers by id and generation;
a due timer is delivered once by the sweep whether or not its wake hint
was written. Each tenant's coordinator runs a sweep pass every 5 s
(agents.sweep_interval_ms, which no flag sets), andPOST sweep, under
theagents_admincapability, runs one on demand and answers its LIST,
GET, hint and timer counts, which/metricsreports as
ravel_agents_sweep_*.eventstakes anoperationfilter, marks the
page that finishes an operation withterminal, and answers a cursor
below the retained window with 410cursor_expired.GET inspector
serves a self-contained HTML page showing an agent's state, mailbox,
timers, steps, effects and events, and why a blocked agent is blocked.
Every route checks its capability before anything else, and effect
inspection lists at most the latest 64 attempts. Areconcileretry
naming the attempt before one a worker's own step authorized is 409
ownership_lost, and a full evidence index gives a settled effect's slot
to a new effect. ravel-agents-clientandravel-cli agents(issue #3355). The new
crate is a typed HTTP client for the agents routes: each error code maps
to itsErrorKind, long polls are clamped to 20 s, and nothing is
retried:send_resolvingmakes one resolve call, and only when an
outcome_unknownbody saysretry: resolve_operation.ravel-cli agents list,inspect,operation,eventsandeffectsread agent HEADs
and objects straight from the bucket with no server running, and every
page is bounded.agents reconcilesettles an effect in doubt through
the server with anagents_admintoken.agents devstarts RustFS from
the compose file, qualifies the bucket and runsravel-server --agents
on loopback with a fresh token; it refuses the memory store before
starting anything, and prints the scaffold command once the server is
ready.agents devandagents reconcileneed aravel-serverbuilt
with theagentsfeature, which the published image's is not (agents devstarts--server-bin,ravel-serveronPATHby default).
agents scaffold DIRruns the TypeScript SDK's generator with node to
write a starter project into a new or empty directory. Every command
takes--json. Timer commands are not in this release; the client exposes
set_timerandcancel_timer.- ravel-agents-sim checks the agent runtime against a reference machine
over seeded fault schedules (issue #3356). Twenty schedules (enqueue and
cancel races, contended refusals, competing senders, lost CAS answers,
worker kills and suspensions, every effect policy under kills and cancels,
timer races and lost hint writes, recovery storms) each run a fixed test and
a 64-seed batch with pinned totals. The crate'snightly.shruns every
batch and prints one line per schedule;RAVEL_SIM_SEEDand
RAVEL_SIM_SEEDSchoose another window, and a newagents-simjob in the
sim-nightlyworkflow runs the default batch, the default batch again at
index page size 2, and a fresh 512-seed window each night. - MCP tools for Ravel Agents (issue #3357). A server built with
--features mcp,agentsand run with--mcp --agentslists six more
tools,ravel_agent_describe,ravel_agent_send,
ravel_agent_operation,ravel_agent_state,ravel_agent_eventsand
ravel_agent_blocked_effects, served in process through the agents
client routes, so theagentscapability check is the route's own and a
principal without it getsunauthorized.ravel_agent_sendis the first
MCP tool that writes; it is idempotent under its required
idempotency_key. Each call is bounded by its effective deadline and
ends with thedeadlineclass past it. Every agents tool takes
deadline_msandmax_response_bytes, andravel_agent_eventsalso
takesmax_rows; only its envelope reports amax_rows, the page size
it used.tools/listis 23,589 bytes with the agents tools and
byte-identical to before without them. ADR-1374 D2 and D7 carry an
amendment for the write tool. - Committed agent history now projects into logs and traces (issue
#3360, ADR-3331 decision 12). Each committed event becomes a log record
and each committed turn a span, under a stable event id that is the same
on every retry, restart and process, so theagent.event_idattribute is
the deduplication key for queries; every process serving or sweeping a
tenant re-projects each agent it touches from its first event, up to
32,768 duplicate records per agent per process. Payloads are redacted to
an unkeyed hash and a size unless the tenant is named by
--agents-projection-payload-tenant. A process with an ingest buffer
projects through its own OTLP log and trace handlers, with the tenant's
admission, limits and IPCrypt rules;--agents-projection-endpointsends
to an OTLP gateway instead and is accepted only as plainhttp://on a
loopback host until the credential model lands.--agentsin a process
with neither refuses to start unless--agents-projection offis given,
andoffbeside an endpoint refuses too. A record carries its turn's
finish time where the projector holds it, else its read time, and says
which inagent.time_source; a record the ingest path refuses for good
(too old, over a structural limit) is skipped and counted while transient refusals are
retried. A projection outage changes no agent state.
--agents-projection-backlogbounds the undelivered records per tenant,
andGET /api/v1/agents/healthand the newravel_agents_projection_*
/metricsfamily report the projection lag, backlog and delivery
counters. - Agent HEAD indexes move into bounded immutable pages (issue #3359).
HEAD format version 2 keeps only the open tail of the inbox, receipts,
results, events and timers inline and references sealedo/<hash>index
pages of at mostmax_page_entriesentries, at mostmax_index_pagesper
list, each writtenCreateIfAbsentbefore the CAS that references it. A
new send reads no index page, a repeat send or a receipt lookup at most
one (by request-id fingerprint), a wake no timer page, and events are read
by sequence range. The runtime's reload, the sweep and the directory scan
read HEAD alone, and the server's poll, result wait, resolve and events
routes read HEAD plus the pages they need; an events read filtered to one
operation reads only the events pages whose recordedaccept_seqrange
holds it. An events cursor into sealed
history is served, and the state route's mailbox lists every queued
input, sealed pages included. No release wrote version 1; a version 1
HEAD from a pre-release build migrates to version 2 on the next command's
CAS. The inspection read,read_head_full, GETs every page. - Ravel Agents has cost benchmarks with pre-registered bands (issue
#3361).cargo bench -p ravel-agents-sim --bench agents_costruns the hot,
slow, many, dormant, index (page size 64 and 2), storm, discovery and
baseline scenarios on an in-memory store with injected latency and a fixed
seed, and prints GET, PUT, CAS and LIST counts per phase, object-body bytes,
CAS conflicts, p50/p99/max latency on the injected clock and peak heap, each
per message, turn, step or activation. Every figure is checked against
benchmarks/agents/prereg.mdand the run exits non-zero on a figure outside its
band, missing, or printed twice. Discovery runs with no wake hint due,
the ceil(N/64)-pass best case, and with the wake half saturated, asserted
against the guaranteed ceil(N/32) passes (313, about 26 minutes at the
5 s sweep interval, for 10,000 agents). Dollars are derived from
benchmarks/agents/prices.toml, labelled as assumptions and never asserted on.
docs/agents-benchmarks.mddescribes the scenarios and the report format. - Agents store checked on a real backend (issue #3331, task #3363).
Theagents_real_storetest binary runs the HEAD publish's
conditional writes and aCreateIfAbsentcollision (one case above the
multipart threshold) on an S3-compatible store named by
RAVEL_REAL_STORE_URLand asserts the typed outcomes equalMemoryStore's;
unset, it prints a skip line.scripts/chaos/agents-replace-worker.sh
SIGKILLs a worker inside a step and the server with a turn in flight, and
asserts that a resend returns the same operation, that the replacement runs
the same effect as attempt 2 within the lease and grace band, and that a
restart neither takes the turn over nor writes the result twice. Both run
nightly in k8s-nightly's newagents-chaosjob against RustFS. - Agents report how close each agent is to its lifetime limits (issue
#3454). The sweep measures every agent's receipts, sealed results, events
and timers pages and encoded HEAD size from the HEAD each directory cycle
already reads, and/metricsadds
ravel_agents_index_fill_max_ratio{list},
ravel_agents_head_bytes_max_ratioandravel_agents_agents_near_cap{list}
(agents at or above 75% of a cap), plus
ravel_agents_budget_refusals_total{cap}counting every command the kernel
refuses for a cap. A cap refusal's error body names the cap in a new
optionalcapfield,GET /api/v1/agents/stateadds an optionalusage
object with the counts behind each cap, andravel-cli agents inspect
prints them. GcsStore, a read-write Google Cloud Storage backend in
ravel-object-store (issue #3458, ADR-3456). It uses the object's
generation as the version token on every path, sends
x-goog-if-generation-match: 0for create-if-absent and the expected
generation for compare-and-swap, and refuses a compare-and-swap token
that is not a positive decimal generation of at mosti64::MAXbefore
sending anything. It lists through the JSON
API'sobjects.list(maxResultsis the page size forlistand
list_after, and 1000 for a delimited listing, as on S3), so a name it
cannot address is reported as unaddressable instead of failing the page.
A resumed call returns its response's names, one short of the page size
when the response held the inclusive start name, rather than sending a
second request, so at a page size above 1 and against full responses
every call is one request. A listed ETag matches the oneheadreports,
for a composite object too.
It takes Application Default Credentials only, and counts its HTTP
attempts intoravel_store_attempts_totaland its responses'Dateinto
the observed store time, as S3 does. It reports no multipart upload, no
upload checksums and no bucket-protection probes. Its qualification
identity isgs://<bucket>, orgs://<bucket>@<base_url>against an
emulator. The contract suite runs against it on an in-process fake GCS
endpoint in every run, and against a real bucket when
RAVEL_GCS_TEST_BUCKETis set.--store gcsselects it in ravel-server
(#3459) and ravel-cli (#3460), below.ravel-server --store gcskeeps its data in a Google Cloud Storage
bucket (issue #3459, ADR-3456).--gcs-bucket(RAVEL_GCS_BUCKET) names
the bucket, and credentials come from Application Default Credentials (GKE
Workload Identity in a cluster). The server refuses to start on an
unqualified bucket, records the backend asgs://<bucket>, and reports into
the same store metrics as S3. The S3-only flags (--s3-*,--s3-kms-key,
--tenant-kms-config,--require-bucket-protection) are refused under
--store gcswhen passed or set through their environment variables, and a
Parquet credential profile on Ravel's own GCS bucket is refused at config
time.- ravel-cli accepts
--store gcs(issue #3460, ADR-3456). A
ravel-clicommand that takes--storecan run against a Google Cloud
Storage bucket named by--gcs-bucket(RAVEL_GCS_BUCKET), with
credentials from Application Default Credentials.store qualify
records the bucket asgs://<bucket>(orgs://<bucket>@<endpoint>) in
sys/qualificationand passes--list-page-sizeto the GCS listing.
Under--store gcs, these refuse with a non-zero exit before any
request:store verify-protection(exit 2, no report, since it does not
check GCS bucket protection),--tenant-kms-config, a--s3-*flag
given on the command line or through its environment variable (a
defaulted one is not refused), andagents dev, which drives the compose
RustFS bucket.
Changed
metricsbench_ingestrequires--anchor-msand records its provenance
(issue #2821). A missing anchor, a negative one, one off the profile's
scrape grid, or one whose last step passes 9223372036854 ms exits 64 with
the reason; no clock default remains.profile.runrecords
workload_version,workload_digest,seed,anchor_msand
stream_digest, and each row recordsoffered_digest.comparableis
now also false when the recorded stream digest differs from the one the
manifest and anchor regenerate. Band 4 checks each classified type against
the generator's own count and the offering identity from the row's own
fields. A comparator row splitsdropped_samplesinto
rejected_by_status, keyed by HTTP status, andretry_exhausted_samples.
Reports written before these fields existed still load.- MetricsBench workload moves to format version 2, and the corpus is
checked against it (issue #2821). Every profile in
benchmarks/metrics/workload.jsondeclares arange_evalgrid (start and
end offsets from the newest sample, and a step) for the corpus's range
queries, and a manifest without it is refused naming the missing block.
The generator's out-of-order sample now lands one and a half scrape
intervals early, between two scrape points, so it never repeats a
timestamp its series already carries; the ci stream digest changes with
it. The corpus repairs two join queries that could not answer
on the generated data (a many-to-manygroup_leftand an unreachable rate
threshold) and gains a gate against the manifest: every selector matches a
generated series, everygroup_leftkey is unique on its one side, and
every range query's window plus lookback fits inside its profile's stream
span. - MetricsBench corpus moves to format version 4, and an entry may declare
a ULP tolerance (issue #2821). Themb_full_range_avg_over_timequery
becomesavg(avg_over_time(metricsbench_gauge_cpu_percent[30m])): it scans
the same data but answers one series instead of one per gauge series. On
the cardinality profile the old answer was 48,400,000 points, above the
256 MiB response cap, so no engine's answer could be compared; the new one
is 121 points. The entry discloses the rewrite inmodified.
tolerance_ulpsis optional, absent means bit-exact, and the runner hands
it to the comparator. The corpus gate refuses a zero tolerance and a
tolerance on an entry without amodifiedreason.
mb_full_range_avg_over_timecarries 2^20 ULPs, so a different summation
order over its 400,000 cardinality series cannot read as an incorrect
answer. The other entries that sum floats across series carry one too:
mb_selective_job_rate_sum(2^18),mb_selective_region_avg(2^19),
mb_selective_error_ratio,mb_fanout_total_rateand
mb_full_range_counter_increase(2^20 each), each derived in its
modifiedreason from the largest series count it sums on any profile:
n-1 ULPs per sum of n non-negative values, plus the inner
rate/increaseroundings and one per division, doubled for two engines
and rounded up to a power of two. The nightly ci run measured Ravel 1 to 4
ULPs off the oracle on four of these five entries and Mimir 1 to 2 ULPs
off on three, which bit-exact comparison read asincorrect;
VictoriaMetrics' gaps of 4.8e14 ULPs and more still do. Single-series
entries stay bit-exact, and a corpus file of an earlier version is
refused. metricsbench_ingestkeeps requests in flight per series shard, the way
Prometheus does (issue #2821, ADR-0927 2026-10-28 amendment). Each HTTP row
routes every series by a BLAKE3 hash of its labels to one of
--http-shardsshards (default 50) and keeps one request in flight per
shard, sending--http-batch-sizepoints per request (default 2000, was
500). Retries back off from 30 ms, doubling to 5 s, instead of a fixed
250 ms, and a 429 is retried. An HTTP row'selapsed_secsis now wall time,
markedelapsed_basis: "wall_in_flight", beside newgeneration_secsand
sender_boundfields; the in-process row staystimed_segments. Per-class
accepted and expected digests are order-independent multiset digests, so
they do not depend on the order points arrive in. The portable Ravel row
keeps the highest(epoch, seq)commit token per shard and refuses two
writers on one shard.--max-ingest-secsstops a row whose projected wall
time exceeds it (exit 1), anddeploy/metricsbench/run.shpasses a ceiling
per profile.- The MetricsBench nightly measures the Ravel it checked out (issue
#2821).metricsbench-nightly.ymlbuilds the root Dockerfile'sserver
target from the run's own commit, keeps the image local, and passes it to
run.shby tag and content Id, so a regression merged later reaches the
next nightly.run.shtakes the new local-build form--ravel-image REF --ravel-image-id sha256:ID, refuses it (exit 65) unlessdocker image inspect REFreports that Id or when the profile is notci, and stamps
ravel_image.kindlocal-buildorpublishedinstamps.jsonand the
provenance. A local-build run is never publishable. The nightly's job
timeout rises from 30 to 140 minutes to cover the full stack run and the
image build. - The MetricsBench stack's object store is s3s-fs, not RustFS (issue
#2821, ADR-0927 amendment 2026-10-29).deploy/metricsbench/run.shgains a
fetch-s3s-fsstep that downloads thes3s-fs-0.17.0-ravel.1release
binary for the Docker engine's arch (docker versionServer.Arch, else
uname -m; x86_64 or aarch64) into a cache outside the output directory,
hashes it withsha256sumorshasum -a 256, refuses it with exit 65
unless its sha256 equals the pin
inrun.sh, and hands its path to compose asS3S_FS_BIN; compose runs it
as thes3sservice on the distroless/cc base the root Dockerfile uses, at
the same 1 CPU and 2 GiB envelope RustFS had.stamps.jsonrecords
store_kind: s3s-fs,host.archand anobject_storeblock (release,
sha256, source) in place ofrustfs_logs; the RustFS log copy is gone,
since s3s-fs logs to stdout intocompose.log. The pin guard
every_comparator_pins_an_image_digest.shgains a seventh category that
fails when either sha256 constant or either comparison is removed.
Figures measured on RustFS are not comparable with figures measured on
s3s-fs. - A warm resolve no longer re-derives each cached L0 segment from its commit
record (ravel-catalog). The two costly steps of turning a cached commit
record into a segment reference, the data object key check and the gated
declared-column stamp read, now run once per record and their result is kept
with the cached record, which is immutable; each listed key is also parsed
once per resolve instead of three times. The full warm resolve over 408
cached records against an in-memory store, as measured on one machine by
the ignoredwarm_resolve_derived_work_splittest harness, fell from about
2450 to about 1280 microseconds. The expected-field checks still run on every cache hit, a
derivation that fails is never kept and fails every resolve, and evicting a
record drops what was kept with it. Every commit-cache entry is now charged
32 more bytes for the slot that holds the kept result, which keeps an
ordinary stats-free entry (896 bytes) inside the 900-byte planning rate; a
record carrying declared-column stamps is also charged the stamp list it
keeps (339 bytes for the four stamps a ClickBench flush carries: 16 bytes of
counters plus, per stamp, its 72-byte struct and its name), so a byte-bound
tenant whose records carry stamps holds fewer of them at once. - High-cardinality
GROUP BYstatements spend less CPU (ravel-sql): a
partial aggregation partition now stops pre-aggregating once its probe finds
a new group for more than one row in ten (ratio 0.1, was 0.5). - Catalog decodes are charged to the process memory budget (issue
#3244). The server's catalog now reserves its snapshot-part, name-postings
and column-statistics decodes against the same ADR-1170 budget the SQL
executor and the fetchers share, and those bytes show under
ravel_memory_reserved_bytes{component="fetch"}. Under memory pressure a
part or postings decode can now be refused, which answers 422 on that
query, a fold tick whose postings decode is refused fails and is retried
by the next tick (a postings object larger than the whole budget is
refused on every tick, so that tenant's fold stays failed), and
ravel_catalog_decode_reserve_retries_totaland
ravel_catalog_decoded_cache_memory_evictions_totalcan move. Nothing
changes where the budget is unlimited (gatewaymode, or an unknown host
memory size with no--memory-budget-bytes).
Outside those cases, the server also caps the catalog's decoded part and
postings caches at 1% of the budget, through the new
CatalogConfig::decoded_cache_max_bytessetting (unset by default). The cap
covers both caches together across every tenant and evicts the least
recently used entries of either cache; a decoded object larger than the
cap is not cached and is decoded again each time it is needed. The new
ravel_catalog_decoded_cache_byte_bound_evictions_totaland
ravel_catalog_decoded_cache_byte_bound_refusals_totalcount the entries
that cap evicts and the objects too large to cache under it. - The cache's S3-FIFO ghost window covers 30% of the cache in bytes
(ravel-cache, issue #3270). A key evicted from probation is remembered
while it is inside the old key window (max_bytes / max_entry_byteskeys)
or while the ghost's evicted sizes sum to at most 30% ofmax_bytes,
whichever is wider. Before, a 6.95 GB cache at the 64 MiB entry cap
remembered about 100 keys, so a repeated statement reading more than the
probation queue plus that window in 1.3 MB ranges was never served from
the cache: in the ClickBench hot sweep simulation q31, q32 and q33 now
serve their second and third timed calls in full, where they served
nothing. Both tiers use the policy. A hot set under a cold scan (ADR-0046
decision 6) and loops larger than the cache are served at least as before
in the simulation. The ghost's key count is capped at one per mille of
max_bytesin ghost bookkeeping. ravel-cli loadrefuses a--target-bytesabove 360,000,000,000, and
every target above 1 now carries its encode allowance in the ack deadline
(issues #3289, #3376 and #3382). The bound is the target whose encode
allowance at 100,000,000 bytes per second is one hour; before, a mistyped
7,400,000,000,000 added 74,000 s of encode allowance, raising every Strict
write's ack deadline past 20 hours. A given target above the bound is
refused for logs, metrics and spans loads alike, whether or not
--ack-marginis given, with--target-bytes must be at most 360000000000 (one hour of encode at 100000000 bytes per second, the allowance the ack deadline adds when --ack-margin is unset); N was given; the
--target-byteshelp and the ingest guide state it as an absolute bound.
A target derived with--target-bytesunset is clamped to the same bound.
The load report's geometry carries a newtarget_clampedfield, and the
geometry line and the no-effect warning name a clamped derived target as
clamped: the warning readsthe derived flush target of 360000000000 bytes (250000 rows at the first batch's estimated <N> bytes per row, clamped to the 360000000000 maximum). With--ack-margingiven, the margin replaces
the allowance. A target at or below one batch's estimated
per-shard slice used to add no encode time, on the assumption that every
write then flushed on its own; shards do not split a batch evenly, so such
a buffer can still accumulate to the target and be released by the age
trigger, and it now gets the same allowance as any larger target. Only
--target-bytes 1adds nothing. The no-effect warning now says its
per-shard slice explanation holds at an even split.- A
LIKE '%lit%'pushed into a logs scan no longer fetches the BLOOM
section (issue #3295), which the reader never reads for it: on the test
fixture of four objects that is 4 fewer GETs and 69,440 fewer bytes. - The log reader's
Containskernel spends less time outside its needle search (issue #3295): on the crate'scontains_kernel_blockbench over one 8,192-row block that time falls from 94.4 to 38.4 µs with a value per row, from 131.1 to 48.5 µs with a 2,000-entry dictionary, and from 153.1 to 122.8 µs with an overflowing attribute on 5% of rows. - Plain string pages decode into one values buffer plus
u32offsets
(ravel-logseg, perf): a decoded plain string column costs 4 bytes per row
plus its data and a presence bit instead of an owned buffer per row, and the
contains_kernel_blockplain case runs at about 17.6 ns per row against
16.4 to 17.3 ns with the #3295 kernel change alone, measured on one shared
machine. agis a reserved Parquet table name, and the IAM templates grant the
Ravel Agents namespacet/*/ag/*(ADR-3331): the gateway and query roles
may list, read and write it, the maintain role may list and read it. An
existing Parquet table namedagstops resolving;ravel-cli parquet repair --strayreports its manifests asReservedName.
What's Changed
- feat(ravel-logseg): write STREAM_DIR and FIELD_DIR after PAGE_DIR by @pmoust in #3232
- perf(ravel-codec): decode integer pages in bulk by @pmoust in #3239
- feat(ravel-sql): prune logs blocks for an integer <> predicate by @pmoust in #3247
- fix(sql): keep string values charged while a downstream operator holds them by @pmoust in #3249
- fix(parquet): walk page headers before the decoder skips a corrupt list by @pmoust in #3258
- feat(server): export column-stats cache and fold drop metrics by @pmoust in #3259
- fix(load): derive the pipeline depth a quarter past the rows bound by @pmoust in #3141
- perf(sql): load column statistics only for the shapes the metadata paths answer by @pmoust in #3257
- Measure bbtree and hash-then-sort against std maps on two hot paths by @pmoust in #3264
- docs(adr-3217): withdraw decision 2 for the competing entry by @pmoust in #3255
- feat(server): size the column-stats cache from the memory budget by @pmoust in #3260
- fix(parquet): serve whole column chunks only in the scan phase by @pmoust in #3265
- Measure hash-then-collect postings on stream-sorted rows by @pmoust in #3268
- feat(ingest): expose the RLOG row-group size as a loader flag by @pmoust in #3267
- perf(catalog): cache column statistics per part and per dictionary by @pmoust in #3272
- feat(logseg): place declared columns first in the RLOG chunk order by @pmoust in #3277
- perf(codec): reuse a per-thread zstd context on read-path decodes by @pmoust in #3278
- fix(parquet): report a decoder error while scanning a file as Corrupt by @pmoust in #3266
- perf(catalog): stream the column-statistics decode one segment at a time by @pmoust in #3279
- fix(cli): run the depth flush with an explicit --target-bytes by @pmoust in #3282
- feat(metricsbench): capture each comparator's effective version and engine by @pmoust in #3286
- perf(sql): cast group keys back to dictionaries above the TopK by @pmoust in #3287
- fix(cli): scale the load ack deadline with the flush target by @pmoust in #3290
- fix(catalog): degrade a refused column-stats reservation to a scan by @pmoust in #3291
- docs(adr-0927): amend the MetricsBench contract before the first portable run by @pmoust in #3284
- chore(metricsbench): bump the Mimir comparator to 3.2.3 by @pmoust in #3285
- feat(bench): anchor and stream the metricsbench ingest replay by @pmoust in #3288
- feat(difftest): bound every PrometheusClient query by a complete-operation deadline by @pmoust in #3292
- feat(sql): push LIKE '%literal%' on a declared str column as Contains by @pmoust in #3293
- fix(ravel-sql): keep partitioned hash join inputs co-partitioned by @pmoust in #3294
- fix(guards): anchor AI attribution trailers in authorship guard by @manulpatel in #3256
- chore(metricsbench): freeze comparator ingest windows and query limits by @pmoust in #3296
- feat(ravel-bench): move MetricsBench workload and corpus to version 2 by @pmoust in #3297
- chore(release): prepare 0.27.0 by @pmoust in #3302
- chore(metricsbench): bound Mimir query memory at 2 GiB by @pmoust in #3305
- feat(bench): require the MetricsBench anchor and record stream provenance by @pmoust in #3298
- feat(bench): classify MetricsBench outcomes into the seven statuses by @pmoust in #3303
- chore(deps): clear the zstd advisories cargo deny reports by @pmoust in #3310
- feat(server): export the catalog's decoded-cache bytes by @pmoust in #3307
- feat(bench): add MetricsBench report schema version 3 and validate by @pmoust in #3315
- perf(logseg): cut the contains kernel's per-row work outside the search by @pmoust in #3308
- perf(ravel-catalog): keep each cached L0 record's derivation by @pmoust in #3311
- perf(query): place only the filter sections the reader reads by @pmoust in #3318
- perf(sql): lower skip-partial aggregation probe ratio to 0.1 by @pmoust in #3322
- docs: state the probe and filter-section bounds as the code applies them by @pmoust in #3324
- perf(cache): widen the S3-FIFO ghost window to 30% of the cache by @pmoust in #3323
- docs: correct ghost-window figures and stale ADR-0046 references by @pmoust in #3325
- feat(metricsbench): deploy Ravel in the portable lane at the comparators' envelope by @pmoust in #3309
- feat(bench): send stale markers, native histograms and Ravel's row over Remote Write by @pmoust in #3314
- docs(adr): amend ADR-0927 for warnings, Mimir limits, band 6 and acks by @pmoust in #3316
- docs: add ADR-3331 Ravel Agents durable agent runtime by @pmoust in #3332
- feat(catalog): bound the decoded caches' held bytes by @pmoust in #3365
- docs(adr): propose ADR-3326, a decoded column-block cache by @pmoust in #3329
- feat(sdk): add the TypeScript Agents SDK workspace skeleton by @pmoust in #3366
- docs(metricsbench): size the max-samples limits by what each one counts by @pmoust in #3328
- feat(server): add --max-series and --max-samples query ceilings by @pmoust in #3333
- feat(bench): read each receiver's accepted set back and enforce band 6 by @pmoust in #3327
- docs: name the region cost and the column id rule for chunk order by @pmoust in #3367
- test: pin chunk order reads through POSTINGS, NumRange and records by @pmoust in #3369
- feat(server): charge catalog decodes to the process memory budget by @pmoust in #3319
- feat(bench): run the PromQL corpus over HTTP against every engine by @pmoust in #3368
- docs: bring two #3244 changelog entries in line with the server wiring by @pmoust in #3371
- fix(cli): bound --target-bytes and give every target its allowance by @pmoust in #3372
- test(tla): add the agents protocol area by @pmoust in #3375
- feat(metricsbench): freeze Ravel's per-query ceilings in the portable lane by @pmoust in #3377
- feat(cli): warn about --chunk-order names no mapping key carries by @pmoust in #3378
- feat(bench): collapse the full-range corpus entry under corpus version 3 by @pmoust in #3374
- feat(bench): retry an unavailable oracle query before voiding the run by @pmoust in #3379
- fix(cli): name a clamped derived target and reword the target bound by @pmoust in #3381
- feat(metricsbench): drive a profile end to end and stamp what ran by @pmoust in #3384
- feat(bench): write a diagnostics sidecar with ULP gaps and oracle retries by @pmoust in #3385
- feat(agents): add ravel-agents-core contracts and skeleton crates by @pmoust in #3383
- feat(bench): let the ingest run skip its in-process row by @pmoust in #3386
- feat(metricsbench): run the large profiles without the in-process row by @pmoust in #3387
- feat(metricsbench): write the query diagnostics sidecar on every run by @pmoust in #3388
- feat(agents): add the ravel-agents-store publication skeleton by @pmoust in #3389
- test(agents-sim): add the reference machine, history checker and scheduler by @pmoust in #3390
- fix(metricsbench): repeat a short read-back until the receiver settles by @pmoust in #3391
- docs(adr): amend ADR-3326 so admission keeps a headroom margin by @pmoust in #3380
- feat(sdk): add the TypeScript SDK core for Ravel Agents by @pmoust in #3392
- feat(server): add the agents feature seam and --agents flags by @pmoust in #3394
- docs(tla): add the agents traceability table by @pmoust in #3396
- docs(adr): amend ADR-3326 so the evictor frees shortfall plus margin by @pmoust in #3395
- feat(agents-runtime): add the turn coordinator over AgentStore by @pmoust in #3397
- docs(agents): add the Ravel Agents docs, key rows and IAM grants, rename the MCP guide by @pmoust in #3400
- feat(metricsbench): keep replay requests in flight per series shard by @pmoust in #3398
- test(ravel-server): re-pin mcp_e2e completed-call cost counters by @pmoust in #3402
- feat(metricsbench): bound summation residue with ULP tolerances by @pmoust in #3405
- feat(agents-sim): milestone 1 and 2 simulation schedules by @pmoust in #3406
- docs(adr): accept ADR-3326 after Stage 0 and record the entry decision by @pmoust in #3407
- chore(deps): bump pulldown-cmark to 0.13.5 for RUSTSEC-2026-0414 by @pmoust in #3409
- ci: add agents, mcp and TypeScript SDK gate lanes by @pmoust in #3403
- feat(server): serve the agents client and worker routes under --agents by @pmoust in #3411
- feat(agents-runtime): journal tool steps and coordinate their effects by @pmoust in #3410
- fix(metricsbench): equal nofile limits, a read-back that fits 1M series by @pmoust in #3414
- perf(logseg): decode plain string pages into one buffer plus offsets by @pmoust in #3413
- feat(tla): model the agents effect journal and one agent end to end by @pmoust in #3418
- test(agents-sim): simulate steps, effects and reconcile by @pmoust in #3420
- feat(sdk): run the agents SDK end to end against ravel-server by @pmoust in #3417
- feat(agents-runtime): fire timers from HEAD and sweep wake hints by @pmoust in #3419
- fix(metricsbench): read back in day windows and keep failed reports by @pmoust in #3423
- feat(ravel-server): serve the agents step, effect and reconcile routes by @pmoust in #3422
- refactor(logseg): hold every decoded column form behind an Arc by @pmoust in #3424
- ci(metricsbench): run the nightly on a Ravel built from its own commit by @pmoust in #3426
- feat(agents-sim): run every seed batch nightly against fresh windows by @pmoust in #3427
- docs(tla): measure and band every agents configuration by @pmoust in #3428
- test(logseg): make the chunk-order prune comparison prune partially by @pmoust in #3429
- feat(sdk): scaffold a starter agent project and generate client types by @pmoust in #3430
- fix(cli): state the target bound as absolute and warn on string resource keys by @pmoust in #3432
- feat(agents): add ravel-agents-client and ravel-cli agents commands by @pmoust in #3440
- feat(agents): serve timer, sweep, event resume and inspector routes by @pmoust in #3436
- feat(metricsbench): run the stack's object store on s3s-fs by @pmoust in #3441
- fix(metricsbench): pull the AWS CLI from Docker Hub by @pmoust in #3447
- test(agents): check the store on RustFS and add an agents chaos lane by @pmoust in #3445
- feat(query): add the ADR-3326 decoded column-block cache, off by default by @pmoust in #3439
- feat(mcp): serve the Ravel Agents tools over MCP by @pmoust in #3446
- feat(agents): page HEAD indexes into bounded immutable objects by @pmoust in #3450
- test(agents-core): seed the kernel sequence proptest by @pmoust in #3455
- docs(adr): run the MetricsBench Ravel row at a 500 ms flush delay by @pmoust in #3449
- feat(metricsbench): run the Ravel row at a 500 ms flush delay by @pmoust in #3462
- docs(adr): add ADR-3456 native GCS object store backend by @pmoust in #3457
- test(metricsbench): pin the stated S3 request budget to its derivation by @pmoust in #3464
- feat(agents): report how close each agent is to its lifetime caps by @pmoust in #3465
- fix(query): keep the ghost credit when a decoded-cache admission is refused by @pmoust in #3466
- docs(adr): keep cache-served columns off the tenant ceiling by @pmoust in #3461
- feat(agents): project committed agent history into logs and traces by @pmoust in #3469
- feat(agents-sim): measure agents cost per message, turn and pass by @pmoust in #3470
- feat(object-store): add GcsStore, a read-write GCS backend by @pmoust in #3467
- fix(query): hold one copy per series in a series listing by @pmoust in #3471
- feat(server): run ravel-server on a GCS bucket with --store gcs by @pmoust in #3472
- feat(cli): accept --store gcs by @pmoust in #3474
- feat(server): run ravel-server on a GCS bucket with --store gcs by @pmoust in #3477
New Contributors
- @manulpatel made their first contribution in #3256
Full Changelog: v0.26.0...v0.28.0
Downloads
The attached binaries were extracted unmodified from the signed container
images this release published, so each one is byte-identical to the file inside
the image you can pull. They are glibc-dynamic, built against Debian 12
(the runtime image is gcr.io/distroless/cc-debian12), so they need glibc 2.36
or newer. On an older distribution, run the container image instead:
docker pull ghcr.io/nofireai/ravel-server:0.28.0One binary per platform, named <binary>-<os>-<arch>:
ravel-server-linux-amd64,ravel-server-linux-arm64ravel-cli-linux-amd64,ravel-cli-linux-arm64ravel-operator-linux-amd64,ravel-operator-linux-arm64ravel-ingest-router-linux-amd64,ravel-ingest-router-linux-arm64
Verifying the downloads
SHA256SUMS covers every attached binary and symbol archive. Check the files
you downloaded, then verify SHA256SUMS itself against the signature and
certificate beside it. The signature is keyless, bound to this release
workflow's identity, so there is no key to obtain:
sha256sum --check --ignore-missing SHA256SUMS
cosign verify-blob \
--certificate SHA256SUMS.pem \
--signature SHA256SUMS.sig \
--certificate-identity 'https://github.com/NOFireAI/ravel/.github/workflows/publish-images.yml@refs/tags/v0.28.0' \
--certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
SHA256SUMSDebug symbols
The shipped binaries are stripped of debug info but keep their symbol table,
so a backtrace resolves function names with nothing extra installed. Line
numbers come from the matching symbols archive. Each binary records the
basename of its symbol file, so unpacking the archive next to the binary is
enough and no symbol-file step is needed:
tar -xzf ravel-debug-symbols-linux-amd64.tar.gz
gdb ./ravel-server-linux-amd64gdb looks for ravel-server.debug in the same directory (and in
.debug/), which is the name inside the archive. Do not rename the extracted
.debug files.