fix(daily-memory): scope compaction context to its authorized agent and user - #3511
Merged
Merged
Conversation
…nd user (#3487) Daily-memory compaction collected every job and chat session for the calendar day, then fed those labels and titles to the model before rewriting one agent's memory. On a multi-agent or multi-user install that crossed both agent and user boundaries. Context retrieval now resolves an explicit scope from the task's execution principal and pushes it into the storage queries: - Jobs::get_jobs_for_day() and the conversation store's list_sessions_for_day_scoped() take an ownership scope instead of filtering a broad result in PHP. - Both use a [midnight, next midnight) range predicate so the day window stays index-friendly. - DailyMemoryTask fails closed when neither dimension is bound. Authorized aggregates remain available as a deliberate choice rather than an accident of omitted predicates: context_scope='agent' keeps the agent-wide compaction, and an absent scope key narrows nothing. Smoke tests selected alongside this change fataled under the real-WordPress backend because they declared WP_Error, WP_CLI, did_action, wp_json_encode and wp_get_ability unguarded. Those stubs are now guarded, the metadata guardrail smoke captures datamachine_log through the real hook system when WordPress supplies it, and it loads conversation-loop.php explicitly since that file declares namespaced functions rather than autoloadable classes. Two smokes that replace the options, hook and abilities APIs with in-memory doubles declare homeboy:host-smoke-backend = standalone.
phpcs flags the unbracketed increment in the interpolated lock token (WordPress.CodeAnalysis.AssignmentInCondition style rule for increment and decrement operators). This file is only linted because #3487 touches it, so clear the one error rather than reformat unrelated lines.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #3487.
Problem
DailyMemoryTaskcollected context by calendar date only. Job rows came from a date-only query and chat sessions came fromlist_sessions_for_day(), which carried no user or agent predicate. Those labels, statuses and titles were then sent to the model and persisted into one agent'sMEMORY.md.On a multi-agent or multi-user install a per-agent compaction could therefore summarize another agent's jobs and another user's chats into the wrong memory file. That crosses both the agent and the user boundary.
Change
Context retrieval resolves an explicit scope from the task's execution principal and pushes it into the storage layer rather than filtering a broad result afterwards.
Jobs::get_jobs_for_day( $date, $scope )andConversationReportingInterface::list_sessions_for_day_scoped( $date, $scope )accept an ownership scope.[midnight, next midnight)range predicate instead of wrappingcreated_atinDATE(), so the day window stays index-friendly.DailyMemoryTask::resolveContextScope()bindsuser_idandagent_id, and the task fails closed when neither dimension is bound.Authorized breadth stays a deliberate decision rather than an accident of omitted predicates.
context_scope = agentpreserves the agent-wide compaction, and an absent scope key narrows nothing, so a caller states the aggregate it wants.Test harness fixes
The smoke tests selected alongside this change fataled under the real-WordPress backend for reasons that predate it.
origin/maincarries the same declarations.WP_Error,WP_CLI,WP_CLI_Command,did_action,doing_action,datamachine_merge_engine_data,wp_json_encodeandwp_get_abilitywere declared unguarded and collided with real WordPress. They are now guarded, matching the convention already used by the smokes that pass in that backend.ai-request-metadata-guardrails-smoke.phpcaptureddatamachine_logby shadowingdo_action. Real WordPress supplies its own, so the smoke now registers a realadd_actionlistener when the stub is not installed.datamachine_conversation_metadata(), which lives inconversation-loop.php. That file declares namespaced functions rather than autoloadable classes, so it is now required explicitly when absent. This also repairs the standalone run, which fataled onmain.ai-concurrency-release-wakeup-smoke.phpandagent-daily-memory-pipeline-scope-smoke.phpreplace the options, hook and abilities APIs with in-memory doubles, so they assert nothing under real WordPress. They declarehomeboy:host-smoke-backend = standalone, honored by Extra-Chill/homeboy-extensions#2846 (released in v3.48.3). The bare-PHP backend still runs them, so the coverage moves rather than disappears.Verification
homeboy review test data-machine --changed-since origin/mainpasses:exit=0,success=true.DailyMemoryTaskContextScopeTest.HOST_SMOKE_SUMMARY:passed=4 failed=0 skipped=2.ai-request-metadata-guardrails-smoke.phpnow passes in that backend, where it previously fataled.agent-daily-memory-pipeline-scope-smoke.php(11 assertions) andai-concurrency-release-wakeup-smoke.php(43 assertions) pass.The one PHPCS error was an unbracketed increment on a pre-existing line of
InMemoryConversationStore.php, which is only linted because this change touches that file. It is bracketed rather than reformatting unrelated lines.AI Assistance
Claude Sonnet 4.5 via OpenCode, orchestrated through Homeboy Cook, implemented the scope binding and storage predicates. Claude Sonnet 4.5 in Claude Code diagnosed the real-WordPress smoke failures from run artifacts, separated the genuinely broken smoke from the two that cannot run in that backend, implemented the harness fixes, and verified each gate run. Chris Huber directed the work and owns it.