diff --git a/README.md b/README.md index 188d810e1e..2e68108354 100644 --- a/README.md +++ b/README.md @@ -27,3 +27,9 @@ docker restart ``` Of course, you can spin up a new container every once in a while if either of the 2 get updated. + +## Brand assets + +The theme (`docs.json`) follows the Unify shared light system: paper/ink canvas, teal `#2f9d97` primary, Space Grotesk headings, and Inter body. + +The banner and OG image in `images/` (`unify-docs-banner-*.svg`, `unify-docs-og.png`) are rendered from the `branding` repo — do not hand-edit them. To regenerate, run `npm run render:docs-diagrams` in `branding/` and copy the refreshed files from `branding/assets/docs/` into `images/`. diff --git a/canvas/developers.mdx b/canvas/developers.mdx index de108a4af7..48c2396213 100644 --- a/canvas/developers.mdx +++ b/canvas/developers.mdx @@ -19,7 +19,8 @@ Two packages own this territory: ## The big picture -![DataManager architecture: the Actor calls primitives.data.*, which lands on DataManager (contract in base.py, implementations in ops/, schemas in types/); ContextRegistry supplies destinations; SimulatedDataManager mirrors the same contract in memory; persistence flows through unisdk HTTP to Orchestra, where contexts hold log events across personal Data/… and Teams/{id}/Data/… roots with federated reads between them.](/images/developers/datamanager-architecture.png) +DataManager architecture: the Actor calls primitives.data.*, which lands on DataManager (contract in base.py, implementations in ops/, schemas in types/); ContextRegistry supplies destinations; SimulatedDataManager mirrors the same contract in memory; persistence flows through unisdk HTTP to Orchestra, where contexts hold log events across personal Data/… and Teams/{id}/Data/… roots with federated reads between them. +DataManager architecture: the Actor calls primitives.data.*, which lands on DataManager (contract in base.py, implementations in ops/, schemas in types/); ContextRegistry supplies destinations; SimulatedDataManager mirrors the same contract in memory; persistence flows through unisdk HTTP to Orchestra, where contexts hold log events across personal Data/… and Teams/{id}/Data/… roots with federated reads between them. Both packages follow the same layered pattern, shared with every state manager in the repo: @@ -129,7 +130,8 @@ Context paths resolve through three private helpers in `DataManager`: `federated_ranked_search`, and `federated_reduce` from [`unify/common/federated_search.py`](https://github.com/unifyai/unify/blob/main/unify/common/federated_search.py). -![Destination scopes: write_root routes each write to exactly one destination — the personal root or a team root, each holding Data and Dashboards contexts — while read_roots federates reads across personal plus all teams; data_scope additionally lets a personal tile read team data.](/images/developers/destination-scopes.png) +Destination scopes: write_root routes each write to exactly one destination — the personal root or a team root, each holding Data and Dashboards contexts — while read_roots federates reads across personal plus all teams; data_scope additionally lets a personal tile read team data. +Destination scopes: write_root routes each write to exactly one destination — the personal root or a team root, each holding Data and Dashboards contexts — while read_roots federates reads across personal plus all teams; data_scope additionally lets a personal tile read team data. `ContextRegistry` ([`unify/common/context_registry.py`](https://github.com/unifyai/unify/blob/main/unify/common/context_registry.py)) @@ -315,7 +317,8 @@ bindings; *the Actor never writes bridge API calls*. ## Life of a live tile -![Live tile lifecycle: the Actor calls create_tile with HTML, data bindings, and on_data; DashboardManager validates and dry-runs the bindings, stores the tile row and registers its token; the Console TileViewer renders the iframe and injects the UnifyData bridge, which runs live filter/reduce/join queries against the Orchestra bridge at render time; results are handed to on_data so the chart updates with fresh data.](/images/developers/live-tile-lifecycle.png) +Live tile lifecycle: the Actor calls create_tile with HTML, data bindings, and on_data; DashboardManager validates and dry-runs the bindings, stores the tile row and registers its token; the Console TileViewer renders the iframe and injects the UnifyData bridge, which runs live filter/reduce/join queries against the Orchestra bridge at render time; results are handed to on_data so the chart updates with fresh data. +Live tile lifecycle: the Actor calls create_tile with HTML, data bindings, and on_data; DashboardManager validates and dry-runs the bindings, stores the tile row and registers its token; the Console TileViewer renders the iframe and injects the UnifyData bridge, which runs live filter/reduce/join queries against the Orchestra bridge at render time; results are handed to on_data so the chart updates with fresh data. The `create_tile` pipeline in `DashboardManager`, step by step: diff --git a/communication/developers/architecture.mdx b/communication/developers/architecture.mdx index c58e752e2d..86e935629c 100644 --- a/communication/developers/architecture.mdx +++ b/communication/developers/architecture.mdx @@ -25,7 +25,8 @@ The communication stack is two cooperating pieces: back through the gateway via [`unify/comms/primitives.py`](https://github.com/unifyai/unify/blob/main/unify/comms/primitives.py). -![unify.gateway architecture: providers feed adapters, which normalize to envelopes delivered through an EnvelopeSink to the ConversationManager; the ConversationManager sends outbound via channels](/images/developers/gateway-architecture.png) +unify.gateway architecture: providers feed adapters, which normalize to envelopes delivered through an EnvelopeSink to the ConversationManager; the ConversationManager sends outbound via channels +unify.gateway architecture: providers feed adapters, which normalize to envelopes delivered through an EnvelopeSink to the ConversationManager; the ConversationManager sends outbound via channels ## The wire contract: envelopes diff --git a/communication/developers/conversation-manager.mdx b/communication/developers/conversation-manager.mdx index ce46416129..27c007482d 100644 --- a/communication/developers/conversation-manager.mdx +++ b/communication/developers/conversation-manager.mdx @@ -10,7 +10,8 @@ state, consumes events, runs the reasoning loop, and delegates outbound I/O. Its package README describes it as the layer that interfaces with users while delegating complex reasoning to the `Actor`. -![Inbound event lifecycle: envelope → CommsManager → EventBroker → ConversationManager.wait_for_events → side effects → slow brain turn → comms tools / act / wait](/images/developers/event-lifecycle.png) +Inbound event lifecycle: envelope → CommsManager → EventBroker → ConversationManager.wait_for_events → side effects → slow brain turn → comms tools / act / wait +Inbound event lifecycle: envelope → CommsManager → EventBroker → ConversationManager.wait_for_events → side effects → slow brain turn → comms tools / act / wait ## The event model diff --git a/communication/developers/voice.mdx b/communication/developers/voice.mdx index 95714bc65a..9b5e718b4b 100644 --- a/communication/developers/voice.mdx +++ b/communication/developers/voice.mdx @@ -8,7 +8,8 @@ sub-second conversational reflexes *and* deep reasoning, and no single loop provides both. The runtime splits the job between two brains that share one conversation. -![Voice call dual-brain architecture: the caller connects through a LiveKit room to the fast brain (Assistant agent with STT/VAD/TTS), which exchanges utterance events and FastBrainNotifications with the slow brain (ConversationManager), supported by fillers/barge-in handling and the LivekitCallManager](/images/developers/voice-dual-brain.png) +Voice call dual-brain architecture: the caller connects through a LiveKit room to the fast brain (Assistant agent with STT/VAD/TTS), which exchanges utterance events and FastBrainNotifications with the slow brain (ConversationManager), supported by fillers/barge-in handling and the LivekitCallManager +Voice call dual-brain architecture: the caller connects through a LiveKit room to the fast brain (Assistant agent with STT/VAD/TTS), which exchanges utterance events and FastBrainNotifications with the slow brain (ConversationManager), supported by fillers/barge-in handling and the LivekitCallManager ## The two brains diff --git a/docs.json b/docs.json new file mode 100644 index 0000000000..6746b9cd3b --- /dev/null +++ b/docs.json @@ -0,0 +1,217 @@ +{ + "$schema": "https://mintlify.com/docs.json", + "theme": "mint", + "name": "Unify", + "description": "Everything you can do with your Unify AI teammate — communication, workspace, integrations, tasks, learning, and more.", + "logo": { + "light": "/images/unify-wordmark-light.svg", + "dark": "/images/unify-wordmark-dark.svg", + "href": "https://unify.ai" + }, + "favicon": "/images/unify-mark.svg", + "colors": { + "primary": "#2f9d97", + "light": "#79efdd", + "dark": "#1c6460" + }, + "background": { + "color": { + "light": "#f5f1ea", + "dark": "#0d0f12" + } + }, + "fonts": { + "heading": { + "family": "Space Grotesk" + }, + "body": { + "family": "Inter" + } + }, + "appearance": { + "default": "system" + }, + "seo": { + "metatags": { + "og:image": "/images/unify-docs-og.png" + } + }, + "navbar": { + "links": [ + { + "label": "GitHub", + "href": "https://github.com/unifyai/unify" + } + ], + "primary": { + "type": "button", + "label": "Console", + "href": "https://console.unify.ai" + } + }, + "navigation": { + "groups": [ + { + "group": "Getting Started", + "pages": [ + "introduction" + ] + }, + { + "group": "Communication", + "pages": [ + "communication/overview", + "communication/console-chat", + "communication/unify-meet", + "communication/phone-calls", + "communication/sms", + "communication/whatsapp", + "communication/email", + "communication/slack", + "communication/microsoft-teams", + "communication/discord", + "communication/meetings", + "communication/setup", + "communication/voice", + "communication/behavior", + "communication/contacts", + "communication/recordings-and-transcripts", + { + "group": "Developers", + "pages": [ + "communication/developers/architecture", + "communication/developers/gateway", + "communication/developers/conversation-manager", + "communication/developers/voice" + ] + } + ] + }, + { + "group": "Workspace", + "pages": [ + "workspace/overview", + "workspace/connecting", + "workspace/email", + "workspace/calendar", + "workspace/files", + "workspace/contacts-and-tasks", + "workspace/security", + { + "group": "Developers", + "pages": [ + "workspace/developers/architecture", + "workspace/developers/oauth-and-secrets", + "workspace/developers/provider-proxy", + "workspace/developers/gateway-channels" + ] + } + ] + }, + { + "group": "Integrations", + "pages": [ + "integrations/overview", + "integrations/connecting", + "integrations/permissions", + "integrations/using", + "integrations/developers" + ] + }, + { + "group": "Tasks", + "pages": [ + "tasks/overview", + "tasks/creating", + "tasks/running", + "tasks/monitoring", + "tasks/developers" + ] + }, + { + "group": "Learning", + "pages": [ + "learning/overview", + "learning/watching-it-work", + "learning/guidance-and-functions", + "learning/knowledge", + "learning/teaching", + { + "group": "Developers", + "pages": [ + "learning/developers/architecture", + "learning/developers/function-manager", + "learning/developers/guidance-manager", + "learning/developers/storage-check", + "learning/developers/knowledge-and-memory" + ] + } + ] + }, + { + "group": "Canvas", + "pages": [ + "canvas/overview", + "canvas/data", + "canvas/dashboards", + "canvas/recipes", + "canvas/developers" + ] + }, + { + "group": "Their Computer", + "pages": [ + "their-computer/overview", + "their-computer/what-it-does", + "their-computer/watching-and-control", + "their-computer/the-machine", + "their-computer/developers" + ] + }, + { + "group": "Your Computer", + "pages": [ + "your-computer/overview", + "your-computer/setup", + "your-computer/how-it-works", + "your-computer/files-and-terminal", + "your-computer/control-and-safety", + { + "group": "Developers", + "pages": [ + "your-computer/developers" + ] + } + ] + }, + { + "group": "Teams", + "pages": [ + "teams/overview", + "teams/membership", + "teams/shared-context", + "teams/managing", + "teams/developers" + ] + }, + { + "group": "Hiring", + "pages": [ + "hiring/overview", + "hiring/twin-vs-teammates", + "hiring/onboarding", + "hiring/liaison" + ] + } + ] + }, + "footer": { + "socials": { + "discord": "https://discord.com/invite/sXyFF8tDtm", + "x": "https://twitter.com/letsunifyai", + "youtube": "https://youtube.com/@unifyai", + "linkedin": "https://www.linkedin.com/company/unifyai", + "github": "https://github.com/unifyai/unify" + } + } +} diff --git a/images/developers/datamanager-architecture-dark.svg b/images/developers/datamanager-architecture-dark.svg new file mode 100644 index 0000000000..e7c202f6a0 --- /dev/null +++ b/images/developers/datamanager-architecture-dark.svg @@ -0,0 +1,5 @@ +UNIFY · DATAMANAGERActor (LLM)primitives.data.*DataManagerbase.py — contractops/ — implementationstypes/ — schemasContextRegistrydestinationsSimulatedDataManager(in-memory)same contractunisdk (HTTP)Orchestra— contexts + log eventsData/…Teams/{id}/Data/…federated reads \ No newline at end of file diff --git a/images/developers/datamanager-architecture-light.svg b/images/developers/datamanager-architecture-light.svg new file mode 100644 index 0000000000..c8302e1567 --- /dev/null +++ b/images/developers/datamanager-architecture-light.svg @@ -0,0 +1,5 @@ +UNIFY · DATAMANAGERActor (LLM)primitives.data.*DataManagerbase.py — contractops/ — implementationstypes/ — schemasContextRegistrydestinationsSimulatedDataManager(in-memory)same contractunisdk (HTTP)Orchestra— contexts + log eventsData/…Teams/{id}/Data/…federated reads \ No newline at end of file diff --git a/images/developers/datamanager-architecture.png b/images/developers/datamanager-architecture.png deleted file mode 100644 index 4c921a9c31..0000000000 Binary files a/images/developers/datamanager-architecture.png and /dev/null differ diff --git a/images/developers/destination-scopes-dark.svg b/images/developers/destination-scopes-dark.svg new file mode 100644 index 0000000000..014b4020f5 --- /dev/null +++ b/images/developers/destination-scopes-dark.svg @@ -0,0 +1,5 @@ +UNIFY · DESTINATION SCOPESwrite_root — exactly one destinationpersonalData/SalesDashboards/TilesDashboards/Layoutsteam:42Data/OpsDashboards/TilesDashboards/Layoutsread_roots — personal + all teamsdata_scope: a personal tile may read team data \ No newline at end of file diff --git a/images/developers/destination-scopes-light.svg b/images/developers/destination-scopes-light.svg new file mode 100644 index 0000000000..511d98f905 --- /dev/null +++ b/images/developers/destination-scopes-light.svg @@ -0,0 +1,5 @@ +UNIFY · DESTINATION SCOPESwrite_root — exactly one destinationpersonalData/SalesDashboards/TilesDashboards/Layoutsteam:42Data/OpsDashboards/TilesDashboards/Layoutsread_roots — personal + all teamsdata_scope: a personal tile may read team data \ No newline at end of file diff --git a/images/developers/destination-scopes.png b/images/developers/destination-scopes.png deleted file mode 100644 index c31211b746..0000000000 Binary files a/images/developers/destination-scopes.png and /dev/null differ diff --git a/images/developers/dev-computer-control-stack-dark.svg b/images/developers/dev-computer-control-stack-dark.svg new file mode 100644 index 0000000000..b27ca590b3 --- /dev/null +++ b/images/developers/dev-computer-control-stack-dark.svg @@ -0,0 +1,5 @@ +UNIFY · COMPUTER CONTROLCodeActActor / ConversationManagerwrites plans · fast pathsact / observe / navigate / screenshotprimitives.computer.*desktopsingleton · one mousewebsession factoryuser_desktoplinked machines_vm_ready gate ·secret injectionHTTP · /act /extract /screenshotMagnitudeBackend → ComputerSessionasync HTTP · Bearer UNIFY_KEYcontainer_url → VMlocal_url → headless webHTTP · /act /extract /screenshotagent-serviceNode · Playwrightdesktop → noVNC pageweb-vm → visible Chromiumweb → headless ChromiumVM display: TigerVNC :5900 · noVNC :6080 · agent :3000 \ No newline at end of file diff --git a/images/developers/dev-computer-control-stack-light.svg b/images/developers/dev-computer-control-stack-light.svg new file mode 100644 index 0000000000..d1ff28d0f2 --- /dev/null +++ b/images/developers/dev-computer-control-stack-light.svg @@ -0,0 +1,5 @@ +UNIFY · COMPUTER CONTROLCodeActActor / ConversationManagerwrites plans · fast pathsact / observe / navigate / screenshotprimitives.computer.*desktopsingleton · one mousewebsession factoryuser_desktoplinked machines_vm_ready gate ·secret injectionHTTP · /act /extract /screenshotMagnitudeBackend → ComputerSessionasync HTTP · Bearer UNIFY_KEYcontainer_url → VMlocal_url → headless webHTTP · /act /extract /screenshotagent-serviceNode · Playwrightdesktop → noVNC pageweb-vm → visible Chromiumweb → headless ChromiumVM display: TigerVNC :5900 · noVNC :6080 · agent :3000 \ No newline at end of file diff --git a/images/developers/dev-computer-control-stack.png b/images/developers/dev-computer-control-stack.png deleted file mode 100644 index 1cafeaec19..0000000000 Binary files a/images/developers/dev-computer-control-stack.png and /dev/null differ diff --git a/images/developers/dev-desktop-lifecycle-dark.svg b/images/developers/dev-desktop-lifecycle-dark.svg new file mode 100644 index 0000000000..5ae9cb6235 --- /dev/null +++ b/images/developers/dev-desktop-lifecycle-dark.svg @@ -0,0 +1,5 @@ +UNIFY · DESKTOP LIFECYCLEStartupEventdesktop_mode · user_desktops (no desktop_url yet)AssistantDesktopReadybinding_id · desktop_url · vm_typepublished when the VMpasses health checksvm_pending —desktop VM is still bootingapply_managed_desktop_ready()set desktop_url + _vm_readypublish liveview URLensure desktop sessionstart file sync (rclone bisync)FileSyncCompleteprior-session files now on disksync_pending —files do not exist on disk yetreadycomputer actions unlocked \ No newline at end of file diff --git a/images/developers/dev-desktop-lifecycle-light.svg b/images/developers/dev-desktop-lifecycle-light.svg new file mode 100644 index 0000000000..7f17ac1a0d --- /dev/null +++ b/images/developers/dev-desktop-lifecycle-light.svg @@ -0,0 +1,5 @@ +UNIFY · DESKTOP LIFECYCLEStartupEventdesktop_mode · user_desktops (no desktop_url yet)AssistantDesktopReadybinding_id · desktop_url · vm_typepublished when the VMpasses health checksvm_pending —desktop VM is still bootingapply_managed_desktop_ready()set desktop_url + _vm_readypublish liveview URLensure desktop sessionstart file sync (rclone bisync)FileSyncCompleteprior-session files now on disksync_pending —files do not exist on disk yetreadycomputer actions unlocked \ No newline at end of file diff --git a/images/developers/dev-desktop-lifecycle.png b/images/developers/dev-desktop-lifecycle.png deleted file mode 100644 index 4cb291f2aa..0000000000 Binary files a/images/developers/dev-desktop-lifecycle.png and /dev/null differ diff --git a/images/developers/dev-screen-share-flow-dark.svg b/images/developers/dev-screen-share-flow-dark.svg new file mode 100644 index 0000000000..271950c7c7 --- /dev/null +++ b/images/developers/dev-screen-share-flow-dark.svg @@ -0,0 +1,5 @@ +UNIFY · SCREEN SHAREConsole (Unify Meet)Show assistant screenEnable mouse & keyboardnoVNC iframeview-only overlay by defaultConversationManagerassistant_screen_share_activeuser_remote_control_activescreenshot bufferfast paths: desktop_act / web_actflags gate prompts + toolsassistant_screen_share_started / stoppeduser_remote_control_started / stoppedactors (act sessions)interjected on controltake / releaseset_user_remote_control()slow brain LLMscreenshots attachedas vision input ·Screenshots/Assistant/JPEG frameslive desktop via noVNCsolid arrows: data flowdotted arrows: events / signals \ No newline at end of file diff --git a/images/developers/dev-screen-share-flow-light.svg b/images/developers/dev-screen-share-flow-light.svg new file mode 100644 index 0000000000..e517027c8c --- /dev/null +++ b/images/developers/dev-screen-share-flow-light.svg @@ -0,0 +1,5 @@ +UNIFY · SCREEN SHAREConsole (Unify Meet)Show assistant screenEnable mouse & keyboardnoVNC iframeview-only overlay by defaultConversationManagerassistant_screen_share_activeuser_remote_control_activescreenshot bufferfast paths: desktop_act / web_actflags gate prompts + toolsassistant_screen_share_started / stoppeduser_remote_control_started / stoppedactors (act sessions)interjected on controltake / releaseset_user_remote_control()slow brain LLMscreenshots attachedas vision input ·Screenshots/Assistant/JPEG frameslive desktop via noVNCsolid arrows: data flowdotted arrows: events / signals \ No newline at end of file diff --git a/images/developers/dev-screen-share-flow.png b/images/developers/dev-screen-share-flow.png deleted file mode 100644 index 4ca0a8e68a..0000000000 Binary files a/images/developers/dev-screen-share-flow.png and /dev/null differ diff --git a/images/developers/event-lifecycle-dark.svg b/images/developers/event-lifecycle-dark.svg new file mode 100644 index 0000000000..ff0a9f2b15 --- /dev/null +++ b/images/developers/event-lifecycle-dark.svg @@ -0,0 +1,5 @@ +UNIFY · EVENT LIFECYCLEenvelope arrives{thread, publish_timestamp, event}CommsManagerevents_map: thread → Event classcontact resolution via MEDIUM_TO_CONTACT_FIELDpublish app:comms:*EventBrokerin-memory pub/subConversationManager.wait_for_events()EventHandler.handle_event(event, cm)side effectsTranscriptManager.log_messages · ContactIndex · NotificationBarrequest_llm_run() → flushslow brain turnDebouncer → build_brain_spec() → single_shot_tool_decisioncomms toolssend_sms · send_email · …act(…)steerable Actor handlewaitend turn silently \ No newline at end of file diff --git a/images/developers/event-lifecycle-light.svg b/images/developers/event-lifecycle-light.svg new file mode 100644 index 0000000000..4b33e51e58 --- /dev/null +++ b/images/developers/event-lifecycle-light.svg @@ -0,0 +1,5 @@ +UNIFY · EVENT LIFECYCLEenvelope arrives{thread, publish_timestamp, event}CommsManagerevents_map: thread → Event classcontact resolution via MEDIUM_TO_CONTACT_FIELDpublish app:comms:*EventBrokerin-memory pub/subConversationManager.wait_for_events()EventHandler.handle_event(event, cm)side effectsTranscriptManager.log_messages · ContactIndex · NotificationBarrequest_llm_run() → flushslow brain turnDebouncer → build_brain_spec() → single_shot_tool_decisioncomms toolssend_sms · send_email · …act(…)steerable Actor handlewaitend turn silently \ No newline at end of file diff --git a/images/developers/event-lifecycle.png b/images/developers/event-lifecycle.png deleted file mode 100644 index 42d06ff024..0000000000 Binary files a/images/developers/event-lifecycle.png and /dev/null differ diff --git a/images/developers/federated-scopes-dark.svg b/images/developers/federated-scopes-dark.svg new file mode 100644 index 0000000000..b1b2600286 --- /dev/null +++ b/images/developers/federated-scopes-dark.svg @@ -0,0 +1,5 @@ +UNIFY · FEDERATED SCOPESfederated_ranked_searchFederatedSearchContext per rootmerged globally by scorepersonal root{user}/{assistant}/Guidance ·Functions/Compositionalteam rootsTeams/{team_id}/... for eachSESSION_DETAILS.team_idsbuiltins catalogueread-only platform project ·seeded skillsContextRegistryread_roots(): personal first, then teamswrite_root(): exactly one destination,membership checkedresolveswrites:personal | team:<id>— never builtins \ No newline at end of file diff --git a/images/developers/federated-scopes-light.svg b/images/developers/federated-scopes-light.svg new file mode 100644 index 0000000000..f8333176dc --- /dev/null +++ b/images/developers/federated-scopes-light.svg @@ -0,0 +1,5 @@ +UNIFY · FEDERATED SCOPESfederated_ranked_searchFederatedSearchContext per rootmerged globally by scorepersonal root{user}/{assistant}/Guidance ·Functions/Compositionalteam rootsTeams/{team_id}/... for eachSESSION_DETAILS.team_idsbuiltins catalogueread-only platform project ·seeded skillsContextRegistryread_roots(): personal first, then teamswrite_root(): exactly one destination,membership checkedresolveswrites:personal | team:<id>— never builtins \ No newline at end of file diff --git a/images/developers/federated-scopes.png b/images/developers/federated-scopes.png deleted file mode 100644 index c24ede8dde..0000000000 Binary files a/images/developers/federated-scopes.png and /dev/null differ diff --git a/images/developers/gateway-architecture-dark.svg b/images/developers/gateway-architecture-dark.svg new file mode 100644 index 0000000000..2b74b450be --- /dev/null +++ b/images/developers/gateway-architecture-dark.svg @@ -0,0 +1,5 @@ +UNIFY · GATEWAYTwilioSMS · voice · WhatsAppGoogle / MicrosoftGmail push · Graph notifications · OAuthSlack / Discord / Consoleevents API · gateway WS · internal routesunify.gateway— create_app() FastAPIadapters/inbound webhook normalizationtwilio.py · google.py · microsoft.pyslack.py · internal.pychannels/outbound + admin APIsphone · whatsapp · gmail · outlookteams · slack · discord · socialoutbound provider calls{thread, event} envelopeEnvelopeSinkHttpEnvelopeSink (local) · Pub/Sub (hosted)ConversationManager· separate processCommsManager → typed Event → slow brainoutbound HTTPvia comms_utils \ No newline at end of file diff --git a/images/developers/gateway-architecture-light.svg b/images/developers/gateway-architecture-light.svg new file mode 100644 index 0000000000..286cc9f449 --- /dev/null +++ b/images/developers/gateway-architecture-light.svg @@ -0,0 +1,5 @@ +UNIFY · GATEWAYTwilioSMS · voice · WhatsAppGoogle / MicrosoftGmail push · Graph notifications · OAuthSlack / Discord / Consoleevents API · gateway WS · internal routesunify.gateway— create_app() FastAPIadapters/inbound webhook normalizationtwilio.py · google.py · microsoft.pyslack.py · internal.pychannels/outbound + admin APIsphone · whatsapp · gmail · outlookteams · slack · discord · socialoutbound provider calls{thread, event} envelopeEnvelopeSinkHttpEnvelopeSink (local) · Pub/Sub (hosted)ConversationManager· separate processCommsManager → typed Event → slow brainoutbound HTTPvia comms_utils \ No newline at end of file diff --git a/images/developers/gateway-architecture.png b/images/developers/gateway-architecture.png deleted file mode 100644 index 1fec1a0d20..0000000000 Binary files a/images/developers/gateway-architecture.png and /dev/null differ diff --git a/images/developers/knowledge-memory-dark.svg b/images/developers/knowledge-memory-dark.svg new file mode 100644 index 0000000000..44fc5d8a45 --- /dev/null +++ b/images/developers/knowledge-memory-dark.svg @@ -0,0 +1,5 @@ +UNIFY · KNOWLEDGE & MEMORYlive(Actor-driven)CodeActActorexecute_function(...)primitives.knowledgeask / update / refactorinner LLM tool loop_search · _filter · _add_rows+ schema toolsKnowledge/* tablestyped columns ·DataManager-backedbackground(every 50 events)EventBusMessage + ManagerMethod eventsMemoryManager.process_chunkunified extraction passContactManagerbios · summaries · response policiesKnowledgeManager.updatedurable domain factsTaskScheduler.updatetask commitments \ No newline at end of file diff --git a/images/developers/knowledge-memory-light.svg b/images/developers/knowledge-memory-light.svg new file mode 100644 index 0000000000..61ec46508c --- /dev/null +++ b/images/developers/knowledge-memory-light.svg @@ -0,0 +1,5 @@ +UNIFY · KNOWLEDGE & MEMORYlive(Actor-driven)CodeActActorexecute_function(...)primitives.knowledgeask / update / refactorinner LLM tool loop_search · _filter · _add_rows+ schema toolsKnowledge/* tablestyped columns ·DataManager-backedbackground(every 50 events)EventBusMessage + ManagerMethod eventsMemoryManager.process_chunkunified extraction passContactManagerbios · summaries · response policiesKnowledgeManager.updatedurable domain factsTaskScheduler.updatetask commitments \ No newline at end of file diff --git a/images/developers/knowledge-memory.png b/images/developers/knowledge-memory.png deleted file mode 100644 index ef65da3a8b..0000000000 Binary files a/images/developers/knowledge-memory.png and /dev/null differ diff --git a/images/developers/learning-loop-dark.svg b/images/developers/learning-loop-dark.svg new file mode 100644 index 0000000000..2371fac773 --- /dev/null +++ b/images/developers/learning-loop-dark.svg @@ -0,0 +1,5 @@ +UNIFY · LEARNING LOOPConversationManagerslow brainact(...) →SteerableToolHandleCodeActActor.act()Phase 1 — doing loopdiscovery gate:FunctionManager_search_functions+ GuidanceManager_searchexecute_code / execute_functionstore_skills (proactive)_StorageCheckHandle: result()resolves after Phase 1Phase 2 — StorageCheckskill librarian LLM looplabel: "Storing reusable skills"often stores nothingFunctionManagerthe WHAT —executable skillsGuidanceManagerthe HOW —playbooks / SOPssearch firststore aftersearch firststore afterLegenddispatch / readstorage write \ No newline at end of file diff --git a/images/developers/learning-loop-light.svg b/images/developers/learning-loop-light.svg new file mode 100644 index 0000000000..56e4dcd9cc --- /dev/null +++ b/images/developers/learning-loop-light.svg @@ -0,0 +1,5 @@ +UNIFY · LEARNING LOOPConversationManagerslow brainact(...) →SteerableToolHandleCodeActActor.act()Phase 1 — doing loopdiscovery gate:FunctionManager_search_functions+ GuidanceManager_searchexecute_code / execute_functionstore_skills (proactive)_StorageCheckHandle: result()resolves after Phase 1Phase 2 — StorageCheckskill librarian LLM looplabel: "Storing reusable skills"often stores nothingFunctionManagerthe WHAT —executable skillsGuidanceManagerthe HOW —playbooks / SOPssearch firststore aftersearch firststore afterLegenddispatch / readstorage write \ No newline at end of file diff --git a/images/developers/learning-loop.png b/images/developers/learning-loop.png deleted file mode 100644 index baab4b8383..0000000000 Binary files a/images/developers/learning-loop.png and /dev/null differ diff --git a/images/developers/live-tile-lifecycle-dark.svg b/images/developers/live-tile-lifecycle-dark.svg new file mode 100644 index 0000000000..419b4d8fa2 --- /dev/null +++ b/images/developers/live-tile-lifecycle-dark.svg @@ -0,0 +1,5 @@ +UNIFY · LIVE TILE LIFECYCLE1Actorcreate_tile(html, data_bindings, on_data)2DashboardManagervalidate + dry-runbindings3Console TileVieweriframe +UnifyData bridge4Orchestra bridgefilter / reduce / joinlive queriesat render timestore tile row +register token5on_data(results)chart updates with fresh data \ No newline at end of file diff --git a/images/developers/live-tile-lifecycle-light.svg b/images/developers/live-tile-lifecycle-light.svg new file mode 100644 index 0000000000..8832a2b9de --- /dev/null +++ b/images/developers/live-tile-lifecycle-light.svg @@ -0,0 +1,5 @@ +UNIFY · LIVE TILE LIFECYCLE1Actorcreate_tile(html, data_bindings, on_data)2DashboardManagervalidate + dry-runbindings3Console TileVieweriframe +UnifyData bridge4Orchestra bridgefilter / reduce / joinlive queriesat render timestore tile row +register token5on_data(results)chart updates with fresh data \ No newline at end of file diff --git a/images/developers/live-tile-lifecycle.png b/images/developers/live-tile-lifecycle.png deleted file mode 100644 index b76ca64ae8..0000000000 Binary files a/images/developers/live-tile-lifecycle.png and /dev/null differ diff --git a/images/developers/storage-check-dark.svg b/images/developers/storage-check-dark.svg new file mode 100644 index 0000000000..bc68d395c9 --- /dev/null +++ b/images/developers/storage-check-dark.svg @@ -0,0 +1,5 @@ +UNIFY · STORAGECHECKtrajectory snapshotmessages + tool calls + result from Phase 1skill librarian LLM loop1. review the trajectory2. search existing stores (dedup)3. decide what persists4. delete superseded entriesstore_skills (mid-run)ProactiveStorage loopsummariesfeed Phase 2FunctionManager_add_functionsreusable code thatran successfullyGuidanceManager_add_guidancerecipes, pitfalls,correctionsentrypoint certificationattach_entrypoint_to_recurring_task+ evidencestore nothingoften the right call \ No newline at end of file diff --git a/images/developers/storage-check-light.svg b/images/developers/storage-check-light.svg new file mode 100644 index 0000000000..b694bf4e25 --- /dev/null +++ b/images/developers/storage-check-light.svg @@ -0,0 +1,5 @@ +UNIFY · STORAGECHECKtrajectory snapshotmessages + tool calls + result from Phase 1skill librarian LLM loop1. review the trajectory2. search existing stores (dedup)3. decide what persists4. delete superseded entriesstore_skills (mid-run)ProactiveStorage loopsummariesfeed Phase 2FunctionManager_add_functionsreusable code thatran successfullyGuidanceManager_add_guidancerecipes, pitfalls,correctionsentrypoint certificationattach_entrypoint_to_recurring_task+ evidencestore nothingoften the right call \ No newline at end of file diff --git a/images/developers/storage-check.png b/images/developers/storage-check.png deleted file mode 100644 index 597818e532..0000000000 Binary files a/images/developers/storage-check.png and /dev/null differ diff --git a/images/developers/tasks-activation-lanes-dark.svg b/images/developers/tasks-activation-lanes-dark.svg new file mode 100644 index 0000000000..1d838f7868 --- /dev/null +++ b/images/developers/tasks-activation-lanes-dark.svg @@ -0,0 +1,5 @@ +UNIFY · ACTIVATION LANESscheduled (live)activation projectedTasks/Activations rowtimer firesCloud Tasks (hosted) ·LocalActivationScheduler (self-host)TaskDue eventConversationManagervalidate · auto-start · silent_by_defaulttriggered (live)inbound messagesms · email · call …mechanical filtermedium + contact include/omitsemantic checkslow brain decides fitprimitives.tasks.executetask_id + trigger_attempt_tokenoffline (headless)due or triggeredexecution_mode = offlinejob dispatchedK8s job (hosted) ·LocalOfflineDispatcher (self-host)offline_runnerno live session · no clarificationsrun persistedTasks/RunsTaskScheduler.execute() → ActiveTasksteerable handle · pause / interject / stop \ No newline at end of file diff --git a/images/developers/tasks-activation-lanes-light.svg b/images/developers/tasks-activation-lanes-light.svg new file mode 100644 index 0000000000..cec74ffd80 --- /dev/null +++ b/images/developers/tasks-activation-lanes-light.svg @@ -0,0 +1,5 @@ +UNIFY · ACTIVATION LANESscheduled (live)activation projectedTasks/Activations rowtimer firesCloud Tasks (hosted) ·LocalActivationScheduler (self-host)TaskDue eventConversationManagervalidate · auto-start · silent_by_defaulttriggered (live)inbound messagesms · email · call …mechanical filtermedium + contact include/omitsemantic checkslow brain decides fitprimitives.tasks.executetask_id + trigger_attempt_tokenoffline (headless)due or triggeredexecution_mode = offlinejob dispatchedK8s job (hosted) ·LocalOfflineDispatcher (self-host)offline_runnerno live session · no clarificationsrun persistedTasks/RunsTaskScheduler.execute() → ActiveTasksteerable handle · pause / interject / stop \ No newline at end of file diff --git a/images/developers/tasks-activation-lanes.png b/images/developers/tasks-activation-lanes.png deleted file mode 100644 index 84b274cce3..0000000000 Binary files a/images/developers/tasks-activation-lanes.png and /dev/null differ diff --git a/images/developers/tasks-anatomy-dark.svg b/images/developers/tasks-anatomy-dark.svg new file mode 100644 index 0000000000..7a29dfb9f1 --- /dev/null +++ b/images/developers/tasks-anatomy-dark.svg @@ -0,0 +1,5 @@ +UNIFY · TASK ANATOMYTask rowname · descriptionpriority · deadlinedestinationpersonal or team:<id>statusschedulestart_attriggermedium · from_contact_ids · recurringXORrepeat: List[RepeatPattern]RRULE subset: frequency · interval ·weekdays · time_of_day · count · untilentrypointNone = agentic · function_id = symbolicofflinelive lane vs headless laneone logical task = many instancestask_id stays fixed · instance_id increments on each re-arm clone \ No newline at end of file diff --git a/images/developers/tasks-anatomy-light.svg b/images/developers/tasks-anatomy-light.svg new file mode 100644 index 0000000000..29c6535797 --- /dev/null +++ b/images/developers/tasks-anatomy-light.svg @@ -0,0 +1,5 @@ +UNIFY · TASK ANATOMYTask rowname · descriptionpriority · deadlinedestinationpersonal or team:<id>statusschedulestart_attriggermedium · from_contact_ids · recurringXORrepeat: List[RepeatPattern]RRULE subset: frequency · interval ·weekdays · time_of_day · count · untilentrypointNone = agentic · function_id = symbolicofflinelive lane vs headless laneone logical task = many instancestask_id stays fixed · instance_id increments on each re-arm clone \ No newline at end of file diff --git a/images/developers/tasks-anatomy.png b/images/developers/tasks-anatomy.png deleted file mode 100644 index e8dc83660d..0000000000 Binary files a/images/developers/tasks-anatomy.png and /dev/null differ diff --git a/images/developers/tasks-lifecycle-dark.svg b/images/developers/tasks-lifecycle-dark.svg new file mode 100644 index 0000000000..025829f591 --- /dev/null +++ b/images/developers/tasks-lifecycle-dark.svg @@ -0,0 +1,5 @@ +UNIFY · TASK LIFECYCLEscheduledhas schedule.start_attriggerablearmed, waiting for eventactiveone runnable instanceactivated_by: schedule | trigger | explicitexecute()execute()completedresult() ok · info summary writtenfailedresult() errorcancelledstop() / interject(cancel)execute() also clones the next instancerecurring: next_repeated_start_at(…) · triggerable: identical re-arm · instance_id + 1 \ No newline at end of file diff --git a/images/developers/tasks-lifecycle-light.svg b/images/developers/tasks-lifecycle-light.svg new file mode 100644 index 0000000000..89d3d26b9f --- /dev/null +++ b/images/developers/tasks-lifecycle-light.svg @@ -0,0 +1,5 @@ +UNIFY · TASK LIFECYCLEscheduledhas schedule.start_attriggerablearmed, waiting for eventactiveone runnable instanceactivated_by: schedule | trigger | explicitexecute()execute()completedresult() ok · info summary writtenfailedresult() errorcancelledstop() / interject(cancel)execute() also clones the next instancerecurring: next_repeated_start_at(…) · triggerable: identical re-arm · instance_id + 1 \ No newline at end of file diff --git a/images/developers/tasks-lifecycle.png b/images/developers/tasks-lifecycle.png deleted file mode 100644 index 047849c86a..0000000000 Binary files a/images/developers/tasks-lifecycle.png and /dev/null differ diff --git a/images/developers/teams-membership-runtime-dark.svg b/images/developers/teams-membership-runtime-dark.svg new file mode 100644 index 0000000000..2c67b28b1f --- /dev/null +++ b/images/developers/teams-membership-runtime-dark.svg @@ -0,0 +1,5 @@ +UNIFY · MEMBERSHIP RUNTIMEsource of truthOrchestrateam memberships · closed sourcestartup payloadSESSION_DETAILSteam_ids · team_summariessystem promptbuild_accessible_teams_blockrouting rulesenv varsTEAM_IDS · TEAM_SUMMARIESworker subprocessesmid-session refreshAssistantUpdateEventupdate_kind='membership'forget_departed_team_rootsdrop cached roots · no stale accessmembership changes land live — a revoked team disappears without a restart \ No newline at end of file diff --git a/images/developers/teams-membership-runtime-light.svg b/images/developers/teams-membership-runtime-light.svg new file mode 100644 index 0000000000..86781f55a9 --- /dev/null +++ b/images/developers/teams-membership-runtime-light.svg @@ -0,0 +1,5 @@ +UNIFY · MEMBERSHIP RUNTIMEsource of truthOrchestrateam memberships · closed sourcestartup payloadSESSION_DETAILSteam_ids · team_summariessystem promptbuild_accessible_teams_blockrouting rulesenv varsTEAM_IDS · TEAM_SUMMARIESworker subprocessesmid-session refreshAssistantUpdateEventupdate_kind='membership'forget_departed_team_rootsdrop cached roots · no stale accessmembership changes land live — a revoked team disappears without a restart \ No newline at end of file diff --git a/images/developers/teams-membership-runtime.png b/images/developers/teams-membership-runtime.png deleted file mode 100644 index d1e61b5565..0000000000 Binary files a/images/developers/teams-membership-runtime.png and /dev/null differ diff --git a/images/developers/teams-scope-model-dark.svg b/images/developers/teams-scope-model-dark.svg new file mode 100644 index 0000000000..5b2c594bf8 --- /dev/null +++ b/images/developers/teams-scope-model-dark.svg @@ -0,0 +1,5 @@ +UNIFY · TEAM SCOPESstate managersGuidance · Knowledge · Functions · Tasks · Data · Secrets · Dashboardsdestination='personal' | 'team:<id>'ContextRegistrydestination → root · membership gatedefaultrequires team_id in SESSION_DETAILS.team_idspersonal root{user_id}/{assistant_id}/…privacy floor — visible to one assistantteam rootsTeams/{team_id}/…one root per membership — visible to current memberssame table names under every rootGuidance · Knowledge · Functions/* · Tasks · Data · Secrets · Dashboards/* · Transcriptswrites target exactly one root — reads fan out across all of them \ No newline at end of file diff --git a/images/developers/teams-scope-model-light.svg b/images/developers/teams-scope-model-light.svg new file mode 100644 index 0000000000..e1da22d27e --- /dev/null +++ b/images/developers/teams-scope-model-light.svg @@ -0,0 +1,5 @@ +UNIFY · TEAM SCOPESstate managersGuidance · Knowledge · Functions · Tasks · Data · Secrets · Dashboardsdestination='personal' | 'team:<id>'ContextRegistrydestination → root · membership gatedefaultrequires team_id in SESSION_DETAILS.team_idspersonal root{user_id}/{assistant_id}/…privacy floor — visible to one assistantteam rootsTeams/{team_id}/…one root per membership — visible to current memberssame table names under every rootGuidance · Knowledge · Functions/* · Tasks · Data · Secrets · Dashboards/* · Transcriptswrites target exactly one root — reads fan out across all of them \ No newline at end of file diff --git a/images/developers/teams-scope-model.png b/images/developers/teams-scope-model.png deleted file mode 100644 index 2e877a148f..0000000000 Binary files a/images/developers/teams-scope-model.png and /dev/null differ diff --git a/images/developers/teams-write-read-paths-dark.svg b/images/developers/teams-write-read-paths-dark.svg new file mode 100644 index 0000000000..52ea80f3c4 --- /dev/null +++ b/images/developers/teams-write-read-paths-dark.svg @@ -0,0 +1,5 @@ +UNIFY · WRITE & READ PATHSWRITEadd_guidance(…)destination='team:42'canonical_destinationnormalize · validate tokenmembership gateteam_id in team_ids?else invalid_destinationwrite_rootTeams/42/Guidanceexactly one rootrows stamped withauthoring_assistant_idREADsearch(…)no destination argread_rootspersonal + everymember teampersonal rootTeams/42Teams/57federated mergefederated_ranked_searchone global windowrows annotated with_federated_source /_federated_contextone destination per write — every reachable root per read \ No newline at end of file diff --git a/images/developers/teams-write-read-paths-light.svg b/images/developers/teams-write-read-paths-light.svg new file mode 100644 index 0000000000..da0ba96e04 --- /dev/null +++ b/images/developers/teams-write-read-paths-light.svg @@ -0,0 +1,5 @@ +UNIFY · WRITE & READ PATHSWRITEadd_guidance(…)destination='team:42'canonical_destinationnormalize · validate tokenmembership gateteam_id in team_ids?else invalid_destinationwrite_rootTeams/42/Guidanceexactly one rootrows stamped withauthoring_assistant_idREADsearch(…)no destination argread_rootspersonal + everymember teampersonal rootTeams/42Teams/57federated mergefederated_ranked_searchone global windowrows annotated with_federated_source /_federated_contextone destination per write — every reachable root per read \ No newline at end of file diff --git a/images/developers/teams-write-read-paths.png b/images/developers/teams-write-read-paths.png deleted file mode 100644 index 1a50cc0280..0000000000 Binary files a/images/developers/teams-write-read-paths.png and /dev/null differ diff --git a/images/developers/user-desktop-architecture-dark.svg b/images/developers/user-desktop-architecture-dark.svg new file mode 100644 index 0000000000..a5702a82e3 --- /dev/null +++ b/images/developers/user-desktop-architecture-dark.svg @@ -0,0 +1,5 @@ +UNIFY · LINKED DESKTOPConversationManagerslow brain — resolves acting useract(...)CodeActActorwrites Python plansprimitives.computer.user_desktop_UserDesktopFactorylist_linked() · session(user_id)UserDesktopHandleact · observe · click · type_text · get_screenshotresolve + consent checkUserDesktopLinkSESSION_DETAILSper owner_user_idHTTPS over outbound tunnelunify cloud relay — machine dials out, nothing listens inbounduser's own computermacOS · Windows · Ubuntuagent-service/act · /screenshot · /api/execlive desktop viewassistant perception onlySFTP $HOMEkey-gated file channelsolid arrows: dispatch flow · dashed line: outbound-only tunnel boundary \ No newline at end of file diff --git a/images/developers/user-desktop-architecture-light.svg b/images/developers/user-desktop-architecture-light.svg new file mode 100644 index 0000000000..9a9ec4493a --- /dev/null +++ b/images/developers/user-desktop-architecture-light.svg @@ -0,0 +1,5 @@ +UNIFY · LINKED DESKTOPConversationManagerslow brain — resolves acting useract(...)CodeActActorwrites Python plansprimitives.computer.user_desktop_UserDesktopFactorylist_linked() · session(user_id)UserDesktopHandleact · observe · click · type_text · get_screenshotresolve + consent checkUserDesktopLinkSESSION_DETAILSper owner_user_idHTTPS over outbound tunnelunify cloud relay — machine dials out, nothing listens inbounduser's own computermacOS · Windows · Ubuntuagent-service/act · /screenshot · /api/execlive desktop viewassistant perception onlySFTP $HOMEkey-gated file channelsolid arrows: dispatch flow · dashed line: outbound-only tunnel boundary \ No newline at end of file diff --git a/images/developers/user-desktop-architecture.png b/images/developers/user-desktop-architecture.png deleted file mode 100644 index de4d2d01a4..0000000000 Binary files a/images/developers/user-desktop-architecture.png and /dev/null differ diff --git a/images/developers/user-desktop-consent-gates-dark.svg b/images/developers/user-desktop-consent-gates-dark.svg new file mode 100644 index 0000000000..f7eb9fa800 --- /dev/null +++ b/images/developers/user-desktop-consent-gates-dark.svg @@ -0,0 +1,5 @@ +UNIFY · CONSENT GATESLAYER 1standing config(from Console)UserDesktopLink.urlcontrol channel linkedfilesys_sync + tunnel= filesys_availablefile access enabledUserFilesysAccessStarted/ UserFilesysAccessStoppedcomms events → grant / revokeLAYER 2live consent(in-memory,per session)_user_desktop_revokedset[user_id] — blocks control_user_filesys_revokedset[user_id] — blocks filesLAYER 3every callre-checksUserDesktopHandle.act(...) · files.pull(...)_assert_user_desktop_allowed / _assert_user_filesys_allowed→ PermissionError on revokerevocation is enforced per call — a mid-session revoke stops the very next action \ No newline at end of file diff --git a/images/developers/user-desktop-consent-gates-light.svg b/images/developers/user-desktop-consent-gates-light.svg new file mode 100644 index 0000000000..4fb899628f --- /dev/null +++ b/images/developers/user-desktop-consent-gates-light.svg @@ -0,0 +1,5 @@ +UNIFY · CONSENT GATESLAYER 1standing config(from Console)UserDesktopLink.urlcontrol channel linkedfilesys_sync + tunnel= filesys_availablefile access enabledUserFilesysAccessStarted/ UserFilesysAccessStoppedcomms events → grant / revokeLAYER 2live consent(in-memory,per session)_user_desktop_revokedset[user_id] — blocks control_user_filesys_revokedset[user_id] — blocks filesLAYER 3every callre-checksUserDesktopHandle.act(...) · files.pull(...)_assert_user_desktop_allowed / _assert_user_filesys_allowed→ PermissionError on revokerevocation is enforced per call — a mid-session revoke stops the very next action \ No newline at end of file diff --git a/images/developers/user-desktop-consent-gates.png b/images/developers/user-desktop-consent-gates.png deleted file mode 100644 index dbd6ecd268..0000000000 Binary files a/images/developers/user-desktop-consent-gates.png and /dev/null differ diff --git a/images/developers/user-desktop-files-flow-dark.svg b/images/developers/user-desktop-files-flow-dark.svg new file mode 100644 index 0000000000..2a7ec6fd7c --- /dev/null +++ b/images/developers/user-desktop-files-flow-dark.svg @@ -0,0 +1,5 @@ +UNIFY · USER FILE CHANNELassistant runtimeuser's home directory ($HOME)SFTP overraw-TCP tunneldocuments & foldersoriginals — never overwritten/.unity-edits/timestamped writeback copiesexcluded: caches · node_modules · .git.ssh · .gnupg · .awsprimitives.computer.user_desktop.fileslist · pull · sync · pushUserHomeSFTPon-demand rclone client · per-link private key~/Unity/Remote/<user_id>/read-only staging mirror of their homelist — browse metadata onlypull / sync — stage copiespush — new timestamped copyreads stage into a local mirror · writes land beside originals, never over them \ No newline at end of file diff --git a/images/developers/user-desktop-files-flow-light.svg b/images/developers/user-desktop-files-flow-light.svg new file mode 100644 index 0000000000..ffece5b76d --- /dev/null +++ b/images/developers/user-desktop-files-flow-light.svg @@ -0,0 +1,5 @@ +UNIFY · USER FILE CHANNELassistant runtimeuser's home directory ($HOME)SFTP overraw-TCP tunneldocuments & foldersoriginals — never overwritten/.unity-edits/timestamped writeback copiesexcluded: caches · node_modules · .git.ssh · .gnupg · .awsprimitives.computer.user_desktop.fileslist · pull · sync · pushUserHomeSFTPon-demand rclone client · per-link private key~/Unity/Remote/<user_id>/read-only staging mirror of their homelist — browse metadata onlypull / sync — stage copiespush — new timestamped copyreads stage into a local mirror · writes land beside originals, never over them \ No newline at end of file diff --git a/images/developers/user-desktop-files-flow.png b/images/developers/user-desktop-files-flow.png deleted file mode 100644 index 0792a522fb..0000000000 Binary files a/images/developers/user-desktop-files-flow.png and /dev/null differ diff --git a/images/developers/voice-dual-brain-dark.svg b/images/developers/voice-dual-brain-dark.svg new file mode 100644 index 0000000000..2e0e7a3a0d --- /dev/null +++ b/images/developers/voice-dual-brain-dark.svg @@ -0,0 +1,5 @@ +UNIFY · VOICE DUAL-BRAINcallerphone · WhatsApp · Unify MeetLiveKit roomunity_{assistant_id}_{medium}Fast Brain— Assistant(Agent)medium_scripts/call.pyDeepgram STT · Silero VADCartesia/ElevenLabs TTS · fast LLMSlow Brain— ConversationManagerpersistent reasoning loopguide_voice_agent · allow_hang_upact(…)InboundPhoneUtterance · eventsFastBrainNotificationSPEAK · guidance · IPC socketFillers & barge-inbuffer filler while slow brain thinksVoiceInterrupt on interruptionLivekitCallManagerdomains/call_manager.pydispatch · hang-up gate · speaker engagement \ No newline at end of file diff --git a/images/developers/voice-dual-brain-light.svg b/images/developers/voice-dual-brain-light.svg new file mode 100644 index 0000000000..6fd70f8fce --- /dev/null +++ b/images/developers/voice-dual-brain-light.svg @@ -0,0 +1,5 @@ +UNIFY · VOICE DUAL-BRAINcallerphone · WhatsApp · Unify MeetLiveKit roomunity_{assistant_id}_{medium}Fast Brain— Assistant(Agent)medium_scripts/call.pyDeepgram STT · Silero VADCartesia/ElevenLabs TTS · fast LLMSlow Brain— ConversationManagerpersistent reasoning loopguide_voice_agent · allow_hang_upact(…)InboundPhoneUtterance · eventsFastBrainNotificationSPEAK · guidance · IPC socketFillers & barge-inbuffer filler while slow brain thinksVoiceInterrupt on interruptionLivekitCallManagerdomains/call_manager.pydispatch · hang-up gate · speaker engagement \ No newline at end of file diff --git a/images/developers/voice-dual-brain.png b/images/developers/voice-dual-brain.png deleted file mode 100644 index 6ab24e0cc8..0000000000 Binary files a/images/developers/voice-dual-brain.png and /dev/null differ diff --git a/images/developers/workspace-email-identities-dark.svg b/images/developers/workspace-email-identities-dark.svg new file mode 100644 index 0000000000..813e5b5777 --- /dev/null +++ b/images/developers/workspace-email-identities-dark.svg @@ -0,0 +1,5 @@ +UNIFY · EMAIL IDENTITIESprimitives.workspace_emailacts as the userprimitives.comms.send_emailacts as the assistantsame assistant, two send identitiesWorkspaceEmailManagersend · list_messages · search · get_messagegateway email channelschannels/gmail · channels/outlook · /email/senduser's connected mailboxGmail API / Microsoft Graph — trusted tokenassistant's managed mailboxcontact graph · threading · outbound historyinbound emailGmail push → /email/gmail · Graph subscriptions → /microsoft/routerwatch / subscriptions wake the assistantworkspace_email sends from the connected account; comms.send_email sends as the assistant. \ No newline at end of file diff --git a/images/developers/workspace-email-identities-light.svg b/images/developers/workspace-email-identities-light.svg new file mode 100644 index 0000000000..5522940b56 --- /dev/null +++ b/images/developers/workspace-email-identities-light.svg @@ -0,0 +1,5 @@ +UNIFY · EMAIL IDENTITIESprimitives.workspace_emailacts as the userprimitives.comms.send_emailacts as the assistantsame assistant, two send identitiesWorkspaceEmailManagersend · list_messages · search · get_messagegateway email channelschannels/gmail · channels/outlook · /email/senduser's connected mailboxGmail API / Microsoft Graph — trusted tokenassistant's managed mailboxcontact graph · threading · outbound historyinbound emailGmail push → /email/gmail · Graph subscriptions → /microsoft/routerwatch / subscriptions wake the assistantworkspace_email sends from the connected account; comms.send_email sends as the assistant. \ No newline at end of file diff --git a/images/developers/workspace-email-identities.png b/images/developers/workspace-email-identities.png deleted file mode 100644 index 8efcef979e..0000000000 Binary files a/images/developers/workspace-email-identities.png and /dev/null differ diff --git a/images/developers/workspace-oauth-plumbing-dark.svg b/images/developers/workspace-oauth-plumbing-dark.svg new file mode 100644 index 0000000000..628b6831aa --- /dev/null +++ b/images/developers/workspace-oauth-plumbing-dark.svg @@ -0,0 +1,5 @@ +UNIFY · WORKSPACE OAUTHConsoleuser approves scopesgateway OAuth callbacksadapters/google.py · adapters/microsoft.pyOrchestra secretsGOOGLE_* / MICROSOFT_* tokensSecretManager synctokens stay in memory · metadata to envin-memory tokensnever written to diskgranted scopes + expiryenv vars onlyreal tokenactor sandboxexecute_codeBearer nonceprovider proxylocalhost · swaps nonce for real tokenGoogle APIs · Microsoft Graphgoogleapis.comgraph.microsoft.comsolid arrows: request flow — dotted arrows: trusted token access. The sandbox never sees a real token. \ No newline at end of file diff --git a/images/developers/workspace-oauth-plumbing-light.svg b/images/developers/workspace-oauth-plumbing-light.svg new file mode 100644 index 0000000000..987052214e --- /dev/null +++ b/images/developers/workspace-oauth-plumbing-light.svg @@ -0,0 +1,5 @@ +UNIFY · WORKSPACE OAUTHConsoleuser approves scopesgateway OAuth callbacksadapters/google.py · adapters/microsoft.pyOrchestra secretsGOOGLE_* / MICROSOFT_* tokensSecretManager synctokens stay in memory · metadata to envin-memory tokensnever written to diskgranted scopes + expiryenv vars onlyreal tokenactor sandboxexecute_codeBearer nonceprovider proxylocalhost · swaps nonce for real tokenGoogle APIs · Microsoft Graphgoogleapis.comgraph.microsoft.comsolid arrows: request flow — dotted arrows: trusted token access. The sandbox never sees a real token. \ No newline at end of file diff --git a/images/developers/workspace-oauth-plumbing.png b/images/developers/workspace-oauth-plumbing.png deleted file mode 100644 index fa9b30f443..0000000000 Binary files a/images/developers/workspace-oauth-plumbing.png and /dev/null differ diff --git a/images/developers/workspace-proxy-policy-dark.svg b/images/developers/workspace-proxy-policy-dark.svg new file mode 100644 index 0000000000..bae7486c3b --- /dev/null +++ b/images/developers/workspace-proxy-policy-dark.svg @@ -0,0 +1,5 @@ +UNIFY · PROXY POLICYsandbox requestGET /google/drive/v3/files…classify()classify.py — path template matchnon_filemail · calendar · contactsfile_readget · list · search · contentfile_writecreate · move · copy · uploadunknown_fileunmatched file-shaped pathpolicy evaluationpolicy.py + ancestry.pywalk parent chain,nearest decision winspassthrough403 default-denyallowedmaskedupstream APIreal token injectedfilter.py masks listings404 / 403item invisible to the assistantNo Console policy configured → everything passes. Once a policy exists, the allowlist governs every file endpoint. \ No newline at end of file diff --git a/images/developers/workspace-proxy-policy-light.svg b/images/developers/workspace-proxy-policy-light.svg new file mode 100644 index 0000000000..555a2bec06 --- /dev/null +++ b/images/developers/workspace-proxy-policy-light.svg @@ -0,0 +1,5 @@ +UNIFY · PROXY POLICYsandbox requestGET /google/drive/v3/files…classify()classify.py — path template matchnon_filemail · calendar · contactsfile_readget · list · search · contentfile_writecreate · move · copy · uploadunknown_fileunmatched file-shaped pathpolicy evaluationpolicy.py + ancestry.pywalk parent chain,nearest decision winspassthrough403 default-denyallowedmaskedupstream APIreal token injectedfilter.py masks listings404 / 403item invisible to the assistantNo Console policy configured → everything passes. Once a policy exists, the allowlist governs every file endpoint. \ No newline at end of file diff --git a/images/developers/workspace-proxy-policy.png b/images/developers/workspace-proxy-policy.png deleted file mode 100644 index 86ea43a352..0000000000 Binary files a/images/developers/workspace-proxy-policy.png and /dev/null differ diff --git a/images/integrations-dev-execution-dark.svg b/images/integrations-dev-execution-dark.svg new file mode 100644 index 0000000000..f213cf539e --- /dev/null +++ b/images/integrations-dev-execution-dark.svg @@ -0,0 +1,5 @@ +UNIFY · EXECUTION & GOVERNANCEActor sandboxawait primitives.integrations.hubspot.search_contacts(…)IntegrationPrimitivesdynamic app namespace → execute_tool(tool_id, args)ops.run_tool → UniSDK run_integration_toolHTTPSOrchestra— governed executiontool policy check (allow / ask / block)audit recordprovider dispatchresult envelopeokconnect_requiredmissing_scopeblocked_by_policyconfirmation_requiredbuild_pending_approval_payload →user approves or deniesresolve_tool_execution(audit_id) + retry \ No newline at end of file diff --git a/images/integrations-dev-execution-light.svg b/images/integrations-dev-execution-light.svg new file mode 100644 index 0000000000..f89a50be38 --- /dev/null +++ b/images/integrations-dev-execution-light.svg @@ -0,0 +1,5 @@ +UNIFY · EXECUTION & GOVERNANCEActor sandboxawait primitives.integrations.hubspot.search_contacts(…)IntegrationPrimitivesdynamic app namespace → execute_tool(tool_id, args)ops.run_tool → UniSDK run_integration_toolHTTPSOrchestra— governed executiontool policy check (allow / ask / block)audit recordprovider dispatchresult envelopeokconnect_requiredmissing_scopeblocked_by_policyconfirmation_requiredbuild_pending_approval_payload →user approves or deniesresolve_tool_execution(audit_id) + retry \ No newline at end of file diff --git a/images/integrations-dev-execution.png b/images/integrations-dev-execution.png deleted file mode 100644 index dd957da18f..0000000000 Binary files a/images/integrations-dev-execution.png and /dev/null differ diff --git a/images/integrations-dev-lanes-dark.svg b/images/integrations-dev-lanes-dark.svg new file mode 100644 index 0000000000..8075553cb5 --- /dev/null +++ b/images/integrations-dev-lanes-dark.svg @@ -0,0 +1,5 @@ +UNIFY · INTEGRATION LANESDiscoveryActivationExecutionProvider-backed(third-party)Builtins catalog —Integrations/Apps + ToolsConsole connect(OAuth or API key)then tool syncprimitives.integrations.<app>.<tool> →Orchestra governed runNative packagesDisk manifest discovery(unity-deploy packages)Required secrets present →functions + guidanceregisteredCustom functions inFunctions/CompositionalSDK + secrets(no gallery entry)Actor checksprimitives.secrets.ask(…)install SDK ·env credentials ·OAuth proxyexecute_code —direct API callsFunctionManager search— one discovery surface for every executable tool \ No newline at end of file diff --git a/images/integrations-dev-lanes-light.svg b/images/integrations-dev-lanes-light.svg new file mode 100644 index 0000000000..3ab86afb5b --- /dev/null +++ b/images/integrations-dev-lanes-light.svg @@ -0,0 +1,5 @@ +UNIFY · INTEGRATION LANESDiscoveryActivationExecutionProvider-backed(third-party)Builtins catalog —Integrations/Apps + ToolsConsole connect(OAuth or API key)then tool syncprimitives.integrations.<app>.<tool> →Orchestra governed runNative packagesDisk manifest discovery(unity-deploy packages)Required secrets present →functions + guidanceregisteredCustom functions inFunctions/CompositionalSDK + secrets(no gallery entry)Actor checksprimitives.secrets.ask(…)install SDK ·env credentials ·OAuth proxyexecute_code —direct API callsFunctionManager search— one discovery surface for every executable tool \ No newline at end of file diff --git a/images/integrations-dev-lanes.png b/images/integrations-dev-lanes.png deleted file mode 100644 index ba865230a2..0000000000 Binary files a/images/integrations-dev-lanes.png and /dev/null differ diff --git a/images/integrations-dev-sync-dark.svg b/images/integrations-dev-sync-dark.svg new file mode 100644 index 0000000000..7b3ade7a78 --- /dev/null +++ b/images/integrations-dev-sync-dark.svg @@ -0,0 +1,5 @@ +UNIFY · PROVIDER TOOL SYNC1Consoleuser connects app (OAuth or API key)integration_tools_sync_requested (event)2IntegrationSyncCoordinatorstatus: syncingprompt block: <integration_tool_sync>also runs at startup:schedule_connected_apps()3FunctionManager.sync_provider_integration_toolsread BuiltinsIntegrations/Toolscompare per-app hash(Functions/Meta)delete + upsertchanged rows4Functions/Primitives rowsprimitives.integrations.<app>.<tool>metadata.source = provider_backedstatus: ready5User notified:“<App> tools are ready.”disconnect → operation: cleanup(rows removed) \ No newline at end of file diff --git a/images/integrations-dev-sync-light.svg b/images/integrations-dev-sync-light.svg new file mode 100644 index 0000000000..aad33eb9cb --- /dev/null +++ b/images/integrations-dev-sync-light.svg @@ -0,0 +1,5 @@ +UNIFY · PROVIDER TOOL SYNC1Consoleuser connects app (OAuth or API key)integration_tools_sync_requested (event)2IntegrationSyncCoordinatorstatus: syncingprompt block: <integration_tool_sync>also runs at startup:schedule_connected_apps()3FunctionManager.sync_provider_integration_toolsread BuiltinsIntegrations/Toolscompare per-app hash(Functions/Meta)delete + upsertchanged rows4Functions/Primitives rowsprimitives.integrations.<app>.<tool>metadata.source = provider_backedstatus: ready5User notified:“<App> tools are ready.”disconnect → operation: cleanup(rows removed) \ No newline at end of file diff --git a/images/integrations-dev-sync.png b/images/integrations-dev-sync.png deleted file mode 100644 index 14355b9076..0000000000 Binary files a/images/integrations-dev-sync.png and /dev/null differ diff --git a/images/unify-docs-banner-dark.svg b/images/unify-docs-banner-dark.svg new file mode 100644 index 0000000000..fef7a9577b --- /dev/null +++ b/images/unify-docs-banner-dark.svg @@ -0,0 +1,5 @@ +UNIFY · DOCSUnifyEverything you can do with your AI teammate \ No newline at end of file diff --git a/images/unify-docs-banner-light.svg b/images/unify-docs-banner-light.svg new file mode 100644 index 0000000000..4db8fcea66 --- /dev/null +++ b/images/unify-docs-banner-light.svg @@ -0,0 +1,5 @@ +UNIFY · DOCSUnifyEverything you can do with your AI teammate \ No newline at end of file diff --git a/images/unify-docs-og.png b/images/unify-docs-og.png new file mode 100644 index 0000000000..945249b8d0 Binary files /dev/null and b/images/unify-docs-og.png differ diff --git a/integrations/developers.mdx b/integrations/developers.mdx index 837e64447c..e7f86583e4 100644 --- a/integrations/developers.mdx +++ b/integrations/developers.mdx @@ -23,7 +23,8 @@ 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. -![Three integration lanes: provider-backed third-party apps discovered via the Builtins catalog and executed through Orchestra; native unity-deploy packages activated by secrets; and the SDK-plus-secrets pattern for apps with no gallery entry — all converging on FunctionManager search](/images/integrations-dev-lanes.png) +Three integration lanes: provider-backed third-party apps discovered via the Builtins catalog and executed through Orchestra; native unity-deploy packages activated by secrets; and the SDK-plus-secrets pattern for apps with no gallery entry — all converging on FunctionManager search +Three integration lanes: provider-backed third-party apps discovered via the Builtins catalog and executed through Orchestra; native unity-deploy packages activated by secrets; and the SDK-plus-secrets pattern for apps with no gallery entry — all converging on FunctionManager search | Lane | Discovery | Activation | Execution | |---|---|---|---| @@ -135,7 +136,8 @@ What it triggers is **materialization**: provider tools from the Builtins catalog are written into the assistant's own `Functions/Primitives` context as first-class FunctionManager rows, so ordinary FM search finds them. -![Provider tool sync and materialization: a Console connect emits an integration_tools_sync_requested event, the IntegrationSyncCoordinator marks the app as syncing, FunctionManager.sync_provider_integration_tools reads the Builtins tools catalog, compares per-app hashes, upserts changed rows into Functions/Primitives, and the user is notified the tools are ready — with a cleanup path on disconnect and a startup pass over all connected apps](/images/integrations-dev-sync.png) +Provider tool sync and materialization: a Console connect emits an integration_tools_sync_requested event, the IntegrationSyncCoordinator marks the app as syncing, FunctionManager.sync_provider_integration_tools reads the Builtins tools catalog, compares per-app hashes, upserts changed rows into Functions/Primitives, and the user is notified the tools are ready — with a cleanup path on disconnect and a startup pass over all connected apps +Provider tool sync and materialization: a Console connect emits an integration_tools_sync_requested event, the IntegrationSyncCoordinator marks the app as syncing, FunctionManager.sync_provider_integration_tools reads the Builtins tools catalog, compares per-app hashes, upserts changed rows into Functions/Primitives, and the user is notified the tools are ready — with a cleanup path on disconnect and a startup pass over all connected apps ### The materialized row @@ -213,7 +215,8 @@ 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. -![Execution and governance: actor sandbox code calls the dynamic integration namespace, which resolves execute_tool, flows through ops.run_tool and UniSDK to Orchestra's governed execution — policy check, audit record, provider dispatch — returning a result envelope whose statuses include ok, connect_required, missing_scope, blocked_by_policy, and confirmation_required, the last of which loops through a pending-approval payload and resolve_tool_execution retry](/images/integrations-dev-execution.png) +Execution and governance: actor sandbox code calls the dynamic integration namespace, which resolves execute_tool, flows through ops.run_tool and UniSDK to Orchestra's governed execution — policy check, audit record, provider dispatch — returning a result envelope whose statuses include ok, connect_required, missing_scope, blocked_by_policy, and confirmation_required, the last of which loops through a pending-approval payload and resolve_tool_execution retry +Execution and governance: actor sandbox code calls the dynamic integration namespace, which resolves execute_tool, flows through ops.run_tool and UniSDK to Orchestra's governed execution — policy check, audit record, provider dispatch — returning a result envelope whose statuses include ok, connect_required, missing_scope, blocked_by_policy, and confirmation_required, the last of which loops through a pending-approval payload and resolve_tool_execution retry The result envelope statuses form the contract: diff --git a/introduction.mdx b/introduction.mdx index 57cf01afb2..22ae1c4070 100644 --- a/introduction.mdx +++ b/introduction.mdx @@ -3,6 +3,9 @@ title: "Welcome" description: "Meet your AI assistant" --- +Unify — everything you can do with your AI teammate +Unify — everything you can do with your AI teammate + Unify gives you a fully remote virtual employee — an AI assistant you can talk to the way you'd talk to any other colleague. Message it, email it, call it, or hop on a video call and share your screen. It remembers your conversations, works on diff --git a/learning/developers/architecture.mdx b/learning/developers/architecture.mdx index b90b4396a5..182a995f73 100644 --- a/learning/developers/architecture.mdx +++ b/learning/developers/architecture.mdx @@ -9,7 +9,8 @@ reading, extending, or embedding the code. It covers the two skill libraries, the actor loop that reads and writes them, and the knowledge and memory subsystems — with symbol names and links into the source throughout. -![The learning loop: ConversationManager dispatches act() to CodeActActor, whose Phase 1 doing loop is gated on FunctionManager and GuidanceManager discovery and whose Phase 2 StorageCheck writes back to both libraries](/images/developers/learning-loop.png) +The learning loop: ConversationManager dispatches act() to CodeActActor, whose Phase 1 doing loop is gated on FunctionManager and GuidanceManager discovery and whose Phase 2 StorageCheck writes back to both libraries +The learning loop: ConversationManager dispatches act() to CodeActActor, whose Phase 1 doing loop is gated on FunctionManager and GuidanceManager discovery and whose Phase 2 StorageCheck writes back to both libraries ## The dual library @@ -82,7 +83,8 @@ sandbox. Both libraries (and Knowledge) read across every scope the assistant can see and write to exactly one: -![Federated scopes: federated_ranked_search fans out to the personal root, team roots, and the read-only builtins catalogue, with ContextRegistry resolving read and write roots](/images/developers/federated-scopes.png) +Federated scopes: federated_ranked_search fans out to the personal root, team roots, and the read-only builtins catalogue, with ContextRegistry resolving read and write roots +Federated scopes: federated_ranked_search fans out to the personal root, team roots, and the read-only builtins catalogue, with ContextRegistry resolving read and write roots - [`ContextRegistry.read_roots`](https://github.com/unifyai/unify/blob/main/unify/common/context_registry.py) returns the personal root first, then `Teams/{team_id}/…` for each id in diff --git a/learning/developers/knowledge-and-memory.mdx b/learning/developers/knowledge-and-memory.mdx index cb267dc3f8..a9f6b05fdf 100644 --- a/learning/developers/knowledge-and-memory.mdx +++ b/learning/developers/knowledge-and-memory.mdx @@ -10,7 +10,8 @@ and a **background consolidation** pass in [`MemoryManager`](https://github.com/unifyai/unify/blob/main/unify/memory_manager/memory_manager.py) that distills recent traffic into durable state. -![Knowledge acquisition pipelines: the live path runs CodeActActor through primitives.knowledge into an inner LLM tool loop over Knowledge tables; the background path runs EventBus events through MemoryManager.process_chunk into ContactManager, KnowledgeManager, and TaskScheduler](/images/developers/knowledge-memory.png) +Knowledge acquisition pipelines: the live path runs CodeActActor through primitives.knowledge into an inner LLM tool loop over Knowledge tables; the background path runs EventBus events through MemoryManager.process_chunk into ContactManager, KnowledgeManager, and TaskScheduler +Knowledge acquisition pipelines: the live path runs CodeActActor through primitives.knowledge into an inner LLM tool loop over Knowledge tables; the background path runs EventBus events through MemoryManager.process_chunk into ContactManager, KnowledgeManager, and TaskScheduler ## KnowledgeManager diff --git a/learning/developers/storage-check.mdx b/learning/developers/storage-check.mdx index c666b9a49e..fa015e3d7c 100644 --- a/learning/developers/storage-check.mdx +++ b/learning/developers/storage-check.mdx @@ -11,7 +11,8 @@ persists. This page covers that pass, its mid-run sibling `store_skills`, and the certification gate that turns stored functions into recurring-task executors. -![StorageCheck flow: the trajectory snapshot feeds a skill librarian LLM loop that can add functions, add guidance, certify a task entrypoint, or store nothing; a mid-run store_skills loop feeds summaries into it](/images/developers/storage-check.png) +StorageCheck flow: the trajectory snapshot feeds a skill librarian LLM loop that can add functions, add guidance, certify a task entrypoint, or store nothing; a mid-run store_skills loop feeds summaries into it +StorageCheck flow: the trajectory snapshot feeds a skill librarian LLM loop that can add functions, add guidance, certify a task entrypoint, or store nothing; a mid-run store_skills loop feeds summaries into it ## `_StorageCheckHandle`: the two-phase wrapper diff --git a/mint.json b/mint.json deleted file mode 100644 index bd1150a9ee..0000000000 --- a/mint.json +++ /dev/null @@ -1,189 +0,0 @@ -{ - "name": "Unify", - "logo": { - "dark": "/images/unify-wordmark-dark.svg", - "light": "/images/unify-wordmark-light.svg", - "href": "https://unify.ai" - }, - "favicon": "/images/unify-mark.svg", - "colors": { - "primary": "#2f9d97", - "light": "#79efdd", - "dark": "#1c6460", - "anchors": { - "from": "#2f9d97", - "to": "#6e4a86" - } - }, - "topbarLinks": [ - { - "name": "GitHub", - "url": "https://github.com/unifyai/unify" - } - ], - "topbarCtaButton": { - "name": "Console", - "url": "https://console.unify.ai" - }, - "navigation": [ - { - "group": "Getting Started", - "pages": [ - "introduction" - ] - }, - { - "group": "Communication", - "pages": [ - "communication/overview", - "communication/console-chat", - "communication/unify-meet", - "communication/phone-calls", - "communication/sms", - "communication/whatsapp", - "communication/email", - "communication/slack", - "communication/microsoft-teams", - "communication/discord", - "communication/meetings", - "communication/setup", - "communication/voice", - "communication/behavior", - "communication/contacts", - "communication/recordings-and-transcripts", - { - "group": "Developers", - "pages": [ - "communication/developers/architecture", - "communication/developers/gateway", - "communication/developers/conversation-manager", - "communication/developers/voice" - ] - } - ] - }, - { - "group": "Workspace", - "pages": [ - "workspace/overview", - "workspace/connecting", - "workspace/email", - "workspace/calendar", - "workspace/files", - "workspace/contacts-and-tasks", - "workspace/security", - { - "group": "Developers", - "pages": [ - "workspace/developers/architecture", - "workspace/developers/oauth-and-secrets", - "workspace/developers/provider-proxy", - "workspace/developers/gateway-channels" - ] - } - ] - }, - { - "group": "Integrations", - "pages": [ - "integrations/overview", - "integrations/connecting", - "integrations/permissions", - "integrations/using", - "integrations/developers" - ] - }, - { - "group": "Tasks", - "pages": [ - "tasks/overview", - "tasks/creating", - "tasks/running", - "tasks/monitoring", - "tasks/developers" - ] - }, - { - "group": "Learning", - "pages": [ - "learning/overview", - "learning/watching-it-work", - "learning/guidance-and-functions", - "learning/knowledge", - "learning/teaching", - { - "group": "Developers", - "pages": [ - "learning/developers/architecture", - "learning/developers/function-manager", - "learning/developers/guidance-manager", - "learning/developers/storage-check", - "learning/developers/knowledge-and-memory" - ] - } - ] - }, - { - "group": "Canvas", - "pages": [ - "canvas/overview", - "canvas/data", - "canvas/dashboards", - "canvas/recipes", - "canvas/developers" - ] - }, - { - "group": "Their Computer", - "pages": [ - "their-computer/overview", - "their-computer/what-it-does", - "their-computer/watching-and-control", - "their-computer/the-machine", - "their-computer/developers" - ] - }, - { - "group": "Your Computer", - "pages": [ - "your-computer/overview", - "your-computer/setup", - "your-computer/how-it-works", - "your-computer/files-and-terminal", - "your-computer/control-and-safety", - { - "group": "Developers", - "pages": [ - "your-computer/developers" - ] - } - ] - }, - { - "group": "Teams", - "pages": [ - "teams/overview", - "teams/membership", - "teams/shared-context", - "teams/managing", - "teams/developers" - ] - }, - { - "group": "Hiring", - "pages": [ - "hiring/overview", - "hiring/twin-vs-teammates", - "hiring/onboarding", - "hiring/liaison" - ] - } - ], - "footerSocials": { - "discord": "https://discord.com/invite/sXyFF8tDtm", - "x": "https://twitter.com/letsunifyai", - "youtube": "https://youtube.com/@unifyai", - "linkedin": "https://www.linkedin.com/company/unifyai", - "github": "https://github.com/unifyai/unify" - } -} diff --git a/package-lock.json b/package-lock.json index 66d05ca1ca..345b859424 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "unify-docs-audit-vuln-docs", + "name": "unify-docs", "lockfileVersion": 3, "requires": true, "packages": { diff --git a/tasks/developers.mdx b/tasks/developers.mdx index c1590e04b3..d0c914bd31 100644 --- a/tasks/developers.mdx +++ b/tasks/developers.mdx @@ -27,7 +27,8 @@ Three design commitments shape everything below: ## The task model -Anatomy of a Task: the task row with name/description, priority/deadline, destination, status; schedule XOR trigger with repeat patterns; entrypoint and offline flags; and the task_id/instance_id split +Anatomy of a Task: the task row with name/description, priority/deadline, destination, status; schedule XOR trigger with repeat patterns; entrypoint and offline flags; and the task_id/instance_id split +Anatomy of a Task: the task row with name/description, priority/deadline, destination, status; schedule XOR trigger with repeat patterns; entrypoint and offline flags; and the task_id/instance_id split The model lives in [`unify/task_scheduler/types/task.py`](https://github.com/unifyai/unify/blob/main/unify/task_scheduler/types/task.py). @@ -75,7 +76,8 @@ the real work: ## The lifecycle -Task lifecycle state machine: scheduled and triggerable states flow into active via execute(), which fans out to completed, failed, and cancelled; execute() also clones the next instance back into scheduled/triggerable +Task lifecycle state machine: scheduled and triggerable states flow into active via execute(), which fans out to completed, failed, and cancelled; execute() also clones the next instance back into scheduled/triggerable +Task lifecycle state machine: scheduled and triggerable states flow into active via execute(), which fans out to completed, failed, and cancelled; execute() also clones the next instance back into scheduled/triggerable [`Status`](https://github.com/unifyai/unify/blob/main/unify/task_scheduler/types/status.py) is a six-value enum: `scheduled`, `triggerable`, `active`, `completed`, @@ -158,7 +160,8 @@ rows) inherit the task's scope. ## Activation: three lanes -Activation lanes: scheduled-live via activations, timers, and TaskDue into the ConversationManager; triggered-live via inbound messages, mechanical filter, semantic check, and primitives.tasks.execute; offline via dispatched jobs running offline_runner — all converging on TaskScheduler.execute returning an ActiveTask +Activation lanes: scheduled-live via activations, timers, and TaskDue into the ConversationManager; triggered-live via inbound messages, mechanical filter, semantic check, and primitives.tasks.execute; offline via dispatched jobs running offline_runner — all converging on TaskScheduler.execute returning an ActiveTask +Activation lanes: scheduled-live via activations, timers, and TaskDue into the ConversationManager; triggered-live via inbound messages, mechanical filter, semantic check, and primitives.tasks.execute; offline via dispatched jobs running offline_runner — all converging on TaskScheduler.execute returning an ActiveTask A task row does nothing by itself. Activation — deciding *now is the moment* — arrives through one of three lanes, all converging on diff --git a/teams/developers.mdx b/teams/developers.mdx index 0dc8cf6f56..38f7cca25d 100644 --- a/teams/developers.mdx +++ b/teams/developers.mdx @@ -24,7 +24,8 @@ namespaces addressed by path. Team sharing is implemented as a pure *namespace* concern: the same tables exist under multiple **roots**, and one class decides which root any given operation touches. -![Two scopes, one registry: state managers pass a destination string to ContextRegistry, which resolves it to either the personal root or a membership-gated team root — with the same table names under every root](/images/developers/teams-scope-model.png) +Two scopes, one registry: state managers pass a destination string to ContextRegistry, which resolves it to either the personal root or a membership-gated team root — with the same table names under every root +Two scopes, one registry: state managers pass a destination string to ContextRegistry, which resolves it to either the personal root or a membership-gated team root — with the same table names under every root There are exactly two kinds of root: @@ -106,7 +107,8 @@ this: ## Writes and reads, end to end -![Write path: a destination string is normalized, membership-gated, and resolved to exactly one root. Read path: read_roots fans out across the personal root and every member team, merged by federated search into one global window](/images/developers/teams-write-read-paths.png) +Write path: a destination string is normalized, membership-gated, and resolved to exactly one root. Read path: read_roots fans out across the personal root and every member team, merged by federated search into one global window +Write path: a destination string is normalized, membership-gated, and resolved to exactly one root. Read path: read_roots fans out across the personal root and every member team, merged by federated search into one global window ### The write path @@ -223,7 +225,8 @@ different semantics worth knowing: ## How membership reaches the runtime -![Membership flow: Orchestra owns team membership and delivers team_ids and team_summaries in the startup payload to SESSION_DETAILS, which feeds the system prompt block and worker env vars; a mid-session AssistantUpdateEvent refreshes membership live and forget_departed_team_roots drops cached roots](/images/developers/teams-membership-runtime.png) +Membership flow: Orchestra owns team membership and delivers team_ids and team_summaries in the startup payload to SESSION_DETAILS, which feeds the system prompt block and worker env vars; a mid-session AssistantUpdateEvent refreshes membership live and forget_departed_team_roots drops cached roots +Membership flow: Orchestra owns team membership and delivers team_ids and team_summaries in the startup payload to SESSION_DETAILS, which feeds the system prompt block and worker env vars; a mid-session AssistantUpdateEvent refreshes membership live and forget_departed_team_roots drops cached roots ### Session state diff --git a/tests/helpers.py b/tests/helpers.py index fc24d3cad0..e72025d968 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -116,19 +116,30 @@ def run_test(filepath: str) -> Tuple[Dict, Dict, bool]: return python_results, shell_results, all_python_passed and all_shell_passed +def _flatten_pages(pages: List[Any]) -> List[str]: + """Flatten a docs.json pages list, expanding nested sub-groups.""" + flat = list() + for page in pages: + if isinstance(page, dict): + flat.extend(_flatten_pages(page.get("pages", []))) + else: + flat.append(page) + return flat + + def group_and_order_results( results: Dict[str, Dict[str, Union[True, str]]], ) -> Dict[str, Dict[str, Dict[str, Union[True, str]]]]: - with open(os.path.join(this_dir, "../mint.json")) as file: - mint_contents = file.read() - mint_json = json.loads(mint_contents) - navigation = mint_json["navigation"] + with open(os.path.join(this_dir, "../docs.json")) as file: + docs_contents = file.read() + docs_json = json.loads(docs_contents) + navigation = docs_json["navigation"]["groups"] results_out = dict() for i, group in enumerate(navigation): if group["group"] in ("", "API Reference"): continue results_out[group["group"]] = dict() - for j, page in enumerate(group["pages"]): + for j, page in enumerate(_flatten_pages(group["pages"])): results_out[group["group"]][page] = results[page] return results_out diff --git a/their-computer/developers.mdx b/their-computer/developers.mdx index 2959fc3834..30ed48e49b 100644 --- a/their-computer/developers.mdx +++ b/their-computer/developers.mdx @@ -17,7 +17,8 @@ are separate closed-source components that speak to these interfaces. ## The control stack -![The computer control stack](/images/developers/dev-computer-control-stack.png) +The computer control stack +The computer control stack Computer control is layered, with a clean seam at each level: @@ -168,7 +169,8 @@ automation API is `{base}/api`. ## Lifecycle: from cold start to ready -![Managed desktop lifecycle](/images/developers/dev-desktop-lifecycle.png) +Managed desktop lifecycle +Managed desktop lifecycle Desktop state lives on the global session singleton ([`session_details.py`](https://github.com/unifyai/unify/blob/main/unify/session_details.py)): @@ -272,7 +274,8 @@ and `USER_DESKTOP` (a linked user machine). Each has a target class in ## Screen share & remote control -![Screen share and remote control flow](/images/developers/dev-screen-share-flow.png) +Screen share and remote control flow +Screen share and remote control flow The Console's Meet controls arrive as system events — `AssistantScreenShareStarted/Stopped`, `UserScreenShareStarted/Stopped`, diff --git a/workspace/developers/architecture.mdx b/workspace/developers/architecture.mdx index d7c3ef9a26..91b739343d 100644 --- a/workspace/developers/architecture.mdx +++ b/workspace/developers/architecture.mdx @@ -38,7 +38,8 @@ The workspace integration is built from four cooperating pieces: [`sharepoint`](https://github.com/unifyai/unify/blob/main/unify/gateway/channels/sharepoint/views.py)) that back sending, inbox watches, and the Console file picker. -![Workspace OAuth plumbing: the Console approves scopes, gateway OAuth callbacks persist GOOGLE_*/MICROSOFT_* tokens to Orchestra secrets, SecretManager syncs them into the runtime keeping raw tokens in memory only, and the localhost provider proxy swaps sandbox nonces for real tokens before calling Google APIs and Microsoft Graph](/images/developers/workspace-oauth-plumbing.png) +Workspace OAuth plumbing: the Console approves scopes, gateway OAuth callbacks persist GOOGLE_*/MICROSOFT_* tokens to Orchestra secrets, SecretManager syncs them into the runtime keeping raw tokens in memory only, and the localhost provider proxy swaps sandbox nonces for real tokens before calling Google APIs and Microsoft Graph +Workspace OAuth plumbing: the Console approves scopes, gateway OAuth callbacks persist GOOGLE_*/MICROSOFT_* tokens to Orchestra secrets, SecretManager syncs them into the runtime keeping raw tokens in memory only, and the localhost provider proxy swaps sandbox nonces for real tokens before calling Google APIs and Microsoft Graph ## The core design decision diff --git a/workspace/developers/gateway-channels.mdx b/workspace/developers/gateway-channels.mdx index 74e24b8d25..ff385c70a6 100644 --- a/workspace/developers/gateway-channels.mdx +++ b/workspace/developers/gateway-channels.mdx @@ -13,7 +13,8 @@ Email is the place where the twin-vs-teammate identity split ([user docs](/hiring/twin-vs-teammates)) becomes concrete in code — there are two entirely separate send paths: -![Two email identities: primitives.workspace_email acts as the user through WorkspaceEmailManager calling Gmail API or Microsoft Graph directly with a trusted token; primitives.comms.send_email acts as the assistant through the gateway email channels to its managed mailbox; inbound email arrives via Gmail push to /email/gmail and Graph subscriptions to /microsoft/router, waking the assistant](/images/developers/workspace-email-identities.png) +Two email identities: primitives.workspace_email acts as the user through WorkspaceEmailManager calling Gmail API or Microsoft Graph directly with a trusted token; primitives.comms.send_email acts as the assistant through the gateway email channels to its managed mailbox; inbound email arrives via Gmail push to /email/gmail and Graph subscriptions to /microsoft/router, waking the assistant +Two email identities: primitives.workspace_email acts as the user through WorkspaceEmailManager calling Gmail API or Microsoft Graph directly with a trusted token; primitives.comms.send_email acts as the assistant through the gateway email channels to its managed mailbox; inbound email arrives via Gmail push to /email/gmail and Graph subscriptions to /microsoft/router, waking the assistant ### `primitives.workspace_email` — acts as the user diff --git a/workspace/developers/provider-proxy.mdx b/workspace/developers/provider-proxy.mdx index 2098debd03..e8c7e5b281 100644 --- a/workspace/developers/provider-proxy.mdx +++ b/workspace/developers/provider-proxy.mdx @@ -10,7 +10,8 @@ at once: give LLM-authored code the **complete** provider REST surface without a real token, and enforce the per-assistant **file-access allowlist** at a single choke point. -![Provider proxy policy pipeline: a sandbox request is classified by classify.py into non_file (passthrough), file_read, file_write, or unknown_file (403 default-deny); file reads and writes go through policy evaluation in policy.py and ancestry.py, with allowed requests forwarded to the upstream API and masked items returned as 404/403](/images/developers/workspace-proxy-policy.png) +Provider proxy policy pipeline: a sandbox request is classified by classify.py into non_file (passthrough), file_read, file_write, or unknown_file (403 default-deny); file reads and writes go through policy evaluation in policy.py and ancestry.py, with allowed requests forwarded to the upstream API and masked items returned as 404/403 +Provider proxy policy pipeline: a sandbox request is classified by classify.py into non_file (passthrough), file_read, file_write, or unknown_file (403 default-deny); file reads and writes go through policy evaluation in policy.py and ancestry.py, with allowed requests forwarded to the upstream API and masked items returned as 404/403 ## Module map diff --git a/your-computer/developers.mdx b/your-computer/developers.mdx index 12c346e8bb..db8722c895 100644 --- a/your-computer/developers.mdx +++ b/your-computer/developers.mdx @@ -25,7 +25,8 @@ Console live view, every call re-checks consent, file access is pull-on-request rather than continuously synced, and writebacks never touch originals. -Architecture of linked user-desktop control: ConversationManager dispatches act(...) to CodeActActor, which reaches primitives.computer.user_desktop; _UserDesktopFactory resolves a UserDesktopLink from SESSION_DETAILS and hands back a UserDesktopHandle whose calls travel over an outbound-only tunnel to the user's machine, where an agent-service, a live desktop view for assistant perception, and a key-gated SFTP file channel run +Architecture of linked user-desktop control: ConversationManager dispatches act(...) to CodeActActor, which reaches primitives.computer.user_desktop; _UserDesktopFactory resolves a UserDesktopLink from SESSION_DETAILS and hands back a UserDesktopHandle whose calls travel over an outbound-only tunnel to the user's machine, where an agent-service, a live desktop view for assistant perception, and a key-gated SFTP file channel run +Architecture of linked user-desktop control: ConversationManager dispatches act(...) to CodeActActor, which reaches primitives.computer.user_desktop; _UserDesktopFactory resolves a UserDesktopLink from SESSION_DETAILS and hands back a UserDesktopHandle whose calls travel over an outbound-only tunnel to the user's machine, where an agent-service, a live desktop view for assistant perception, and a key-gated SFTP file channel run ## The link: session state @@ -126,7 +127,8 @@ Three deliberate differences from the assistant-desktop path: ## The file channel: `user_desktop.files` -File flow between the assistant runtime and the user's home directory: the files namespace calls UserHomeSFTP, list browses metadata only, pull and sync stage copies into the read-only mirror at ~/Unity/Remote/<user_id>/, and push writes a new timestamped copy into the remote /.unity-edits/ tree, never touching originals; noise and credential directories are excluded +File flow between the assistant runtime and the user's home directory: the files namespace calls UserHomeSFTP, list browses metadata only, pull and sync stage copies into the read-only mirror at ~/Unity/Remote/<user_id>/, and push writes a new timestamped copy into the remote /.unity-edits/ tree, never touching originals; noise and credential directories are excluded +File flow between the assistant runtime and the user's home directory: the files namespace calls UserHomeSFTP, list browses metadata only, pull and sync stage copies into the read-only mirror at ~/Unity/Remote/<user_id>/, and push writes a new timestamped copy into the remote /.unity-edits/ tree, never touching originals; noise and credential directories are excluded File access is a **separate channel with separate consent**, implemented in [`unify/file_manager/sync/user_sftp.py`](https://github.com/unifyai/unify/blob/main/unify/file_manager/sync/user_sftp.py) @@ -219,7 +221,8 @@ and the computer environment context ## Consent: three gates, checked at three times -Three consent layers for the linked desktop: standing config from the Console (UserDesktopLink.url for control, filesys_sync plus tunnel coordinates for files), live in-memory revoke sets (_user_desktop_revoked and _user_filesys_revoked, driven by UserFilesysAccessStarted/Stopped comms events), and per-call assertions in UserDesktopHandle and the files namespace that raise PermissionError on revoke +Three consent layers for the linked desktop: standing config from the Console (UserDesktopLink.url for control, filesys_sync plus tunnel coordinates for files), live in-memory revoke sets (_user_desktop_revoked and _user_filesys_revoked, driven by UserFilesysAccessStarted/Stopped comms events), and per-call assertions in UserDesktopHandle and the files namespace that raise PermissionError on revoke +Three consent layers for the linked desktop: standing config from the Console (UserDesktopLink.url for control, filesys_sync plus tunnel coordinates for files), live in-memory revoke sets (_user_desktop_revoked and _user_filesys_revoked, driven by UserFilesysAccessStarted/Stopped comms events), and per-call assertions in UserDesktopHandle and the files namespace that raise PermissionError on revoke Access control is layered, and each layer answers a different question: