Skip to content

Fix libsql prompt scope regressions#1651

Merged
henrypark133 merged 1 commit intostagingfrom
codex/fix-libsql-prompt-scope
Mar 25, 2026
Merged

Fix libsql prompt scope regressions#1651
henrypark133 merged 1 commit intostagingfrom
codex/fix-libsql-prompt-scope

Conversation

@henrypark133
Copy link
Copy Markdown
Collaborator

Summary

  • scope chat system prompt generation to the incoming message user workspace
  • normalize cron schedules on routine creation so create/update store the same canonical form
  • fix stale tests that were seeding data into the owner workspace instead of the active test user

Testing

  • cargo fmt --all -- --check
  • cargo test --test multi_tenant_system_prompt -- --nocapture
  • cargo test --test identity_scope_isolation -- --nocapture
  • cargo test --test e2e_builtin_tool_coverage tests::routine_create_list -- --exact --nocapture
  • cargo test --test e2e_builtin_tool_coverage tests::routine_create_grouped -- --exact --nocapture
  • cargo test --test e2e_workspace_coverage tests::identity_in_system_prompt -- --exact --nocapture
  • cargo test --no-default-features --features libsql -- --nocapture

Copilot AI review requested due to automatic review settings March 25, 2026 21:22
@github-actions github-actions Bot added size: M 50-199 changed lines risk: medium Business logic, config, or moderate-risk modules scope: agent Agent core (agent loop, router, scheduler) scope: tool/builtin Built-in tools scope: workspace Persistent memory / workspace contributor: core 20+ merged PRs and removed risk: medium Business logic, config, or moderate-risk modules labels Mar 25, 2026
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses several regressions related to libsql prompt scoping and routine management. The primary goal is to enhance multi-tenancy by ensuring that system prompts are generated within the correct user's workspace, thereby maintaining proper context isolation. Additionally, it standardizes cron schedule storage for routines, leading to more predictable behavior, and includes fixes for existing tests to accurately reflect these multi-tenant scenarios.

Highlights

  • System Prompt Scoping: Chat system prompt generation is now correctly scoped to the incoming message's user workspace, preventing cross-user context leakage.
  • Cron Schedule Normalization: Routine creation now normalizes cron schedules, ensuring a consistent canonical form is stored for create and update operations.
  • Test Fixes: Stale tests that incorrectly seeded data into the owner workspace instead of the active test user's workspace have been corrected.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request enhances multi-tenancy support by ensuring system prompts and workspace configurations are correctly scoped to individual users. Key changes include introducing a scoped_to_user method in the Workspace struct, adding Clone derives to WorkspaceStorage and Repository, and modifying the agent's dispatcher to utilize user-scoped workspaces for system prompt generation. Additionally, cron expressions in routine triggers are now normalized, with corresponding test updates. A performance and readability improvement was suggested for the read_user_ids logic in src/workspace/mod.rs, recommending the use of a HashSet to optimize its complexity from O(n^2) to O(n).

Comment thread src/workspace/mod.rs
Comment on lines +597 to +607
let mut read_user_ids = vec![user_id.clone()];
for scope in &self.read_user_ids {
if scope != &self.user_id && !read_user_ids.contains(scope) {
read_user_ids.push(scope.clone());
}
}
for scope in crate::workspace::layer::MemoryLayer::read_scopes(&memory_layers) {
if !read_user_ids.contains(&scope) {
read_user_ids.push(scope);
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

For improved performance and readability, you can refactor the logic for building read_user_ids. The current implementation uses Vec::contains within loops, resulting in O(n^2) complexity. By using a HashSet to track seen scopes, you can achieve O(n) complexity.

Here's a suggested refactoring that also simplifies the logic by chaining iterators:

        use std::collections::HashSet;

        let mut read_user_ids = vec![user_id.clone()];
        let mut seen_scopes: HashSet<String> = [user_id.clone()].into();

        // Combine scopes from the original workspace (excluding the old user) and the updated memory layers.
        let other_scopes = self.read_user_ids.iter()
            .filter(|s| *s != &self.user_id)
            .cloned()
            .chain(crate::workspace::layer::MemoryLayer::read_scopes(&memory_layers));

        for scope in other_scopes {
            if seen_scopes.insert(scope.clone()) {
                read_user_ids.push(scope);
            }
        }
References
  1. The use of HashSet for tracking seen scopes aligns with the principle of using efficient data structures for deduplication to improve performance, similar to how HashMap is recommended for O(1) lookups in other deduplication scenarios, avoiding linear scans.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. I agree a HashSet-backed dedupe would be a reasonable cleanup if this path grows, but I am going to leave it as-is for this change. In practice read_user_ids is very small here, this path is not performance-sensitive, and the current Vec-based construction keeps insertion order obvious and deterministic for debugging. If we revisit it later, I would likely keep the Vec for stable ordering and add a HashSet only for seen tracking rather than switching the representation outright.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, sounds good!

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes multi-tenant regressions where chat system prompt generation and routine cron schedules were being scoped/serialized inconsistently, particularly when using libSQL-backed workspaces.

Changes:

  • Scope chat system prompt generation to the incoming message’s user workspace (rather than the startup/owner workspace).
  • Normalize cron expressions on routine creation so create/update persist the same canonical schedule string.
  • Update E2E/regression tests to seed identity data into the active test user scope and expect normalized cron schedules.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated no comments.

Show a summary per file
File Description
tests/multi_tenant_system_prompt.rs Updates test module docs to reflect the regression being covered.
tests/e2e_workspace_coverage.rs Seeds IDENTITY.md into the active test user workspace instead of the owner workspace.
tests/e2e_builtin_tool_coverage.rs Updates expected stored cron schedule to the normalized 7-field form.
src/workspace/repository.rs Derives Clone for Repository to support cloning workspace storage.
src/workspace/mod.rs Derives Clone for WorkspaceStorage and adds Workspace::scoped_to_user() for per-user scoping.
src/tools/builtin/routine.rs Normalizes cron schedule when building cron triggers during routine creation + adds a unit test.
src/agent/dispatcher.rs Uses a user-scoped workspace for system prompt generation based on IncomingMessage.user_id.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@nickpismenkov
Copy link
Copy Markdown
Collaborator

I think there's one valid issue:

  1. PERFORMANCE CONCERN (O(n²) algorithm) — Flagged by AI reviewers

In src/workspace/mod.rs lines 595-603, the read_user_ids loop has O(n²) complexity:

let mut read_user_ids = vec![user_id.clone()];
for scope in &self.read_user_ids {
if scope != &self.user_id && !read_user_ids.contains(scope) { // ← O(n) contains() per iteration
read_user_ids.push(scope.clone());
}
}

Fix: Use HashSet instead:
let mut read_user_ids = {
let mut set = std::collections::HashSet::new();
set.insert(user_id.clone());
for scope in &self.read_user_ids {
if scope != &self.user_id {
set.insert(scope.clone());
}
}
for scope in crate::workspace::layer::MemoryLayer::read_scopes(&memory_layers) {
set.insert(scope);
}
set.into_iter().collect()
};

Copy link
Copy Markdown
Collaborator

@nickpismenkov nickpismenkov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@henrypark133 henrypark133 merged commit 86d1143 into staging Mar 25, 2026
18 checks passed
@henrypark133 henrypark133 deleted the codex/fix-libsql-prompt-scope branch March 25, 2026 21:36
bkutasi pushed a commit to bkutasi/ironclaw that referenced this pull request Mar 28, 2026
drchirag1991 pushed a commit to drchirag1991/ironclaw that referenced this pull request Apr 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contributor: core 20+ merged PRs scope: agent Agent core (agent loop, router, scheduler) scope: tool/builtin Built-in tools scope: workspace Persistent memory / workspace size: M 50-199 changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants