O Python SDK autônomo para Atlas Agent Engine Memória — memória de longo prazo para agentes de IA, utilizável dentro ou fora do Atlas Agent Engine.
Ele fornece a um agente um objeto, Memory, para gravar retornos de conversa e recuperar o contexto relevante posteriormente: feitos que ele aprendera (semântica), conversas anteriores (episódica), conhecimento do domínio (taxonomic) e procedimentos reutilizáveis. O pacote é instalado sozinho e depende apenas de pydantic e httpx, portanto, ele cai em qualquer agente sem extrair a pilha de plataformas.
Os agentes distribuídos na plataforma usam esse mesmo SDK. Eles não o constroem — o tempo de execução entrega ao agente um agent_engine_sdk_memory.Memory, já conectado ao transporte no cluster e à identidade da invocação que está tratando. É a mesma classe e as mesmas assinaturas de método; apenas o transporte abaixo é diferente. Portanto, o código que você escreve em um agente externo é executado inalterado quando você o implementa na plataforma, e o que você aprender com este README se aplica em ambos os lugares.
Duas coisas diferem: de onde vem a identidade (consulte a conexão) e um conjunto de chamadas que o transporte vinculado à aplicação não pode atender e que elevam MemoryNotSupportedError — listadas nas funcionalidades de cada conexão.
Comece explicando como a memória funciona se isso for novo para você — a forma do sistema explica a maior parte da API. Em seguida, integrá-lo em três etapas: instalar, conectar e usá-lo.As configurações do lado do servidor são abordadas na configuração do servidor de memória. O material de referência sobre identidade, erros e modelos segue, com os componentes internos do pacote por último.
Conteúdos
user_id and visibilityresponder a perguntas diferentes <#user_id-and-visibility-resposta-diferentes-perguntas>`__
Como funciona a memória
O problema que ele resolve
A memória oferece ao agente um armazenamento persistente dos fato s, conversas, procedimentos e vocabulário que são importantes — e preenche esse armazenamento, extraindo fatores aprendizados duráveis das conversas que seu agente tem com um LLM. A extração em si é feita por um LLM que você configura.
Para um agente distribuído em plataforma , nada disso precisa de fiação. O tempo de execução registra cada conversa que se volta para a memória de curto prazo à medida que o fluxo de trabalho do agente é executado, e a promoção e a extração prosseguem de forma assíncrona em segundo plano, para que nada bloqueie uma resposta enquanto um fato é destilado. Um agente que já conhece algo durável não precisa esperar a extração para encontrá-lo — ele pode escrever o fato diretamente com save_semantic e as outras gravações digitadas.
Ler de volta funciona de qualquer maneira. Um agente pode ir atrás de um tipo de fato por vez — search_semantic para o que aprender, search_episodes para o que aconteceu antes, search_taxonomic para o que um termo significa, discover_procedures para como algo é feito — e lidar os resultados em si.
Ou pode entregar todo o tarefa . build_context_from_sources usa uma especificação por fonte, cada uma declarando seu próprio modo de recuperação, filtro e contagem de candidatos e, em seguida, recupera de cada fonte em seus próprios termos, classifica e elimina a duplicação dos resultados juntos, corta-os para um orçamento de token e retorna um único bloco de contexto para aparecer no próximo prompt. build_context faz o mesmo com um modo e um filtro para cada fonte; alcance o formulário por fonte quando eles precisam diferir.
Duas camadas
A memória é divisão pela duração da vida das coisas e como são moldadas.
A memória de curto prazo (STM) é a conversa bruta: um registro por vez, com escopo para uma sessão, escrito como acontece. É barato escrever e concluir – tudo disse, em ordem.
A memória de longo prazo é o que sobrevive à conversa, destilada em quatro formas porque respondem a perguntas diferentes:
type | detém | respostas |
|---|---|---|
| um fato rotulado | “o que posso saber sobre isso? |
| um capítulo resumido | "o que aconteceu antes?" |
| um procedimento reutilizável | "como faço isso?" |
| um termo e sua definição | "O que essa palavra média aqui?" |
Os projetos também podem declarar tipos personalizados para registros de domínio que as quatro formas não se encaixam.
Como as gravações se tornam memória
Você não escreve memória de longo prazo manualmente, embora possa. O caminho normal é:
record_turn(...) you write turns as the conversation happens │ ▼ turns accumulate into a session snapshot a contiguous run of turns, summarised │ ▼ an LLM reads the snapshot and extracts what is durable semantic · episodic · procedural · taxonomic
A extração é assíncrona e executada em segundo plano, portanto, record_turn mantém uma gravação rápida. Um fato aprendizado em uma conversa está, portanto, disponível para a próxima, não para a próxima conversa.
A consequência que vale a pena projetar: mudanças recentes são visíveis imediatamente por meio do STM, o conhecimento extraído aparece um pouco mais tarde. Pede stm quando precisar do que acabou de ser dita.
Quando a extração falha
A extração em segundo plano depende de um serviço de incorporação, de um LLM de extração e do banco de dados, e qualquer um deles pode falhar. Suas escritas são isoladas disso: record_turn fez sucesso quando retornou, independente do que aconteça downstream.
Nos bastidor, uma etapa com falha é classificada por uma pergunta — a condição pode mudar sem que o pedido mude? Falhas ambientais (erros de rede, interrupções do fornecedor, limites de taxa, uma chave de API no meio da rotação) são repetidas automaticamente com backoff, com passo a passo para a falha: um blip de rede tenta novamente em segundos, um problema de credencial mais lentamente, pois as chaves são giradas por humanos. Falhas determinadas por conteúdo não são repetidas, porque uma nova tentativa não pode alterar o resultado; eles são registrados no lado do servidor, onde os operadores podem vê-los, em vez de fazer um loop para sempre.
Isso se aplica a todo o caminho de extração, não apenas às bordas externas. Uma etapa que falha não produz mais silenciosamente um resultado vazio: ela tenta novamente ou é registrada onde os operadores podem agir sobre ela.
Um item inutilizável não descarta o resto. Se uma única memória não puder ser incorporada – seu conteúdo é muito longo para o modelo ou o fornecedor a rejeita – essa memória ainda será armazenada e as outras no mesmo lote não serão afetadas. O que falta na memória armazenada é um vetor. Na prática:
Ele ainda é encontrado pela pesquisa
texte pelohybrid— o híbrido combina ambas as classificações, então o lado do texto ainda o apresenta.Não é encontrado pela pesquisa
semantic, que compara apenas vetores.
Esta é uma boa razão para preferir hybrid como seu modo de recuperação padrão. Já é a melhor escolha para identificadores exatos e palavras raras, e também significa que uma memória que não pôde ser incorporada ainda chega até você.
O que isso significa para um agente:
Uma interrupção do fornecedor atrasa o conhecimento extraído; não perde suas voltas. O STM é escrito de forma síncrona e não é afetado - continue solicitando
stmquando precisar do que acabou de ser mencionado.As tentativas são limitadas: uma interrupção de dependência que as ultrapassa para de tentar novamente, em vez de fazer um loop indefinidamente, e a falha é registrada em vez de descartada.
Nada disso aparece no SDK como um erro. Falhas de extração são uma preocupação do lado do servidor; o sinal visível do SDK é uma memória extraída que (ainda) não aparece na recuperação, ou aparece por meio da pesquisa de texto e híbrida, mas não semântica.
Como funciona a leitura
Prosa montada, ou os próprios registros – duas perguntas diferentes.
``build_context_from_sources(...)`` é o único a ser buscado por padrão. Você faz uma query e um orçamento de token; ele recupera os tipos de memória que você nomeia, classifica os resultados juntos, descarta quase duplicatas, corta o orçamento e retorna algo que você pode colocar diretamente em um prompt. É todo o pipeline de recuperação por trás de uma chamada e é necessária uma especificação por fonte, portanto, modos e filtros são definidos por fonte em vez de uma vez para todos eles.
A recuperação pode corresponder de três maneiras. semantic compara incorporações e encontra coisas que média o mesmo; text corresponde a palavras e encontra coisas que dizem o mesmo; hybrid executa ambos e funde as classificações. Híbrido geralmente é o padrão correto — a pesquisa vetorial por si só ignora identificadores exatos e palavras raras, e a pesquisa de texto por si só ignora paráfrases.
``build_context(...)`` é o construtor mais simples: o mesmo pipeline, mas um modo e um filtro se aplicam a cada fonte que lê.
``search(...)`` e as pesquisas por tipo (search_semantic, search_episodes e assim por diante) retornam registros classificados em vez de prosa montada, para quando você mesmo quiser inspecioná-los ou pós-processá-los.
Quais escopos uma memória
Cada registro carrega a identidade sob a qual foi escrito e cada leitura é filtrada por essa identidade na query do banco de dados e não depois. Os campos são: organização, usuário, projeto, sessão e, opcionalmente, o agente, além de uma visibilidade que decide se um registro é privado para seu usuário ou legível de forma mais ampla.
Isso é importante por um motivo prático: uma pesquisa ou build_context com escopo definido para um usuário não exibirá a memória privada de outro usuário, porque a restrição faz parte da query e não de um filtro aplicado aos resultados. Portanto, bind(...) não é uma conveniência — é como você declara o escopo em que as leituras e gravações subsequentes operam, e errar grava a memória de um usuário sob a identidade de outro.
user_id e visibility respondem a perguntas diferentes
user_id é de quem é a memória de um registro. visibility é o quão longe ele atinge:
visibilidade | quem pode ler |
|---|---|
| apenas o usuário nomeado em |
| qualquer usuário no projeto — obsoleto, veja abaixo |
| qualquer usuário no projeto |
Nenhum valor de visibilidade atinge fora do projeto em que o registro foi gravado. A memória é armazenada por projeto, portanto, um limite de projeto não é algo que a visibilidade possa ultrapassar — org é um nome histórico e significa "não restrito a um usuário", não “visível para outros projetos”.
Aviso
6} ``shared`` está obsoleto e será removido em uma versão futura. Use org para conhecimento que deve ir além de um único usuário e private para qualquer coisa que tenha como escopo seu proprietário. Como shared e org já têm o mesmo alcance, alternar um registro existente de shared para org não altera quem pode lê-lo. Os registros já gravados como shared continuam a ser lidos de volta por enquanto.
Os dois são independentes e, em uma leitura, combinam-se como e,nunca como ou. Cada campo fornecido adiciona mais uma condição de igualdade à query; cada campo que você omite deixa essa dimensão sem restrições:
você escopo a leitura por | você recebe de volta |
|---|---|
| tudo o que o usuário possui, em qualquer visibilidade |
| todos os registros nessa visibilidade, quem quer que seja o proprietário |
Ambos | somente registros correspondentes a ambos — a leitura mais limitada |
nem | tudo no projeto |
A terceira linha é a que surpreede as pessoas. search_semantic(query, user_id="user_1", visibility="org") não média "memórias do usuário_1 mais a organização". Significa "as memória visíveis à organização que user_1 possui", que é menor do que qualquer restrição sozinha. Não há união: para ler o conhecimento compartilhado e a memória privada do usuário, faça duas chamadas e mescle os resultados você mesmo.
Instalar
pip install agent-engine-sdk-memory
from agent_engine_sdk_memory import Memory, MemoryRequestContext
Conectando
O local para onde seu agente é executado decide como ele se conecta, e a diferença está principalmente em quem fornece a identidade.
seu agente executa | você constrói | identidade vem de | Veja |
|---|---|---|---|
na plataforma | nada — o tempo de execução injeta | o tempo de execução, por invocação | |
em qualquer outro lugar |
| seu token de conta de serviço, além do que você | |
localmente, em desenvolvimento |
| o que você |
A API é idêntica em todos os três. O código escrito em um agente implantado externamente é executado inalterado quando movido para a plataforma – você exclui a chamada do construtor e o tempo de execução fornece o objeto .
Os agentes distribuídos na plataforma obtêm identidade gratuitamente, e essa é a diferença árdua. O tempo de execução já conhece a organização, projeto, usuário e sessão para a invocação que está tratando, então ele os vincula para você. Um agente externo sabe apenas o que seu token de conta de serviço implica - o projeto - portanto, ele deve informar à memória a qual usuário e sessão cada chamada pertence. Veja isso errado e você estará escrevendo a memória de um usuário sob a identidade de outro usuário, e é por isso que o caminho externo exige que você seja explícito.
A plataforma hospedada
O serviço gerenciado. Passe um token de acesso à conta de serviço e o ID do seu projeto .
Crie o token com a CLI agentengine. Crie uma conta de serviço uma vez — o segredo do cliente é mostrado apenas uma vez, então salve-o imediatamente:
agentengine service-account create my-agent --project-id <your-project-id> --role AGENT_DEVELOPER
Em seguida, troque o ID e o segredo do cliente por um token de acesso de curta duração (1 horas) (o curl solicita o segredo do cliente para que ele fique fora do histórico de shell):
ACCESS_TOKEN=$(curl --fail-with-body --silent --show-error --user <client-id> --data grant_type=client_credentials https://agentengine.mongodb.com/api/v1/oauth/token | jq -er .access_token)
memory = Memory(service_account_token="<your-access-token>", project_id="<your-project-id>")
Entrada | Retorna para | Notas |
|---|---|---|
|
| Token de acesso à conta de serviço, enviado como credencial de portador. Lembre-se novamente quando expirar. |
|
| O projeto de ler e escrever. Necessário para o serviço hospedado. |
|
| Opcional. Substitui o host para direcionar uma pilha que não seja de produção. |
A plataforma fixa sua credencial ao projeto dela, portanto, um project_id que não corresponde é rejeitado. Um service_account_token em branco gera ValueError.
api_key (e AGENTIC_MEMORY_API_KEY) permanecem aceitos como um alias obsoleto e emitem um DeprecationWarning; passar a entrada nova e a legado aumenta ValueError. As chaves API do projeto não podem mais ser criadas por HTTP, portanto, novas integrações devem usar um token de conta de serviço.
Desenvolvimento local
Aponte em um backend que você mesmo executa, como a pilha agentengine dev up começa.
memory = Memory(base_url="http://localhost:8080")
Entrada | Retorna para | Notas |
|---|---|---|
|
| O URL do seu backend. |
|
| Opcional. Omita-o para o desenvolvimento local. ( |
Deixe project_id vazio para desenvolvimento local.
Dentro de um agente de plataforma (limitado à aplicação)
Quando seu agente é executado no Atlas Agent Engine, você não constrói ou conecta Memory nada — a plataforma injeta uma instância pronta e pré-conectada, com identidade, locatário e transporte já vinculados. O código do aplicativo não passa por nenhuma das entradas acima; ele usa o identificador que o tempo de execução o entrega. A identidade (usuário, sessão, organização, projeto) é resolvida a partir do contexto de tempo de execução do ambiente, para que as operações possam ser chamadas diretamente:
# `memory` is supplied by the platform runtime — do not construct it. memory.record_turn(role="user", content="I'm allergic to penicillin.") context = memory.build_context(query="What medications should I avoid?") # bind(...) is still available to scope a call chain to a specific identity.
O caminho vinculado ao aplicativo tem algumas lacunas de recursos (metadados de chamada de ferramenta/giro de modelo e algumas leituras de estilo de lista); consulte a coluna Vinculado à aplicação em O que cada conexão suporta e `docs/capability-matrix.md <docs/capability-matriz>'__.
Env vars são um fallback
Cada entrada também volta para uma variável de ambiente AGENTIC_MEMORY_* quando você omite o argumento. Um argumento explícito sempre vence. Memory() sem argumentos lê todos os três do ambiente.
Como funciona o roteamento
project_id decide para onde as chamadas vão. A autenticação nunca o faz.
Defina
project_ide o SDK chamará as rotas do seu projeto,/api/v1/projects/{project_id}/memory/*.Deixe-o vazio e o SDK chamará o backend diretamente,
/api/v1/memory/*.
Se project_id estiver definido, mas o backend não tiver rota correspondente, como um backend local, a chamada gerará `MemoryRouteNotFoundError <#errors>`__ com uma dica para desmarcar. O inverso também se aplica. Direcione o serviço hospedado sem project_id e a chamada 404s com uma dica para defini-lo.
Avançado: backend direto autenticado
Autenticação e roteamento são independentes, portanto, você pode passar um service_account_token com project_id vazio. Em seguida, o SDK envia chamadas autenticadas diretamente para o backend em base_url, nas rotas diretas, ignorando o caminho do projeto . Isso é adequado para um mecanismo de orquestração hospedado (OE) acessado diretamente.
O que cada conexão suporta
Os recursos seguem onde a chamada é roteada, não a autenticação.
(operação) | Hospedado (project_id set) | Direcionado (project_id vazio) | Vinculado à aplicação |
|---|---|---|---|
| ✓ | ✓ | ✓ |
| ✓ | ✓ | ✓ |
| ✓ | ✓ | ✗ |
| ✓ | ✓ | ✓ |
| ✓ | ✓ | ✓ |
| ✓ | ✓ | ✓ |
CRUD específico do tipo ( | ✓ | ✓ | ✓ com lacunas |
tipo personalizado | ✓ (sinalizador) | ✗ sem contexto de execução | ✓ |
Uma chamada sem suporte gera `MemoryNotSupportedError <#errors>`__ — antes de qualquer solicitação de rede para lacunas conhecidas por backend ou após a resposta para lacunas de recurso que somente a plataforma pode relatar (consulte Erros) — nunca uma falha silenciosa ou uma falha bruta Erro de HTTP. A referência completa por backend, incluindo lacunas vinculadas ao aplicativo em CRUD específico do tipo, está em `docs/capability-matrix.md <docs/capability-matriz>'__.
Os agentes vinculados a aplicativos são a exceção a todos os itens acima. Dentro de um agente de plataforma implementado , a plataforma injeta um runtime pronto, de modo que o código do aplicação não passe nenhuma dessas entradas.
Use-o
Uma viagem de ida e volta completa: construir memória, vincular uma identidade de conversa, registrar uma mudança e recuperar o contexto.
from agent_engine_sdk_memory import Memory, MemoryRequestContext memory = Memory(service_account_token="<your-access-token>", project_id="<your-project-id>") # Scope every call to a user and conversation. session = memory.bind(MemoryRequestContext(user_id="user_1", session_id="thread_123")) # Record what happened. session.record_turn(role="user", content="I'm allergic to penicillin.") session.record_turn(role="assistant", content="Noted — I'll avoid it.") # Later, pull back the relevant context for a new prompt. Include "stm" # to surface the turns just recorded (the default is episodic, semantic). # max_tokens is an optional gross context-construction budget. context = session.build_context( query="What medications should I avoid?", enabled_sources={"stm", "episodic", "semantic"}, max_tokens=2048, ) # context is a ContextResponse — inject its content into the next prompt.
bind(ctx) retorna um novo identificador com escopo para ctx sem alterar o original, de modo que um Memory possa atender a muitos usuários e sessões simultaneamente.
Fontes padrão ``build_context``. enabled_sources omitido tem como padrão episodic e semantic; stm, taxonomic e procedural exigem um conjunto explícito.
``max_tokens``. orçamento bruto positivo opcional para a construção de contexto. Não é um custo de busca ou um tamanho de saída comprometido: após a recuperação e classificação, o servidor subtrai uma reserva de formatação de token 500 e, em seguida, seleciona avidamente chunks de memória inteiros que cabem no restante. Valores positivos iguais ou inferiores a 500 não deixam orçamento para recordações. Valores acima de 500 ainda podem gerar um contexto vazio quando nenhum chunk se encaixa. metadata.token_count reporta somente a saída formatada e exclui a reserva. Omita max_tokens para manter o comportamento anterior.
``format_type`` e ``include_memories``. build_context e build_context_from_sources aceitam duas opções de modelagem de resposta. format_style ("openai", "claude" ou "jinja2"; o enumeração FormatStyle é exportado para anotações de tipo) seleciona o formato de formatted_context, e valores inválidos aumentam ValueError localmente. Omitido, o servidor infere o formato a partir do modelo configurado. Uma ressalva: o servidor atualmente reinfere quando o valor explícito corresponde ao seu tipo de modelo padrão, portanto, um "openai" explícito é honrado literalmente apenas em servidores com um modelo de família OpenAI configurado; "claude" e "jinja2" são sempre honrados. include_memories=True preenche o selected_memories da resposta com a lista MemoryChunk pós-orçamento, para que você possa inspecionar exatamente quais memória foram selecionadas. Ambos exigem uma conexão HTTP hospedada ou direta; o modo vinculado a aplicativos aumenta MemoryNotSupportedError quando qualquer um está definido.
Contexto por fonte — ``build_context_from_sources``. Onde build_context aplica um filtro e recuperação semântica a cada fonte, esse método permite que cada fonte declare sua própria recuperação mode (text, semantic ou hybrid), metadata_filter e top_k via um SourceSpec. Os resultados são mesclados e duplicados entre fontes, opcionalmente reordenados por uma relevância rerank, depois formatados e orçados como build_context. metadata.ranking_strategy e metadata.source_outcomes informam como o pedido final foi produzido e como cada fonte se comportou. sources deve estar não vazio e pode listar cada fonte no máximo uma vez, e o top_k de cada fonte deve estar entre 1 e 200; session_id só é necessário quando a origem stm está incluída.
O híbrido geralmente é o padrão correto para fontes portadoras de texto: a pesquisa vetorial por si só ignora identificadores exatos e palavras raras, a pesquisa de texto por si só ignora as paráfrases. mode padroniza para semantic quando uma especificação o omite.
Declarando metadados filtráveis. Um metadata_filter só pode nomear metadados que seu projeto tenha declarado, porque o filtro é aplicado dentro do índice de pesquisa e não sobre seus resultados. Declare os atributos e as partes do índice que devem servi-los em project-config.yaml:
metadata_partition_key: # Filterable metadata attributes (max 10) - name: tier type: string # string | number | boolean | date - name: confidence type: number metadata_partition_index: semantic: [both] # both legs, so a hybrid source can filter short_term: [both] # stm is searchable only once listed here short_term: embed_on_write: true # required to give stm a vector leg
Observação
Requer servidor de memória 0.0.81 ou mais recente. As chaves de partição só são honradas por um tempo de execução que as suporta, e o servidor de memória lê sua configuração na inicialização — armazenar uma configuração não é aplicá-la. Depois de editar project-config.yaml, carregue-o com agentengine memory configure e, em seguida, role o tempo de execução na imagem atual:
agentengine memory apply --upgrade
--upgrade move o tempo de execução para a imagem que seu ambiente fixa atualmente, que é como você escolhe um servidor de memória mais recente; sem ele, o tempo de execução mantém a imagem em que já está. agentengine memory apply --dry-run relata a imagem em uso se você quiser verificar antes de alterar qualquer coisa, e --wait bloqueia até que os relatórios de tempo de execução estejam prontos.
quatro consequências que vale a pena saber antes de escrever um filtro:
A chave de filtro é o caminho do índice qualificado, não o nome declarado. Um atributo declarado como
tieré indexado emmetadata.tier, e é isso que o filtro deve dizer — nada é prefixado para você.Uma chave é filtrável somente nas etapas que você lista.
metadata_partition_indexmapeia uma fonte para[vector],[text]ou[both], e uma fontehybridprecisa de[both]: um filtro que sua leg de texto não pode servir é rejeitado antes que a query seja executada em vez de retornar silenciosamente menos.Uma chave errada falha alto. Ao contrário do único
metadata_filterde nível superior dobuild_context, as chaves por fonte são validadas em relação ao conjunto declarado. Um caminho não declarado ou com erros ortográficos geraMemoryBadRequestErrorcuja mensagem lista os caminhos aceitos, de modo que um erro de digitação falha na chamada em vez de retornar silenciosamente um conjunto de resultados limitado.A memória de curto prazo não tem índice próprio. Ele é filtrável — e pesquisável por esse método — somente uma vez que o projeto liste
short_termemmetadata_partition_index. Até então, uma fontestmfalha ao invés de retornar nada: os erros de pesquisa, e a fonte é relatada com umerroremmetadata.source_outcomes. Sestmfoi a única fonte solicitada, todas as fontes falharam e a chamada retorna 503 em vez de um contexto vazio, porque um resultado vazio seria indistinguível de uma pesquisa saudável em um corpus vazio. Dar a ela uma etapa vetorial ([vector]ou[both]) também requershort_term.embed_on_write: true, e a configuração é rejeitada sem ela, porque um índice vetorial sobre voltas que nunca são incorporadas seria um peso morto. A etapa de texto não precisa de incorporações.
O type declarado define a semântica correspondente. Uma chave string é indexada como um token, portanto, uma correspondência é de valor inteiro e diferencia maiúsculas de minúsculas: "gold" não corresponde a Gold nem gold-tier. Os operadores de faixa exigem uma tecla number ou date.
Cláusulas suportadas: igualdade; $gt / $gte / $lt / $lte, com limites combinados fundindo-se em um intervalo; $in / $nin para conjuntos; $ne; $exists; e $and / $or / $nor para composição.
agent_id é filtrável sem ser declarado. Os campos de aluguel org_id, user_id, project_id, session_id, visibility, deleted, is_latest e has_embedding são reservados para controle de acesso e não podem ser filtrados por chamadores.
Colocando juntos. Com a configuração acima:
from agent_engine_sdk_memory import SourceSpec context = session.build_context_from_sources( query="what did we decide about the refund policy?", sources=[ # Exact match on a string key, and a range on a numeric one. SourceSpec( source=MemorySource.SEMANTIC, mode=RetrievalMode.HYBRID, metadata_filter={ "metadata.tier": "gold", "metadata.confidence": {"$gt": 0.8}, }, top_k=20, ), # Sources without a filter are unrestricted. SourceSpec(source=MemorySource.EPISODIC, mode=RetrievalMode.HYBRID, top_k=5), SourceSpec(source=MemorySource.STM, mode=RetrievalMode.TEXT, top_k=10), ], rerank=True, )
Esse método atinge o backend em todos os três modos de conexão. Na rota do projeto hospedado (conjunto project_id), o Gateway faz proxy para o mesmo manipulador por origem, carimboso org/ projeto da sessão autenticada; na rota direta (project_id vazio), o proxy OE o encaminha; No tempo de execução na plataforma (limitado à aplicação), a plataforma carimbos de aluguel e carrega a resposta completa por meio da execução durável.
Além de record_turn e build_context, o objeto Memory expõe:
Pesquisa —
search(query, sources=[...])se distribui entre os tipos de memória e retorna umlist[MemoryChunk]classificado;search_semantic,search_episodes,search_taxonomicediscover_procedurestêm como alvo um único tipo.Leituras e escritas específicas do tipo (onde a conexão suporta CRUD) —
save_semantic/get_semantic,save_episode/list_episodes,save_taxonomic/get_taxonomic_term/list_domainsesave_procedure/get_procedure.Tipos de memória personalizados —
save(memory_type, content, tags=...)eretrieve(memory_type, query, tags=..., top_k=...)operam em tipos declarados na configuração de memória do projeto. Os nomes de tipo integrados são rejeitados — use os métodos dedicados acima. Essas chamadas não carregam campos de identidade; a plataforma carimbos org, projeto e user da solicitação. Em uma plataforma que não atende a rotas de tipo personalizado ou em que o recurso esteja desativado, a chamada aumenta`MemoryNotSupportedError<#errors>'__.
Um pequeno exemplo usando o limite session acima – escreva um fato, leia-o de volta por rótulo e pesquise os tipos:
# Save a semantic fact (user_id is inherited from the bound session). session.save_semantic(text="Prefers window seats on flights.", label="seat-preference") # Read it straight back by label. fact = session.get_semantic("seat-preference") # Search across memory types; returns one ranked list[MemoryChunk]. hits = session.search("travel preferences", sources=["semantic", "episodic"], top_k=5)
Memory também é um gerenciador de contexto; with Memory(service_account_token=...) as memory: libera o transporte subjacente na saída.
Configurando o servidor de memória
O SDK lê e escreve memória; ele não configura o servidor. Configurações — incorporações, a extração LLM, quais tipos de memória são extraídos, metadados filtráveis — existem no bloco memory: do project-config.yaml do seu projeto e são aplicadas com a CLI. Somente esse subdocumento é carregado; o resto do arquivo é ignorado.
agentengine memory configure # store the memory: block for this project agentengine memory apply --wait # roll the memory server so it takes effect
O armazenamento não está se aplicando. configure salva a configuração e a plataforma a entrega ao servidor de memória do seu projeto por conta própria, mas o servidor lê sua configuração somente na inicialização. Até que um pod seja reiniciado, as novas configurações estarão paradas no disco não lidas. agentengine memory apply executa essa reinicialização — e provisiona o tempo de execução da memória primeiro se o projeto ainda não tiver um.
agentengine memory apply --upgrade além disso, move o tempo de execução para a imagem do servidor de memória que seu ambiente fixa atualmente, que é como você escolhe um servidor mais recente. Sem --upgrade a imagem é deixada sozinha. agentengine memory status relata o estado do tempo de execução a qualquer momento; --wait em apply blocos até que esteja pronto.
Configurações que você não poderá alterar mais tarde
Algumas partes da configuração são efetivamente para gravação uma única vez, então decida-as antes de começar a escrever memória:
Chaves de partição de metadados — os atributos filtráveis declarados. Uma chave não pode ser removida ou ter seu tipo alterado depois de declarado.
Declarações de tipo de memória personalizada — uma vez que um tipo seja aceito, sua coleção e conjunto de tags não poderão ser editados ou removidos por meio da API de configuração; declare um novo nome de tipo.
Índices de pesquisa — provisionados somente quando ainda não existem. Adicionar uma chave da partição após o início do servidor não reconstrói o índice existente; o desvio é registrado e um filtro na nova chave falha no banco de dados em vez de ser silenciosamente ignorado. Recriar o índice é uma operação manual.
O nível de registro, o LLM de extração e os tipos de extração habilitados podem ser alterados e reaplicados. Alterar o modelo ou dimensão de incorporação requer a migração e a reincorporação de memória existentes; as mudanças nas dimensões também exigem a reconstrução dos índices de pesquisa vetorial.
Como a identidade é resolvida
As operações de memória têm o escopo de user_id, agent_id e session_id, transportadas em um MemoryRequestContext. Para cada chamada, todos os campo são resolvidos por três níveis, primeiro a maior precedência:
Argumento de chamada — um valor passado diretamente para o método (por exemplo,
search_semantic(query, user_id="user_2")).Contextovinculado — o
MemoryRequestContextpassado parabind(...).Contexto de execução — identidade do ambiente fornecida pelo tempo de execução, usada pelo caminho vinculado à aplicação.
Valores em branco ou somente espaço em branco contam como não definidos em cada nível; agent_id é sempre opcional. A identidade é validada no lado do cliente somente para as gravações específicas do tipo: save_semantic, save_taxonomic e save_procedure exigem um user_id e save_episode exigem user_id e session_id - cada um gera MemoryIdentityError quando o campo não puder ser resolvido. As operações de fluxo de trabalho (record_turn, build_context, as pesquisas) e as leituras get_* / list_* não impõem identidade localmente; eles encaminham o que for resolvido para o backend, que pode rejeitar a solicitação como um erro de transporte.
``session_id`` tem escopo por operação. Identifica uma conversa, portanto, é herdado (de bind/runtime) apenas para E/S de conversa: record_turn e a parte de memória de curto prazo de build_context. As pesquisas análogas e semânticas não a herdam — search_episodes, list_episodes e a parte fragmentada de search() resolvem session_id apenas do argumento de chamada. A memória periódica é armazenada sem escopo de sessão (episódios consolidados carregam session_id: null), de modo que uma sessão vinculada filtraria silenciosamente todas as memória duráveis e retornaria uma lista vazia sem erros. Passe session_id= explicitamente na chamada de pesquisa quando quiser uma leitura fragmentada com escopo de sessão. (Isto alinha o SDK com o caminho da plataforma agent-engine-sdk-langgraph / TenantRuntime, que já requer um session_id explícito na pesquisa fragmentada.) user_id e agent_id não são afetados e ainda herdam de bind/runtime em leituras.
Como definir visibilidade em uma gravação
Cada gravação requer um visibility. O padrão difere de acordo com o tipo de memória, porque os tipos são usados de maneira diferente:
escrever | visibilidade padrão |
|---|---|
|
|
|
|
A memória taxativa tem como padrão org porque um vocabulário de domínio é compartilhado por definição — um termo e seu significado raramente são negócios privados de um usuário. Todo o resto é padronizado como private, então um fato que você salvou tem como escopo o usuário do qual foi aprender, a menos que você disser o contrário.
record_turn é a exceção: não leva visibility e nenhum user_id também. Uma vez de conversa é sempre escrita sob o usuário vinculado e sessão, então bind(...) antes de gravar voltas — não há maneira por chamada de definir o usuário em uma volta.
# Private to user_1 — the default. session.save_semantic(text="Prefers window seats.", label="seat-preference") # Readable across the whole organization. session.save_semantic( text="Refunds over $500 need manager approval.", label="refund-policy", visibility="org", )
Escopo de uma leitura
As leituras deixam visibility sem definição, portanto, são limitadas apenas pela identidade que se resolve — geralmente o limite user_id. Esse é o padrão correto para personalização: você obtém tudo o que o usuário possui, em qualquer visibilidade.
Para alcançar o conhecimento compartilhado, você passa por uma visibilidade e, aqui, a regra não funciona. Um identificador vinculado a user_id="user_1" ainda contribui com esse user_id, de modo que ele lê somente os registros visíveis à organização que user_1 possui:
session = memory.bind(MemoryRequestContext(user_id="user_1", session_id="thread_123")) session.search_semantic("refund policy", visibility="org") # user_1 AND org
Passar user_id=None não o amplia. Um argumento None ou em branco conta como "não fornecido" em todos os níveis, portanto, ele atinge o valor limite. Leia todos os usuários com um identificador que não tem limite user_id:
# The original handle is unbound, so it carries no user_id. memory.search_semantic("refund policy", visibility="org") # Or bind only the parts you want. thread = memory.bind(MemoryRequestContext(session_id="thread_123")) thread.search_semantic("refund policy", visibility="org")
Assim, um assistente que precisa do histórico do usuário e do conhecimento compartilhado da equipe emite duas leituras e as mescla:
personal = session.search_semantic(query, top_k=5) # user_1, any visibility shared = memory.search_semantic(query, visibility="org", top_k=5) # org-wide, any owner
Errors
Os erros se dividem em duas família. Os erros do lado do cliente subclassificam ValueError e geralmente são gerados antes de qualquer chamada de rede:
MemoryClientError— base para erros de uso.MemoryIdentityError— não foi possível resolver um campo de identidade obrigatório. SubclassificaValueErrordiretamente (um grupo deMemoryClientError, então não é capturado porexcept MemoryClientError).MemoryNotSupportedError— a operação não estiver disponível para a conexão ativa (consulte Conectando). A mensagem nomeia a operação e o motivo. Para os métodos de tipo personalizado (save/retrieve), ele também pode ser gerado após a chamada HTTP, quando a resposta mostra que a plataforma não tem a capacidade: um 404/405 simples (plataforma muito antiga para veicular a rota) ou os 400 estruturados do gateway que reportam os tipos de memória personalizada desabilitados no sistema. Um tipo desconhecido estruturado 404 é um erro de solicitação, não uma lacuna de recurso, e aumentaMemoryBadRequestError.
Os erros de transporte derivam de MemoryAPIError e carregam o status HTTP e o corpo da resposta:
MemoryAuthError— falha na autenticação ou autorização (401/403).MemoryBadRequestError— a solicitação foi rejeitada (um 4xx diferente de autenticação ou não provisionado).MemoryRouteNotFoundError— uma solicitação de loop principal 404'd, então a forma da rota provavelmente não corresponde ao backend. A mensagem fornece uma dica direcional para definir ou desmarcarproject_id. SubclassesMemoryBadRequestError.MemoryNotProvisionedError— o tempo de execução da memória do projeto ainda não está acessível.MemoryServerError— o backend falhou (5xx) ou devolveu um corpo não analisável ou inesperado.MemoryConnectionError— não foi possível alcançar o backend.
Modelos
Os tipos de solicitação e resposta são exportados da raiz do pacote e reexportados de agent_engine_sdk_memory.models. A recuperação retorna MemoryChunk e ContextResponse; as gravações retornam resultados digitados como WriteTurnResult, CreateSemanticResult e CreateEpisodicResult. SearchSource enumera os tipos pesquisáveis. A construção de contexto é configurada com kwares no Memory.build_context (por exemplo enabled_sources e max_tokens), não um modelo de configuração separado. Importe-os de agent_engine_sdk_memory, não de agent_engine_sdk - os modelos costumavam residir lá e foram movidos para aqui.
Lendo de volta seus próprios metadados
record_turn pega um dicionário metadata opcional e a recuperação de curto prazo o retorna em seu próprio slot no bloco: chunk.metadata["metadata"]. Suas chaves são mantidas separadas dos campos de rotação da plataforma (session_id, role, turn_seq, ...) em vez de misturadas com elas, de modo que uma chave sua nunca colide com uma delas — você pode nomear uma chave role ou session_id e leia seu próprio valor de volta.
Dois limites que a segurança de colisão não cobre. Os valores precisam sobreviver à viagem de ida e volta, portanto, devem ser serializáveis em JSON e de tamanho saudável. E, para ser filtrável, uma chave adicionalmente deve ser declarável como uma chave da partição de metadados : cada segmento separado por ponto deve corresponder a ^[a-z][a-z0-9_]{0,63}$, no máximo um ponto é permitido e alguns nomes são reservados (org_id, project_id, user_id, agent_id, content, embedding, embedding_model_id, created_at). Uma chave fora dessa forma — Tier, $foo, a.b.c — ainda é armazenada e retornada, simplesmente não pode ser filtrada.
Um dicionário vazio é tratado como sem metadados: metadata={} registra a vez sem um slot metadata no bloco, então leia-o com chunk.metadata.get("metadata", {}) se o chamador não tiver fornecido nenhum. A memória semântica se comporta de forma idêntica, e é o que permite que um caminho de leitura cubra ambos. O endócrino, o taxativo e o processual se comportam da mesma maneira: o que você escreve volta em chunk.metadata["metadata"], e um vazio significa nenhum slot. Opisódica é a única exceção: um filtro de compatibilidade descarta todo o dicionário, incluídas as chaves não relacionadas, se ele contiver citation_score, llm_confidence_score e combined_score juntos, já que essa forma também corresponde a um blob interno de pré-migração. A recuperação também pode carregar chunk.metadata["contextual_metadata"] — que é o artefato de extração da própria plataforma, não seus dados, e não faz parte de nenhum contrato do qual você deva depender.
A memória de curto prazo tem o escopo de uma sessão, de modo que a escrita e a leitura precisam nomear a mesma — vincule-a uma vez e passe-a:
from agent_engine_sdk_memory import ( Memory, MemoryRequestContext, MemorySource, RetrievalMode, SourceSpec, ) session_id = "session-42" memory = Memory(base_url="http://localhost:8080").bind( MemoryRequestContext(user_id="user-1", session_id=session_id) ) memory.record_turn( role="user", content="The engagement is green and the rollout continues.", metadata={"engagement_id": "eng-alpha", "tier": 2}, ) context = memory.build_context_from_sources( query="what is the engagement status?", sources=[ SourceSpec( source=MemorySource.STM, mode=RetrievalMode.HYBRID, metadata_filter={"metadata.engagement_id": "eng-alpha"}, ) ], session_id=session_id, include_memories=True, ) for chunk in context.selected_memories or []: print(chunk.metadata["metadata"]["engagement_id"])
O Atlas Search indexa a escrita de forma assíncrona, portanto, uma leitura emitida imediatamente depois pode não ver a vez ainda.
Uma diferença de ortografia a saber: você lê o nome simples dentro do dicionário aninhado, chunk.metadata["metadata"]["engagement_id"], mas filtra o caminho do índice qualificado, {"metadata.engagement_id": ...}. Ambos reconhecem o aninhamento — o filtro aborda o documento armazenado , onde seu dicionário realmente vive sob metadata.
A filtragem no nome simples é rejeitada com um erro listando os caminhos aceitos, de modo que o erro informe a resposta.
Os metadados filtráveis devem ser declarados para o projeto primeiro: engagement_id acima deve aparecer em metadata_partition_key, com short_term listado em metadata_partition_index.Consulte Declarando metadados filtráveis em Use-os.
Internals (Internos)
As seções abaixo descrevem como o pacote é criado. Eles não são necessários para usá-lo.
layout do pacote
agent_engine_sdk_memory/ ├── memory.py # Memory — the public, transport-free facade ├── protocol.py # MemoryRequestContext, MemoryRuntime + MemoryCrudClient seams ├── identity.py # resolve_identity — call arg > bind ctx > runtime ctx ├── errors.py # MemoryIdentityError + the typed transport-error family ├── models.py # Pydantic models for memory requests/responses ├── validation.py # require_positive_max_tokens — public build_context guard ├── _transport.py # _HttpTransport — shared retry / error-mapping / lifecycle ├── _http_runtime.py # _HttpMemoryRuntime — the workflow ops + per-backend profiles ├── _direct_crud.py # empty-tenancy MemoryCrudClient over the shared transport ├── _wire.py # custom-type request-body builders shared by the CRUD clients ├── _tag_syntax.py # client-side custom-type name + tag-syntax checks ├── _client.py # MemoryClient — internal HTTP client for the Memory Server API └── _denylist.py # do-not-add dependency denylist (see below)
Memory é livre de transporte: ele delega as operações de fluxo de trabalho (record_turn, build_context, as pesquisas, discover_procedures) para um MemoryRuntime injetado e o CRUD específico do tipo para um MemoryCrudClient injetado. A superfície pública é congelada por um teste de snapshot em tests/test_package_contract.py; MemoryClient permanece interno. Locação por chamada (org_id, em nível de corpo project_id) nunca aparece em uma assinatura pública; o único project_id na superfície pública é o seletor de forma de rota do construtor opcional, que entra no URL, não em um corpo de solicitação.
Lista de bloqueio de dependência
A restrição somente pydantic + httpx é imposta, não desejável. _denylist.py lista distribuições conhecidas e nomes de importação (langchain, fastapi, pymongo, ...), e tests/test_package_contract.py falha se a importação do pacote extrair qualquer um deles para sys.modules.
Quem usa
agent-engine-runner-shared declara este pacote como uma dependência de espaço de trabalho e constrói o MemoryClient interno em agent_engine_runner_shared/memory.py, passando sua pesquisa de contexto de execução como um execution_id_provider chamável para que os cabeçalhos de execução-id por solicitação funcionem sem que este pacote importe qualquer código de plataforma.