unifyai/unify repo. Everything above
this page describes what users experience; this page describes how the
runtime makes it happen — module by module, with the real symbol names.
The hosted control plane (connection storage, tool policies, audit, provider
dispatch) lives in the closed-source Orchestra backend, reached over HTTPS.
The unify runtime owns everything on the assistant’s side of that line:
discovery, tool materialization, execution wrappers, sync lifecycle, and the
prompts that teach the actor when to use what.
The big picture: three lanes
There is no single “integrations pathway”. Three lanes coexist, each with its own discovery, activation, and execution story — but they all converge on one rule: FunctionManager search is the single discovery surface for executable tools. Whatever lane a capability arrives through, the actor finds it the same way it finds everything else.
The rest of this page walks each lane, then the shared plumbing.
The unify.integrations package
Everything provider-backed funnels through
unify/integrations/,
a deliberately thin package with sharply separated responsibilities.
primitives.py — the actor-facing surface
unify/integrations/primitives.py
defines IntegrationPrimitives, mounted at primitives.integrations in the
actor sandbox. Its design principle, from the class docstring:
Actor-facing discovery is intentionally app-only: native Unity-deploy packages and third-party provider apps are searched through Orchestra’s global app catalog, then enriched with current-assistant state in Unity. Executable functions/tools are still discovered through normal FunctionManager search after an app is active and materialized.Four methods are indexed in FunctionManager as regular primitives:
search_integrations(query, ...)— app-level discovery across both native and third-party sources. Each result carries the fields an agent needs to reason about readiness:canonical_app_slug,source_type(native|third_party),connection_status,sync_status(not_enabled→missing_required_secrets→not_connected→not_yet_synced→materialized), and anext_actionhint like “Ask the user to connect this integration in Console Integrations.”review_tool_permissions(connection_id)andupdate_tool_permissions(...)— read and patch the per-connection tool policy (allow / ask-every-time / block, plus bulk presets by action class).resolve_tool_execution(audit_id, decision, ...)— approve or deny a pending sensitive-tool execution, optionally persisting the decision as policy.
execute_tool, list_connected, search_tools,
get_tool_schema, manage_connection, callable_for_tool, …) is
deliberately excluded from FunctionManager indexing via the
ToolSurfaceRegistry entry in
unify/function_manager/primitives/registry.py
— they’re runtime plumbing, not tools the actor should discover.
The magic dot-path — primitives.integrations.hubspot.search_contacts(...)
— is implemented by __getattr__ on IntegrationPrimitives: any unknown
attribute returns an _IntegrationAppNamespace, whose own __getattr__
resolves the tool name against the materialized FunctionManager row and
returns an async closure over execute_tool.
ops.py — the transport seam
unify/integrations/ops.py
is the only place the runtime touches the network for integrations, and it
keeps itself honest:
This module is intentionally not a raw HTTP client and it intentionally does not wrap the helpers in a stateful client object. Unity calls these small functions, the functions call Unify, and Unify owns the Orchestra route mapping, base URL normalization, auth headers, and payload shape.One flat function per operation —
list_connections, run_tool,
get_tool_policy, patch_tool_policy, approve_tool_execution,
deny_tool_execution, test_connection — each delegating to the UniSDK
HTTP helper of the same shape and normalizing failures into a uniform
{status: "error", error: {...}} envelope. If you’re tracing a request,
this is the seam: above it is pure runtime logic, below it is UniSDK →
Orchestra.
builtins_catalog.py — the shared app/tool catalog
unify/integrations/builtins_catalog.py
reads and writes the platform-wide Builtins catalog — three log contexts
in the public-read Builtins project:
seed_builtin_integrations(...) reconciles the catalog hash-by-hash (units
keyed app:{backend}:{slug} / tools:{backend}:{slug}), so reseeding is
cheap and prunable. list_catalog_apps(query) powers semantic app search
(via the row’s embedding text); list_catalog_tools(canonical_app_slug) is
what materialization reads.
The catalog is populated by
scripts/seed_builtins_catalog.py,
which ships inside the unify application image. Given an
integration-bootstrap manifest (TOML, providers.* sections with a sync
block — mode: partial|full, app_slugs, tool_limit_per_app,
prune_unlisted_apps, …), it registers provider backends with Orchestra,
runs the catalog sync, and seeds the three contexts. Deployments differ only
in their manifest: a partial three-app staging sync and a full production
sync are the same code path.
Provider tools: materialization and sync
Connecting an app in the Console doesn’t directly give the actor anything. What it triggers is materialization: provider tools from the Builtins catalog are written into the assistant’s ownFunctions/Primitives context
as first-class FunctionManager rows, so ordinary FM search finds them.
The materialized row
FunctionManager._integration_tool_to_function_row in
unify/function_manager/function_manager.py
shapes each tool into a primitive row with a stable identity:
- Callable name:
primitives.integrations.<app_slug>.<tool_name> - Stable
tool_id:{backend_id}:{app_slug}:{tool_name} function_id: derived from a SHA-256 of the tool identity, so re-syncs are idempotentimplementation: None— there is no stored Python source; execution dispatches throughIntegrationPrimitives.callable_for_toolat runtimemetadata.source: "provider_backed"and ametadata.integrationpayload (tool id, backend, app slug, input schema,action_class,confirmation_required) — accessors for all of this live inunify/integrations/function_metadata.pyverify: Trueis set automatically for tools that are confirmation-gated or whose action class iswrite,destructive, orbulk_export
The sync algorithm
FunctionManager.sync_provider_integration_tools is an explicit sync path,
not query-time magic. Per its docstring:
Materialize active provider-backed tools into the Primitives context. This is an explicit sync path, not a FunctionManager query-time search. It builds expected rows, compares stable per-app hashes, and only deletes/upserts the affected app rows when changed.It lists the assistant’s connections, filters to
status == "connected",
reads the expected tools from Integrations/Tools, groups them by
{backend_id}:{app_slug}, and compares against per-app hashes stored in the
assistant’s Functions/Meta context (integration_tool_hash_by_app). Only
changed apps get their rows deleted and re-inserted. A cleanup operation
handles disconnects: when an app’s last connection is gone, its rows are
removed.
The lifecycle coordinator
unify/integrations/sync_state.py
owns the in-memory lifecycle: IntegrationSyncCoordinator tracks per-app
IntegrationSyncState (pending → syncing → ready | failed |
removed), spawns the actual FM sync on a worker thread, and renders a
prompt_summary() — the <integration_tool_sync> block injected into the
ConversationManager’s state message so the assistant knows which apps are
mid-sync and can defer using them.
Two things trigger it:
- Startup —
schedule_connected_apps()runs when a session boots, reconciling all connected apps (usually a no-op thanks to the hashes). - Live events — the Console/Orchestra side emits an
integration_tools_sync_requestedsystem event when a user connects or disconnects an app. The ConversationManager maps this to anIntegrationToolsSyncRequestedevent (seeunify/conversation_manager/events.py), and the handlers inunify/conversation_manager/domains/integration_sync.pydrive the coordinator and surface the user-facing notifications — ” tools are syncing and will be available shortly.” → ” tools are ready.”
One naming trap: the onboarding milestone event
integration_connected
(fired when a user saves an integration during onboarding) is a
conversational event only — it does not trigger tool sync. The sync
trigger is always integration_tools_sync_requested.Execution and governance
At call time, the runtime is a thin, honest client — every policy decision happens server-side, and every non-ok outcome comes back as a typed status
the actor is prompted to handle rather than swallow.
The approval loop
When Orchestra returnsconfirmation_required,
unify/integrations/approval.py’s
build_pending_approval_payload reshapes the provider envelope into an
integration_tool_pending_approval notification: an approval block
(audit id, behavior hints, argument summary) for presenting the decision to
the user, and a resume block carrying everything needed to retry. The user
decides; resolve_tool_execution(audit_id, decision) records it (optionally
persisting a standing policy), and the original call is retried with the
confirmation token. The
CodeActActor
wires this into its notification queue so approvals ride the same channel as
every other mid-action interaction.
Where execution is dispatched
Two paths converge on the same closure:- Dynamic namespace —
primitives.integrations.<app>.<tool>(...)in sandbox code resolves throughcallable_for_app_tool. execute_function— when the actor executes a discovered FM row,get_primitive_callableinunify/function_manager/primitives/runtime.pydetectsis_provider_backed_functionand routes toIntegrationPrimitives.callable_for_toolinstead of a normalprimitive_methodlookup (there is no stored implementation to run).
Native packages: unify.integration_status
The second lane has nothing to do with providers. Native integration
packages are code-and-guidance bundles (shipped in the private
unity-deploy repo) that the open runtime discovers and activates locally.
unify/integration_status/discovery.py
is the source of truth for what exists:
This module enumerates every manifest under unity_deploy’s package roots and projects each one into a lightweight metadata record. It is the single source of truth for “what integration packages exist” at runtime.
discover_available_packages() returns one record per package: slug, label,
required_secrets / optional_secrets, function_names, and
guidance_titles.
unify/integration_status/__init__.py
then runs two decoupled mechanisms:
- Registration (startup) —
register_available_integrations()walks discovered packages whose secrets are satisfied and registers their custom functions intoFunctions/Compositionaland their guidance into the GuidanceManager. - Enablement (query time) —
get_enabled_integrations()computes which packages are live (required_secretsall present, or at least oneoptional_secret), andbuild_function_filter_scope()/build_guidance_filter_scope()hide a disabled package’s functions and guidance from search entirely. An inactive integration doesn’t error — it simply doesn’t exist from the actor’s perspective, andsearch_integrationsreports it asmissing_required_secretswith the secrets to add.
The escape hatch: SDK + secrets
The third lane has no catalog at all. For services outside the gallery, the actor is prompted (the_EXTERNAL_APP_INTEGRATION block in
unify/actor/prompt_builders.py)
to follow a disciplined pattern:
- Check credentials first —
primitives.secrets.ask(...)inspects the secret vault by name and placeholder only; raw values never enter the prompt. Missing credentials → direct the user to the Console Integrations tab, never ask for keys in chat. - Install the official SDK and integrate in
execute_code, reading credentials from the environment. - For Google/Microsoft OAuth, use
get_oauth_access_token(provider)fromunify/common/runtime_oauth.py— it returns a local capability handle, not a raw token; a local proxy injects the real token and enforces the file-access allowlist, so sandbox code can never exfiltrate credentials. - Persist what worked — store the pattern as a reusable function and guidance entry so the next similar job skips the exploration.
Extending the system
Adding a provider-backed app is configuration, not code: ensure the app exists in the provider’s catalog, add it to the bootstrap manifest, runseed_builtins_catalog.py, and the entire connect → sync → materialize →
execute pipeline works with zero new runtime code. Optionally add prompt
examples in
unify/actor/prompt_examples.py.
Shipping a native package means writing a manifest (secrets +
capabilities), functions, and guidance stems in a package directory —
discovery, gating, and registration are automatic from there.
Debugging a failing tool call, from the outside in:
- Is the app materialized?
search_integrations→sync_status, or filterFunctions/Primitivesformetadata.source == "provider_backed". - Is the connection healthy?
list_connected, or Test in the Console. - What did execution return? The envelope status names the failure mode
precisely — most “failures” are
connect_required/missing_scope/blocked_by_policystates with a user-facing remedy. - Cross the seam: UniSDK request logs and Orchestra traces pick up where the runtime hands off.