Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -1362,10 +1362,10 @@ export const startMcpAuth = async (
};

/**
* Returns the current memory if one exists. If the memory is stale or missing,
a background generation is triggered and the endpoint returns the stale
memory (200) or 404 if none exists yet.
* @summary Get the authenticated user's latest memory.
* Returns whichever memories currently exist. If either memory is stale or
missing, a background generation for it is triggered and the endpoint
returns the stale values (200), or 404 if neither exists yet.
* @summary Get the authenticated user's latest personal and team memories.
*/
export type getMemoryHandlerResponse200 = {
data: MemoryResponse;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ export * from './jwtPayload';
export * from './mcpAuthCallbackParams';
export * from './memoryErrorBody';
export * from './memoryResponse';
export * from './memoryResponseMemory';
export * from './memoryResponseTeamMemory';
export * from './messageWithAttachments';
export * from './newAttachment';
export * from './newChatMessage';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,16 @@
* Document Cognition Service
* OpenAPI spec version: 1.0.0
*/
import type { MemoryResponseMemory } from './memoryResponseMemory';
import type { MemoryResponseTeamMemory } from './memoryResponseTeamMemory';

/**
* The user's latest memory.
* The user's latest memories.
*/
export interface MemoryResponse {
/** The generated memory text. */
memory: string;
/** The user's personal memory, if one has been generated. */
memory?: MemoryResponseMemory;
/** The latest memory of the user's team, if the user belongs to a team
and a team memory has been generated. */
team_memory?: MemoryResponseTeamMemory;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* Generated by orval v7.21.0 🍺
* Do not edit manually.
* Document Cognition Service
* OpenAPI spec version: 1.0.0
*/

/**
* The user's personal memory, if one has been generated.
*/
export type MemoryResponseMemory = string | null;
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* Generated by orval v7.21.0 🍺
* Do not edit manually.
* Document Cognition Service
* OpenAPI spec version: 1.0.0
*/

/**
* The latest memory of the user's team, if the user belongs to a team
and a team memory has been generated.
*/
export type MemoryResponseTeamMemory = string | null;
19 changes: 11 additions & 8 deletions js/app/packages/service-clients/service-cognition/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -1166,12 +1166,12 @@
"/memory": {
"get": {
"tags": ["memory"],
"summary": "Get the authenticated user's latest memory.",
"description": "Returns the current memory if one exists. If the memory is stale or missing,\na background generation is triggered and the endpoint returns the stale\nmemory (200) or 404 if none exists yet.",
"summary": "Get the authenticated user's latest personal and team memories.",
"description": "Returns whichever memories currently exist. If either memory is stale or\nmissing, a background generation for it is triggered and the endpoint\nreturns the stale values (200), or 404 if neither exists yet.",
"operationId": "get_memory_handler",
"responses": {
"200": {
"description": "Latest memory for the user",
"description": "Latest personal and team memories for the user",
"content": {
"application/json": {
"schema": {
Expand All @@ -1181,7 +1181,7 @@
}
},
"404": {
"description": "No memory exists for this user yet (generation triggered)"
"description": "No memory exists for this user or their team yet (generation triggered)"
},
"500": {
"description": "Internal server error",
Expand Down Expand Up @@ -2784,12 +2784,15 @@
},
"MemoryResponse": {
"type": "object",
"description": "The user's latest memory.",
"required": ["memory"],
"description": "The user's latest memories.",
"properties": {
"memory": {
"type": "string",
"description": "The generated memory text."
"type": ["string", "null"],
"description": "The user's personal memory, if one has been generated."
},
"team_memory": {
"type": ["string", "null"],
"description": "The latest memory of the user's team, if the user belongs to a team\nand a team memory has been generated."
}
}
},
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,10 @@ mod test;
pub use test::test_api_context;
pub(crate) type NotificationIngressType = SqsNotificationIngress<SqsQueue>;

pub type DcsMemoryService =
memory::domain::service::MemoryServiceImpl<memory::outbound::pg_memory_repo::PgMemoryRepo>;
pub type DcsMemoryService = memory::domain::service::MemoryServiceImpl<
memory::outbound::pg_memory_repo::PgMemoryRepo,
memory::outbound::pg_team_memory_repo::PgTeamMemoryRepo,
>;

/// Concrete MCP router state for DCS.
pub type DcsMcpRouterState = mcp_client::inbound::McpRouterState<
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -335,9 +335,12 @@ pub async fn test_api_context(pool: sqlx::Pool<sqlx::Postgres>) -> std::sync::Ar
Arc::new(all_tools.prompt.to_string());

let memory_repo = memory::outbound::pg_memory_repo::PgMemoryRepo::new(pool.clone());
let team_memory_repo =
memory::outbound::pg_team_memory_repo::PgTeamMemoryRepo::new(pool.clone());
let memory_service = Arc::new(memory::domain::service::MemoryServiceImpl::new(
pool.clone(),
memory_repo,
team_memory_repo,
tool_service_context.clone(),
all_tools,
));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -280,14 +280,14 @@ async fn send_chat_message_inner(
.filter_map(|r| r.parts)
.collect();

// Fetch user memory (triggers background generation if stale/missing)
let user_memory = ctx
// Fetch the user's personal and team memories (triggers background
// generation of whichever is stale or missing)
let memories = ctx
.memory_service
.get_or_generate_memory((*user_id).clone())
.await
.inspect_err(|e| tracing::error!(error = ?e, "failed to fetch user memory"))
.ok()
.flatten();
.inspect_err(|e| tracing::error!(error = ?e, "failed to fetch memories"))
.unwrap_or_default();

// Build the chat messages
let tools_prompt = choose_tools_prompt(&payload, &*ctx.all_tools_prompt);
Expand Down Expand Up @@ -321,11 +321,16 @@ async fn send_chat_message_inner(
.as_deref()
.unwrap_or_default();
let mut prompt = format!("{}\n{}", tools_prompt, additional);
if let Some(memory) = user_memory.as_deref() {
if let Some(memory) = memories.user.as_deref() {
prompt.push_str("\n\n<user_memory>\n");
prompt.push_str(memory);
prompt.push_str("\n</user_memory>");
}
if let Some(memory) = memories.team.as_deref() {
prompt.push_str("\n\n<team_memory>\n");
prompt.push_str(memory);
prompt.push_str("\n</team_memory>");
}
prompt
};

Expand Down
4 changes: 3 additions & 1 deletion rust/cloud-storage/document_cognition_service/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -391,11 +391,13 @@ async fn main() -> anyhow::Result<()> {
let all_tools_prompt: Arc<dyn std::fmt::Display + Send + Sync> =
Arc::new(all_tools.prompt.to_string());

// Build memory service
// Build memory service (personal + team memory)
let memory_repo = memory::outbound::pg_memory_repo::PgMemoryRepo::new(db.clone());
let team_memory_repo = memory::outbound::pg_team_memory_repo::PgTeamMemoryRepo::new(db.clone());
let memory_service = Arc::new(memory::domain::service::MemoryServiceImpl::new(
db.clone(),
memory_repo,
team_memory_repo,
tool_service_context.clone(),
all_tools,
));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
-- Team-level memory lives in the existing memory table: each row is scoped to
-- either a user (personal memory) or a team (team memory), exactly one of the two.
ALTER TABLE memory ALTER COLUMN user_id DROP NOT NULL;
ALTER TABLE memory ADD COLUMN team_id UUID REFERENCES team (id) ON DELETE CASCADE;
ALTER TABLE memory ADD CONSTRAINT memory_user_or_team CHECK (num_nonnulls(user_id, team_id) = 1);

-- Replace the plain unique constraint with partial unique indexes so both
-- scopes keep one-row-per-owner upsert semantics.
ALTER TABLE memory DROP CONSTRAINT memory_user_id_unique;
CREATE UNIQUE INDEX memory_user_id_unique ON memory (user_id) WHERE user_id IS NOT NULL;
CREATE UNIQUE INDEX memory_team_id_unique ON memory (team_id) WHERE team_id IS NOT NULL;
5 changes: 4 additions & 1 deletion rust/cloud-storage/memory/src/domain/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
pub mod ports;
pub mod service;

pub use ports::{Memory, MemoryError, MemoryRecord, MemoryRepo, MemoryService, Result};
pub use ports::{
Memories, Memory, MemoryError, MemoryRecord, MemoryRepo, MemoryService, Result, TeamMemoryRepo,
TeamOverview,
};
Loading
Loading