AbstractCore integration¶
This integration wires AbstractRuntime effects to AbstractCore so workflows can execute:
- EffectType.LLM_CALL
- EffectType.TOOL_CALLS
Implementation pointers (this repo):
- factories: src/abstractruntime/integrations/abstractcore/factory.py
- effect handlers: src/abstractruntime/integrations/abstractcore/effect_handlers.py
- tool executors: src/abstractruntime/integrations/abstractcore/tool_executor.py
- default toolsets (incl. comms gating): src/abstractruntime/integrations/abstractcore/default_tools.py
Install¶
pip install abstractruntime
The base install includes AbstractCore 2.13.38 or newer. That is the supported baseline for the current server auth split (Authorization for server auth, X-AbstractCore-Provider-API-Key for provider overrides), generated-media contracts, image upscaling, capability catalog, prompt-cache control-plane endpoints, durable bloc prompt-cache helpers, bindings and lifecycle operations, task-aware model residency for text/image/video/TTS/STT, current tool catalog, AbstractCore's public output-selector contract, async/sync text-generation output-selector parity, video generation endpoints, the public local vision-cache catalog helper used by Runtime discovery, vision adapter discovery plus batch/LoRA media controls, and the released shared workspace/file-filter utility surface used by Runtime packaging and integration checks.
The base install also includes the remote-light media/capability plugins needed
for AbstractCore's multimodal generate(..., output=...) path. Local
image/video/voice/music generation still depends on configured AbstractCore
capability backends and hardware profiles:
pip install "abstractruntime[apple]"
pip install "abstractruntime[gpu]"
With abstractmusic>=0.1.12, the base music integration includes the lightweight remote ACE Music backend without local model-runtime extras. The MCP worker entrypoint is included in the base Runtime install.
Execution modes¶
The factories implement three execution modes (ADR-0002):
- Local: in-process AbstractCore providers + local tool execution
- Remote: HTTP to an AbstractCore server (/v1/chat/completions) + tool passthrough
- Hybrid: remote LLM + local tool execution
Local mode currently uses MultiLocalAbstractCoreLLMClient as the built-in LLM router. Despite the name, it is not a
local+remote combo client: it routes among multiple in-process local (provider, model) clients and keeps them warm in
the current process. Remote model execution is a separate topology exposed through create_remote_runtime(...) and
create_hybrid_runtime(...).
Factory functions (exported from abstractruntime.integrations.abstractcore):
- create_local_runtime(...)
- create_remote_runtime(...)
- create_hybrid_runtime(...)
Runtime stays explicit at the boundary: Gateway/hosts construct these clients with the Core server URL, Core server auth headers, provider/model defaults, retry policy, tool executor, and artifact store they intend to use. Runtime does not read ABSTRACTGATEWAY_* environment variables directly and does not reinterpret Gateway bearer tokens as Core server tokens or provider keys. Gateway-owned config should be consumed by Gateway, then passed to Runtime through explicit run state, effect payloads, constructor arguments, or Runtime-owned environment variables.
Minimal LLM workflow¶
from abstractruntime import Effect, EffectType, StepPlan, WorkflowSpec
from abstractruntime.integrations.abstractcore import create_local_runtime
def ask_model(run, ctx):
return StepPlan(
node_id="ask_model",
effect=Effect(
type=EffectType.LLM_CALL,
payload={
"prompt": "Answer in one sentence: what is durable workflow state?",
"params": {"temperature": 0.0, "max_tokens": 128},
},
result_key="llm",
),
next_node="done",
)
def done(run, ctx):
llm = run.vars.get("llm") or {}
return StepPlan(node_id="done", complete_output={"answer": llm.get("content")})
workflow = WorkflowSpec(
workflow_id="abstractcore_llm_demo",
entry_node="ask_model",
nodes={"ask_model": ask_model, "done": done},
)
rt = create_local_runtime(provider="ollama", model="qwen3:4b")
run_id = rt.start(workflow=workflow)
state = rt.tick(workflow=workflow, run_id=run_id)
print(state.output)
LLM_CALL payload (recommended shape)¶
Effect(type=EffectType.LLM_CALL, payload=...)
{
"prompt": "...",
"text": "optional text alias, useful for TTS",
"messages": [{"role": "user", "content": "..."}],
"system_prompt": "...",
"media": ["path/or/artifact-ref"],
"output": {"modality": "text|image|video|voice|music", "task": "optional"},
"tools": [{"name": "...", "description": "...", "parameters": {...}}],
"params": {
"temperature": 0.0,
"max_tokens": 256,
"base_url": null
}
}
Notes:
- Remote mode supports per-request dynamic routing by forwarding params.base_url to the AbstractCore server request body (src/abstractruntime/integrations/abstractcore/llm_client.py).
- Remote mode sends per-request provider key overrides from params.api_key / params.provider_api_key as X-AbstractCore-Provider-API-Key headers. Server/master auth should be supplied separately through the client's configured headers, usually Authorization: Bearer <ABSTRACTCORE_SERVER_API_KEY>.
- Local mode treats base_url and provider API keys as provider-construction concerns. MultiLocalAbstractCoreLLMClient can construct a per-call client when a host injects params.base_url plus params.api_key or params.provider_api_key (for example from a Gateway provider endpoint profile), then strips those fields before calling the provider.
- media accepts one item or a list. Durable artifact refs such as {"$artifact": "...", "filename": "speech.wav"} are materialized to temporary files for AbstractCore and never stored as raw bytes in RunState.
- output may be top-level or inside params; top-level outputs is accepted as a runtime alias for AbstractCore's output.
- output.tags, when present, are merged into the generated artifact metadata. Runtime metadata such as run_id and tags is used by AbstractRuntime's ArtifactStore boundary and is not forwarded as provider-specific generation kwargs.
- Host-supplied run defaults such as run.vars["_runtime"]["provider"] and run.vars["_runtime"]["model"] are persisted as JSON-safe routing metadata; provider clients, auth objects, downloaded model handles, and server sessions are not durable runtime state.
Runtime grounding¶
AbstractRuntime records per-call grounding as structured response metadata under metadata.runtime_grounding. The current fields include local datetime, timezone when detectable, country, source, whether prompt injection occurred, and an optional user identity when supplied by trace metadata or local environment.
For text/chat LLM calls only, the same grounding is rendered into the current user turn as a tagged runtime envelope:
<runtime_metadata>{"country":"FR","local_datetime":"2026-05-13T18:00:00+02:00"}</runtime_metadata>
hello
This makes time/location/user context visible to the LLM without mutating the durable human message into a natural-language prefix. If a model echoes the runtime-owned envelope, AbstractRuntime removes that envelope from user-facing response text while preserving metadata.runtime_grounding for audit.
Direct media requests, including image generation, TTS, and transcription, do not receive prompt-injected grounding. They still receive trace headers/tags for observability and artifact ownership, but TTS input and image prompts remain the literal text supplied by the workflow.
Multimodal generation¶
AbstractRuntime forwards AbstractCore's unified generate(..., output=...) selector and normalizes multimodal responses into JSON-safe, artifact-backed results.
Generate an image:
Effect(
type=EffectType.LLM_CALL,
payload={
"prompt": "A red ceramic mug on a white table.",
"output": {"modality": "image", "format": "png", "width": 1024, "height": 1024},
},
result_key="image_result",
)
Generate speech:
Effect(
type=EffectType.LLM_CALL,
payload={
"text": "Hello from AbstractRuntime.",
"output": {"modality": "voice", "voice": "coral", "format": "wav"},
},
result_key="speech_result",
)
Generate music:
Effect(
type=EffectType.LLM_CALL,
payload={
"text": "Warm lo-fi piano with brushed drums.",
"output": {"modality": "music", "provider": "acemusic", "model": "ace-step", "format": "wav"},
},
result_key="music_result",
)
Generate video:
Effect(
type=EffectType.LLM_CALL,
payload={
"prompt": "Glowing data streams converge into a geometric logo.",
"output": {
"modality": "video",
"task": "text_to_video",
"provider": "mlx-gen",
"model": "Wan-AI/Wan2.2-TI2V-5B-Diffusers",
"format": "mp4",
"num_frames": 41,
"fps": 24,
"steps": 10,
},
},
result_key="video_result",
)
Image to video:
Effect(
type=EffectType.LLM_CALL,
payload={
"prompt": "Add a slow camera orbit.",
"media": {"$artifact": "source_image_artifact_id", "type": "image", "role": "source"},
"output": {
"modality": "video",
"task": "image_to_video",
"provider": "mlx-gen",
"model": "Wan-AI/Wan2.2-TI2V-5B-Diffusers",
"format": "mp4",
},
},
result_key="video_result",
)
Transcribe/analyze audio:
Effect(
type=EffectType.LLM_CALL,
payload={
"media": {"$artifact": "audio_artifact_id", "filename": "speech.wav"},
"output": "text",
},
result_key="transcript",
)
Generated binary media requires a runtime ArtifactStore and is stored there. The persisted result contains artifact references:
{
"outputs": {
"image": [
{
"modality": "image",
"task": "image_generation",
"artifact_id": "...",
"artifact_ref": {"$artifact": "...", "content_type": "image/png"}
}
]
}
}
Media-only normalized results now distinguish orchestration identity from the actual media backend:
runtime_provider/runtime_model: the runtime-side orchestration identity, when relevantmedia_provider/media_model: the actual image/video/voice/music backend identity surfaced from the generated output
For local one-shot subprocess image generation, runtime metadata also records execution_mode="local_one_shot_subprocess".
Long-running generated media may expose provider progress callbacks. Runtime injects a transient on_progress callback during LLM_CALL execution and persists each callback as an EMIT_EVENT ledger record named abstract.progress. The callback itself is never stored in the effect payload or run vars.
Remote runtimes support chat media by sending OpenAI-compatible data URL content arrays to AbstractCore Server. They also support image generation (/v1/images/generations), image edits (/v1/images/edits or /{provider}/v1/images/edits), image upscaling (/v1/images/upscale or /{provider}/v1/images/upscale), text-to-video (/v1/videos/generations), image-to-video (/v1/videos/edits or /{provider}/v1/videos/edits), TTS (/v1/audio/speech), music generation (/v1/audio/music), and STT (/v1/audio/transcriptions) with the same artifact-backed result shape. The Runtime/Core request surface now forwards task-specific media controls including count/n, seeds, ordered lora_adapters, and video flow_shift. Remote media endpoint calls do not inherit the chat model by default; pass an output-specific model only when you want a remote provider/model instead of the server's configured capability default. Remote STT requires exactly one audio media item that resolves to a local file path or artifact-backed temporary file. Remote image edits, image upscaling, and image-to-video require one source image media item resolving to a local path or artifact-backed temporary file. For voice clone/register or reference-guided TTS, use local execution so AbstractCore can use its in-process capability dispatcher. Runtime does not import abstractmusic directly; local music support comes through the configured AbstractCore capability stack.
Remote multimodal generation currently supports one output selector per LLM_CALL. Hybrid runtimes use the same remote LLM/media path as remote mode while executing tools locally. Local runtimes can use AbstractCore's in-process multimodal dispatcher for richer capability plugin behavior.
Local media residency is intentionally explicit when unsupported. MODEL_RESIDENCY results for local image_generation, image_upscale, video_generation, text_to_video, image_to_video, tts, stt, and music_generation return:
code="model_residency_unsupported"requires_long_lived_server=trueconfig_hintpointing toABSTRACTCORE_SERVER_BASE_URL
Image/video-generation residency responses also include execution_mode="local_one_shot_subprocess" because local generated media can be isolated into one-shot workers unless a long-lived Core server owns the media backend.
When the workflow marks residency as optional (required=false), the effect still completes durably but includes status_hint="warning" and degraded=true so hosts can render the no-op honestly.
Remote auth example:
from abstractruntime.integrations.abstractcore import create_remote_runtime
rt = create_remote_runtime(
server_base_url="http://127.0.0.1:8000",
model="openai/gpt-4o-mini",
headers={"Authorization": "Bearer server-master-key"},
)
Then pass a per-request upstream provider key through params.provider_api_key only when the AbstractCore server is acting as a provider proxy for that request:
payload = {
"prompt": "Summarize this in one sentence.",
"params": {
"provider_api_key": "sk-provider-key",
"base_url": "http://127.0.0.1:1234/v1",
},
}
TOOL_CALLS payload¶
{
"tool_calls": [
{
"name": "tool_name",
"arguments": {"x": 1},
"call_id": "optional (provider id)",
"runtime_call_id": "optional (stable; runtime-generated)"
}
],
"allowed_tools": ["optional allowlist (order-insensitive)"]
}
Notes:
- runtime_call_id is generated/normalized by the runtime for durability (src/abstractruntime/core/runtime.py).
- In remote/passthrough mode, a host/worker boundary can use runtime_call_id as an idempotency key.
Tool execution modes¶
Tool execution is controlled by the configured ToolExecutor (src/abstractruntime/integrations/abstractcore/tool_executor.py):
- Executed (trusted local): use
MappingToolExecutor(recommended) orAbstractCoreToolExecutor. - Passthrough (untrusted/server/edge): use
PassthroughToolExecutor. - The
TOOL_CALLShandler returns a durableWAITINGrun state. - The host executes the tool calls externally and resumes the run with results (
Runtime.resume(...)/Scheduler.resume_event(...)). - Approval-gated local execution: wrap a trusted executor with
ApprovalToolExecutor. - Safe read-only/default bridge tools can run immediately.
- Riskier or unknown tools return a durable
approval_requiredwait. - A thin client can resume with
{"approved": true}to execute the approved calls in-runtime, or{"approved": false, "reason": "..."}to return structured tool errors.
Approval example:
from abstractruntime.integrations.abstractcore import (
ApprovalToolExecutor,
MappingToolExecutor,
ToolApprovalPolicy,
create_local_runtime,
)
def write_file(*, path: str, content: str):
with open(path, "w", encoding="utf-8") as f:
f.write(content)
return {"path": path, "bytes": len(content.encode("utf-8"))}
tools = ApprovalToolExecutor(
delegate=MappingToolExecutor({"write_file": write_file}),
policy=ToolApprovalPolicy(),
)
rt = create_local_runtime(provider="ollama", model="qwen3:4b", tool_executor=tools)
Prompt-cache control plane and durable blocs¶
AbstractRuntime's AbstractCore integration now exposes a public host-control facade for prompt-cache, durable bloc/KV prompt-cache operations, and model-residency operations:
get_abstractcore_host_facade(runtime)AbstractCoreHostFacadeget_prompt_cache_capabilities(...)get_prompt_cache_stats(...)prompt_cache_set(...)prompt_cache_update(...)prompt_cache_fork(...)prompt_cache_clear(...)prompt_cache_prepare_modules(...)list_prompt_cache_exports(...)prompt_cache_export(...)prompt_cache_import(...)upsert_text_bloc(...)get_bloc_record(...)list_blocs(...)get_bloc_kv_manifest(...)ensure_bloc_kv_artifact(...)load_bloc_kv_artifact(...)list_bloc_kv_artifacts(...)delete_bloc_kv_artifact(...)prune_bloc_kv_artifacts(...)delete_bloc(...)get_model_residency_capabilities(...)list_model_residency(...)load_model_residency(...)unload_model_residency(...)
Behavior by execution mode:
- Local (
MultiLocalAbstractCoreLLMClient/LocalAbstractCoreLLMClient): delegates to the in-process AbstractCore provider and normalizes responses into the same JSON-safe shape used by the endpoint. - Remote / Hybrid (
RemoteAbstractCoreLLMClient): proxies/acore/prompt_cache/*and/acore/models/*on the configured AbstractCore server. - When the remote target is the multi-provider AbstractCore server proxy rather than a direct AbstractEndpoint, callers can forward upstream
base_urlthrough these prompt-cache methods. Per-request provider key overrides supplied asapi_key/provider_api_keyare converted toX-AbstractCore-Provider-API-Keyheaders, not request bodies or query strings. - For durable bloc/KV methods,
base_urltakes precedence over local loaded-runtime selectors. Runtime omitsprovider,model, andruntime_idwhenbase_urlis supplied so Core takes the upstream endpoint branch cleanly.
Contract notes:
- Capability discovery is explicit: callers can branch on
capabilities.mode(none,keyed,local_control_plane) andsupports_*flags. - Unsupported operations return structured payloads with
supported=false,operation,code, andcapabilities. - When a provider reports
mode=local_control_plane(for example MLX, or GGUF models whose llama.cpp chat format has an exact cached renderer), the runtime can maintain a compartmentalizedsystem | tools | historycache path automatically. - When a provider reports
mode=keyed, the runtime still forwards stableprompt_cache_keys but skips module preparation/fork/update orchestration. - This surface is intentionally host-oriented; the runtime effect handlers still only use prompt caching during LLM execution, but gateway/CLI hosts can now manage prompt caches and durable bloc/KV artifacts through the public facade instead of reaching through to provider internals.
- Automatic per-session prompt-cache keys are enabled by
run.vars["_runtime"]["prompt_cache"],LLM_CALL.params.prompt_cache_key, or the Runtime-ownedABSTRACTRUNTIME_PROMPT_CACHEprocess default. Gateway-specific prompt-cache env vars should be translated by Gateway into_runtime.prompt_cache. - Durable exact reuse uses
LLM_CALL.params.prompt_cache_binding. If a binding includeskey, Runtime adopts it as the effective cache key, rejects mismatches before provider execution, and skips auto-derived session-key injection for that call. - Automatic prompt-cache key derivation is text/chat-only. Non-text output selectors such as image, voice, music, and transcription may carry an explicit
prompt_cache_binding, but Runtime does not derive a session cache key for them. - Local Runtime owns the bloc store root policy:
- default local root:
~/.abstractruntime/blocs - default file-runtime root:
<base_dir>/blocs - explicit
bloc_root_dir=...overrides are allowed when hosts need a different root - The three prompt-cache tracks are distinct:
- session prompt cache: best-effort volatile reuse
- durable bloc prompt cache: exact reuse through bloc/KV/binding
- host-local prompt-cache export/import admin: optional operator tooling around live local provider cache state, separate from durable workflow memory
Host-side prompt-cache example:
from abstractruntime.integrations.abstractcore import (
create_local_runtime,
get_abstractcore_host_facade,
)
rt = create_local_runtime(provider="mlx", model="mlx-community/Qwen3-4B-4bit")
facade = get_abstractcore_host_facade(rt)
caps = facade.get_prompt_cache_capabilities()
if caps.get("capabilities", {}).get("supports_prepare_modules"):
facade.prompt_cache_prepare_modules(
namespace="assistant",
modules=[
{"module_id": "system", "system_prompt": "You are concise."},
{"module_id": "tools", "tools": [{"name": "read_file", "parameters": {"type": "object"}}]},
],
)
Host-side durable bloc example:
from abstractruntime.integrations.abstractcore import (
create_local_file_runtime,
get_abstractcore_host_facade,
)
rt = create_local_file_runtime(
base_dir="./runtime-data",
provider="mlx",
model="mlx-community/Qwen3-4B-4bit",
)
facade = get_abstractcore_host_facade(rt)
record = facade.upsert_text_bloc(
path="assistant/system.txt",
content="Long-lived system prompt or memory text",
)
artifact = facade.ensure_bloc_kv_artifact(
provider="mlx",
model="mlx-community/Qwen3-4B-4bit",
sha256=record["sha256"],
)
loaded = facade.load_bloc_kv_artifact(
provider="mlx",
model="mlx-community/Qwen3-4B-4bit",
sha256=record["sha256"],
)
binding = loaded["artifact"]["prompt_cache_binding"]
Host-local prompt-cache export/import example:
saved = facade.prompt_cache_export(
name="orbit-cache",
key="sess:orbit",
q8=True,
)
listed = facade.list_prompt_cache_exports()
loaded_cache = facade.prompt_cache_import(
name="orbit-cache",
key="loaded:orbit",
clear_existing=True,
)
Host-local export/import contract:
- This surface is local-only. Remote and hybrid runtimes return structured
prompt_cache_local_onlypayloads instead of proxying host filesystem state through Core Server. - Runtime owns the export root policy:
- default local root:
~/.abstractruntime/prompt_cache_exports - default file-runtime root:
<base_dir>/prompt_cache_exports - explicit
prompt_cache_export_root_dir=...overrides are allowed when a host needs a different local catalog root - Exports stay partitioned by provider/model under that root, so the same logical export name can coexist safely across different local backends.
- This is a secondary operator/admin feature, not the primary durable app
contract. For replay-safe exact reuse inside workflows, prefer
prompt_cache_bindingfrom durable bloc/KV artifacts instead.
Host-side durable bloc lifecycle example:
records = facade.list_blocs()
artifacts = facade.list_bloc_kv_artifacts(bloc_id=record["record"]["bloc_id"])
# Preview a safe delete first.
preview = facade.delete_bloc_kv_artifact(
bloc_id=record["record"]["bloc_id"],
artifact_path=artifacts["artifacts"][0]["artifact_path"],
dry_run=True,
)
# Remove one derived KV artifact but keep the durable text bloc.
facade.delete_bloc_kv_artifact(
bloc_id=record["record"]["bloc_id"],
artifact_path=artifacts["artifacts"][0]["artifact_path"],
clear_loaded=True,
)
# Remove the whole bloc and all derived artifacts under it.
facade.delete_bloc(
bloc_id=record["record"]["bloc_id"],
clear_loaded=True,
)
Then use the binding in a normal runtime LLM_CALL:
Effect(
type=EffectType.LLM_CALL,
payload={
"prompt": "Use the durable cached prefix.",
"params": {"prompt_cache_binding": binding},
},
result_key="llm",
)
Storage semantics¶
- For local and local-file runtimes,
upsert_text_bloc(...)persists one durable text snapshot under the Runtime-owned bloc root. Runtime chooses the root (~/.abstractruntime/blocsby default, or<base_dir>/blocsforcreate_local_file_runtime(...)), while AbstractCore'sFileBlocStoredefines the on-disk layout under that root. - Within one bloc root, the durable source of truth is content-addressed by SHA256. Re-upserting the same text/file hash reuses or updates the same bloc record; it does not intentionally create several independent bloc copies under that same root.
- Deduplication is therefore per bloc root, not global across every Runtime instance. If several runtimes should share one durable bloc store, point them at the same
bloc_root_dir. Separate roots intentionally isolate storage and can hold separate copies of the same text. - The durable text bloc and the provider/model cache are different layers:
- one bloc: durable extracted text plus metadata
- zero or more derived KV artifacts: one per
(provider, model)pair, stored under that bloc'skv/area - Derived KV artifacts are not portable across providers or models. The same text bloc can legitimately have several provider/model-native artifacts, but each artifact remains tied to one provider/backend/model rendering path.
prompt_cache_bindingis a request-time proof that a specific runtime cache key still points at the exact loaded bloc artifact. It is not the durable text itself.- For remote and hybrid runtimes using
base_url, Runtime does not create its own local bloc copy; it proxies the bloc/KV operation to the configured AbstractCore server or upstream endpoint, and that remote side owns the store.
Lifecycle operations¶
- Runtime now exposes public host methods for:
- listing durable bloc records
- listing provider/model KV artifacts under those blocs
- deleting one derived KV artifact while keeping the bloc text
- pruning matching KV artifacts by filter
- deleting one durable bloc and, by default, its derived KV artifacts
- Safety behavior mirrors the public AbstractCore contract:
dry_run=Truepreviews the delete/prune result without mutating storageclear_loaded=Trueclears matching live prompt-cache keys before deletion when the relevant provider/model is resident in the current runtime or the remote Core serverforce=Truebypasses that safety check and should be treated as an explicit operator choicedelete_bloc_kv_artifact(...)deletes exactly one artifact. If the selector matches several provider/model artifacts, Runtime returns a structured error rather than guessing.delete_bloc(...)removes the durable text bloc itself. By default it also removes derived KV artifacts under that bloc; passdelete_kv=Falseonly if you intentionally want to leave those artifacts behind.
Host-local comms and Telegram wrappers¶
Runtime also exposes the remaining Gateway-facing host/operator wrappers for email and Telegram:
get_abstractcore_host_facade(runtime)now includes:list_email_accounts(...)list_emails(...)read_email(...)send_email(...)abstractruntime.integrations.abstractcore.comms_facadealso exposes:list_email_accounts(...)list_emails(...)read_email(...)send_email(...)abstractruntime.integrations.abstractcore.telegram_facadeexposes:TelegramTdlibNotAvailablebootstrap_telegram_auth_from_env(...)get_global_telegram_client(start=False)stop_global_telegram_client()send_telegram_message(...)
Contract notes:
- These are host-local wrappers over current public AbstractCore tool modules. They do not proxy through the remote AbstractCore server.
- The host facade email methods and the standalone
comms_facadefunctions use the same Runtime-owned email wrapper layer; choose whichever is more natural for the host surface you are building. - They are intentionally nondurable. They do not write Runtime run history on their own.
- Direct
send_email(...)on the host facade and directtelegram_facade.send_telegram_message(...)are for operator-owned host-local flows only. If the outbound send belongs to a workflow/run, prefer the durable run facade methods shown below. - Even for remote and hybrid runtimes, they still use the current host process env/config, local TDLib installation, and the host's own outbound network access.
- The Telegram global client is process-wide, not runtime-instance scoped.
Host-side operator example:
from abstractruntime.integrations.abstractcore import (
create_local_runtime,
get_abstractcore_host_facade,
)
from abstractruntime.integrations.abstractcore.telegram_facade import (
TelegramTdlibNotAvailable,
bootstrap_telegram_auth_from_env,
send_telegram_message,
)
rt = create_local_runtime(provider="ollama", model="qwen3:4b")
facade = get_abstractcore_host_facade(rt)
accounts = facade.list_email_accounts()
sent = facade.send_email(
["ops@example.com"],
"Runtime status",
body_text="All green.",
)
try:
bootstrap = bootstrap_telegram_auth_from_env(timeout_s=30)
except TelegramTdlibNotAvailable:
bootstrap = {"success": False, "error": "TDLib is not installed on this host."}
notify = send_telegram_message(chat_id=123456, text="Runtime check complete.")
Discovery snapshots¶
AbstractRuntime's AbstractCore integration also exposes a public host discovery facade for snapshot/query reads:
get_abstractcore_discovery_facade(runtime)AbstractCoreDiscoveryFacadelist_providers(...)list_provider_models(...)get_model_capabilities(...)get_voice_catalog(...)list_tts_models(...)list_stt_models(...)list_music_providers(...)list_music_models(...)list_vision_provider_models(...)list_cached_vision_models(...)
Behavior by execution mode:
- Local (
MultiLocalAbstractCoreLLMClient/LocalAbstractCoreLLMClient): uses public AbstractCore registries, capability facades, and local vision cache inspection to return JSON-safe snapshot payloads. - Remote / Hybrid (
RemoteAbstractCoreLLMClient): proxies/providers,/v1/models,/v1/audio/*, and/v1/vision/*on the configured AbstractCore server. Per-request provider key overrides supplied asapi_key/provider_api_keybecomeX-AbstractCore-Provider-API-Keyheaders.
list_provider_models(provider, ...) accepts the legacy input_type and
output_type filters plus Core's precise capability_route filter. Local mode
normalizes route filters before calling AbstractCore's provider registry; remote
mode forwards them to /v1/models?capability_route=...:
models = facade.list_provider_models(
"lmstudio",
capability_route=["input.image", "output.text"],
)
embeddings = facade.list_provider_models("lmstudio", capability_route="embedding.text")
Contract notes:
- This surface is query-oriented. It does not create durable Runtime history on its own.
- Hosts should still ask Runtime for these reads instead of rebuilding Core catalog logic or importing Core server helpers directly.
- Model capability lookup is static metadata, not a live server probe. Replay should treat it as a recorded snapshot, not as a query to re-run.
list_cached_vision_models(...)may still depend on the current local machine state. It is a Runtime-owned snapshot query, not durable run truth.- Remote discovery methods accept
timeout_s=...through facade kwargs. Local discovery remains synchronous helper code; async hosts should offload it to a worker thread if they do not want to block their event loop.
Host-side discovery example:
from abstractruntime.integrations.abstractcore import (
create_remote_runtime,
get_abstractcore_discovery_facade,
)
rt = create_remote_runtime(
server_base_url="http://127.0.0.1:8000",
model="openai/gpt-4o-mini",
headers={"Authorization": "Bearer server-master-key"},
)
facade = get_abstractcore_discovery_facade(rt)
providers = facade.list_providers(include_models=False)
voices = facade.get_voice_catalog(provider="openai", providers_only=True)
music = facade.list_music_providers(task="text_to_music")
vision = facade.list_vision_provider_models(task="text_to_image", providers_only=True)
upscalers = facade.list_vision_provider_models(task="image_upscale")
adapters = facade.list_vision_adapters(
task="text_to_video",
model="AbstractFramework/wan2.2-t2v-a14b-diffusers-8bit",
)
Durable run-scoped media and comms execution¶
Hosts sometimes need to trigger image/TTS/music/STT work or outbound comms sends for an existing run. That work should still execute through Runtime so the child run ledger, artifact ownership, and replay surface remain Runtime-authored.
Public durable entry points:
get_abstractcore_run_facade(runtime)AbstractCoreRunFacadeexecute_llm_call(...)execute_tool_calls(...)resume_tool_calls(...)generate_image(...)edit_image(...)upscale_image(...)generate_video(...)image_to_video(...)generate_voice(...)generate_music(...)transcribe_audio(...)send_email(...)send_telegram_message(...)
These helpers create child runs under an existing parent run and execute the real LLM_CALL or TOOL_CALLS through Runtime rather than doing external work in host/controller code.
Example:
from abstractruntime.integrations.abstractcore import (
create_local_runtime,
get_abstractcore_run_facade,
)
rt = create_local_runtime(provider="mlx", model="qwen-chat")
facade = get_abstractcore_run_facade(rt)
child = facade.generate_image(
"existing-parent-run-id",
prompt="A red mug on a white table.",
output={
"provider": "mlx-gen",
"model": "AbstractFramework/qwen-image-2512-8bit",
"format": "png",
"count": 2,
"seeds": [101, 102],
"lora_adapters": [
{"id": "pixel-art", "scale": 0.7},
{"id": "cool-grade", "scale": 0.2},
],
},
)
assert child.status.value == "completed"
result = child.output["result"]
print(child.run_id, result["media_model"], result["outputs"]["image"][0]["artifact_id"])
Image upscaling uses the same durable child-run boundary and Core-owned image_upscale selector:
child = facade.upscale_image(
"existing-parent-run-id",
media={"$artifact": "source-image-artifact-id", "type": "image"},
output={
"provider": "mlx-gen",
"format": "png",
"scale": 2,
"resolution": 1024,
},
)
print(child.run_id, child.output["result"]["outputs"]["image"][0]["artifact_id"])
For video, use the same child-run boundary:
child = facade.generate_video(
"existing-parent-run-id",
prompt="Glowing data streams converge into a geometric logo.",
output={
"provider": "mlx-gen",
"model": "AbstractFramework/wan2.2-t2v-a14b-diffusers-8bit",
"format": "mp4",
"num_frames": 41,
"count": 2,
"seeds": [401, 402],
"flow_shift": 3.0,
"lora_adapters": [{"id": "documentary-motion", "scale": 0.6}],
},
)
print(child.run_id, child.output["result"]["outputs"]["video"][0]["artifact_id"])
Outbound comms sends that belong to a run should use the same durable child-run surface:
email_child = facade.send_email(
"existing-parent-run-id",
to=["ops@example.com"],
subject="Workflow alert",
body_text="The workflow completed.",
)
telegram_child = facade.send_telegram_message(
"existing-parent-run-id",
chat_id=123456,
text="Workflow completed.",
)
Contract notes for durable comms sends:
- Runtime records the send request and the send outcome in the child run ledger.
- Replay should show the recorded result; it should not resend the external email or Telegram message.
- Local and hybrid runtimes usually execute those sends immediately when the configured tool executor can run them.
- Remote runtimes may still enter a durable tool wait if the configured tool executor is passthrough/delegated or approval-gated. That wait/resume path is still Runtime-authored truth.
- To resume a waiting durable comms/tool child run through the same public boundary, use
get_abstractcore_run_facade(runtime).resume_tool_calls(child_run_id, payload=...).
Attachment registration limits¶
When local read_file tool outputs are captured as session attachments, Runtime bounds the file bytes it stores. The limit is resolved in this order:
TOOL_CALLS.payload.max_attachment_bytesrun.vars["_runtime"]["max_attachment_bytes"]ABSTRACTRUNTIME_MAX_ATTACHMENT_BYTES- the default of 25 MiB
Gateway-specific attachment env vars should be translated by Gateway into one of the explicit Runtime inputs above.
Default toolsets (incl. comms)¶
default_tools.get_default_toolsets() provides a host-side convenience catalog of common tools:
- file/web/system tools
- optional comms tools behind env-var gating (docs/tools-comms.md)
This is useful when building a MappingToolExecutor quickly.
See also¶
../architecture.md— effect handler boundaries and durability invariants../tools-comms.md— enabling email/WhatsApp/Telegram tools../adr/0002_execution_modes_local_remote_hybrid.md— rationale for local/remote/hybrid