[ AxiomCore · Documentation ]
Managed logic and concurrency
Write bounded business logic in managed actions, declare typed failures, private setup, transactions, parallel groups, operation and client policies, and durable jobs.
Managed logic lets you write an action's business rules in Acore instead of a
native callback: immutable locals, pure calls, branches, typed failures and
explicit await calls. This page is the reference for those statements and for
the declarations around them: failures, private configuration and secrets,
transactions, parallel groups, operation policies, durable jobs, and client
cache and retry policies. Backend examples were checked with
axiom check from CLI 0.149.1.
Statements
A managed { ... } block belongs to an action. Effects happen only at an
explicit await.
| Statement | Form |
|---|---|
| Local | let name = expression or let name = await call |
| Guard | guard condition else fail Failures.name(payload) |
| Branch | if condition { ... } else { ... }, including else if |
| Match | match value { case ... { ... } }, exhaustive over an enum, optional or outcome |
| Fail | fail Failures.name({field: value}) |
| Return | return expression |
| Await | await Resources.name.operation(input: value), await Actions.name(input: record), await parallel(...) { ... }, await transaction ..., await Jobs.name.enqueue(...) |
| Handle a failure | await call on_error problem { match problem { case Failures.name(binding) { ... } } } |
project(id: "managed-example", version: "1")
model Rename { title: String }
model Task { title: String }
model Problem { message: String }
failure invalidTitle(payload: Models.Problem, status: 422)
pure fn normalizeTitle(value: String): String = value.trim()
action rename(kind: ActionKind.command, input: Models.Rename, output: Models.Task) {
errors(Failures.invalidTitle)
managed {
let title = normalizeTitle(Input.title)
guard title.length > 0 else fail Failures.invalidTitle({message: "Title is required"})
if title.length > 200 {
fail Failures.invalidTitle({message: "Title is too long"})
} else {
return {title: title}
}
}
}The compiler rejects these, with AXSERVER_MANAGED_FLOW diagnostics:
- Shadowing a local (
locals must be immutable distinct identifiers without shadowing). - A statement after
returnorfail(unreachable statement after a terminal outcome). - A path that neither returns nor fails (
managed action must return or fail on every path). - Recursive functions or actions, and pure expressions that capture a resource, the clock or state.
- An awaited failure that is neither handled nor listed in the action's
errors(unhandled call failures must appear in this action's errors declaration). - More than 4,096 statement nodes in one block.
Every branch contributes to inferred requirements, including branches not taken
during a request. Branch locals stay in their scope. A handler must use an
exhaustive match on its failure binding and end every path with return or
fail; there is no implicit fallthrough and no assignment after a failed
await. Evaluation runs left to right and short-circuits Boolean and
coalescing expressions. Arithmetic is checked and string length counts Unicode
scalar values. An await cannot undo an earlier committed effect.
The checked expression grammar lists the operators you can use. The formatter keeps statement boundaries and tokens. Editor analysis uses the source on disk and unsaved buffers; it never runs providers or guests.
One checked data boundary
Models, validators and codecs form one checked boundary, described in
Validation. In managed code, backend inputs,
provider outputs and action outputs remain authoritative: a form uses only its
selected signed schema, and a guest helper cannot admit its own output or gain
an operation grant. Exact Int64 defaults and codecs never pass through a
floating-point representation.
Typed failures
An action declares a failure's full payload. An endpoint chooses which fields and audiences it may expose, and can map a different status.
project(id: "failure-example", version: "1")
model Selector { id: Int32 }
model Problem { message: String internalReason: String }
failure missing(payload: Models.Problem, status: 404)
action read(kind: ActionKind.query, input: Models.Selector, output: String) {
managed {
fail Failures.missing({message: "Task is unavailable", internalReason: "lookup failed"})
}
errors(Failures.missing)
}
endpoint readTask(method: GET, path: "/tasks/{id}") {
request { path("id", type: Int32) }
handle(action: Actions.read, input: {id: Input.path.id})
response(type: String)
errors(Failures.missing)
exposeFailure(failure: Failures.missing, fields: [.message], audiences: ["web"])
failureStatus(failure: Failures.missing, status: 410)
}| Declaration | Signature |
|---|---|
failure | failure name(payload: TypeRef, status: Int, category: FailureCategory?, retryable: Boolean?) |
errors | errors(Failures.a, Failures.b) or errors(infer) |
exposeFailure | exposeFailure(failure: Failure, fields: [.field], audiences: ["name"]) |
failureStatus | failureStatus(failure: Failure, status: Int) overrides the declared status |
FailureCategory values: invalidInput, unauthenticated, forbidden,
domain, invariant, policyExhausted, cancelled, deadline, internal,
indeterminate.
- The HTTP failure contains only
error.codeand the selectederror.payload. The audience selects locked artifacts; a caller header cannot select a larger payload. - The selected UI schema includes each case's payload and status. UI operation
error state exposes typed optional cases, such as
error.cases.missing?.message. - Malformed envelopes, unknown cases, extra fields and wrong representations become sanitized transport errors. New consumers never infer a payload from a message string. Code-only v1 consumers keep their declared legacy adapter.
- The initial public payload profile allows flat scalar or enum fields, or lists
of them. Nested models and opaque
Jsonneed a separately reviewed public payload. Portable integer payloads must stay in the exact JavaScript range. - Cancellation, deadlines, exhausted budgets, stale owners and host faults are
execution aborts, not business failures. An
on_errorhandler cannot recover them or publish disposed state.
Trusted tooling can generate a TypeScript decoder from a verified public
contract for one endpoint and audience. The output imports
@axiomcore/extension-sdk/schema, exports the selected case descriptor, and
derives a discriminated Failure type and a checked decode(wire, status)
function. Unapproved audiences and invalid descriptors fail generation, and
private payload fields are absent. Generation provides typing and validation
only; deployment still grants calls separately. Rust guests enable the matching
workspace SDK's optional schema feature for the same descriptors and exact
Int64 conversion. This helper belongs to the workspace toolchain; published
SDKs keep their declared APIs. Validate the exact Wasm artifact against its host
limits, including first-use initialization and conversion fuel, before
activation.
Failure inference
Opt in with errors(infer) on a managed action or a handled endpoint. Omitting
errors still means no declared cases, and an explicit list is still an
allowlist.
project(id: "inferred-failure", version: "1.0.0")
model Rename { title: String }
model Task { id: String title: String }
model Problem { message: String }
failure invalidTitle(payload: Models.Problem, status: 422)
pure fn normalize(value: String): String = value.trim()
action rename(kind: ActionKind.command, input: Models.Rename, output: Models.Task) {
errors(infer)
managed {
let title = normalize(Input.title)
guard title.length > 0 else fail Failures.invalidTitle({message: "Enter a title"})
let current = {id: "example", title: Input.title}
return {current with title: title}
}
}
endpoint renameTask(method: POST, path: "/tasks/rename") {
request { body("title") }
handle(action: Actions.rename, input: {title: Input.body.title})
errors(infer)
exposeFailure(failure: Failures.invalidTitle, fields: [.message], audiences: ["web"])
}The owning AxiomDeps.toml declares type = "backend" and audiences = ["web"].
- Inferred failures are the union across awaited actions and resources and all branches. An exhaustive terminal handler consumes its cases; re-failing or translating contributes the resulting case.
- Collected parallel outcomes stay values until you fail with them.
- Native, sandbox and resource implementations keep explicit failure signatures.
- Recursive inference, mixed
inferand explicit lists, and incomplete handlers are rejected. - Public case sets close before contract and plan validation.
exposeFailurestill selects payload fields and audiences. The failure's declared status is the default, andfailureStatusoverrides it.
Freeze the approved public interface
axiom check backend/main.acore --frozen-interface approved/backend.axiom
axiom build backend/main.acore --frozen-interface approved/backend.axiom --out backend.axiomYou choose the approved artifact. The check compares public interface
semantics, including types, failure cases and selected failure metadata. Any
change is rejected until the owner reviews the semantic diff and replaces the
approved baseline. Rejection happens before any project output is replaced. The
option does not authenticate a publisher on its own, so use verified release
artifacts in CI. It is separate from FlatBuffers slot compatibility
(--compatibility-baseline) and from disposable local baselines.
Editor repairs
For an undeclared awaited failure, the editor offers an explicit private
errors(Failures.name) repair and a terminal handler that propagates the typed
payload. For a partial handler, it can add the missing cases and keep existing
case bodies. Each proposed edit is compiled against the current unsaved imports
before it is offered. These fixes never add public projections, endpoint
exposure, grants or payload fields. A failure that needs more public review
cannot be repaired by widening its exposure.
Private configuration and secrets
project(id: "configuration-example", version: "1")
configuration billingSettings(public: [.maxConnections], audiences: ["web"]) {
origin: String(format: "https-origin")
maxConnections: Int32(min: 1, max: 16) = 4
}
secret billingKey(purpose: SecretPurpose.signRequest)
resource billing(
lifetime: ProviderLifetime.application,
configuration: Configurations.billingSettings,
secrets: [Secrets.billingKey]
) {
operation charge(input: String, output: String, effect: EffectKind.networkCall)
}| Declaration | Signature |
|---|---|
configuration | configuration name(public: [.field]?, audiences: [String]?) { field: Type = default } |
secret | secret name(purpose: SecretPurpose); the only purpose is signRequest |
resource | resource name(lifetime: ProviderLifetime?, dependencies: [Resource]?, configuration: Configuration?, transactionProfile: TransactionProfile?, secrets: [Secret]?) { operation ... } |
operation | operation name(input: TypeRef, output: TypeRef, effect: EffectKind, errors: [Failure]?) |
ProviderLifetime: application, invocation. EffectKind: read, write,
delete, networkCall, publish, consume, fileRead, fileWrite,
secretUse, schedule, spawnJob, log, trace, clock, random.
- Configuration schemas, defaults, checks and secret references stay in the private plan. Trusted startup resolves references, validates values and gives a compatible factory an exact schema digest.
- Fields listed in
publicget a separate audience-checked projection. The example exposes the pool limit, not the origin or the signing material. A trusted publisher can serve that projection through an ordinary checked endpoint. No public package gains private resolver access. - Bindings carry environment, file or vault references and explicit operation grants. The default resolver reads environment names; file and vault resolution need a qualified trusted resolver. Resolution happens only at startup, so rotation requires a restart unless a separately qualified provider supports more.
- A trusted factory receives an opaque
signRequesthandle that computes bounded HMAC-SHA256 signatures. Acore source and guests cannot read its bytes or serialize the handle. - Declaring a resource, inferring a dependency or resolving a secret does not grant an operation. Inferred resources describe requirements; private bindings, typed factory compatibility and grants still control substitution and execution. Native factory implementations remain trusted code within their declared and observed evidence boundaries.
See Secrets at runtime for bindings.
Owned calls and database transactions
Each action invocation owns a root context. Awaited actions and providers inherit its deadline, cancellation, generation and remaining budgets; a child cannot reset its caller's allowance. Immutable values can leave a block, but a transaction-scoped provider name cannot.
project(id: "transaction-example", version: "1")
model Create { title: String eventId: String idempotencyKey: String }
model Insert { title: String idempotencyKey: String }
model Outbox { eventId: String taskId: String }
model Task { id: String title: String }
model Event { eventId: String }
resource tasks(
lifetime: ProviderLifetime.application,
transactionProfile: TransactionProfile.postgres17_11
) {
operation insert(input: Models.Insert, output: Models.Task, effect: EffectKind.write)
operation appendOutbox(input: Models.Outbox, output: Models.Event, effect: EffectKind.write)
}
action create(kind: ActionKind.command, input: Models.Create, output: Models.Task) {
managed {
let result = await transaction database(
resource: Resources.tasks,
isolation: TransactionIsolation.serializable,
receipt: Input.idempotencyKey
) {
let task = await database.insert(input: {
title: Input.title, idempotencyKey: Input.idempotencyKey
})
await database.appendOutbox(input: {eventId: Input.eventId, taskId: task.id})
return task
}
return result
}
}resource,isolationandreceiptare required.TransactionIsolation:readCommitted,repeatableRead,serializable.TransactionProfile:postgres17_11.- Inside the block, call operations only through the scoped provider name
(
databasehere), and end withreturn name. - The initial profile accepts 1 to 16 operations and at most 32 SQL steps in one application-lifetime PostgreSQL 17.11 provider. Inputs are records of scalar input fields, earlier result fields and literals. Component isolation and receipt contracts must agree.
- HTTP calls, guest execution, unrelated resources, branch or loop bodies and mixed write and delete programs are rejected.
- Offline database preparation combines the component programs into one derived provider operation. Activation needs that prepared program, the exact typed interface, a reviewed binding and an explicit grant for the derived operation. Compiling the source alone does not establish an atomic database boundary, and runtime deadlines, budgets, cancellation, cleanup and commit recovery remain necessary.
- The receipt-bearing component must capture exactly
Input.idempotencyKey; changing that key changes the recovery identity.
See Migrations and persistence for preparation, tenant enforcement, commit recovery and durable outbox behavior.
Cleanup
Owned providers are released once, in reverse acquisition order, including after a business failure or cancellation. Release gets its own bounded cooperative budget and cannot restart business work. A primary failure keeps its code and payload; release failures appear separately as bounded cleanup issues. A release failure after an otherwise successful action becomes a host fault. Trusted native hooks must cooperate: their admission slot stays occupied until they actually exit. Qualified Wasm hosts also stop guest work at their containment boundary.
In the frontend, disposing an app, session, route or component invalidates old
generations. Responses and guest proposals from disposed owners cannot publish.
A stale revision is a typed conflict, and a rejected multi-patch proposal leaves
the supported local store unchanged. A synchronous UI action segment commits
before its next await or navigation; this does not make a remote mutation, or
several stores across adapters, one transaction.
Parallel groups
Acore describes the work and its bounds. The host owns admission, dispatch, cancellation, timers and joining. Pure functions stay deterministic and have no thread, sleep, clock or enqueue authority.
let summary = await parallel(limit: 2) {
first = Actions.find(input: Input)
second = Resources.tasks.lookup(input: Input)
}
return {first: summary.first, second: summary.second}- The result is a record whose fields have each call's output type. Field association follows declaration order; JSON key order and completion order are not guarantees.
- Branches capture immutable values from before the group, and cannot use another branch's result.
- The backend profile accepts query actions and resource reads. Writes, transactions, enqueue, streams and nested groups are rejected as direct branches. A query action must also pass the usual effect and action checks.
- A group has 1 to 32 distinct names and an explicit
limitof 1 to 8 active branches (parallel requires 1..32 branches and limit: 1..8). These bounds do not raise deployment grants, parent deadlines or root work budgets. - The native host checks action admission and resource grants before dispatch, and operation admission still applies at each boundary. Dispatched native work stays owned until its callback returns, even after cooperative cancellation.
ParallelMode.failFast is the default: a branch failure cancels siblings, stops
queued branches and drains started work. Parent cancellation or an invalid host
output prevents publication of the joined result. A propagated failure must
appear in the enclosing action's errors.
ParallelMode.collect returns a checked outcome for each branch. Match success
and every declared ordinary failure:
let sections = await parallel(mode: ParallelMode.collect, limit: 2) {
first = Actions.find(input: Input)
second = Actions.find(input: Input)
}
match sections.first {
case .success(task) { return task }
case Failures.unavailable(problem) { return {title: problem.message} }
}Collect handles declared business outcomes only. Admission denial, cancellation, invalid boundary output, exhausted budgets and host faults end the group, so collect cannot turn denied authority into a partial authorized result.
Operation policies
An operationPolicy applies to one named resource call. serverPolicy keeps its
action ceilings, and client retry policies keep their meaning.
operationPolicy taskRead(
totalTimeout: 2s,
attemptTimeout: 500ms,
attempts: 3,
backoff: Exponential(initial: 100ms, max: 500ms, multiplier: 2.0),
jitter: Jitter.full,
retryOn: [Failures.unavailable],
safety: RetrySafety.readOnly,
maxInFlight: 8,
queueWait: 100ms,
maxQueued: 8,
circuitThreshold: 5,
resetAfter: 1s
)let task = await Resources.tasks.lookup(
input: Input,
policy: OperationPolicies.taskRead
)
return task| Argument | Required | Notes |
|---|---|---|
totalTimeout | Yes | Covers queue wait, every attempt and backoff |
attemptTimeout | Yes | Can only tighten the remaining parent ceiling |
safety | Yes | RetrySafety.readOnly, idempotent, receiptBound or unsafe |
attempts | No | unsafe requires exactly one attempt and no retry cases |
backoff | No | Fixed(delay: 100ms) or Exponential(initial: 100ms, max: 500ms, multiplier: 2.0). The multiplier is 1 to 16 with at most three decimal places. |
jitter | No | Jitter.none or Jitter.full |
retryOn | No | Failures the operation declares that are marked retryable: true |
receipt | No | Receipt name for receiptBound safety |
maxInFlight, queueWait, maxQueued | No | Admission bounds |
circuitThreshold, resetAfter | No | Circuit breaker |
- Operation-policy backoff arguments differ from the client
FixedandExponentialconstructors below. - Cancellation and elapsed deadlines take precedence over a late success.
- A circuit is keyed by the declared policy, resource and operation, with one recovery probe. Input values cannot create circuit keys.
- Authorization, malformed input or output, cancellation, validation,
indeterminate commits and unrelated provider faults never become retryable,
whatever the HTTP status (
retryOn requires explicitly retryable domain cases). - A trusted provider factory independently attests
readOnly,idempotentor an establishedreceiptBoundmapping. Declaring a read effect, naming a receipt or completing a snippet does not prove replay safety. Activation rejects mismatched evidence and a second automatic retry owner beneath a retrying policy. Policies on actions and nested policy wrappers are rejected.
Children share the invocation's work accounting. A new HTTP request and a worker's next durable delivery attempt each get their own invocation tree. For a job that allows three deliveries over a provider that allows three physical attempts, the upper bound is nine provider attempts, subject to each tree's budgets and the job's total deadline. This is an upper bound, not a promise that all attempts run. Attempt observations identify the policy, root, operation, attempt, eligibility and remaining work, and omit inputs and credentials.
Durable jobs
Only a managed command can enqueue a declared job. The job's action must be guarded.
project(id: "enqueue-example", version: "1.0.0")
model Reminder { taskId: String producerId: String }
model Receipt { id: String }
guard signed(kind: GuardKind.authenticated)
action sendReminder(kind: ActionKind.command, input: Models.Reminder, output: Models.Receipt) {
require(guard: Guards.signed)
managed { return {id: Input.taskId} }
}
job remindTask(action: Actions.sendReminder, attempts: 3, deadline: 2h)
action scheduleReminder(kind: ActionKind.command, input: Models.Reminder, output: Models.Receipt) {
managed {
let receipt = await Jobs.remindTask.enqueue(
input: Input,
key: Input.producerId,
delay: 1h
)
return receipt
}
}| Rule | Diagnostic when broken |
|---|---|
| The job's action is guarded and not a stream | background bindings require a guarded non-stream action |
| Enqueue happens in a command | job enqueue requires a command boundary |
delay is at most seven days | job delay exceeds seven days |
delay is shorter than the job's deadline | job delay must be smaller than its durable total deadline |
- The compiler checks
inputagainst the job action's input and infers the receipt type{id: String}.keyis the producer key string. - The action's private plan records its exact job dependencies. Activation needs
a trusted delivery controller bound to the same plan, an
enqueue:<job>delivery grant and explicit worker identities. Missing configuration is rejected before serving. - The receipt confirms durable submission. Producer identity and payload and delay fingerprints keep the existing duplicate and conflict rules. They do not make external effects exactly-once. Derive namespaced producer keys consistently for your tenant and request boundary.
- Worker authorization is re-checked on every delivery attempt, and browser credentials never become worker credentials. Frontends request job creation through an authorized public command.
axiom server invoke,validate,run,devandaxiom serveaccept an explicit--delivery /absolute/path/delivery.jsonnext to the reviewed server deployment. The configuration stays private.devreload prepares a new exact-plan configuration from the captured inputs and keeps the reviewed grants and credential references.- A durable namespace is bound to its plan. Changing the plan needs a reviewed new namespace or migration; otherwise reload is rejected and the last validated service stays active. Queued work for an old plan still needs its original qualified worker. Starting a development server does not start a durable worker. Container and Workers packaging need their own qualified controller and worker integration.
- Schedules keep their timezone, overlap, missed-run, bounded catch-up, lease, fencing, dead-letter, cancellation and explicit redrive rules.
See Events, jobs and webhooks for the private delivery configuration and worker workflow.
Frontend groups and foreground timers
A frontend action can join declared query operations with their captured arguments:
let sections = await parallel(limit: 2) {
taskList = tasks.run()
taskCounts = counts.run()
}- Each branch must be an enabled, finite, non-paginated query operation. The group does not update the individual operations' data snapshots.
- Fail-fast cancels and drains. Collect returns each field as
{ok, value, error}with nullable success and error members. Only checked ordinary contract errors are collected; transport, schema, authorization, disposal and budget faults end the group. - Disposing the owner cancels its groups and suppresses late publication.
Duration options keep query timing readable:
query tasks = Tasks.list_tasks() options(
debounce: 150ms,
poll: 30s,
focus: true,
reconnect: true
) when session.status == "authenticated"debounce_msis still supported. Equal values agree; contradictorydebounceanddebounce_msvalues are rejected.pollranges from 100ms to one day and is query-only. The next timer is scheduled after the current operation settles.- A hidden owner cancels polled work and pauses timers; returning to the foreground schedules a fresh read. Disposal, a disabled state, argument changes and session changes invalidate earlier timer generations.
- Browser visibility events supply foreground state. Native generated code needs the facade's explicit foreground-event capability; an older host rejects polling instead of promising background execution. Timers need a live foreground owner. Use server jobs and schedules for durable background work.
Client cache and retry policies
A service contract can declare cache and retry intent for clients. The runtime applies only what the selected client target implements, so inspect the generated contract and validate the target before relying on it. These declarations are not server enforcement and do not replace backend rate limits, timeouts, circuit breakers or observability.
project(id: "policy-example", version: "1.0.0")
policies(
cache: CachePolicy(strategy: CacheStrategy.staleWhileRevalidate, ttlSeconds: 3600),
retry: RetryPolicy(
maxAttempts: 3,
retryOnCodes: [502, 503, 504],
retryOnTimeout: true,
backoff: ExponentialBackoff(
initialDelay: "500ms", maxDelay: "10s", multiplier: 2.0, useJitter: true
)
)
)
endpoint get_profile(method: GET, path: "/profile") {
response(type: Json)
cache(strategy: CacheStrategy.cacheFirst, ttl: 5m)
retry(attempts: 2)
}Use defaults for project-wide settings and policy for a named, reusable set:
project(id: "policy-example2", version: "1.0.0")
defaults {
cache(strategy: CacheStrategy.networkFirst, ttl: 30s)
}
policy reads {
cache(strategy: CacheStrategy.cacheFirst, ttl: 2m)
retry(
attempts: 3,
onStatus: [502, 503, 504],
onTimeout: true,
backoff: Exponential(initial: 250ms, max: 2s, multiplier: 2.0, jitter: true)
)
}
endpoint listProjects(method: GET, path: "/projects") {
response(type: Json)
use(reads)
}| Form | Signature and defaults |
|---|---|
cache(...) | cache(strategy: CacheStrategy, ttl: Duration); whole seconds |
retry(...) | retry(attempts: Int = 3, onStatus: List<Int> = [502, 503, 504], onTimeout: Boolean = true, backoff: Backoff = Exponential()) |
Exponential(...) | initial = 1s, max = 30s, multiplier = 2.0, jitter = true |
Fixed(...) | initial = 1s, max = 30s, jitter = true |
Linear(...) | initial = 1s, max = 30s, step = 1s, jitter = true |
CachePolicy(...) | strategy and ttlSeconds, both required |
RetryPolicy(...) | maxAttempts = 3, retryOnCodes = [502, 503, 504], retryOnTimeout = true, backoff = ExponentialBackoff |
ExponentialBackoff(...) | initialDelay = "1s", maxDelay = "30s", multiplier = 2.0, useJitter = true. FixedBackoff and LinearBackoff (with step = "1s") take the same strings. |
use(name) | Same as use(policy: name); allowed in endpoints, policies and defaults |
- An explicit endpoint policy wins over a variant cache policy, which wins over project defaults. Do not infer an override from the HTTP method.
- A reused policy expands into the endpoint declaration; it is not a runtime
call. Defining the same setting twice, including through
use, is an error, and so are policy cycles and unknown names. - Inspect effective settings and their origins with
axiom explain main.acore --provenance. - Review retries for operations that are not idempotent, and declare security or idempotency requirements where needed.
| Authoring value | Contract value | Intent |
|---|---|---|
CacheStrategy.networkOnly | network_only | Never answer from the contract cache |
CacheStrategy.cacheFirst | cache_first | Use a valid cached value before the network |
CacheStrategy.networkFirst | network_first | Prefer the network, then the target's fallback |
CacheStrategy.staleWhileRevalidate | stale_while_revalidate | Serve an existing value while refreshing |
Storage, eviction, privacy and background refresh are target and runtime
concerns. Private serverCache policies are a separate host boundary: their
identity includes the exact plan, schema, codec, normalized input and verified
identity or permission partition. Every read authenticates and runs guards,
even on a hit. A shared fill has independent waiter cancellation and a fixed
action deadline capped at 30 seconds. invalidateOn observes accepted command
success; it does not attest an external transaction commit. See
Integrations and transports.
Extensions
Guests cannot create detached threads, raise concurrency or budgets, supply
worker credentials or attest their own retry safety. The Rust SDK has a separate
EffectGroup descriptor (axiom-extension-effect-group/v1) with named
branches, a limit and fail-fast or collect mode, sent with
ExtensionResponse::yield_group. An ordered EffectPlan stays sequential.
The broker preflights every exact grant and initially permits contract queries. It reserves byte capacity equally across branches before dispatch, and unused capacity cannot be borrowed while siblings run. Only a trusted adapter that supports groups activates them. Domain outcomes may be collected; denial, revocation, invalid input or output, exhaustion, host faults and parent cancellation are terminal. Started adapter futures are drained before release or guest resume, and the one-batched-resume and kernel retirement rules still apply. Guest scheduling uses granted host queue operations; there is no ambient guest timer or retained private scheduler. See Extension reference.
Editor, toolchain and testing
- The shared language server shows group signatures, inferred record and
outcome types, branch and policy navigation, job input signatures and
compiler diagnostics in unsaved buffers. VS Code and Zed include the
acore-parallel,acore-operation-policy,acore-enqueueandacore-pollstarters. A policy hover describes the declaration; provider attestation is still checked at activation. Editing or completing source never dispatches work. - VS Code uses contextual tokens and starters; Zed uses the matching Tree-sitter parser and queries. Both rely on compiler validation for meaning. Zed offers source navigation and explicit CLI reports where no live panel exists. Neither resolves configuration values while you edit.
- Build the compiler, language server, UI helper, host, SDK and grammar from one
compatible toolchain. Owned frontend operations require IR reader 12 and
acore-owned-operations/v1; programs that do not use them keep their older reader requirements. - Test real overlap with controlled barriers and keep active work through cancellation. Test timers with a virtual clock and assert one owner timer, foreground pause, session invalidation and no late publication. Test durable recovery separately against Redis and real worker processes; a timer test cannot qualify job recovery.