Plugin Directory

Changeset 3788796


Ignore:
Timestamp:
11/06/2026 10:15:08 PM (less than one hour ago)
Author:
jboydston
Message:

Release 3.2.0: evidence-linked Suggestions inbox, reviewed memory application and optional native processing

Location:
botcreds-agent-memory
Files:
16 added
14 edited
1 copied

Legend:

Unmodified
Added
Removed
  • botcreds-agent-memory/tags/3.2.0/adapters/reference/outbox.py

    r3788734 r3788796  
    124124                               (scope, resource, identity)).fetchone() is not None
    125125
     126    def _purge_scope_derivatives(self, scope):
     127        """Extension point; called inside the same SQLite transaction as source purge."""
     128
     129    def _purge_session_derivatives(self, scope, identity):
     130        """Extension point for managed processing drafts."""
     131
     132    def _purge_resource_derivatives(self, scope, resource, identity):
     133        """Extension point for explicitly negotiated processing invalidations."""
     134
    126135    def _purge_session(self, scope, identity):
    127136        # Caller holds the SQLite transaction. Retain only an opaque permanent tombstone.
    … …  
    137146                self.db.execute("INSERT OR IGNORE INTO deleted_resources VALUES (?,'checkpoint',?)", (scope, row["identity"]))
    138147                self.db.execute("DELETE FROM checkpoints WHERE identity=?", (row["identity"],))
     148
     149        self._purge_session_derivatives(scope, identity)
    139150
    140151    def delete_conversation(self, scope, source, session_id, *, confirm_session_id, send=None):
    … …  
    225236            self.db.execute("DELETE FROM queue WHERE scope=?", (scope,))
    226237            self.db.execute("DELETE FROM checkpoints WHERE scope=?", (scope,))
     238            self._purge_scope_derivatives(scope)
    227239        return dict(self.status(scope), local_storage_maintenance=self._storage_cleanup(),
    228240                    backup_copies="not_purged", secure_physical_erasure="not_claimed")
    … …  
    255267                if (not isinstance(change, dict) or type(change.get("id")) not in (int, str)
    256268                        or not re.fullmatch(r"[0-9]{1,18}", str(change["id"])) or int(change["id"]) <= last
    257                         or change.get("resource") not in ("archive", "checkpoint", "session", "artifact", "scope")
    258269                        or (change.get("operation"), change.get("resource")) not in (
    259270                            ("stored", "archive"), ("stored", "checkpoint"), ("stored", "session"), ("stored", "artifact"),
    260                             ("retired", "scope"), ("deleted", "session"), ("deleted", "checkpoint"), ("deleted", "artifact"))
     271                            ("retired", "scope"), ("deleted", "session"), ("deleted", "checkpoint"), ("deleted", "artifact"),
     272                            ("stored", "proposal"), ("deleted", "proposal"))
    261273                        or not isinstance(change.get("identity_hash"), str)
    262274                        or not re.fullmatch(r"[a-f0-9]{64}", change["identity_hash"])):
    … …  
    276288                        elif change["resource"] == "checkpoint":
    277289                            self.db.execute("DELETE FROM checkpoints WHERE scope=? AND identity=?", (scope, identity))
     290                        self._purge_resource_derivatives(scope, change["resource"], identity)
    278291                # The entire page and its cursor commit together, including deletion content removal.
    279292                if reply["retired"]:
    … …  
    283296                    self.db.execute("DELETE FROM queue WHERE scope=?", (scope,))
    284297                    self.db.execute("DELETE FROM checkpoints WHERE scope=?", (scope,))
     298                    self._purge_scope_derivatives(scope)
    285299                    purged = True
    286300                self.db.execute("INSERT OR REPLACE INTO change_cursors VALUES (?,?)", (scope, reply["next_cursor"]))
  • botcreds-agent-memory/tags/3.2.0/botcreds-agent-memory.php

    r3788734 r3788796  
    44 * Plugin URI:  https://botcreds.com/
    55 * Description: Portable memory store for AI agents. REST API + MCP endpoint with entry relationships. KV mode by default, semantic vector search when OpenAI key is configured.
    6  * Version:     3.1.0
     6 * Version:     3.2.0
    77 * Author:      Joe Boydston
    88 * Author URI:  https://botcreds.com
    … …  
    1818}
    1919
    20 define( 'BOTCREDS_MEMORY_VERSION', '3.1.0' );
     20define( 'BOTCREDS_MEMORY_VERSION', '3.2.0' );
    2121define( 'BOTCREDS_MEMORY_FILE', __FILE__ );
    2222define( 'BOTCREDS_MEMORY_DIR', plugin_dir_path( __FILE__ ) );
    … …  
    3434require_once BOTCREDS_MEMORY_DIR . 'includes/class-sessions.php';
    3535require_once BOTCREDS_MEMORY_DIR . 'includes/class-artifacts.php';
     36require_once BOTCREDS_MEMORY_DIR . 'includes/class-proposals.php';
     37require_once BOTCREDS_MEMORY_DIR . 'includes/class-native-processor.php';
     38require_once BOTCREDS_MEMORY_DIR . 'includes/class-applications.php';
     39require_once BOTCREDS_MEMORY_DIR . 'includes/class-suggestions.php';
    3640require_once BOTCREDS_MEMORY_DIR . 'includes/class-rest-api.php';
    3741require_once BOTCREDS_MEMORY_DIR . 'includes/class-mcp.php';
    … …  
    4953register_activation_hook( BOTCREDS_MEMORY_FILE, array( 'Botcreds_Memory_Sessions', 'install' ) );
    5054register_activation_hook( BOTCREDS_MEMORY_FILE, array( 'Botcreds_Memory_Artifacts', 'install' ) );
     55register_activation_hook( BOTCREDS_MEMORY_FILE, array( 'Botcreds_Memory_Proposals', 'install' ) );
    5156register_activation_hook( BOTCREDS_MEMORY_FILE, array( 'Botcreds_Memory_DB', 'backfill_namespaces' ) );
    5257register_activation_hook( BOTCREDS_MEMORY_FILE, array( 'Botcreds_Memory_Hardening', 'on_activation' ) );
    … …  
    8489                Botcreds_Memory_DB::create_table();
    8590                Botcreds_Memory_DB::backfill_namespaces();
     91        }
     92        if ( get_option( 'botcreds_memory_proposal_schema', '' ) !== '1' ) {
     93                Botcreds_Memory_Proposals::install();
    8694        }
    8795        if ( get_option( 'botcreds_memory_sync_schema', '' ) !== '2' ) {
    … …  
    123131add_action( 'rest_api_init', array( 'Botcreds_Memory_Sessions', 'register_routes' ) );
    124132add_action( 'rest_api_init', array( 'Botcreds_Memory_Artifacts', 'register_routes' ) );
     133add_action( 'rest_api_init', array( 'Botcreds_Memory_Proposals', 'register_routes' ) );
     134add_action( 'rest_api_init', array( 'Botcreds_Memory_Native_Processor', 'register_routes' ) );
     135add_action( 'rest_api_init', array( 'Botcreds_Memory_Applications', 'register_routes' ) );
     136// Register after the parent menu so WordPress assigns the correct page hook and URL.
     137add_action( 'admin_menu', array( 'Botcreds_Memory_Suggestions', 'menu' ), 20 );
    125138
    126139/*
  • botcreds-agent-memory/tags/3.2.0/includes/class-db.php

    r3776180 r3788796  
    281281
    282282                $key   = sanitize_text_field( $data['key'] ?? '' );
     283                // Managed application keys may only be written through the reviewed proposal pipeline.
     284                if ( class_exists( 'Botcreds_Memory_Applications', false ) && Botcreds_Memory_Applications::managed_key( $key ) ) { return null; }
    283285                $value = $data['value'] ?? '';
    284286
    … …  
    288290
    289291                $existing = self::get_by_key( $key, true );
     292                // Match the stored key too: database collations may equate accented/case variants.
     293                if ( $existing && class_exists( 'Botcreds_Memory_Applications', false ) && Botcreds_Memory_Applications::managed_key( $existing['key'] ) ) { return null; }
    290294                $user = wp_get_current_user();
    291295                $default_author = $existing['author'] ?? '';
  • botcreds-agent-memory/tags/3.2.0/includes/class-mcp.php

    r3776180 r3788796  
    368368                }
    369369
     370                if ( class_exists( 'Botcreds_Memory_Applications', false ) && Botcreds_Memory_Applications::protected_key( $key ) ) {
     371                        return self::tool_error( $id, 'Revise, review and apply the originating proposal instead of overwriting managed memory.' );
     372                }
     373
    370374                $data = array(
    371375                        'key'   => $key,
  • botcreds-agent-memory/tags/3.2.0/includes/class-rest-api.php

    r3776180 r3788796  
    360360                }
    361361
     362                if ( class_exists( 'Botcreds_Memory_Applications', false ) && Botcreds_Memory_Applications::protected_key( $key ) ) {
     363                        return new WP_Error( 'botcreds_memory_managed_key', 'Revise, review and apply the originating proposal instead of overwriting managed memory.', array( 'status' => 409 ) );
     364                }
     365
    362366                $data = array(
    363367                        'key'   => $key,
  • botcreds-agent-memory/tags/3.2.0/includes/class-sync.php

    r3788734 r3788796  
    88        private static function content_tables(): array {
    99                global $wpdb;
    10                 return array_map( function ( $suffix ) use ( $wpdb ) { return $wpdb->prefix . 'botcreds_memory_' . $suffix; }, array( 'archive', 'checkpoints', 'sessions', 'artifacts' ) );
     10
     11                $suffixes = array( 'archive', 'checkpoints', 'sessions', 'artifacts' );
     12                if ( class_exists( 'Botcreds_Memory_Proposals', false ) ) { $suffixes[] = 'proposals'; }
     13                return array_map( function ( $suffix ) use ( $wpdb ) { return $wpdb->prefix . 'botcreds_memory_' . $suffix; }, $suffixes );
    1114        }
    1215        // Schema setup uses fixed plugin-owned table identifiers; WordPress has no custom-table schema/cache API.
    … …  
    5558        private static function transactional(): bool {
    5659                global $wpdb;
    57                 foreach ( array_merge( self::content_tables(), array( self::table(), self::changes_table() ) ) as $table ) {
     60                $tables = array_merge( self::content_tables(), array( self::table(), self::changes_table() ) );
     61                if ( class_exists( 'Botcreds_Memory_Applications', false ) ) { $tables = array_merge( $tables, array( Botcreds_Memory_DB::table_name(), Botcreds_Memory_DB::revisions_table_name(), Botcreds_Memory_DB::relationships_table_name() ) ); }
     62                foreach ( $tables as $table ) {
    5863                        $engine = $wpdb->get_var( $wpdb->prepare( 'SELECT ENGINE FROM information_schema.TABLES WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME=%s', $table ) );
    5964                        if ( ! is_string( $engine ) || strtolower( $engine ) !== 'innodb' ) { return false; }
    … …  
    140145                        $identity = self::session_identity( $scope, $source, $session );
    141146                        if ( ! self::deleted( $scope, 'session', $identity ) ) {
     147                                if ( class_exists( 'Botcreds_Memory_Proposals', false ) ) { Botcreds_Memory_Proposals::purge_session( $scope, $source, $session ); }
    142148                                // Stream associated records in bounded pages. Hash verification precedes any destructive write.
    143149                                foreach ( array( 'checkpoint' => Botcreds_Memory_Checkpoints::table(), 'artifact' => Botcreds_Memory_Artifacts::table() ) as $kind => $table ) {
    … …  
    200206                        global $wpdb; $table = self::table();
    201207                        // Replays purge again safely; retirement is permanent even if a delayed uploader retries.
     208                        if ( class_exists( 'Botcreds_Memory_Applications', false ) ) { Botcreds_Memory_Applications::purge_scope( $scope ); }
    202209                        foreach ( self::content_tables() as $content ) {
    203210                                if ( false === $wpdb->query( $wpdb->prepare( "DELETE FROM `{$content}` WHERE BINARY scope=BINARY %s", $scope ) ) ) { throw new RuntimeException( 'purge failed' ); }
    … …  
    242249                        return new WP_Error( 'sync_cursor_invalid', 'Invalid change cursor.', array( 'status' => 400 ) );
    243250                }
     251                $processing = $request->get_param( 'include_processing' ) ?? '0';
     252                if ( ! in_array( $processing, array( '0', '1', 0, 1 ), true ) ) { return new WP_Error( 'sync_processing_invalid', 'include_processing must be 0 or 1.', array( 'status' => 400 ) ); }
    244253                $table = self::changes_table();
    245                 $rows = $wpdb->get_results( $wpdb->prepare( "SELECT id,resource,identity_hash,operation,created_at FROM `{$table}` WHERE BINARY scope=BINARY %s AND id>%d ORDER BY id ASC LIMIT 20", $scope, (int) $after ), ARRAY_A );
     254                $rows = $wpdb->get_results( $wpdb->prepare( "SELECT id,resource,identity_hash,operation,created_at FROM `{$table}` WHERE BINARY scope=BINARY %s AND id>%d AND (%d=1 OR resource<>'proposal') ORDER BY id ASC LIMIT 20", $scope, (int) $after, (int) $processing ), ARRAY_A );
    246255                if ( ! empty( $wpdb->last_error ) ) { return new WP_Error( 'sync_storage_failed', 'Change feed unavailable.', array( 'status' => 503 ) ); }
    247256                foreach ( $rows ?: array() as $row ) { $after = (string) $row['id']; }
  • botcreds-agent-memory/tags/3.2.0/readme.txt

    r3788734 r3788796  
    55Tested up to: 7.1
    66Requires PHP: 7.4
    7 Stable tag: 3.1.0
     7Stable tag: 3.2.0
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
    … …  
    2424Save explicitly selected original conversation text outside a chat tool, with resumable uploads, session records, artifact references and compaction checkpoints. Retrieve original passages and search archive text within an explicitly enrolled scope. The included reference client reads operator-reviewed JSONL files; it is not an automatic OpenClaw or Claude Code collector.
    2525
    26 Manually delete a conversation and its managed archive/checkpoint/session/artifact records, with permanent deletion markers that prevent stale clients from re-uploading it. No automatic age-based forgetting is enabled. Curated KV memory, external files and backups are not deleted by archive deletion.
     26Manually delete a conversation and its managed archive/checkpoint/session/artifact records, with permanent deletion markers that prevent stale clients from re-uploading it. No automatic age-based forgetting is enabled. Independent curated KV memory, external files and backups are not deleted by archive deletion. In 3.2, managed derived memories tied to deleted sources are purged as described below.
    2727
    2828Archive access is separate from shared-memory grants: a host administrator must explicitly enroll each scope/user, including administrators, using `wp agent-memory archive-scope project/name USER_ID`. Server sync operations require InnoDB tables. Checkpoints verify submitted archive ranges, not completeness of native client capture. No original conversations are captured or uploaded automatically.
     29
     30**Suggestions and evidence-backed memory (3.2):**
     31
     32Agent Memory → Suggestions provides original-source selection, optional WordPress AI Client/Connectors generation, an evidence-linked review inbox, immutable revisions, and explicit application of approved facts/decisions to current shared memory. Approval alone does not publish a memory. Generated Skill files are never installed or executed.
     33
     34Archive scopes require explicit host enrollment, separate from curated-memory grants, including for administrators. Native generation requires compatible WordPress AI Client/Connectors APIs and a configured provider; basic storage/search and external proposal clients do not. Applying a suggestion publishes into the shared curated store, whose readers may differ from archive participants. Source deletion also purges managed derived memory. Automatic conversation capture is not included.
    2935
    3036**Key features:**
    … …  
    101107
    102108== Changelog ==
     109
     110= 3.2.0 =
     111* Add an evidence-linked Suggestions inbox with original-source inspection, approval, dismissal and immutable revisions.
     112* Add optional native processing previews using configured WordPress AI Client/Connectors providers, with explicit export/cost consent and no automatic saving.
     113* Apply approved facts/decisions explicitly to managed shared-store memory with safe replacements and idempotent application receipts.
     114* Purge managed derived memory and matching revision snapshots when their original source is deleted. Never restore superseded decisions automatically.
     115* Include cumulative 3.1 archive, session, artifact, checkpoint and deletion-aware sync foundations. Automatic OpenClaw/Claude Code capture is not included.
     116* Retain external processor support and basic storage/search without an AI provider. Generated Skill files are not installed or executed.
    103117
    104118= 3.1.0 =
    … …  
    216230== Upgrade Notice ==
    217231
     232= 3.2.0 =
     233Adds evidence-linked suggestions and explicit reviewed-memory application. Archive enrollment is separate from memory grants. Applying publishes to the shared store; source deletion purges managed derived memory. Automatic conversation capture is not included.
     234
    218235= 3.1.0 =
    219236Adds opt-in conversation archives, checkpoint/session/artifact records, original-text retrieval and manual deletion. Archive scopes require explicit host enrollment, separate from memory grants. Automatic capture and age-based forgetting are not included.
  • botcreds-agent-memory/trunk/adapters/reference/outbox.py

    r3788734 r3788796  
    124124                               (scope, resource, identity)).fetchone() is not None
    125125
     126    def _purge_scope_derivatives(self, scope):
     127        """Extension point; called inside the same SQLite transaction as source purge."""
     128
     129    def _purge_session_derivatives(self, scope, identity):
     130        """Extension point for managed processing drafts."""
     131
     132    def _purge_resource_derivatives(self, scope, resource, identity):
     133        """Extension point for explicitly negotiated processing invalidations."""
     134
    126135    def _purge_session(self, scope, identity):
    127136        # Caller holds the SQLite transaction. Retain only an opaque permanent tombstone.
    … …  
    137146                self.db.execute("INSERT OR IGNORE INTO deleted_resources VALUES (?,'checkpoint',?)", (scope, row["identity"]))
    138147                self.db.execute("DELETE FROM checkpoints WHERE identity=?", (row["identity"],))
     148
     149        self._purge_session_derivatives(scope, identity)
    139150
    140151    def delete_conversation(self, scope, source, session_id, *, confirm_session_id, send=None):
    … …  
    225236            self.db.execute("DELETE FROM queue WHERE scope=?", (scope,))
    226237            self.db.execute("DELETE FROM checkpoints WHERE scope=?", (scope,))
     238            self._purge_scope_derivatives(scope)
    227239        return dict(self.status(scope), local_storage_maintenance=self._storage_cleanup(),
    228240                    backup_copies="not_purged", secure_physical_erasure="not_claimed")
    … …  
    255267                if (not isinstance(change, dict) or type(change.get("id")) not in (int, str)
    256268                        or not re.fullmatch(r"[0-9]{1,18}", str(change["id"])) or int(change["id"]) <= last
    257                         or change.get("resource") not in ("archive", "checkpoint", "session", "artifact", "scope")
    258269                        or (change.get("operation"), change.get("resource")) not in (
    259270                            ("stored", "archive"), ("stored", "checkpoint"), ("stored", "session"), ("stored", "artifact"),
    260                             ("retired", "scope"), ("deleted", "session"), ("deleted", "checkpoint"), ("deleted", "artifact"))
     271                            ("retired", "scope"), ("deleted", "session"), ("deleted", "checkpoint"), ("deleted", "artifact"),
     272                            ("stored", "proposal"), ("deleted", "proposal"))
    261273                        or not isinstance(change.get("identity_hash"), str)
    262274                        or not re.fullmatch(r"[a-f0-9]{64}", change["identity_hash"])):
    … …  
    276288                        elif change["resource"] == "checkpoint":
    277289                            self.db.execute("DELETE FROM checkpoints WHERE scope=? AND identity=?", (scope, identity))
     290                        self._purge_resource_derivatives(scope, change["resource"], identity)
    278291                # The entire page and its cursor commit together, including deletion content removal.
    279292                if reply["retired"]:
    … …  
    283296                    self.db.execute("DELETE FROM queue WHERE scope=?", (scope,))
    284297                    self.db.execute("DELETE FROM checkpoints WHERE scope=?", (scope,))
     298                    self._purge_scope_derivatives(scope)
    285299                    purged = True
    286300                self.db.execute("INSERT OR REPLACE INTO change_cursors VALUES (?,?)", (scope, reply["next_cursor"]))
  • botcreds-agent-memory/trunk/botcreds-agent-memory.php

    r3788734 r3788796  
    44 * Plugin URI:  https://botcreds.com/
    55 * Description: Portable memory store for AI agents. REST API + MCP endpoint with entry relationships. KV mode by default, semantic vector search when OpenAI key is configured.
    6  * Version:     3.1.0
     6 * Version:     3.2.0
    77 * Author:      Joe Boydston
    88 * Author URI:  https://botcreds.com
    … …  
    1818}
    1919
    20 define( 'BOTCREDS_MEMORY_VERSION', '3.1.0' );
     20define( 'BOTCREDS_MEMORY_VERSION', '3.2.0' );
    2121define( 'BOTCREDS_MEMORY_FILE', __FILE__ );
    2222define( 'BOTCREDS_MEMORY_DIR', plugin_dir_path( __FILE__ ) );
    … …  
    3434require_once BOTCREDS_MEMORY_DIR . 'includes/class-sessions.php';
    3535require_once BOTCREDS_MEMORY_DIR . 'includes/class-artifacts.php';
     36require_once BOTCREDS_MEMORY_DIR . 'includes/class-proposals.php';
     37require_once BOTCREDS_MEMORY_DIR . 'includes/class-native-processor.php';
     38require_once BOTCREDS_MEMORY_DIR . 'includes/class-applications.php';
     39require_once BOTCREDS_MEMORY_DIR . 'includes/class-suggestions.php';
    3640require_once BOTCREDS_MEMORY_DIR . 'includes/class-rest-api.php';
    3741require_once BOTCREDS_MEMORY_DIR . 'includes/class-mcp.php';
    … …  
    4953register_activation_hook( BOTCREDS_MEMORY_FILE, array( 'Botcreds_Memory_Sessions', 'install' ) );
    5054register_activation_hook( BOTCREDS_MEMORY_FILE, array( 'Botcreds_Memory_Artifacts', 'install' ) );
     55register_activation_hook( BOTCREDS_MEMORY_FILE, array( 'Botcreds_Memory_Proposals', 'install' ) );
    5156register_activation_hook( BOTCREDS_MEMORY_FILE, array( 'Botcreds_Memory_DB', 'backfill_namespaces' ) );
    5257register_activation_hook( BOTCREDS_MEMORY_FILE, array( 'Botcreds_Memory_Hardening', 'on_activation' ) );
    … …  
    8489                Botcreds_Memory_DB::create_table();
    8590                Botcreds_Memory_DB::backfill_namespaces();
     91        }
     92        if ( get_option( 'botcreds_memory_proposal_schema', '' ) !== '1' ) {
     93                Botcreds_Memory_Proposals::install();
    8694        }
    8795        if ( get_option( 'botcreds_memory_sync_schema', '' ) !== '2' ) {
    … …  
    123131add_action( 'rest_api_init', array( 'Botcreds_Memory_Sessions', 'register_routes' ) );
    124132add_action( 'rest_api_init', array( 'Botcreds_Memory_Artifacts', 'register_routes' ) );
     133add_action( 'rest_api_init', array( 'Botcreds_Memory_Proposals', 'register_routes' ) );
     134add_action( 'rest_api_init', array( 'Botcreds_Memory_Native_Processor', 'register_routes' ) );
     135add_action( 'rest_api_init', array( 'Botcreds_Memory_Applications', 'register_routes' ) );
     136// Register after the parent menu so WordPress assigns the correct page hook and URL.
     137add_action( 'admin_menu', array( 'Botcreds_Memory_Suggestions', 'menu' ), 20 );
    125138
    126139/*
  • botcreds-agent-memory/trunk/includes/class-db.php

    r3776180 r3788796  
    281281
    282282                $key   = sanitize_text_field( $data['key'] ?? '' );
     283                // Managed application keys may only be written through the reviewed proposal pipeline.
     284                if ( class_exists( 'Botcreds_Memory_Applications', false ) && Botcreds_Memory_Applications::managed_key( $key ) ) { return null; }
    283285                $value = $data['value'] ?? '';
    284286
    … …  
    288290
    289291                $existing = self::get_by_key( $key, true );
     292                // Match the stored key too: database collations may equate accented/case variants.
     293                if ( $existing && class_exists( 'Botcreds_Memory_Applications', false ) && Botcreds_Memory_Applications::managed_key( $existing['key'] ) ) { return null; }
    290294                $user = wp_get_current_user();
    291295                $default_author = $existing['author'] ?? '';
  • botcreds-agent-memory/trunk/includes/class-mcp.php

    r3776180 r3788796  
    368368                }
    369369
     370                if ( class_exists( 'Botcreds_Memory_Applications', false ) && Botcreds_Memory_Applications::protected_key( $key ) ) {
     371                        return self::tool_error( $id, 'Revise, review and apply the originating proposal instead of overwriting managed memory.' );
     372                }
     373
    370374                $data = array(
    371375                        'key'   => $key,
  • botcreds-agent-memory/trunk/includes/class-rest-api.php

    r3776180 r3788796  
    360360                }
    361361
     362                if ( class_exists( 'Botcreds_Memory_Applications', false ) && Botcreds_Memory_Applications::protected_key( $key ) ) {
     363                        return new WP_Error( 'botcreds_memory_managed_key', 'Revise, review and apply the originating proposal instead of overwriting managed memory.', array( 'status' => 409 ) );
     364                }
     365
    362366                $data = array(
    363367                        'key'   => $key,
  • botcreds-agent-memory/trunk/includes/class-sync.php

    r3788734 r3788796  
    88        private static function content_tables(): array {
    99                global $wpdb;
    10                 return array_map( function ( $suffix ) use ( $wpdb ) { return $wpdb->prefix . 'botcreds_memory_' . $suffix; }, array( 'archive', 'checkpoints', 'sessions', 'artifacts' ) );
     10
     11                $suffixes = array( 'archive', 'checkpoints', 'sessions', 'artifacts' );
     12                if ( class_exists( 'Botcreds_Memory_Proposals', false ) ) { $suffixes[] = 'proposals'; }
     13                return array_map( function ( $suffix ) use ( $wpdb ) { return $wpdb->prefix . 'botcreds_memory_' . $suffix; }, $suffixes );
    1114        }
    1215        // Schema setup uses fixed plugin-owned table identifiers; WordPress has no custom-table schema/cache API.
    … …  
    5558        private static function transactional(): bool {
    5659                global $wpdb;
    57                 foreach ( array_merge( self::content_tables(), array( self::table(), self::changes_table() ) ) as $table ) {
     60                $tables = array_merge( self::content_tables(), array( self::table(), self::changes_table() ) );
     61                if ( class_exists( 'Botcreds_Memory_Applications', false ) ) { $tables = array_merge( $tables, array( Botcreds_Memory_DB::table_name(), Botcreds_Memory_DB::revisions_table_name(), Botcreds_Memory_DB::relationships_table_name() ) ); }
     62                foreach ( $tables as $table ) {
    5863                        $engine = $wpdb->get_var( $wpdb->prepare( 'SELECT ENGINE FROM information_schema.TABLES WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME=%s', $table ) );
    5964                        if ( ! is_string( $engine ) || strtolower( $engine ) !== 'innodb' ) { return false; }
    … …  
    140145                        $identity = self::session_identity( $scope, $source, $session );
    141146                        if ( ! self::deleted( $scope, 'session', $identity ) ) {
     147                                if ( class_exists( 'Botcreds_Memory_Proposals', false ) ) { Botcreds_Memory_Proposals::purge_session( $scope, $source, $session ); }
    142148                                // Stream associated records in bounded pages. Hash verification precedes any destructive write.
    143149                                foreach ( array( 'checkpoint' => Botcreds_Memory_Checkpoints::table(), 'artifact' => Botcreds_Memory_Artifacts::table() ) as $kind => $table ) {
    … …  
    200206                        global $wpdb; $table = self::table();
    201207                        // Replays purge again safely; retirement is permanent even if a delayed uploader retries.
     208                        if ( class_exists( 'Botcreds_Memory_Applications', false ) ) { Botcreds_Memory_Applications::purge_scope( $scope ); }
    202209                        foreach ( self::content_tables() as $content ) {
    203210                                if ( false === $wpdb->query( $wpdb->prepare( "DELETE FROM `{$content}` WHERE BINARY scope=BINARY %s", $scope ) ) ) { throw new RuntimeException( 'purge failed' ); }
    … …  
    242249                        return new WP_Error( 'sync_cursor_invalid', 'Invalid change cursor.', array( 'status' => 400 ) );
    243250                }
     251                $processing = $request->get_param( 'include_processing' ) ?? '0';
     252                if ( ! in_array( $processing, array( '0', '1', 0, 1 ), true ) ) { return new WP_Error( 'sync_processing_invalid', 'include_processing must be 0 or 1.', array( 'status' => 400 ) ); }
    244253                $table = self::changes_table();
    245                 $rows = $wpdb->get_results( $wpdb->prepare( "SELECT id,resource,identity_hash,operation,created_at FROM `{$table}` WHERE BINARY scope=BINARY %s AND id>%d ORDER BY id ASC LIMIT 20", $scope, (int) $after ), ARRAY_A );
     254                $rows = $wpdb->get_results( $wpdb->prepare( "SELECT id,resource,identity_hash,operation,created_at FROM `{$table}` WHERE BINARY scope=BINARY %s AND id>%d AND (%d=1 OR resource<>'proposal') ORDER BY id ASC LIMIT 20", $scope, (int) $after, (int) $processing ), ARRAY_A );
    246255                if ( ! empty( $wpdb->last_error ) ) { return new WP_Error( 'sync_storage_failed', 'Change feed unavailable.', array( 'status' => 503 ) ); }
    247256                foreach ( $rows ?: array() as $row ) { $after = (string) $row['id']; }
  • botcreds-agent-memory/trunk/readme.txt

    r3788734 r3788796  
    55Tested up to: 7.1
    66Requires PHP: 7.4
    7 Stable tag: 3.1.0
     7Stable tag: 3.2.0
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
    … …  
    2424Save explicitly selected original conversation text outside a chat tool, with resumable uploads, session records, artifact references and compaction checkpoints. Retrieve original passages and search archive text within an explicitly enrolled scope. The included reference client reads operator-reviewed JSONL files; it is not an automatic OpenClaw or Claude Code collector.
    2525
    26 Manually delete a conversation and its managed archive/checkpoint/session/artifact records, with permanent deletion markers that prevent stale clients from re-uploading it. No automatic age-based forgetting is enabled. Curated KV memory, external files and backups are not deleted by archive deletion.
     26Manually delete a conversation and its managed archive/checkpoint/session/artifact records, with permanent deletion markers that prevent stale clients from re-uploading it. No automatic age-based forgetting is enabled. Independent curated KV memory, external files and backups are not deleted by archive deletion. In 3.2, managed derived memories tied to deleted sources are purged as described below.
    2727
    2828Archive access is separate from shared-memory grants: a host administrator must explicitly enroll each scope/user, including administrators, using `wp agent-memory archive-scope project/name USER_ID`. Server sync operations require InnoDB tables. Checkpoints verify submitted archive ranges, not completeness of native client capture. No original conversations are captured or uploaded automatically.
     29
     30**Suggestions and evidence-backed memory (3.2):**
     31
     32Agent Memory → Suggestions provides original-source selection, optional WordPress AI Client/Connectors generation, an evidence-linked review inbox, immutable revisions, and explicit application of approved facts/decisions to current shared memory. Approval alone does not publish a memory. Generated Skill files are never installed or executed.
     33
     34Archive scopes require explicit host enrollment, separate from curated-memory grants, including for administrators. Native generation requires compatible WordPress AI Client/Connectors APIs and a configured provider; basic storage/search and external proposal clients do not. Applying a suggestion publishes into the shared curated store, whose readers may differ from archive participants. Source deletion also purges managed derived memory. Automatic conversation capture is not included.
    2935
    3036**Key features:**
    … …  
    101107
    102108== Changelog ==
     109
     110= 3.2.0 =
     111* Add an evidence-linked Suggestions inbox with original-source inspection, approval, dismissal and immutable revisions.
     112* Add optional native processing previews using configured WordPress AI Client/Connectors providers, with explicit export/cost consent and no automatic saving.
     113* Apply approved facts/decisions explicitly to managed shared-store memory with safe replacements and idempotent application receipts.
     114* Purge managed derived memory and matching revision snapshots when their original source is deleted. Never restore superseded decisions automatically.
     115* Include cumulative 3.1 archive, session, artifact, checkpoint and deletion-aware sync foundations. Automatic OpenClaw/Claude Code capture is not included.
     116* Retain external processor support and basic storage/search without an AI provider. Generated Skill files are not installed or executed.
    103117
    104118= 3.1.0 =
    … …  
    216230== Upgrade Notice ==
    217231
     232= 3.2.0 =
     233Adds evidence-linked suggestions and explicit reviewed-memory application. Archive enrollment is separate from memory grants. Applying publishes to the shared store; source deletion purges managed derived memory. Automatic conversation capture is not included.
     234
    218235= 3.1.0 =
    219236Adds opt-in conversation archives, checkpoint/session/artifact records, original-text retrieval and manual deletion. Archive scopes require explicit host enrollment, separate from memory grants. Automatic capture and age-based forgetting are not included.
Note: See TracChangeset for help on using the changeset viewer.