Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions data-machine.php
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,9 @@ function datamachine_activate_for_site() {
// Assign orphaned resources (agent_id IS NULL) to sole agent on single-agent installs (idempotent).
datamachine_assign_orphaned_resources_to_sole_agent();

// Migrate USER.md to network-scoped paths and create NETWORK.md on multisite (idempotent).
datamachine_migrate_user_md_to_network_scope();

// Clean up legacy per-agent-type log level options (idempotent).
foreach ( array( 'pipeline', 'chat', 'system' ) as $legacy_agent_type ) {
delete_option( "datamachine_log_level_{$legacy_agent_type}" );
Expand Down
4 changes: 3 additions & 1 deletion inc/Abilities/File/AgentFileAbilities.php
Original file line number Diff line number Diff line change
Expand Up @@ -626,7 +626,7 @@ private function resolveFilePath( DirectoryManager $dm, int $user_id, string $fi
* Resolve a layer identifier to its directory path.
*
* @param DirectoryManager $dm Directory manager instance.
* @param string $layer Layer identifier ('shared', 'agent', 'user').
* @param string $layer Layer identifier ('shared', 'agent', 'user', 'network').
* @param int $user_id Effective user ID.
* @param int $agent_id Agent ID.
* @return string Directory path.
Expand All @@ -637,6 +637,8 @@ private function resolveLayerDirectory( DirectoryManager $dm, string $layer, int
return $dm->get_shared_directory();
case MemoryFileRegistry::LAYER_USER:
return $dm->get_user_directory( $user_id );
case MemoryFileRegistry::LAYER_NETWORK:
return $dm->get_network_directory();
case MemoryFileRegistry::LAYER_AGENT:
default:
return $dm->resolve_agent_directory( array(
Expand Down
17 changes: 12 additions & 5 deletions inc/Cli/Commands/MemoryCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -894,8 +894,9 @@ public function paths( array $args, array $assoc_args ): void {
$agent_dir = $directory_manager->get_agent_identity_directory_for_user( $effective_user_id );
}

$shared_dir = $directory_manager->get_shared_directory();
$user_dir = $directory_manager->get_user_directory( $effective_user_id );
$shared_dir = $directory_manager->get_shared_directory();
$user_dir = $directory_manager->get_user_directory( $effective_user_id );
$network_dir = $directory_manager->get_network_directory();

$site_root = untrailingslashit( ABSPATH );
$relative = \WP_CLI\Utils\get_flag_value( $assoc_args, 'relative', false );
Expand All @@ -922,15 +923,21 @@ public function paths( array $args, array $assoc_args ): void {
'layer' => 'user',
'directory' => $user_dir,
),
array(
'file' => 'NETWORK.md',
'layer' => 'network',
'directory' => $network_dir,
),
);

$format = \WP_CLI\Utils\get_flag_value( $assoc_args, 'format', 'json' );

if ( 'json' === $format ) {
$layers = array(
'shared' => $shared_dir,
'agent' => $agent_dir,
'user' => $user_dir,
'shared' => $shared_dir,
'agent' => $agent_dir,
'user' => $user_dir,
'network' => $network_dir,
);

$files = array();
Expand Down
58 changes: 55 additions & 3 deletions inc/Core/FilesRepository/DirectoryManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -180,18 +180,70 @@ public function get_agent_identity_directory( string $agent_slug ): string {
/**
* Get user layer directory path.
*
* On multisite, users are network-wide so USER.md must live in a
* network-global location — the main site's uploads directory. On
* single-site installs, the path is unchanged.
*
* @since 0.36.1
* @since 0.48.0 Network-scoped on multisite (resolves to main site uploads).
*
* @param int $user_id WordPress user ID.
* @return string Full path to user layer directory.
*/
public function get_user_directory( int $user_id ): string {
$upload_dir = wp_upload_dir();
$base = trailingslashit( $upload_dir['basedir'] ) . self::REPOSITORY_DIR;
$user_id = absint( $user_id );
$base = $this->get_network_base_directory();
$user_id = absint( $user_id );

return "{$base}/users/{$user_id}";
}

/**
* Get network-level directory path.
*
* Returns the network/ subdirectory under the network-global base.
* Used for NETWORK.md and other network-scoped files on multisite.
* On single-site installs, this resolves to the same base as shared/.
*
* @since 0.48.0
* @return string Full path to network directory.
*/
public function get_network_directory(): string {
return $this->get_network_base_directory() . '/network';
}

/**
* Get the network-global base directory for Data Machine files.
*
* On multisite, resolves to the main site's uploads directory so that
* network-scoped files (users/, network/) live in a single location
* regardless of which subsite is active. On single-site installs,
* returns the standard uploads-based path.
*
* @since 0.48.0
* @return string Base directory path (without trailing slash).
*/
private function get_network_base_directory(): string {
if ( ! is_multisite() ) {
$upload_dir = wp_upload_dir();
return trailingslashit( $upload_dir['basedir'] ) . self::REPOSITORY_DIR;
}

// On multisite, always use the main site's upload directory.
// This avoids per-site paths like wp-content/uploads/sites/7/.
$current_blog_id = get_current_blog_id();
$main_site_id = get_main_site_id();

if ( $current_blog_id !== $main_site_id ) {
switch_to_blog( $main_site_id );
$upload_dir = wp_upload_dir();
restore_current_blog();
} else {
$upload_dir = wp_upload_dir();
}

return trailingslashit( $upload_dir['basedir'] ) . self::REPOSITORY_DIR;
}

/**
* Resolve effective user ID for layered memory context.
*
Expand Down
9 changes: 5 additions & 4 deletions inc/Engine/AI/Directives/CoreMemoryFilesDirective.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*
* Loads memory files from the MemoryFileRegistry and injects them into
* every AI call. Files are resolved to their layer directories:
* shared → agents/{slug} → users/{id}
* shared → agents/{slug} → users/{id} → network/
*
* The registry is the single source of truth for which files exist,
* what layer they belong to, and what order they load in.
Expand Down Expand Up @@ -51,12 +51,13 @@ public static function get_outputs( string $provider_name, array $tools, ?string

// Resolve layer directories once.
$layer_dirs = array(
MemoryFileRegistry::LAYER_SHARED => $directory_manager->get_shared_directory(),
MemoryFileRegistry::LAYER_AGENT => $directory_manager->resolve_agent_directory( array(
MemoryFileRegistry::LAYER_SHARED => $directory_manager->get_shared_directory(),
MemoryFileRegistry::LAYER_AGENT => $directory_manager->resolve_agent_directory( array(
'agent_id' => (int) ( $payload['agent_id'] ?? 0 ),
'user_id' => $user_id,
) ),
MemoryFileRegistry::LAYER_USER => $directory_manager->get_user_directory( $user_id ),
MemoryFileRegistry::LAYER_USER => $directory_manager->get_user_directory( $user_id ),
MemoryFileRegistry::LAYER_NETWORK => $directory_manager->get_network_directory(),
);

$outputs = array();
Expand Down
7 changes: 4 additions & 3 deletions inc/Engine/AI/Directives/MemoryFilesReader.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,13 @@ public static function read( array $memory_files, string $scope_label, int $scop

// Resolve all layer directories once.
$layer_dirs = array(
MemoryFileRegistry::LAYER_SHARED => $directory_manager->get_shared_directory(),
MemoryFileRegistry::LAYER_AGENT => $directory_manager->resolve_agent_directory( array(
MemoryFileRegistry::LAYER_SHARED => $directory_manager->get_shared_directory(),
MemoryFileRegistry::LAYER_AGENT => $directory_manager->resolve_agent_directory( array(
'agent_id' => $agent_id,
'user_id' => $user_id,
) ),
MemoryFileRegistry::LAYER_USER => $directory_manager->get_user_directory( $user_id ),
MemoryFileRegistry::LAYER_USER => $directory_manager->get_user_directory( $user_id ),
MemoryFileRegistry::LAYER_NETWORK => $directory_manager->get_network_directory(),
);

$outputs = array();
Expand Down
11 changes: 6 additions & 5 deletions inc/Engine/AI/MemoryFileRegistry.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,10 @@ class MemoryFileRegistry {
/**
* Valid layer identifiers.
*/
const LAYER_SHARED = 'shared';
const LAYER_AGENT = 'agent';
const LAYER_USER = 'user';
const LAYER_SHARED = 'shared';
const LAYER_AGENT = 'agent';
const LAYER_USER = 'user';
const LAYER_NETWORK = 'network';

/**
* Registered memory files.
Expand All @@ -53,7 +54,7 @@ class MemoryFileRegistry {
* @param array $args {
* Optional. Registration arguments.
*
* @type string $layer One of 'shared', 'agent', 'user'. Default 'agent'.
* @type string $layer One of 'shared', 'agent', 'user', 'network'. Default 'agent'.
* @type bool $protected Whether the file is protected from deletion. Default false.
* @type string $label Human-readable display label. Default derived from filename.
* @type string $description Optional description of the file's purpose.
Expand All @@ -68,7 +69,7 @@ public static function register( string $filename, int $priority = 50, array $ar
}

$layer = $args['layer'] ?? self::LAYER_AGENT;
if ( ! in_array( $layer, array( self::LAYER_SHARED, self::LAYER_AGENT, self::LAYER_USER ), true ) ) {
if ( ! in_array( $layer, array( self::LAYER_SHARED, self::LAYER_AGENT, self::LAYER_USER, self::LAYER_NETWORK ), true ) ) {
$layer = self::LAYER_AGENT;
}

Expand Down
10 changes: 9 additions & 1 deletion inc/bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -89,13 +89,21 @@
'description' => 'Accumulated knowledge. Grows over time.',
) );

// User layer — human preferences, visible to all agents for this user.
// User layer — human preferences, network-scoped on multisite.
MemoryFileRegistry::register( 'USER.md', 25, array(
'layer' => MemoryFileRegistry::LAYER_USER,
'protected' => true,
'label' => 'User Profile',
'description' => 'Information about the human the agent works with.',
) );

// Network layer — multisite topology, only meaningful on multisite installs.
MemoryFileRegistry::register( 'NETWORK.md', 5, array(
'layer' => MemoryFileRegistry::LAYER_NETWORK,
'protected' => true,
'label' => 'Network Context',
'description' => 'WordPress multisite network topology and shared resources.',
) );
// SiteContext is autoloaded (Core\WordPress\SiteContext) — register its cache invalidation hook here.
add_action( 'init', array( \DataMachine\Core\WordPress\SiteContext::class, 'register_cache_invalidation' ) );
require_once __DIR__ . '/Engine/AI/Directives/SiteContextDirective.php';
Expand Down
Loading
Loading