Ambient/background runtime
View SourceThe 0.3.0 ambient runtime accepts event, queue, and schedule deliveries without
turning transport consumers into invocation owners. A registered trigger holds
an immutable adk_runner value. Each accepted event gets a stable binary
reference and one temporary child under adk_ambient_job_sup; the child owns
admission, retry, the current adk_run, deadline cleanup, and exactly one
ambient terminal outcome.
Public API
ok | {error, Reason} =
adk_ambient:register_trigger(Name, Runner, TriggerOptions),
{ok, EventRef} | {ok, EventRef, duplicate} | {error, Reason} =
adk_ambient:submit(Name, Event),
{ok, Status} = adk_ambient:status(EventRef),
Outcome = adk_ambient:await(EventRef, Timeout),
ok = adk_ambient:cancel(EventRef, Reason),
{ok, TriggerStatus} = adk_ambient:trigger_status(Name),
ok = adk_ambient:unregister_trigger(Name).run/2,3 is the submit-then-await convenience. unregister_trigger/1 rejects
a busy trigger rather than orphaning its queued or active events.
Every event is a map with these keys:
payload(required): non-empty UTF-8 text or a validated versionedadk_contentvalue accepted by Runner;idempotency_key(required): non-empty UTF-8 binary used for node-local dedupe;session: required only by theexplicitsession policy;timeout_ms: optional shorter deadline than the trigger maximum;metadata: optional application metadata returned by status.
Unknown event and trigger option keys, non-JSON metadata/content, and any
secret-bearing key or token-bearing URL fail before admission. Canonical
binary-key metadata and content are bounded by max_event_bytes before they
are retained. Registration also validates that its opaque value is an actual
adk_runner; malformed routes do not survive until delivery time.
Outcomes and status
Ambient outcomes are:
{completed, #{run_id := RunId, output := Output}}
{paused, #{run_id := RunId, event := PauseEvent}}
{failed, Reason}
{timed_out, deadline_exceeded}
{cancelled, Reason}A pause is terminal for the ambient delivery and exposes the underlying run ID
for the application's normal adk_run:resume/2,3 policy. Status never exposes
worker pids, monitor references, Runner records, or admission permits.
Session policies
Registration must select exactly one policy:
#{mode => per_event, user_id => <<"worker">>, prefix => <<"event-">>}
#{mode => explicit}
#{mode => shared, user_id => <<"worker">>, session_id => <<"batch">>}per_event hashes the idempotency key into a stable distinct session ID.
Retries therefore see the same event session while different deliveries cannot
cross conversation histories. explicit lets a verified transport adapter
supply user/session identity. shared is intentional shared history and must
not be selected merely as a convenience default.
Bounds and failure semantics
The registry reads application-wide max_triggers and max_events ceilings
from application:get_env(erlang_adk, ambient_runtime, #{}) when its
supervision tree starts.
Each trigger separately bounds concurrency, queued events, retained terminal
events, event bytes, and waiters. Queue intake is a gen_server:call, so a
source learns whether the delivery was accepted before acknowledging it. The
runtime owns one timer for the earliest queued deadline rather than one timer
per delivery.
One admission permit covers the complete event, including retry backoff. It is
released synchronously before the ambient terminal notification is published.
admission_id defaults to the trigger name; deployments should align that
identity's admission-controller limit with the trigger's max_concurrency.
If node-wide admission rejects a job after local acceptance, the event fails
explicitly and a durable source may redeliver with the same idempotency key.
Each retry attempt starts a new independently supervised adk_run; an attempt
guard cancels that run if a hard retry timeout or job cancellation kills its
awaiter. Queue wait, backoff, and every attempt consume one monotonic absolute
deadline. Retry jitter supports none and full jitter through adk_retry.
Idempotency entries live exactly as long as retained terminal status. The core does not claim distributed or restart-durable dedupe. A durable broker remains the delivery source of truth and should redeliver using its stable delivery ID. Tools with external effects must independently enforce idempotency because an entire failed invocation can be retried.
Trigger sources
adk_trigger_source defines the supervised adapter contract. The bundled
adk_trigger_schedule adapter uses a single fixed-delay timer, derives a key
from schedule ID plus wall-clock interval slot, and submits through the same
bounded API. It deliberately does not parse cron expressions.
Provider adapters for Pub/Sub, Eventarc, Kafka, RabbitMQ, SQS, or cron services
belong in the deployment application. They verify/authenticate provider input,
normalize it to the event map, call adk_ambient:submit/2 with backpressure,
and acknowledge only an accepted or duplicate delivery. No cloud client SDK is
a dependency of erlang_adk.
Verification
adk_ambient_testcovers queue bounds/deadlines, dedupe, retries, cancellation cleanup, explicit and per-event sessions, and schedule sources.adk_ambient_concurrency_SUITEsubmits 100 events, observes an exact concurrency ceiling of eight, and verifies empty runtime/admission state.- Dialyzer checks the public/status terms and cleanup paths.