Repository navigation
feat(import): skip connector-synced content in the legacy import #246
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,58 @@ | ||||||
| //! What counts as a connector sync, for `LegacyWorkspace::skip_connector_syncs`. | ||||||
| //! | ||||||
| //! v1 synced outside services (Gmail, Slack, Notion, Linear, GitHub, ClickUp, | ||||||
| //! ... through Composio and the connector path) into its stores. That data is | ||||||
| //! re-synced by the connectors themselves, so an opt-in import leaves it out. | ||||||
| //! Every predicate here is used by both the scan and `counts()`, so the counts | ||||||
| //! stay exactly what `items()` yields. | ||||||
| //! | ||||||
| //! - `memory_docs`: logical namespace `skill-*` (Composio `SkillDoc` sync, | ||||||
| //! `skill-{toolkit}`) or `source:*` (connector path, | ||||||
| //! `source:{toolkit}:{conn}`). Taint is deliberately not consulted: v1 also | ||||||
| //! marked the agent's own `global` and flow notes `external_sync`. | ||||||
| //! - chunks: see [`chunk_source_by_identity`] and [`OWNER_SYNC_PATTERN`]. | ||||||
| //! - `user_profile`: `facet_id` starting `skill-` (Composio identity facets | ||||||
| //! `skill-{toolkit}-{conn}-{kind}`). | ||||||
| //! - `graph_namespace`: namespace starting `skill-`, `source:` or `source_`. | ||||||
|
|
||||||
| /// Connector toolkits whose chunk `source_id` is `{toolkit}:{conn}[:{item}]` | ||||||
| /// (Composio before July: slack chat `slack:{conn}`, docs | ||||||
| /// `{toolkit}:{conn}:{id}`; current: `{toolkit}:{conn}:{item}`). | ||||||
| pub(crate) const CONNECTOR_TOOLKIT_PREFIXES: [&str; 6] = [ | ||||||
| "gmail:", "slack:", "notion:", "linear:", "github:", "clickup:", | ||||||
| ]; | ||||||
|
|
||||||
| /// The `source_kind` v1 gave Gmail threads. | ||||||
| pub(crate) const EMAIL_SOURCE_KIND: &str = "email"; | ||||||
|
|
||||||
| /// SQL `LIKE` pattern for a chunk `owner` written by a connector | ||||||
| /// (`{toolkit}-sync:{conn}`). | ||||||
| pub(crate) const OWNER_SYNC_PATTERN: &str = "%-sync:%"; | ||||||
|
|
||||||
| /// Whether a `memory_docs` logical namespace is a connector sync. | ||||||
| pub(crate) fn is_connector_namespace(logical: &str) -> bool { | ||||||
| logical.starts_with("skill-") || logical.starts_with("source:") | ||||||
| } | ||||||
|
|
||||||
| /// Whether a chunk source is a connector sync judging by its kind and id | ||||||
| /// alone (the `owner` column is checked by the chunk reader). | ||||||
| pub(crate) fn chunk_source_by_identity(source_kind: &str, source_id: &str) -> bool { | ||||||
| source_kind == EMAIL_SOURCE_KIND | ||||||
| || CONNECTOR_TOOLKIT_PREFIXES | ||||||
| .iter() | ||||||
| .any(|prefix| source_id.starts_with(prefix)) | ||||||
| } | ||||||
|
|
||||||
| /// SQL condition true for a `graph_namespace` row that is NOT a connector | ||||||
| /// sync (`skill-*`, `source:*`, or the sanitised `source_*`). | ||||||
| pub(crate) const GRAPH_NAMESPACE_KEPT: &str = "NOT (substr(COALESCE(namespace, ''), 1, 6) = 'skill-' \ | ||||||
| OR substr(COALESCE(namespace, ''), 1, 7) = 'source:' \ | ||||||
| OR substr(COALESCE(namespace, ''), 1, 7) = 'source_')"; | ||||||
|
Comment on lines
+49
to
+50
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When the option is enabled after a non-Composio source was ingested, this filter also removes that source's graph relations. The legacy ingestion path extracted graphs under the same sanitized namespace as its document, so generic Useful? React with 👍 / 👎. |
||||||
|
|
||||||
| /// SQL condition true for a `user_profile` row that is NOT a Composio | ||||||
| /// identity facet (`facet_id` starting `skill-`). | ||||||
| pub(crate) const PROFILE_KEPT: &str = "substr(facet_id, 1, 6) != 'skill-'"; | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Keep profiles whose facet ID is NULL In SQL,
Suggested change
[RULE] null-filtering · |
||||||
|
|
||||||
| #[cfg(test)] | ||||||
| #[path = "connector_tests.rs"] | ||||||
| mod tests; | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| //! Tests for the connector-sync predicates. | ||
|
|
||
| use super::*; | ||
|
|
||
| #[test] | ||
| fn namespaces() { | ||
| assert!(is_connector_namespace("skill-gmail")); | ||
| assert!(is_connector_namespace("source:gmail:conn1")); | ||
| assert!(!is_connector_namespace("source_gmail")); | ||
| assert!(!is_connector_namespace("global")); | ||
| assert!(!is_connector_namespace("document:notes")); | ||
| assert!(!is_connector_namespace("skills")); | ||
| } | ||
|
|
||
| #[test] | ||
| fn chunk_identity() { | ||
| assert!(chunk_source_by_identity("email", "anything")); | ||
| assert!(chunk_source_by_identity("chat", "slack:conn1")); | ||
| assert!(chunk_source_by_identity("document", "notion:c:page")); | ||
| for toolkit in CONNECTOR_TOOLKIT_PREFIXES { | ||
| assert!(chunk_source_by_identity("document", &format!("{toolkit}x"))); | ||
| } | ||
| assert!(!chunk_source_by_identity("document", "mem_src:folder")); | ||
| assert!(!chunk_source_by_identity("chat", "conversations:agent")); | ||
| assert!(!chunk_source_by_identity("document", "slackish:x")); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -49,6 +49,9 @@ pub struct LegacyWorkspace { | |
| pub(crate) schema: MemorySchema, | ||
| /// `memory_tree/chunks.db`, when present and usable. | ||
| pub(crate) chunks: Option<ChunkStore>, | ||
| /// Whether rows synced from connectors are left out; see | ||
| /// [`Self::skip_connector_syncs`]. | ||
| pub(crate) skip_connector_syncs: bool, | ||
| } | ||
|
|
||
| impl LegacyWorkspace { | ||
|
|
@@ -146,9 +149,23 @@ impl LegacyWorkspace { | |
| memory, | ||
| schema, | ||
| chunks, | ||
| skip_connector_syncs: false, | ||
| }) | ||
| } | ||
|
|
||
| /// Leaves out everything v1 synced from connectors (Composio and the | ||
| /// connector path: Gmail, Slack, Notion, Linear, GitHub, ClickUp, ...): | ||
| /// `memory_docs` in `skill-*` / `source:*` namespaces, chunk sources of | ||
| /// kind `email`, with a connector toolkit prefix in `source_id`, or with a | ||
| /// `*-sync:*` owner, `skill-*` profile facets, and `skill-*` / `source:*` | ||
| /// / `source_*` graph namespaces. [`Self::counts`] excludes the same rows. | ||
| /// Off by default. See the module README for the exact rules. | ||
| #[must_use] | ||
| pub fn skip_connector_syncs(mut self, skip: bool) -> Self { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No end-to-end test drives the skip_connector_syncs import behaviour
[RULE] e2e-uncovered · |
||
| self.skip_connector_syncs = skip; | ||
| self | ||
| } | ||
|
|
||
| /// The suffixes of the per-profile v1 stores beside the main one in the | ||
| /// workspace at `path` (`-1`, `-2`, …), sorted: every `memory<suffix>` or | ||
| /// `memory_tree<suffix>` directory with a valid suffix. Each may still | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When
skip_connector_syncs(true)is enabled for a workspace containing folder, file, RSS, web, or GitHub-repository sources, this predicate drops theirmemory_docstoo. The legacyMemorySourceSink::accept_source_itemsusedsource:{source_id}for every source kind, not only Composio, so the namespace prefix alone cannot identify connector content; inspect the persisted source kind/metadata before skipping these rows or the migration silently omits user source documents.Useful? React with 👍 / 👎.