From e684b0ecc37431df23283a779d1a13b5871c6a0d Mon Sep 17 00:00:00 2001 From: "cristiano.hoshikawa" Date: Thu, 30 Jul 2026 12:03:41 -0300 Subject: [PATCH] Disclaimer best practicies Oracle for Security --- .env | 14 + .env.example | 16 +- Long_Term_Memory_Implementation_Guide_EN.md | 520 +++++++++++++++++ Manual_Long_Term_Memory_PT.md | 520 +++++++++++++++++ README.md | 3 +- README_en.md | 3 + libs/agent_framework/docs/LONG_TERM_MEMORY.md | 11 + .../docs/LONG_TERM_MEMORY_AUTONOMOUS_EN.md | 63 ++ .../docs/LONG_TERM_MEMORY_AUTONOMOUS_PT.md | 63 ++ .../src/agent_framework/config/settings.py | 10 + .../memory/long_term_extractor.py | 25 + .../memory/long_term_memory.py | 64 ++ .../memory/long_term_models.py | 26 + .../agent_framework/memory/long_term_store.py | 546 ++++++++++++++++++ .../agent_framework/runtime/agent_runtime.py | 12 + specs/Disclaimer Auth and Security_PT.md | 40 ++ specs/Disclaimer_Auth_and_Security_EN.md | 40 ++ templates/agent_template_backend/.env | 14 + .../app/__pycache__/__init__.cpython-313.pyc | Bin 0 -> 177 bytes .../app/__pycache__/main.cpython-313.pyc | Bin 0 -> 31074 bytes ...mcp_gateway_client_factory.cpython-313.pyc | Bin 0 -> 1025 bytes .../app/__pycache__/state.cpython-313.pyc | Bin 0 -> 1909 bytes .../__pycache__/billing_agent.cpython-313.pyc | Bin 0 -> 4012 bytes .../__pycache__/orders_agent.cpython-313.pyc | Bin 0 -> 3920 bytes .../__pycache__/product_agent.cpython-313.pyc | Bin 0 -> 4036 bytes .../__pycache__/prompting.cpython-313.pyc | Bin 0 -> 1174 bytes .../__pycache__/runtime.cpython-313.pyc | Bin 0 -> 376 bytes .../__pycache__/support_agent.cpython-313.pyc | Bin 0 -> 3932 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 248 bytes .../__pycache__/grl_examples.cpython-313.pyc | Bin 0 -> 1830 bytes .../__pycache__/ic_examples.cpython-313.pyc | Bin 0 -> 1786 bytes .../__pycache__/mcp_examples.cpython-313.pyc | Bin 0 -> 1928 bytes .../__pycache__/noc_examples.cpython-313.pyc | Bin 0 -> 1875 bytes .../observer_examples.cpython-313.pyc | Bin 0 -> 1384 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 191 bytes .../telemetry_observer.cpython-313.pyc | Bin 0 -> 4802 bytes templates/agent_template_backend/app/state.py | 3 + .../__pycache__/agent_graph.cpython-313.pyc | Bin 0 -> 44723 bytes .../app/workflows/agent_graph.py | 15 +- .../data/agent_framework.db | Bin 32464896 -> 32460800 bytes .../scripts/test_long_term_memory.py | 29 + .../agent_template_backend_day_zero/.env | 14 + .../app/__pycache__/__init__.cpython-313.pyc | Bin 0 -> 186 bytes .../app/__pycache__/main.cpython-313.pyc | Bin 0 -> 31083 bytes ...mcp_gateway_client_factory.cpython-313.pyc | Bin 0 -> 1034 bytes .../app/__pycache__/state.cpython-313.pyc | Bin 0 -> 1918 bytes .../__pycache__/billing_agent.cpython-313.pyc | Bin 0 -> 2541 bytes .../__pycache__/orders_agent.cpython-313.pyc | Bin 0 -> 2459 bytes .../__pycache__/product_agent.cpython-313.pyc | Bin 0 -> 2564 bytes .../__pycache__/prompting.cpython-313.pyc | Bin 0 -> 1183 bytes .../__pycache__/runtime.cpython-313.pyc | Bin 0 -> 385 bytes .../__pycache__/support_agent.cpython-313.pyc | Bin 0 -> 2461 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 200 bytes .../telemetry_observer.cpython-313.pyc | Bin 0 -> 4811 bytes .../app/state.py | 3 + .../__pycache__/agent_graph.cpython-313.pyc | Bin 0 -> 44732 bytes .../app/workflows/agent_graph.py | 15 +- .../scripts/test_long_term_memory.py | 29 + .../unit/test_long_term_memory_autonomous.py | 44 ++ 59 files changed, 2138 insertions(+), 4 deletions(-) create mode 100644 Long_Term_Memory_Implementation_Guide_EN.md create mode 100644 Manual_Long_Term_Memory_PT.md create mode 100644 libs/agent_framework/docs/LONG_TERM_MEMORY.md create mode 100644 libs/agent_framework/docs/LONG_TERM_MEMORY_AUTONOMOUS_EN.md create mode 100644 libs/agent_framework/docs/LONG_TERM_MEMORY_AUTONOMOUS_PT.md create mode 100644 libs/agent_framework/src/agent_framework/memory/long_term_extractor.py create mode 100644 libs/agent_framework/src/agent_framework/memory/long_term_memory.py create mode 100644 libs/agent_framework/src/agent_framework/memory/long_term_models.py create mode 100644 libs/agent_framework/src/agent_framework/memory/long_term_store.py create mode 100644 specs/Disclaimer Auth and Security_PT.md create mode 100644 specs/Disclaimer_Auth_and_Security_EN.md create mode 100644 templates/agent_template_backend/app/__pycache__/__init__.cpython-313.pyc create mode 100644 templates/agent_template_backend/app/__pycache__/main.cpython-313.pyc create mode 100644 templates/agent_template_backend/app/__pycache__/mcp_gateway_client_factory.cpython-313.pyc create mode 100644 templates/agent_template_backend/app/__pycache__/state.cpython-313.pyc create mode 100644 templates/agent_template_backend/app/agents/__pycache__/billing_agent.cpython-313.pyc create mode 100644 templates/agent_template_backend/app/agents/__pycache__/orders_agent.cpython-313.pyc create mode 100644 templates/agent_template_backend/app/agents/__pycache__/product_agent.cpython-313.pyc create mode 100644 templates/agent_template_backend/app/agents/__pycache__/prompting.cpython-313.pyc create mode 100644 templates/agent_template_backend/app/agents/__pycache__/runtime.cpython-313.pyc create mode 100644 templates/agent_template_backend/app/agents/__pycache__/support_agent.cpython-313.pyc create mode 100644 templates/agent_template_backend/app/examples/__pycache__/__init__.cpython-313.pyc create mode 100644 templates/agent_template_backend/app/examples/__pycache__/grl_examples.cpython-313.pyc create mode 100644 templates/agent_template_backend/app/examples/__pycache__/ic_examples.cpython-313.pyc create mode 100644 templates/agent_template_backend/app/examples/__pycache__/mcp_examples.cpython-313.pyc create mode 100644 templates/agent_template_backend/app/examples/__pycache__/noc_examples.cpython-313.pyc create mode 100644 templates/agent_template_backend/app/examples/__pycache__/observer_examples.cpython-313.pyc create mode 100644 templates/agent_template_backend/app/observability/__pycache__/__init__.cpython-313.pyc create mode 100644 templates/agent_template_backend/app/observability/__pycache__/telemetry_observer.cpython-313.pyc create mode 100644 templates/agent_template_backend/app/workflows/__pycache__/agent_graph.cpython-313.pyc create mode 100644 templates/agent_template_backend/scripts/test_long_term_memory.py create mode 100644 templates/agent_template_backend_day_zero/app/__pycache__/__init__.cpython-313.pyc create mode 100644 templates/agent_template_backend_day_zero/app/__pycache__/main.cpython-313.pyc create mode 100644 templates/agent_template_backend_day_zero/app/__pycache__/mcp_gateway_client_factory.cpython-313.pyc create mode 100644 templates/agent_template_backend_day_zero/app/__pycache__/state.cpython-313.pyc create mode 100644 templates/agent_template_backend_day_zero/app/agents/__pycache__/billing_agent.cpython-313.pyc create mode 100644 templates/agent_template_backend_day_zero/app/agents/__pycache__/orders_agent.cpython-313.pyc create mode 100644 templates/agent_template_backend_day_zero/app/agents/__pycache__/product_agent.cpython-313.pyc create mode 100644 templates/agent_template_backend_day_zero/app/agents/__pycache__/prompting.cpython-313.pyc create mode 100644 templates/agent_template_backend_day_zero/app/agents/__pycache__/runtime.cpython-313.pyc create mode 100644 templates/agent_template_backend_day_zero/app/agents/__pycache__/support_agent.cpython-313.pyc create mode 100644 templates/agent_template_backend_day_zero/app/observability/__pycache__/__init__.cpython-313.pyc create mode 100644 templates/agent_template_backend_day_zero/app/observability/__pycache__/telemetry_observer.cpython-313.pyc create mode 100644 templates/agent_template_backend_day_zero/app/workflows/__pycache__/agent_graph.cpython-313.pyc create mode 100644 templates/agent_template_backend_day_zero/scripts/test_long_term_memory.py create mode 100644 tests/unit/test_long_term_memory_autonomous.py diff --git a/.env b/.env index c2ab86e..674c89e 100644 --- a/.env +++ b/.env @@ -162,3 +162,17 @@ MEMORY_MAX_SUMMARY_CHARS=6000 MEMORY_SUMMARY_USE_LLM=true MEMORY_INJECT_RECENT_MESSAGES=true MEMORY_INJECT_SUMMARY=true + +############################################################################### +# LONG-TERM MEMORY +############################################################################### +ENABLE_LONG_TERM_MEMORY=true +LONG_TERM_MEMORY_PROVIDER=sqlite +LONG_TERM_MEMORY_SQLITE_PATH=./data/agent_framework.db +LONG_TERM_MEMORY_TABLE=agentfw_long_term_memory +# For Autonomous/Oracle, defaults to ${ADB_TABLE_PREFIX}_LONG_TERM_MEMORY +# LONG_TERM_MEMORY_ORACLE_TABLE=AGENTFW_LONG_TERM_MEMORY +LONG_TERM_MEMORY_MAX_CONTEXT_ITEMS=20 +LONG_TERM_MEMORY_MIN_CONFIDENCE=0.70 +LONG_TERM_MEMORY_AUTO_EXTRACT=true +LONG_TERM_MEMORY_INJECT_CONTEXT=true diff --git a/.env.example b/.env.example index 874b1c2..98ad2e4 100644 --- a/.env.example +++ b/.env.example @@ -180,4 +180,18 @@ MCP_GATEWAY_URL=http://localhost:8300 MCP_GATEWAY_TIMEOUT_SECONDS=60 # MCP_GATEWAY_TOKEN= MCP_GATEWAY_AGENT_ID=telecom_contas -MCP_GATEWAY_TENANT_ID=default \ No newline at end of file +MCP_GATEWAY_TENANT_ID=default + +############################################################################### +# LONG-TERM MEMORY +############################################################################### +ENABLE_LONG_TERM_MEMORY=true +LONG_TERM_MEMORY_PROVIDER=sqlite +LONG_TERM_MEMORY_SQLITE_PATH=./data/agent_framework.db +LONG_TERM_MEMORY_TABLE=agentfw_long_term_memory +# For Autonomous/Oracle, defaults to ${ADB_TABLE_PREFIX}_LONG_TERM_MEMORY +# LONG_TERM_MEMORY_ORACLE_TABLE=AGENTFW_LONG_TERM_MEMORY +LONG_TERM_MEMORY_MAX_CONTEXT_ITEMS=20 +LONG_TERM_MEMORY_MIN_CONFIDENCE=0.70 +LONG_TERM_MEMORY_AUTO_EXTRACT=true +LONG_TERM_MEMORY_INJECT_CONTEXT=true \ No newline at end of file diff --git a/Long_Term_Memory_Implementation_Guide_EN.md b/Long_Term_Memory_Implementation_Guide_EN.md new file mode 100644 index 0000000..6cb2e5a --- /dev/null +++ b/Long_Term_Memory_Implementation_Guide_EN.md @@ -0,0 +1,520 @@ +### Long-Term Memory Implementation Guide + +### Concept + +Long-Term Memory (LTM) is the `agent_framework` capability that stores and retrieves durable facts beyond the lifetime of a conversation session. + +Unlike message history, which is normally associated with a `session_id`, Long-Term Memory is associated with the business identity of the user or customer. In the current implementation, this identity consists of: + +```text +tenant_id +agent_id +customer_key +``` + +This allows an agent to retrieve preferences, identity information, projects and constraints even when a new session is created. + +### Purpose + +Long-Term Memory is used to: + +- maintain continuity across sessions; +- personalize responses; +- prevent users from repeating previously supplied information; +- reduce the need to send the full conversation history to the model; +- store preferences, current projects, preferred names and constraints; +- isolate memory across tenants, agents and customers. + +Example: + +```text +Session A: +"Call me Cris. My preferred language is Python." + +Session B, with another session_id and the same customer_key: +"What do you remember about me?" + +Expected response: +"Your preferred name is Cris and your preferred language is Python." +``` + +### Memory type differences + +#### Conversation Memory + +Stores messages from the current conversation and is normally associated with the `session_id`. + +#### Summary Memory + +Stores a summary of the conversation to reduce the context size sent to the model. + +#### Long-Term Memory + +Stores durable facts across sessions and is associated with the business identity, primarily the `customer_key`. + +### Components + +#### LongTermMemoryManager + +Coordinates: + +- memory loading; +- identity-based retrieval; +- context rendering; +- durable fact extraction; +- fact persistence; +- deduplication and updates. + +#### LongTermMemoryStore + +Persistence interface used by the manager. + +#### SQLiteLongTermMemoryStore + +Reference implementation based on SQLite. + +It is suitable for: + +- local development; +- testing; +- demonstrations; +- low-scale environments. + +#### InMemoryLongTermMemoryStore + +In-memory implementation used for quick tests. + +Its content is lost when the backend process stops. + +#### LongTermMemoryExtractor + +Identifies durable facts in messages. + +Examples: + +```text +preferred_name = Cris +preferred_language = Python +current_project = Atlas +``` + +#### LongTermMemoryItem + +Data model representing a persisted item, including identity, key, value, category, confidence and metadata. + +#### AgentRuntime + +Loads memory before agent execution and injects the rendered context into the prompt. + +#### persist_long_term_memory node + +LangGraph node responsible for persisting facts after the final response has been generated and validated. + +### File structure + +```text +libs/ +└── agent_framework/ + └── src/ + └── agent_framework/ + └── memory/ + ├── __init__.py + ├── long_term_extractor.py + ├── long_term_memory.py + ├── long_term_models.py + └── long_term_store.py +``` + +### Execution flow + +```text +User message + │ + ▼ +AgentRuntime.prepare_memory_context() + │ + ├── Conversation Memory + ├── Summary Memory + └── Long-Term Memory + │ + ▼ + long_term_memory_context + │ + ▼ + Agent prompt + │ + ▼ + Agent + │ + ▼ + Guardrails / Judges / Supervisor + │ + ▼ + persist_long_term_memory + │ + ▼ + LongTermMemoryExtractor + │ + ▼ + LongTermMemoryStore +``` + +### Framework configuration + +### New modules + +Copy: + +```text +libs/agent_framework/src/agent_framework/memory/long_term_extractor.py +libs/agent_framework/src/agent_framework/memory/long_term_memory.py +libs/agent_framework/src/agent_framework/memory/long_term_models.py +libs/agent_framework/src/agent_framework/memory/long_term_store.py +``` + +### Update memory/__init__.py + +Export the Long-Term Memory components: + +```python +from agent_framework.memory.long_term_memory import ( + LongTermMemoryManager, + create_long_term_memory_manager, +) +from agent_framework.memory.long_term_models import LongTermMemoryItem +from agent_framework.memory.long_term_store import ( + InMemoryLongTermMemoryStore, + LongTermMemoryStore, + SQLiteLongTermMemoryStore, + create_long_term_memory_store, +) +``` + +### Update settings.py + +Add: + +```python +ENABLE_LONG_TERM_MEMORY: bool = False +LONG_TERM_MEMORY_PROVIDER: str = "sqlite" +LONG_TERM_MEMORY_SQLITE_PATH: str = "./data/agent_framework.db" +LONG_TERM_MEMORY_TABLE: str = "agentfw_long_term_memory" +LONG_TERM_MEMORY_MAX_CONTEXT_ITEMS: int = 20 +LONG_TERM_MEMORY_MIN_CONFIDENCE: float = 0.70 +LONG_TERM_MEMORY_AUTO_EXTRACT: bool = True +LONG_TERM_MEMORY_INJECT_CONTEXT: bool = True +``` + +### AgentRuntime integration + +The runtime must: + +1. verify that the feature is enabled; +2. create the manager when needed; +3. retrieve facts using the identity; +4. populate the workflow state; +5. inject the rendered context into the prompt. + +State fields: + +```python +long_term_memories: list[dict] +long_term_memory_context: str +long_term_memory_write_result: dict +``` + +### AgentWorkflow initialization + +Create the manager in `AgentWorkflow`: + +```python +self.long_term_memory_manager = create_long_term_memory_manager( + settings, + telemetry=telemetry, +) +``` + +### Correct agent initialization + +Do not pass `long_term_memory_manager` through `agent_kwargs` when the constructors of `BillingAgent`, `ProductAgent`, `OrdersAgent` and `SupportAgent` do not declare that parameter. + +This initialization causes an error: + +```python +agent_kwargs = { + "telemetry": telemetry, + "settings": settings, + "memory": memory, + "summary_memory": summary_memory, + "long_term_memory_manager": self.long_term_memory_manager, +} + +self.billing = BillingAgent(llm, **agent_kwargs) +``` + +Resulting error: + +```text +TypeError: BillingAgent.__init__() got an unexpected keyword argument +'long_term_memory_manager' +``` + +The recommended approach is to create agents using their existing signatures and inject the manager as an attribute after initialization: + +```python +agent_kwargs = { + "telemetry": telemetry, + "tool_router": getattr(self, "tool_router", None), + "rag_service": self.rag_service, + "cache": self.cache, + "settings": settings, + "observer": self.observer, + "memory": memory, + "summary_memory": summary_memory, +} + +self.billing = BillingAgent(llm, **agent_kwargs) +self.product = ProductAgent(llm, **agent_kwargs) +self.orders = OrdersAgent(llm, **agent_kwargs) +self.support = SupportAgent(llm, **agent_kwargs) + +for agent in ( + self.billing, + self.product, + self.orders, + self.support, +): + agent.long_term_memory_manager = self.long_term_memory_manager +``` + +This approach avoids changing every agent constructor and keeps the feature encapsulated in the framework. + +### LangGraph configuration + +Register the node: + +```python +builder.add_node( + "persist_long_term_memory", + self._node( + "persist_long_term_memory", + self.persist_long_term_memory, + ), +) +``` + +Update the edges: + +```python +builder.add_edge( + "supervisor_review", + "persist_long_term_memory", +) +builder.add_edge( + "persist_long_term_memory", + "persist", +) +``` + +Implement: + +```python +async def persist_long_term_memory( + self, + state: AgentState, +) -> dict[str, object]: + result = await self.long_term_memory_manager.persist_turn(state) + + return { + "long_term_memory_write_result": result, + } +``` + +Final flow: + +```text +supervisor_review + │ + ▼ +persist_long_term_memory + │ + ▼ +persist +``` + +### Environment variables + +```env +ENABLE_LONG_TERM_MEMORY=true + +LONG_TERM_MEMORY_PROVIDER=sqlite +LONG_TERM_MEMORY_SQLITE_PATH=./data/agent_framework.db +LONG_TERM_MEMORY_TABLE=agentfw_long_term_memory + +LONG_TERM_MEMORY_MAX_CONTEXT_ITEMS=20 +LONG_TERM_MEMORY_MIN_CONFIDENCE=0.70 +LONG_TERM_MEMORY_AUTO_EXTRACT=true +LONG_TERM_MEMORY_INJECT_CONTEXT=true +``` + +### SQLite database path + +A relative path is resolved from the directory in which the backend is started. + +To prevent different databases from being created accidentally, prefer an absolute path in development environments: + +```env +LONG_TERM_MEMORY_SQLITE_PATH=/mnt/c/Asus_Projects/agent_platform_oci_long_term_memory/data/agent_framework.db +``` + +Create the directory before starting: + +```bash +mkdir -p data +``` + +### Testing + +### Test 1 — Persistence + +Send: + +```json +{ + "session_id": "default:telecom_contas:memory-session-a", + "customer_key": "11999999999", + "message": "Call me Cris. My preferred language is Python and my current project is Atlas." +} +``` + +### Test 2 — Retrieval in another session + +Use another `session_id` while keeping the same `customer_key`: + +```json +{ + "session_id": "default:telecom_contas:memory-session-b", + "customer_key": "11999999999", + "message": "What do you remember about me, my preferences and my project?" +} +``` + +Expected result: + +```text +Your preferred name is Cris. +Your preferred language is Python. +Your current project is Atlas. +``` + +### Test 3 — Isolation + +Use another customer: + +```json +{ + "session_id": "default:telecom_contas:memory-session-c", + "customer_key": "another-customer", + "message": "What is my preferred name and current project?" +} +``` + +The data associated with `11999999999` must not be returned. + +### Test 4 — Frontend reset + +Restart or reset the frontend and verify that it still sends the same `customer_key`. + +Memory must survive a `session_id` change. Resetting the frontend does not delete the SQLite database. + +### Test 5 — Backend restart + +Restart Uvicorn and repeat the query. + +With: + +```env +LONG_TERM_MEMORY_PROVIDER=sqlite +``` + +memory must remain available. + +With: + +```env +LONG_TERM_MEMORY_PROVIDER=memory +``` + +memory is lost when the process stops. + +### Direct SQLite verification + +Find the database: + +```bash +find . -name "agent_framework.db" -type f +``` + +Open it: + +```bash +sqlite3 ./data/agent_framework.db +``` + +Query: + +```sql +SELECT + tenant_id, + agent_id, + customer_key, + memory_type, + memory_key, + memory_value, + confidence, + created_at, + updated_at +FROM agentfw_long_term_memory +ORDER BY updated_at DESC; +``` + +### Success criteria + +The implementation is working when: + +- memory is retrieved with another `session_id`; +- the same `customer_key` retrieves previous facts; +- another `customer_key` cannot access those facts; +- restarting the frontend does not erase memory; +- restarting the backend does not erase memory when using SQLite; +- the `persist_long_term_memory` node runs; +- the prompt receives `long_term_memory_context`. + +### Best practices + +- Persist only durable facts. +- Do not store the complete conversation as Long-Term Memory. +- Isolate data by `tenant_id`, `agent_id` and `customer_key`. +- Do not use `session_id` as the permanent user identity. +- Persist only after final validations. +- Avoid persisting temporary tool results. +- Record telemetry for reads, writes, updates and failures. +- Define retention and deletion policies. +- Use an absolute SQLite path in environments with multiple working directories. +- Move to an enterprise database for production and high-availability environments. + +### Reference implementation limitations + +The current implementation uses rule-based extraction and SQLite as the reference provider. + +Recommended future enhancements: + +- LLM-based fact extraction; +- vector-based semantic memory; +- episodic memory; +- expiration and versioning; +- semantic deduplication; +- consent policies; +- query and deletion APIs; +- Oracle Autonomous Database provider; +- encryption and sensitive-data classification. diff --git a/Manual_Long_Term_Memory_PT.md b/Manual_Long_Term_Memory_PT.md new file mode 100644 index 0000000..462a401 --- /dev/null +++ b/Manual_Long_Term_Memory_PT.md @@ -0,0 +1,520 @@ +### Manual de Implementação — Long-Term Memory + +### Conceito + +A Long-Term Memory (LTM) é a capacidade do `agent_framework` de armazenar e recuperar fatos duradouros além da duração de uma sessão de conversa. + +Diferentemente do histórico de mensagens, que normalmente está associado a um `session_id`, a memória de longo prazo é associada à identidade de negócio do usuário ou cliente. Na implementação atual, essa identidade é composta por: + +```text +tenant_id +agent_id +customer_key +``` + +Isso permite que um agente recupere preferências, informações de identidade, projetos e restrições mesmo quando uma nova sessão é criada. + +### Para que serve + +A Long-Term Memory serve para: + +- manter continuidade entre sessões; +- personalizar respostas; +- evitar que o usuário repita informações já fornecidas; +- reduzir a necessidade de enviar todo o histórico ao modelo; +- armazenar preferências, projetos atuais, nomes preferidos e restrições; +- isolar a memória entre tenants, agentes e clientes. + +Exemplo: + +```text +Sessão A: +"Me chame de Cris. Minha linguagem preferida é Python." + +Sessão B, com outro session_id e o mesmo customer_key: +"O que você lembra sobre mim?" + +Resposta esperada: +"Seu nome preferido é Cris e sua linguagem preferida é Python." +``` + +### Diferença entre os tipos de memória + +#### Conversation Memory + +Mantém as mensagens da conversa atual e normalmente está associada ao `session_id`. + +#### Summary Memory + +Mantém um resumo da conversa para reduzir o tamanho do contexto enviado ao modelo. + +#### Long-Term Memory + +Mantém fatos duradouros entre sessões e é associada à identidade de negócio, principalmente ao `customer_key`. + +### Componentes da funcionalidade + +#### LongTermMemoryManager + +Responsável por coordenar: + +- carregamento das memórias; +- recuperação por identidade; +- renderização do contexto; +- extração de novos fatos; +- persistência dos fatos; +- deduplicação e atualização. + +#### LongTermMemoryStore + +Interface de persistência utilizada pelo manager. + +#### SQLiteLongTermMemoryStore + +Implementação de referência baseada em SQLite. + +É apropriada para: + +- desenvolvimento local; +- testes; +- demonstrações; +- ambientes de baixa escala. + +#### InMemoryLongTermMemoryStore + +Implementação em memória utilizada para testes rápidos. + +O conteúdo é perdido quando o processo do backend é encerrado. + +#### LongTermMemoryExtractor + +Responsável por identificar fatos duradouros nas mensagens. + +Exemplos de fatos: + +```text +preferred_name = Cris +preferred_language = Python +current_project = Atlas +``` + +#### LongTermMemoryItem + +Modelo que representa um item persistido, incluindo identidade, chave, valor, categoria, confiança e metadados. + +#### AgentRuntime + +Carrega a memória antes da execução do agente e injeta o contexto no prompt. + +#### Nó persist_long_term_memory + +Nó do LangGraph responsável por persistir os fatos após a geração e validação da resposta final. + +### Estrutura dos arquivos + +```text +libs/ +└── agent_framework/ + └── src/ + └── agent_framework/ + └── memory/ + ├── __init__.py + ├── long_term_extractor.py + ├── long_term_memory.py + ├── long_term_models.py + └── long_term_store.py +``` + +### Fluxo de execução + +```text +Mensagem do usuário + │ + ▼ +AgentRuntime.prepare_memory_context() + │ + ├── Conversation Memory + ├── Summary Memory + └── Long-Term Memory + │ + ▼ + long_term_memory_context + │ + ▼ + Prompt do agente + │ + ▼ + Agente + │ + ▼ + Guardrails / Judges / Supervisor + │ + ▼ + persist_long_term_memory + │ + ▼ + LongTermMemoryExtractor + │ + ▼ + LongTermMemoryStore +``` + +### Configuração do framework + +### Novos módulos + +Copie os arquivos: + +```text +libs/agent_framework/src/agent_framework/memory/long_term_extractor.py +libs/agent_framework/src/agent_framework/memory/long_term_memory.py +libs/agent_framework/src/agent_framework/memory/long_term_models.py +libs/agent_framework/src/agent_framework/memory/long_term_store.py +``` + +### Atualização de memory/__init__.py + +Exporte os componentes da Long-Term Memory: + +```python +from agent_framework.memory.long_term_memory import ( + LongTermMemoryManager, + create_long_term_memory_manager, +) +from agent_framework.memory.long_term_models import LongTermMemoryItem +from agent_framework.memory.long_term_store import ( + InMemoryLongTermMemoryStore, + LongTermMemoryStore, + SQLiteLongTermMemoryStore, + create_long_term_memory_store, +) +``` + +### Atualização de settings.py + +Adicione as configurações: + +```python +ENABLE_LONG_TERM_MEMORY: bool = False +LONG_TERM_MEMORY_PROVIDER: str = "sqlite" +LONG_TERM_MEMORY_SQLITE_PATH: str = "./data/agent_framework.db" +LONG_TERM_MEMORY_TABLE: str = "agentfw_long_term_memory" +LONG_TERM_MEMORY_MAX_CONTEXT_ITEMS: int = 20 +LONG_TERM_MEMORY_MIN_CONFIDENCE: float = 0.70 +LONG_TERM_MEMORY_AUTO_EXTRACT: bool = True +LONG_TERM_MEMORY_INJECT_CONTEXT: bool = True +``` + +### Integração com AgentRuntime + +O runtime deve: + +1. verificar se a funcionalidade está habilitada; +2. criar o manager quando necessário; +3. recuperar os fatos pela identidade; +4. preencher o estado; +5. injetar o contexto no prompt. + +Campos adicionados ao estado: + +```python +long_term_memories: list[dict] +long_term_memory_context: str +long_term_memory_write_result: dict +``` + +### Inicialização no AgentWorkflow + +O manager deve ser criado no `AgentWorkflow`: + +```python +self.long_term_memory_manager = create_long_term_memory_manager( + settings, + telemetry=telemetry, +) +``` + +### Inicialização correta dos agentes + +O `long_term_memory_manager` não deve ser passado pelo `agent_kwargs` caso os construtores de `BillingAgent`, `ProductAgent`, `OrdersAgent` e `SupportAgent` não declarem esse parâmetro. + +Esta inicialização causa erro: + +```python +agent_kwargs = { + "telemetry": telemetry, + "settings": settings, + "memory": memory, + "summary_memory": summary_memory, + "long_term_memory_manager": self.long_term_memory_manager, +} + +self.billing = BillingAgent(llm, **agent_kwargs) +``` + +Erro resultante: + +```text +TypeError: BillingAgent.__init__() got an unexpected keyword argument +'long_term_memory_manager' +``` + +A forma recomendada é criar os agentes com a assinatura já existente e injetar o manager como atributo após a inicialização: + +```python +agent_kwargs = { + "telemetry": telemetry, + "tool_router": getattr(self, "tool_router", None), + "rag_service": self.rag_service, + "cache": self.cache, + "settings": settings, + "observer": self.observer, + "memory": memory, + "summary_memory": summary_memory, +} + +self.billing = BillingAgent(llm, **agent_kwargs) +self.product = ProductAgent(llm, **agent_kwargs) +self.orders = OrdersAgent(llm, **agent_kwargs) +self.support = SupportAgent(llm, **agent_kwargs) + +for agent in ( + self.billing, + self.product, + self.orders, + self.support, +): + agent.long_term_memory_manager = self.long_term_memory_manager +``` + +Essa abordagem evita alterar os construtores de todos os agentes e mantém a funcionalidade encapsulada no framework. + +### Configuração do LangGraph + +Registre o nó: + +```python +builder.add_node( + "persist_long_term_memory", + self._node( + "persist_long_term_memory", + self.persist_long_term_memory, + ), +) +``` + +Altere o fluxo: + +```python +builder.add_edge( + "supervisor_review", + "persist_long_term_memory", +) +builder.add_edge( + "persist_long_term_memory", + "persist", +) +``` + +Implemente o método: + +```python +async def persist_long_term_memory( + self, + state: AgentState, +) -> dict[str, object]: + result = await self.long_term_memory_manager.persist_turn(state) + + return { + "long_term_memory_write_result": result, + } +``` + +Fluxo final: + +```text +supervisor_review + │ + ▼ +persist_long_term_memory + │ + ▼ +persist +``` + +### Variáveis de ambiente + +```env +ENABLE_LONG_TERM_MEMORY=true + +LONG_TERM_MEMORY_PROVIDER=sqlite +LONG_TERM_MEMORY_SQLITE_PATH=./data/agent_framework.db +LONG_TERM_MEMORY_TABLE=agentfw_long_term_memory + +LONG_TERM_MEMORY_MAX_CONTEXT_ITEMS=20 +LONG_TERM_MEMORY_MIN_CONFIDENCE=0.70 +LONG_TERM_MEMORY_AUTO_EXTRACT=true +LONG_TERM_MEMORY_INJECT_CONTEXT=true +``` + +### Caminho do banco SQLite + +O caminho relativo é resolvido a partir do diretório em que o backend é iniciado. + +Para evitar que bancos diferentes sejam criados acidentalmente, prefira um caminho absoluto em ambientes de desenvolvimento: + +```env +LONG_TERM_MEMORY_SQLITE_PATH=/mnt/c/Asus_Projects/agent_platform_oci_long_term_memory/data/agent_framework.db +``` + +Crie a pasta antes de iniciar: + +```bash +mkdir -p data +``` + +### Como testar + +### Teste 1 — Gravação + +Envie: + +```json +{ + "session_id": "default:telecom_contas:memory-session-a", + "customer_key": "11999999999", + "message": "Me chame de Cris. Minha linguagem preferida é Python e meu projeto atual se chama Atlas." +} +``` + +### Teste 2 — Recuperação em outra sessão + +Utilize outro `session_id`, mantendo o mesmo `customer_key`: + +```json +{ + "session_id": "default:telecom_contas:memory-session-b", + "customer_key": "11999999999", + "message": "O que você lembra sobre mim, minhas preferências e meu projeto?" +} +``` + +Resultado esperado: + +```text +Seu nome preferido é Cris. +Sua linguagem preferida é Python. +Seu projeto atual se chama Atlas. +``` + +### Teste 3 — Isolamento + +Utilize outro cliente: + +```json +{ + "session_id": "default:telecom_contas:memory-session-c", + "customer_key": "outro-cliente", + "message": "Qual é meu nome preferido e qual é meu projeto atual?" +} +``` + +Os dados de `11999999999` não devem aparecer. + +### Teste 4 — Reinicialização do frontend + +Reinicie ou resete o frontend e confirme que ele continua enviando o mesmo `customer_key`. + +A memória deve sobreviver à troca do `session_id`. O reset do frontend não apaga o SQLite. + +### Teste 5 — Reinicialização do backend + +Reinicie o Uvicorn e repita a consulta. + +Com: + +```env +LONG_TERM_MEMORY_PROVIDER=sqlite +``` + +a memória deve continuar disponível. + +Com: + +```env +LONG_TERM_MEMORY_PROVIDER=memory +``` + +a memória será perdida quando o processo for encerrado. + +### Verificação direta no SQLite + +Localize o banco: + +```bash +find . -name "agent_framework.db" -type f +``` + +Abra: + +```bash +sqlite3 ./data/agent_framework.db +``` + +Consulte: + +```sql +SELECT + tenant_id, + agent_id, + customer_key, + memory_type, + memory_key, + memory_value, + confidence, + created_at, + updated_at +FROM agentfw_long_term_memory +ORDER BY updated_at DESC; +``` + +### Critérios de sucesso + +A implementação está funcionando quando: + +- a memória é recuperada com outro `session_id`; +- o mesmo `customer_key` recupera os fatos anteriores; +- outro `customer_key` não acessa esses fatos; +- reiniciar o frontend não apaga a memória; +- reiniciar o backend não apaga a memória quando o provider é SQLite; +- o nó `persist_long_term_memory` é executado; +- o prompt recebe `long_term_memory_context`. + +### Boas práticas + +- Persistir somente fatos duradouros. +- Não armazenar a conversa completa como Long-Term Memory. +- Isolar dados por `tenant_id`, `agent_id` e `customer_key`. +- Não utilizar `session_id` como identidade permanente do usuário. +- Persistir somente depois das validações finais. +- Evitar armazenar resultados temporários de ferramentas. +- Registrar telemetria de leitura, escrita, atualização e falha. +- Definir políticas de retenção e exclusão. +- Usar caminho absoluto para SQLite em ambientes com múltiplos diretórios de execução. +- Migrar para um banco corporativo em ambientes de produção e alta disponibilidade. + +### Limitações da implementação de referência + +A implementação atual utiliza extração baseada em regras e SQLite como provider de referência. + +Evoluções recomendadas: + +- extração de fatos com LLM; +- memória semântica com vetores; +- memória episódica; +- expiração e versionamento; +- deduplicação semântica; +- política de consentimento; +- API de consulta e exclusão; +- provider Oracle Autonomous Database; +- criptografia e classificação de dados sensíveis. diff --git a/README.md b/README.md index 0a1d8f1..9a04aae 100644 --- a/README.md +++ b/README.md @@ -105,8 +105,9 @@ Detalha o framework de avaliação e certificação, incluindo arquitetura de av Define o modelo de readiness operacional e SRE da plataforma, incluindo componentes operados, health checks, readiness, SLOs, métricas, dashboards, alertas, runbooks, gestão de incidentes, capacidade e checklist de produção. +### [IMPORTANTE: Disclaimer Auth and Security](specs/Disclaimer%20Auth%20and%20Security_PT.md) - +Recomendações de melhores práticas de segurança da Oracle Cloud Infrastructure. --- ## 1. Visão geral da arquitetura diff --git a/README_en.md b/README_en.md index c25cc4f..fadf8e4 100644 --- a/README_en.md +++ b/README_en.md @@ -104,6 +104,9 @@ Details the evaluation and certification framework, including evaluation archite Defines the platform operational readiness and SRE model, including managed components, health checks, readiness, SLOs, metrics, dashboards, alerts, runbooks, incident management, capacity planning, and production checklists. +### [IMPORTANT: Disclaimer Auth and Security](specs/Disclaimer%20Auth%20and%20Security_EN.md) + +Oracle Cloud Infrastructure Security Best Practice Recommendations. --- diff --git a/libs/agent_framework/docs/LONG_TERM_MEMORY.md b/libs/agent_framework/docs/LONG_TERM_MEMORY.md new file mode 100644 index 0000000..6d5e4af --- /dev/null +++ b/libs/agent_framework/docs/LONG_TERM_MEMORY.md @@ -0,0 +1,11 @@ +# Long-Term Memory + +Capacidade nativa do `agent_framework`, isolada por `tenant_id + agent_id + customer_key`. + +O runtime carrega e injeta as memórias automaticamente. Os dois templates persistem os fatos após `supervisor_review`. Os agentes individuais não precisam de alteração. + +## Teste + +```bash +PYTHONPATH=libs/agent_framework/src python templates/agent_template_backend/scripts/test_long_term_memory.py +``` diff --git a/libs/agent_framework/docs/LONG_TERM_MEMORY_AUTONOMOUS_EN.md b/libs/agent_framework/docs/LONG_TERM_MEMORY_AUTONOMOUS_EN.md new file mode 100644 index 0000000..0c461cd --- /dev/null +++ b/libs/agent_framework/docs/LONG_TERM_MEMORY_AUTONOMOUS_EN.md @@ -0,0 +1,63 @@ +### Long-Term Memory on Oracle Autonomous Database + +### Activation + +```env +ENABLE_LONG_TERM_MEMORY=true +LONG_TERM_MEMORY_PROVIDER=autonomous + +ADB_USER=ADMIN +ADB_PASSWORD= +ADB_DSN= +ADB_WALLET_LOCATION=/path/to/wallet +ADB_WALLET_PASSWORD= +ADB_TABLE_PREFIX=AGENTFW + +# Optional. Default: ${ADB_TABLE_PREFIX}_LONG_TERM_MEMORY. +LONG_TERM_MEMORY_ORACLE_TABLE=AGENTFW_LONG_TERM_MEMORY +``` + +`LONG_TERM_MEMORY_PROVIDER=oracle` is also accepted. + +### Dependency + +```bash +pip install oracledb +``` + +### Schema initialization + +On the first operation, the provider automatically creates the table and index. The user configured in `ADB_USER` needs permission to create tables and indexes. If a DBA provisions the schema beforehand, initialization accepts the existing objects. + +### Identity and isolation + +The logical key consists of: + +```text +tenant_id + agent_id + subject_key + category + memory_key +``` + +In the current integration, `subject_key` is derived from `customer_key`. + +### Test + +1. Start the backend with the `autonomous` provider. +2. Store facts in session A. +3. Open session B with the same `customer_key`. +4. Verify retrieval. +5. Restart the backend and repeat the query. +6. Query `AGENTFW_LONG_TERM_MEMORY` in Autonomous Database. + +```sql +SELECT TENANT_ID, AGENT_ID, SUBJECT_KEY, CATEGORY, MEMORY_KEY, + MEMORY_VALUE, CONFIDENCE, UPDATED_AT +FROM AGENTFW_LONG_TERM_MEMORY +ORDER BY UPDATED_AT DESC; +``` + +### Notes + +- The provider uses `python-oracledb` in thin mode. +- Synchronous operations run through `asyncio.to_thread`. +- A wallet is optional when walletless TLS is configured. +- SQLite and InMemory remain available for development and testing. diff --git a/libs/agent_framework/docs/LONG_TERM_MEMORY_AUTONOMOUS_PT.md b/libs/agent_framework/docs/LONG_TERM_MEMORY_AUTONOMOUS_PT.md new file mode 100644 index 0000000..eee61fb --- /dev/null +++ b/libs/agent_framework/docs/LONG_TERM_MEMORY_AUTONOMOUS_PT.md @@ -0,0 +1,63 @@ +### Long-Term Memory no Oracle Autonomous Database + +### Ativação + +```env +ENABLE_LONG_TERM_MEMORY=true +LONG_TERM_MEMORY_PROVIDER=autonomous + +ADB_USER=ADMIN +ADB_PASSWORD= +ADB_DSN= +ADB_WALLET_LOCATION=/caminho/para/wallet +ADB_WALLET_PASSWORD= +ADB_TABLE_PREFIX=AGENTFW + +# Opcional. O padrão é ${ADB_TABLE_PREFIX}_LONG_TERM_MEMORY. +LONG_TERM_MEMORY_ORACLE_TABLE=AGENTFW_LONG_TERM_MEMORY +``` + +Também é aceito `LONG_TERM_MEMORY_PROVIDER=oracle`. + +### Dependência + +```bash +pip install oracledb +``` + +### Inicialização do schema + +Na primeira operação, o provider cria automaticamente a tabela e o índice. O usuário configurado em `ADB_USER` precisa de permissão para criar tabela e índice. Se o schema for provisionado previamente por DBA, a inicialização reconhece os objetos existentes. + +### Identidade e isolamento + +A chave lógica é composta por: + +```text +tenant_id + agent_id + subject_key + category + memory_key +``` + +Na integração atual, `subject_key` é derivado do `customer_key`. + +### Teste + +1. Inicie o backend com provider `autonomous`. +2. Grave fatos na sessão A. +3. Abra a sessão B com o mesmo `customer_key`. +4. Confirme a recuperação. +5. Reinicie o backend e repita a consulta. +6. Consulte a tabela `AGENTFW_LONG_TERM_MEMORY` no Autonomous Database. + +```sql +SELECT TENANT_ID, AGENT_ID, SUBJECT_KEY, CATEGORY, MEMORY_KEY, + MEMORY_VALUE, CONFIDENCE, UPDATED_AT +FROM AGENTFW_LONG_TERM_MEMORY +ORDER BY UPDATED_AT DESC; +``` + +### Observações + +- O provider usa `python-oracledb` em thin mode. +- As operações síncronas são executadas com `asyncio.to_thread`. +- Wallet é opcional quando a conexão TLS sem wallet estiver configurada. +- SQLite e InMemory continuam disponíveis para desenvolvimento e testes. diff --git a/libs/agent_framework/src/agent_framework/config/settings.py b/libs/agent_framework/src/agent_framework/config/settings.py index 455b976..4c8b785 100644 --- a/libs/agent_framework/src/agent_framework/config/settings.py +++ b/libs/agent_framework/src/agent_framework/config/settings.py @@ -61,6 +61,16 @@ class Settings(BaseSettings): MEMORY_INJECT_RECENT_MESSAGES: bool = True MEMORY_INJECT_SUMMARY: bool = True + ENABLE_LONG_TERM_MEMORY: bool = False + LONG_TERM_MEMORY_PROVIDER: Literal['memory','sqlite','autonomous','oracle'] = 'sqlite' + LONG_TERM_MEMORY_SQLITE_PATH: str | None = None + LONG_TERM_MEMORY_TABLE: str = 'agentfw_long_term_memory' + LONG_TERM_MEMORY_ORACLE_TABLE: str | None = None + LONG_TERM_MEMORY_MAX_CONTEXT_ITEMS: int = 20 + LONG_TERM_MEMORY_MIN_CONFIDENCE: float = 0.70 + LONG_TERM_MEMORY_AUTO_EXTRACT: bool = True + LONG_TERM_MEMORY_INJECT_CONTEXT: bool = True + # LangGraph enterprise checkpointing ENABLE_RESILIENT_CHECKPOINTER: bool = True ENABLE_CHECKPOINT_INTEGRITY: bool = True diff --git a/libs/agent_framework/src/agent_framework/memory/long_term_extractor.py b/libs/agent_framework/src/agent_framework/memory/long_term_extractor.py new file mode 100644 index 0000000..15e7279 --- /dev/null +++ b/libs/agent_framework/src/agent_framework/memory/long_term_extractor.py @@ -0,0 +1,25 @@ +from __future__ import annotations +import re +from typing import Any + +_PATTERNS = [ + ('identity', 'preferred_name', re.compile(r'\b(?:me chame de|pode me chamar de|meu nome preferido é)\s+([A-Za-zÀ-ÿ][A-Za-zÀ-ÿ0-9 _-]{1,40})', re.I)), + ('preference', 'preferred_language', re.compile(r'\b(?:minha linguagem preferida é|prefiro programar em)\s+(Python|Java|JavaScript|TypeScript|Go|Rust|C#|C\+\+)\b', re.I)), + ('project', 'current_project', re.compile(r'\b(?:meu projeto atual se chama|estou trabalhando no projeto|o projeto se chama)\s+([A-Za-zÀ-ÿ0-9._ -]{2,60})', re.I)), + ('constraint', 'meeting_restriction', re.compile(r'\b(não (?:marque|agende) reuniões?[^.!?\n]{3,120})', re.I)), + ('preference', 'communication_style', re.compile(r'\b(?:prefiro respostas|responda de forma)\s+(curtas?|detalhadas?|objetivas?|técnicas?|didáticas?)', re.I)), +] + +def extract_long_term_memory(text: str, min_confidence: float = 0.70) -> list[dict[str, Any]]: + normalized = ' '.join((text or '').split()) + output: list[dict[str, Any]] = [] + seen: set[tuple[str, str]] = set() + for category, key, pattern in _PATTERNS: + match = pattern.search(normalized) + if not match or (category, key) in seen: + continue + seen.add((category, key)) + confidence = 0.98 + if confidence >= min_confidence: + output.append({'category': category, 'key': key, 'value': match.group(1).strip(' .,;:'), 'confidence': confidence, 'metadata': {'extractor': 'regex-v1'}}) + return output diff --git a/libs/agent_framework/src/agent_framework/memory/long_term_memory.py b/libs/agent_framework/src/agent_framework/memory/long_term_memory.py new file mode 100644 index 0000000..82bdf19 --- /dev/null +++ b/libs/agent_framework/src/agent_framework/memory/long_term_memory.py @@ -0,0 +1,64 @@ +from __future__ import annotations +import logging +from .long_term_extractor import extract_long_term_memory +from .long_term_store import create_long_term_memory_store + +logger = logging.getLogger('agent_framework.memory.long_term') + +class LongTermMemoryManager: + def __init__(self, settings, store=None, telemetry=None): + self.settings = settings + self.store = store or create_long_term_memory_store(settings) + self.telemetry = telemetry + + @property + def enabled(self): + return bool(getattr(self.settings, 'ENABLE_LONG_TERM_MEMORY', False)) + + def identity(self, state): + context = state.get('context') or {} + session = context.get('session') or {} + business = context.get('business_context') or state.get('business_context') or {} + metadata = session.get('metadata') or {} + tenant = str(state.get('tenant_id') or session.get('tenant_id') or 'default') + agent = str(state.get('agent_id') or state.get('route') or session.get('active_agent') or 'default') + subject = business.get('customer_key') or state.get('customer_key') or context.get('user_id') or session.get('user_id') or metadata.get('customer_key') + return tenant, agent, str(subject) if subject else None + + async def load(self, state): + if not self.enabled: + return [] + tenant, agent, subject = self.identity(state) + if not subject: + return [] + try: + return await self.store.search(tenant_id=tenant, agent_id=agent, subject_key=subject, limit=int(getattr(self.settings, 'LONG_TERM_MEMORY_MAX_CONTEXT_ITEMS', 20))) + except Exception: + logger.exception('Falha não crítica ao carregar LTM') + return [] + + async def persist_turn(self, state): + if not self.enabled or not bool(getattr(self.settings, 'LONG_TERM_MEMORY_AUTO_EXTRACT', True)): + return {'saved': 0, 'enabled': self.enabled} + tenant, agent, subject = self.identity(state) + if not subject: + return {'saved': 0, 'warning': 'customer_key ausente'} + text = str(state.get('sanitized_input') or state.get('user_text') or '') + candidates = extract_long_term_memory(text, float(getattr(self.settings, 'LONG_TERM_MEMORY_MIN_CONFIDENCE', 0.70))) + try: + saved = await self.store.upsert_many(tenant_id=tenant, agent_id=agent, subject_key=subject, items=candidates, source_session_id=str(state.get('conversation_key') or state.get('session_id') or ''), source_message_id=str((state.get('context') or {}).get('message_id') or '')) + return {'saved': len(saved), 'items': [item.to_dict() for item in saved]} + except Exception as exc: + logger.exception('Falha não crítica ao persistir LTM') + return {'saved': 0, 'error': str(exc)} + + def render(self, items): + if not items: + return '' + lines = ['Memórias duráveis relevantes do usuário atual:'] + lines.extend(f'- {item.key}: {item.value}' for item in items) + lines.extend(['Use somente estas memórias; não invente lembranças.', 'A mensagem atual prevalece se houver conflito.']) + return '\n'.join(lines) + +def create_long_term_memory_manager(settings, telemetry=None): + return LongTermMemoryManager(settings, telemetry=telemetry) diff --git a/libs/agent_framework/src/agent_framework/memory/long_term_models.py b/libs/agent_framework/src/agent_framework/memory/long_term_models.py new file mode 100644 index 0000000..d51b0c7 --- /dev/null +++ b/libs/agent_framework/src/agent_framework/memory/long_term_models.py @@ -0,0 +1,26 @@ +from __future__ import annotations +from dataclasses import asdict, dataclass, field +from datetime import datetime, timezone +from typing import Any + +def utc_now() -> str: + return datetime.now(timezone.utc).isoformat() + +@dataclass(slots=True) +class LongTermMemoryItem: + memory_id: str + tenant_id: str + agent_id: str + subject_key: str + category: str + key: str + value: str + confidence: float = 1.0 + source_session_id: str | None = None + source_message_id: str | None = None + created_at: str = field(default_factory=utc_now) + updated_at: str = field(default_factory=utc_now) + metadata: dict[str, Any] = field(default_factory=dict) + + def to_dict(self) -> dict[str, Any]: + return asdict(self) diff --git a/libs/agent_framework/src/agent_framework/memory/long_term_store.py b/libs/agent_framework/src/agent_framework/memory/long_term_store.py new file mode 100644 index 0000000..45d782b --- /dev/null +++ b/libs/agent_framework/src/agent_framework/memory/long_term_store.py @@ -0,0 +1,546 @@ +from __future__ import annotations + +import asyncio +import json +import re +import sqlite3 +import uuid +from contextlib import contextmanager +from datetime import datetime, timezone +from pathlib import Path +from typing import Any, Protocol, Sequence + +from .long_term_models import LongTermMemoryItem, utc_now + + +class LongTermMemoryStore(Protocol): + async def upsert_many( + self, + *, + tenant_id: str, + agent_id: str, + subject_key: str, + items: Sequence[dict[str, Any]], + source_session_id: str | None = None, + source_message_id: str | None = None, + ) -> list[LongTermMemoryItem]: ... + + async def search( + self, + *, + tenant_id: str, + agent_id: str, + subject_key: str, + limit: int = 20, + ) -> list[LongTermMemoryItem]: ... + + +class InMemoryLongTermMemoryStore: + def __init__(self): + self._items: dict[tuple[str, str, str, str, str], LongTermMemoryItem] = {} + + async def upsert_many(self, **kwargs): + saved = [] + now = utc_now() + for raw in kwargs["items"]: + key = ( + kwargs["tenant_id"], + kwargs["agent_id"], + kwargs["subject_key"], + str(raw.get("category") or "fact"), + str(raw.get("key") or ""), + ) + if not key[-1] or not raw.get("value"): + continue + old = self._items.get(key) + item = LongTermMemoryItem( + old.memory_id if old else str(uuid.uuid4()), + key[0], key[1], key[2], key[3], key[4], + str(raw["value"]), + float(raw.get("confidence", 1.0)), + kwargs.get("source_session_id"), + kwargs.get("source_message_id"), + old.created_at if old else now, + now, + dict(raw.get("metadata") or {}), + ) + self._items[key] = item + saved.append(item) + return saved + + async def search(self, *, tenant_id, agent_id, subject_key, limit=20): + values = [ + value for key, value in self._items.items() + if key[:3] == (tenant_id, agent_id, subject_key) + ] + return sorted( + values, + key=lambda item: (item.confidence, item.updated_at), + reverse=True, + )[:limit] + + +class SQLiteLongTermMemoryStore: + def __init__( + self, + path: str = "./data/agent_framework.db", + table: str = "agentfw_long_term_memory", + ): + self.path = str(path) + self.table = _validate_identifier(table, upper=False) + Path(self.path).parent.mkdir(parents=True, exist_ok=True) + self._ready = False + self._lock = asyncio.Lock() + + def _connect(self): + return sqlite3.connect(self.path) + + def _init_sync(self): + sql = f"""CREATE TABLE IF NOT EXISTS {self.table} ( + memory_id TEXT PRIMARY KEY, tenant_id TEXT NOT NULL, agent_id TEXT NOT NULL, + subject_key TEXT NOT NULL, category TEXT NOT NULL, memory_key TEXT NOT NULL, + memory_value TEXT NOT NULL, confidence REAL NOT NULL, source_session_id TEXT, + source_message_id TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, + metadata_json TEXT, UNIQUE(tenant_id,agent_id,subject_key,category,memory_key))""" + with self._connect() as db: + db.execute(sql) + db.execute( + f"CREATE INDEX IF NOT EXISTS idx_{self.table}_subject " + f"ON {self.table}(tenant_id,agent_id,subject_key,updated_at)" + ) + + async def _ensure(self): + if self._ready: + return + async with self._lock: + if not self._ready: + await asyncio.to_thread(self._init_sync) + self._ready = True + + def _upsert_sync( + self, tenant_id, agent_id, subject_key, items, + source_session_id, source_message_id, + ): + now = utc_now() + saved = [] + with self._connect() as db: + for raw in items: + category = str(raw.get("category") or "fact").lower() + key = str(raw.get("key") or "").lower() + value = str(raw.get("value") or "").strip() + if not key or not value: + continue + row = db.execute( + f"SELECT memory_id,created_at FROM {self.table} " + "WHERE tenant_id=? AND agent_id=? AND subject_key=? " + "AND category=? AND memory_key=?", + (tenant_id, agent_id, subject_key, category, key), + ).fetchone() + memory_id = row[0] if row else str(uuid.uuid4()) + created_at = row[1] if row else now + confidence = float(raw.get("confidence", 1.0)) + metadata = dict(raw.get("metadata") or {}) + db.execute( + f"""INSERT INTO {self.table}( + memory_id,tenant_id,agent_id,subject_key,category,memory_key, + memory_value,confidence,source_session_id,source_message_id, + created_at,updated_at,metadata_json) + VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?) + ON CONFLICT(tenant_id,agent_id,subject_key,category,memory_key) + DO UPDATE SET memory_value=excluded.memory_value, + confidence=excluded.confidence, + source_session_id=excluded.source_session_id, + source_message_id=excluded.source_message_id, + updated_at=excluded.updated_at, + metadata_json=excluded.metadata_json""", + ( + memory_id, tenant_id, agent_id, subject_key, category, key, + value, confidence, source_session_id, source_message_id, + created_at, now, json.dumps(metadata, ensure_ascii=False), + ), + ) + saved.append(LongTermMemoryItem( + memory_id, tenant_id, agent_id, subject_key, category, key, + value, confidence, source_session_id, source_message_id, + created_at, now, metadata, + )) + return saved + + async def upsert_many(self, **kwargs): + await self._ensure() + return await asyncio.to_thread( + self._upsert_sync, + kwargs["tenant_id"], kwargs["agent_id"], kwargs["subject_key"], + list(kwargs["items"]), kwargs.get("source_session_id"), + kwargs.get("source_message_id"), + ) + + def _search_sync(self, tenant_id, agent_id, subject_key, limit): + with self._connect() as db: + rows = db.execute( + f"SELECT memory_id,tenant_id,agent_id,subject_key,category,memory_key," + f"memory_value,confidence,source_session_id,source_message_id," + f"created_at,updated_at,metadata_json FROM {self.table} " + "WHERE tenant_id=? AND agent_id=? AND subject_key=? " + "ORDER BY confidence DESC,updated_at DESC LIMIT ?", + (tenant_id, agent_id, subject_key, int(limit)), + ).fetchall() + return [ + LongTermMemoryItem(*row[:12], metadata=json.loads(row[12] or "{}")) + for row in rows + ] + + async def search(self, **kwargs): + await self._ensure() + return await asyncio.to_thread( + self._search_sync, + kwargs["tenant_id"], kwargs["agent_id"], kwargs["subject_key"], + kwargs.get("limit", 20), + ) + + +def _validate_identifier(value: str, *, upper: bool = True) -> str: + identifier = str(value or "").strip() + if not re.fullmatch(r"[A-Za-z][A-Za-z0-9_$#]{0,127}", identifier): + raise ValueError(f"Invalid SQL identifier: {value!r}") + return identifier.upper() if upper else identifier + + +def _as_iso(value: Any) -> str: + if isinstance(value, datetime): + if value.tzinfo is None: + value = value.replace(tzinfo=timezone.utc) + return value.isoformat() + return str(value) + + +def _load_json(value: Any) -> dict[str, Any]: + if value is None: + return {} + if hasattr(value, "read"): + value = value.read() + if isinstance(value, bytes): + value = value.decode("utf-8") + try: + loaded = json.loads(value) + return loaded if isinstance(loaded, dict) else {} + except (TypeError, ValueError, json.JSONDecodeError): + return {} + + +class OracleAutonomousLongTermMemoryStore: + """Long-Term Memory provider for Oracle Autonomous Database. + + The implementation uses python-oracledb in thin mode and reuses the + framework's ADB_* settings. Synchronous database operations run in worker + threads so FastAPI/LangGraph's event loop is not blocked. + """ + + def __init__(self, settings): + self.user = str(getattr(settings, "ADB_USER", "") or "") + self.password = str(getattr(settings, "ADB_PASSWORD", "") or "") + self.dsn = str(getattr(settings, "ADB_DSN", "") or "") + self.wallet_location = getattr(settings, "ADB_WALLET_LOCATION", None) + self.wallet_password = getattr(settings, "ADB_WALLET_PASSWORD", None) + default_table = ( + f"{getattr(settings, 'ADB_TABLE_PREFIX', 'AGENTFW')}_LONG_TERM_MEMORY" + ) + configured_table = ( + getattr(settings, "LONG_TERM_MEMORY_ORACLE_TABLE", None) + or default_table + ) + self.table = _validate_identifier(configured_table) + self.index_name = _validate_identifier(f"IX_{self.table}_SUBJECT") + self.constraint_name = _validate_identifier(f"UQ_{self.table}_FACT") + self._ready = False + self._lock = asyncio.Lock() + if not self.user or not self.password or not self.dsn: + raise RuntimeError( + "ADB_USER, ADB_PASSWORD and ADB_DSN are required when " + "LONG_TERM_MEMORY_PROVIDER is autonomous/oracle" + ) + + @contextmanager + def _connect(self): + try: + import oracledb + except ImportError as exc: + raise RuntimeError( + "python-oracledb is required for the Autonomous Long-Term " + "Memory provider. Install it with: pip install oracledb" + ) from exc + + oracledb.defaults.fetch_lobs = False + kwargs: dict[str, Any] = {} + if self.wallet_location: + kwargs["config_dir"] = self.wallet_location + kwargs["wallet_location"] = self.wallet_location + if self.wallet_password: + kwargs["wallet_password"] = self.wallet_password + connection = oracledb.connect( + user=self.user, + password=self.password, + dsn=self.dsn, + **kwargs, + ) + try: + yield connection + connection.commit() + except Exception: + connection.rollback() + raise + finally: + connection.close() + + @staticmethod + def _ignore_already_exists(cursor, ddl: str) -> None: + try: + cursor.execute(ddl) + except Exception as exc: + message = str(exc) + if "ORA-00955" in message or "ORA-01408" in message: + return + raise + + def _init_sync(self) -> None: + with self._connect() as connection: + cursor = connection.cursor() + self._ignore_already_exists(cursor, f""" + CREATE TABLE {self.table} ( + MEMORY_ID VARCHAR2(36) PRIMARY KEY, + TENANT_ID VARCHAR2(128) NOT NULL, + AGENT_ID VARCHAR2(128) NOT NULL, + SUBJECT_KEY VARCHAR2(512) NOT NULL, + CATEGORY VARCHAR2(128) NOT NULL, + MEMORY_KEY VARCHAR2(256) NOT NULL, + MEMORY_VALUE CLOB NOT NULL, + CONFIDENCE NUMBER(5,4) DEFAULT 1 NOT NULL, + SOURCE_SESSION_ID VARCHAR2(512), + SOURCE_MESSAGE_ID VARCHAR2(256), + CREATED_AT TIMESTAMP WITH TIME ZONE NOT NULL, + UPDATED_AT TIMESTAMP WITH TIME ZONE NOT NULL, + METADATA_JSON CLOB CHECK (METADATA_JSON IS JSON), + CONSTRAINT {self.constraint_name} UNIQUE ( + TENANT_ID, AGENT_ID, SUBJECT_KEY, CATEGORY, MEMORY_KEY + ) + ) + """) + self._ignore_already_exists(cursor, f""" + CREATE INDEX {self.index_name} + ON {self.table} ( + TENANT_ID, AGENT_ID, SUBJECT_KEY, UPDATED_AT DESC + ) + """) + + async def _ensure(self) -> None: + if self._ready: + return + async with self._lock: + if not self._ready: + await asyncio.to_thread(self._init_sync) + self._ready = True + + def _find_existing( + self, cursor, tenant_id: str, agent_id: str, subject_key: str, + category: str, memory_key: str, + ) -> tuple[str, Any] | None: + cursor.execute( + f"""SELECT MEMORY_ID, CREATED_AT FROM {self.table} + WHERE TENANT_ID = :tenant_id + AND AGENT_ID = :agent_id + AND SUBJECT_KEY = :subject_key + AND CATEGORY = :category + AND MEMORY_KEY = :memory_key""", + tenant_id=tenant_id, + agent_id=agent_id, + subject_key=subject_key, + category=category, + memory_key=memory_key, + ) + return cursor.fetchone() + + def _upsert_sync( + self, tenant_id: str, agent_id: str, subject_key: str, + items: Sequence[dict[str, Any]], source_session_id: str | None, + source_message_id: str | None, + ) -> list[LongTermMemoryItem]: + now = datetime.now(timezone.utc) + saved: list[LongTermMemoryItem] = [] + with self._connect() as connection: + cursor = connection.cursor() + for raw in items: + category = str(raw.get("category") or "fact").strip().lower() + memory_key = str(raw.get("key") or "").strip().lower() + value = str(raw.get("value") or "").strip() + if not memory_key or not value: + continue + + existing = self._find_existing( + cursor, tenant_id, agent_id, subject_key, + category, memory_key, + ) + memory_id = str(existing[0]) if existing else str(uuid.uuid4()) + created_at = existing[1] if existing else now + confidence = float(raw.get("confidence", 1.0)) + metadata = dict(raw.get("metadata") or {}) + metadata_json = json.dumps(metadata, ensure_ascii=False, default=str) + + cursor.execute(f""" + MERGE INTO {self.table} target + USING ( + SELECT + :tenant_id AS TENANT_ID, + :agent_id AS AGENT_ID, + :subject_key AS SUBJECT_KEY, + :category AS CATEGORY, + :memory_key AS MEMORY_KEY + FROM dual + ) source + ON ( + target.TENANT_ID = source.TENANT_ID + AND target.AGENT_ID = source.AGENT_ID + AND target.SUBJECT_KEY = source.SUBJECT_KEY + AND target.CATEGORY = source.CATEGORY + AND target.MEMORY_KEY = source.MEMORY_KEY + ) + WHEN MATCHED THEN UPDATE SET + target.MEMORY_VALUE = :memory_value, + target.CONFIDENCE = :confidence, + target.SOURCE_SESSION_ID = :source_session_id, + target.SOURCE_MESSAGE_ID = :source_message_id, + target.UPDATED_AT = :updated_at, + target.METADATA_JSON = :metadata_json + WHEN NOT MATCHED THEN INSERT ( + MEMORY_ID, TENANT_ID, AGENT_ID, SUBJECT_KEY, + CATEGORY, MEMORY_KEY, MEMORY_VALUE, CONFIDENCE, + SOURCE_SESSION_ID, SOURCE_MESSAGE_ID, + CREATED_AT, UPDATED_AT, METADATA_JSON + ) VALUES ( + :memory_id, :tenant_id, :agent_id, :subject_key, + :category, :memory_key, :memory_value, :confidence, + :source_session_id, :source_message_id, + :created_at, :updated_at, :metadata_json + ) + """, { + "memory_id": memory_id, + "tenant_id": tenant_id, + "agent_id": agent_id, + "subject_key": subject_key, + "category": category, + "memory_key": memory_key, + "memory_value": value, + "confidence": confidence, + "source_session_id": source_session_id, + "source_message_id": source_message_id, + "created_at": created_at, + "updated_at": now, + "metadata_json": metadata_json, + }) + saved.append(LongTermMemoryItem( + memory_id=memory_id, + tenant_id=tenant_id, + agent_id=agent_id, + subject_key=subject_key, + category=category, + key=memory_key, + value=value, + confidence=confidence, + source_session_id=source_session_id, + source_message_id=source_message_id, + created_at=_as_iso(created_at), + updated_at=_as_iso(now), + metadata=metadata, + )) + return saved + + async def upsert_many(self, **kwargs): + await self._ensure() + return await asyncio.to_thread( + self._upsert_sync, + kwargs["tenant_id"], + kwargs["agent_id"], + kwargs["subject_key"], + list(kwargs["items"]), + kwargs.get("source_session_id"), + kwargs.get("source_message_id"), + ) + + def _search_sync( + self, tenant_id: str, agent_id: str, subject_key: str, limit: int, + ) -> list[LongTermMemoryItem]: + safe_limit = max(1, min(int(limit), 500)) + with self._connect() as connection: + cursor = connection.cursor() + cursor.execute(f""" + SELECT + MEMORY_ID, TENANT_ID, AGENT_ID, SUBJECT_KEY, + CATEGORY, MEMORY_KEY, MEMORY_VALUE, CONFIDENCE, + SOURCE_SESSION_ID, SOURCE_MESSAGE_ID, + CREATED_AT, UPDATED_AT, METADATA_JSON + FROM {self.table} + WHERE TENANT_ID = :tenant_id + AND AGENT_ID = :agent_id + AND SUBJECT_KEY = :subject_key + ORDER BY CONFIDENCE DESC, UPDATED_AT DESC + FETCH FIRST {safe_limit} ROWS ONLY + """, { + "tenant_id": tenant_id, + "agent_id": agent_id, + "subject_key": subject_key, + }) + rows = cursor.fetchall() + + result: list[LongTermMemoryItem] = [] + for row in rows: + result.append(LongTermMemoryItem( + memory_id=str(row[0]), + tenant_id=str(row[1]), + agent_id=str(row[2]), + subject_key=str(row[3]), + category=str(row[4]), + key=str(row[5]), + value=str(row[6]), + confidence=float(row[7]), + source_session_id=str(row[8]) if row[8] is not None else None, + source_message_id=str(row[9]) if row[9] is not None else None, + created_at=_as_iso(row[10]), + updated_at=_as_iso(row[11]), + metadata=_load_json(row[12]), + )) + return result + + async def search(self, **kwargs): + await self._ensure() + return await asyncio.to_thread( + self._search_sync, + kwargs["tenant_id"], + kwargs["agent_id"], + kwargs["subject_key"], + kwargs.get("limit", 20), + ) + + +AutonomousLongTermMemoryStore = OracleAutonomousLongTermMemoryStore + + +def create_long_term_memory_store(settings): + provider = str( + getattr(settings, "LONG_TERM_MEMORY_PROVIDER", "sqlite") + ).strip().lower() + if provider == "memory": + return InMemoryLongTermMemoryStore() + if provider in {"autonomous", "oracle"}: + return OracleAutonomousLongTermMemoryStore(settings) + if provider != "sqlite": + raise ValueError( + "Unsupported LONG_TERM_MEMORY_PROVIDER: " + f"{provider!r}. Expected memory, sqlite, autonomous or oracle." + ) + path = ( + getattr(settings, "LONG_TERM_MEMORY_SQLITE_PATH", None) + or getattr(settings, "SQLITE_DB_PATH", "./data/agent_framework.db") + ) + return SQLiteLongTermMemoryStore( + path, + getattr(settings, "LONG_TERM_MEMORY_TABLE", "agentfw_long_term_memory"), + ) diff --git a/libs/agent_framework/src/agent_framework/runtime/agent_runtime.py b/libs/agent_framework/src/agent_framework/runtime/agent_runtime.py index 4e5a2f5..c84a457 100644 --- a/libs/agent_framework/src/agent_framework/runtime/agent_runtime.py +++ b/libs/agent_framework/src/agent_framework/runtime/agent_runtime.py @@ -817,6 +817,16 @@ class AgentRuntimeMixin: state["memory_context"] = memory_context state["memory_context_metadata"] = memory_context.metadata + if bool(getattr(settings, "ENABLE_LONG_TERM_MEMORY", False)): + manager = getattr(self, "long_term_memory_manager", None) + if manager is None: + from agent_framework.memory.long_term_memory import create_long_term_memory_manager + manager = create_long_term_memory_manager(settings, telemetry=getattr(self, "telemetry", None)) + self.long_term_memory_manager = manager + items = await manager.load(state) + state["long_term_memories"] = [item.to_dict() for item in items] + state["long_term_memory_context"] = manager.render(items) + if memory_context.compressed: await self._emit_ic( "IC.MEMORY_COMPRESSION_TRIGGERED", @@ -891,6 +901,8 @@ class AgentRuntimeMixin: runtime = self.get_runtime_context(state) sections = [] sections.extend(self._render_memory_sections(state)) + if bool(getattr(getattr(self, "settings", None), "LONG_TERM_MEMORY_INJECT_CONTEXT", True)) and state.get("long_term_memory_context"): + sections.append(str(state["long_term_memory_context"])) sections.extend([ f"Mensagem do usuário:\n{user_text if user_text is not None else runtime.sanitized_input}", f"Intent/rota escolhidos pelo framework:\nintent={state.get('intent')} route={state.get('route')}", diff --git a/specs/Disclaimer Auth and Security_PT.md b/specs/Disclaimer Auth and Security_PT.md new file mode 100644 index 0000000..d275613 --- /dev/null +++ b/specs/Disclaimer Auth and Security_PT.md @@ -0,0 +1,40 @@ +### Recomendações de segurança, autenticação e autorização + +Os componentes e templates deste framework podem ser adaptados a diferentes arquiteturas e requisitos de segurança. Para ambientes produtivos, recomenda-se que a solução seja avaliada de acordo com as políticas corporativas, os requisitos regulatórios aplicáveis e as melhores práticas de segurança da Oracle Cloud Infrastructure. + +Como orientação geral, recomenda-se considerar autenticação e autorização em todas as interfaces acessíveis por usuários, canais, sistemas externos ou outros serviços. Na OCI, uma opção é utilizar o OCI API Gateway, ou uma camada equivalente, em conjunto com OAuth 2.0/OpenID Connect, validação de tokens e políticas de autorização por rota, scope, papel e tenant. + +Métodos como HTTP Basic e API keys podem ser adequados para determinados cenários de integração, especialmente ambientes controlados ou sistemas legados. Nesses casos, recomenda-se utilizá-los sobre TLS, manter as credenciais em um serviço seguro de gerenciamento de segredos e adotar mecanismos de expiração e rotação. + +A avaliação de segurança deve considerar, conforme os componentes utilizados pela solução: + +- Agent Gateway, Channel Gateway e MCP Gateway; +- backends de agentes e comunicação entre gateways e backends; +- aplicações frontend e APIs consumidas pelo navegador; +- callbacks e webhooks provenientes de canais externos; +- conexões SSE, WebSocket ou outros mecanismos de streaming; +- histórico, memória, checkpoints e dados de sessão; +- endpoints administrativos, de debug, documentação, health e métricas; +- integrações com LLMs, bancos de dados, caches, mensageria e plataformas de observabilidade. + +Recomenda-se tratar identificadores recebidos em payloads ou headers — como `tenant_id`, `agent_id`, `user_id`, `customer_id` e `session_id` — como informações de contexto, e não como evidência suficiente da identidade do solicitante. Quando aplicável, esses identificadores podem ser derivados de claims validadas ou relacionados à identidade autenticada antes da execução da operação. + +Além da autenticação, recomenda-se avaliar a autorização sobre cada recurso acessado. Essa verificação pode considerar se o usuário ou serviço autenticado possui permissão para acessar o tenant, agente, sessão, histórico, checkpoint, backend ou ferramenta MCP solicitado. + +Para comunicação entre serviços, podem ser consideradas identidades específicas por workload e mecanismos como OAuth 2.0 client credentials, OCI IAM, OKE Workload Identity, Instance Principals, Resource Principals ou mTLS. A escolha deve considerar a plataforma de execução e o modelo de confiança definido para a solução. + +Para callbacks e webhooks, recomenda-se avaliar os mecanismos disponibilizados pelo provedor do canal, como assinatura digital ou HMAC, JWT, timestamp, identificador de mensagem, proteção contra replay e idempotência. + +Em relação aos endpoints operacionais, é recomendável avaliar separadamente: + +- endpoints de liveness, com resposta mínima sobre o estado do processo; +- endpoints de readiness, preferencialmente acessíveis apenas pela infraestrutura; +- endpoints de métricas, destinados aos coletores autorizados; +- endpoints de debug e teste, normalmente restritos a ambientes não produtivos; +- documentação OpenAPI, que pode ser desabilitada ou protegida em produção. + +Também é recomendável utilizar TLS nas comunicações, restringir a exposição de serviços por meio de redes privadas, sub-redes, NSGs e allowlists, e considerar rate limiting, auditoria, rastreabilidade e monitoramento de acessos negados. + +Segredos, tokens, senhas, certificados e chaves podem ser mantidos no OCI Secret Management ou em solução corporativa equivalente, evitando seu armazenamento em código-fonte ou arquivos de configuração versionados. Recomenda-se estabelecer políticas de acesso de menor privilégio, expiração e rotação compatíveis com a criticidade de cada credencial. + +Estas recomendações representam uma referência inicial de melhores práticas. A definição final dos mecanismos de autenticação, autorização, proteção de rede e gestão de segredos permanece sob responsabilidade da equipe responsável pela arquitetura e pelo deployment, considerando o contexto, os riscos e os requisitos específicos de cada implementação. \ No newline at end of file diff --git a/specs/Disclaimer_Auth_and_Security_EN.md b/specs/Disclaimer_Auth_and_Security_EN.md new file mode 100644 index 0000000..02c312b --- /dev/null +++ b/specs/Disclaimer_Auth_and_Security_EN.md @@ -0,0 +1,40 @@ +### Security, Authentication, and Authorization Recommendations + +The components and templates in this framework can be adapted to different architectures and security requirements. For production environments, it is recommended that the solution be assessed in accordance with corporate policies, applicable regulatory requirements, and Oracle Cloud Infrastructure security best practices. + +As general guidance, authentication and authorization should be considered for all interfaces accessible by users, channels, external systems, or other services. In OCI, one option is to use OCI API Gateway, or an equivalent layer, together with OAuth 2.0/OpenID Connect, token validation, and authorization policies based on route, scope, role, and tenant. + +Methods such as HTTP Basic authentication and API keys may be suitable for certain integration scenarios, particularly in controlled environments or with legacy systems. In such cases, it is recommended that they be used over TLS, that credentials be stored in a secure secrets management service, and that expiration and rotation mechanisms be adopted. + +The security assessment should consider, according to the components used by the solution: + +- Agent Gateway, Channel Gateway, and MCP Gateway; +- agent backends and communication between gateways and backends; +- frontend applications and APIs consumed by the browser; +- callbacks and webhooks originating from external channels; +- SSE, WebSocket, or other streaming connections; +- history, memory, checkpoints, and session data; +- administrative, debug, documentation, health, and metrics endpoints; +- integrations with LLMs, databases, caches, messaging systems, and observability platforms. + +Identifiers received in payloads or headers—such as `tenant_id`, `agent_id`, `user_id`, `customer_id`, and `session_id`—should be treated as contextual information rather than sufficient proof of the requester's identity. When applicable, these identifiers may be derived from validated claims or associated with the authenticated identity before the operation is executed. + +In addition to authentication, authorization should be evaluated for each accessed resource. This verification may consider whether the authenticated user or service has permission to access the requested tenant, agent, session, history, checkpoint, backend, or MCP tool. + +For service-to-service communication, dedicated workload identities and mechanisms such as OAuth 2.0 client credentials, OCI IAM, OKE Workload Identity, Instance Principals, Resource Principals, or mTLS may be considered. The choice should take into account the execution platform and the trust model defined for the solution. + +For callbacks and webhooks, the mechanisms provided by the channel provider should be evaluated, such as digital signatures or HMAC, JWT, timestamps, message identifiers, replay protection, and idempotency. + +Operational endpoints should also be evaluated separately: + +- liveness endpoints, with a minimal response regarding the process status; +- readiness endpoints, preferably accessible only by the infrastructure; +- metrics endpoints, intended for authorized collectors; +- debug and test endpoints, typically restricted to non-production environments; +- OpenAPI documentation, which may be disabled or protected in production. + +It is also recommended to use TLS for communications, restrict service exposure through private networks, subnets, NSGs, and allowlists, and consider rate limiting, auditing, traceability, and monitoring of denied access attempts. + +Secrets, tokens, passwords, certificates, and keys may be stored in OCI Secret Management or an equivalent corporate solution, avoiding storage in source code or version-controlled configuration files. Least-privilege access policies, expiration, and rotation practices appropriate to the criticality of each credential should be established. + +These recommendations provide an initial reference for best practices. The final definition of authentication, authorization, network protection, and secrets management mechanisms remains the responsibility of the team accountable for the architecture and deployment, taking into consideration the context, risks, and specific requirements of each implementation. diff --git a/templates/agent_template_backend/.env b/templates/agent_template_backend/.env index 7745db8..0ac21b5 100644 --- a/templates/agent_template_backend/.env +++ b/templates/agent_template_backend/.env @@ -178,3 +178,17 @@ MCP_GATEWAY_TIMEOUT_SECONDS=60 # MCP_GATEWAY_TOKEN= MCP_GATEWAY_AGENT_ID=telecom_contas MCP_GATEWAY_TENANT_ID=default + +############################################################################### +# LONG-TERM MEMORY +############################################################################### +ENABLE_LONG_TERM_MEMORY=true +LONG_TERM_MEMORY_PROVIDER=sqlite +LONG_TERM_MEMORY_SQLITE_PATH=./data/agent_framework.db +LONG_TERM_MEMORY_TABLE=agentfw_long_term_memory +# For Autonomous/Oracle, defaults to ${ADB_TABLE_PREFIX}_LONG_TERM_MEMORY +# LONG_TERM_MEMORY_ORACLE_TABLE=AGENTFW_LONG_TERM_MEMORY +LONG_TERM_MEMORY_MAX_CONTEXT_ITEMS=20 +LONG_TERM_MEMORY_MIN_CONFIDENCE=0.70 +LONG_TERM_MEMORY_AUTO_EXTRACT=true +LONG_TERM_MEMORY_INJECT_CONTEXT=true diff --git a/templates/agent_template_backend/app/__pycache__/__init__.cpython-313.pyc b/templates/agent_template_backend/app/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..81e4d1571b96052124225a3c406a034a3ec1a91f GIT binary patch literal 177 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 I$89wHzbJpK!~g&Q literal 0 HcmV?d00001 diff --git a/templates/agent_template_backend/app/__pycache__/mcp_gateway_client_factory.cpython-313.pyc b/templates/agent_template_backend/app/__pycache__/mcp_gateway_client_factory.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f2aaf8396189872ff778707a921f22611b12741a GIT binary patch literal 1025 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 Jh*ZqE{{NR@5z7A-gQH(a z!wnZpf_kHf3w$w~A6CUe3HqYaU^e^wqL6)Glyk#yIqEEBc)u%>rB$8b`MjK0d0ubd z=eJ~Ylvqm{1-Om2Bd9%mhtWEE?|8J%*x#5)1B1~!4lTmoan~W#b;qf75B{C$+=X>0 z+j1Oqh#5YF-|pw&!al@;%QI-MgzSln0tHhicNgVE>OMWw1zT zG(*qX_hvUK&feUwrR(gWx;iq{AyS-8DTnY~ui(3ze9n_JbKXPGP5k*nJKO_?&&c(E zp=GG+T(dv-XX>;!ZTB zyz(T`B+Ydm;x$`8Vo-`o|G~hI49pprW>EUy@cRe+HsBW-H{YNVUFo&(^knkN+=Zz* zj-NVocHEGv0gR1`i7sir7HmeFk`F_+I3l{LQ+s*?4HlT=gzlU!tP z&0Oo*6MK2`DnEH8JwLTD4}ULRnwliyef3Z*DrHH99Uy)wD`q#pU^gGK?yxA7B`^V8R+^l&{Z!SM|Nt+fe@ZWUzZmypODGDzmZS&;quK%#Dj zO>msZ?$~oz{T57KxqS7~6gUc#>#ifv-BlOEXO{)Ar*~OU_@AiX@R#m6sLfjFfNRM$ zkwjI@K@Meju>d(LpVfn)yCjKOl{c}@s>nY50$W30#7vZ za*OF%Ea#;hj4BH3u3(Z1rPTZwkLIXs!Xx<gaBWG*jv+J&ZI>VYj^vTd~ zhqSiiT3b-_^?#OqFj$R^Z?&D$qJtnPz{2u?2L!o2KmmBr>%+BU7b?dte3hvlo2~WF zR+yvfPR$vvIXf%P&QGUn&RE46J0SUK>_jbgvJyMFHJ+-*-qzx8)Z*in`1sbT>1upN z>rK{rPgHtOJbJU*dw$18`~2Gu)H$^6GxoQW{q1Cb0#W1*)I5obC-GpQ>KUmqBU{YK z(?GZuh*bhHu*8$-C^<{p;;zNq>Dgvb$AA{;(>ew~TzedZv^&-bDHKD*?tID2x(ieZ=s`iaN>FL|JRO=tD^p8Gzr`mt6 z+H+p(jo(k)OKc5JReLY)*ok3XC_KC!FkI&#u5%FA5eT8S(7OBigbDok+=C+Lp3^WN zsDVq=4h=Thi6Z-P0RI(g#}6xhR6c@x@5| zc;E?>@hI_Wn4^%=fn|-?g^vTRAKm}DTFdu8T!F0X0x;fNu%i}CR)TP2s==|EcWjFp zt9ZwLR7maqL9mw6S+a?9WM10sa6_P@U3tL6Xz literal 0 HcmV?d00001 diff --git a/templates/agent_template_backend/app/agents/__pycache__/product_agent.cpython-313.pyc b/templates/agent_template_backend/app/agents/__pycache__/product_agent.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c959856de76f4329a28401c54fac281a9a1cffd4 GIT binary patch literal 4036 zcma)9Z%iA>6`!@&X8i{nV~mX+of=Z9Qlz%heY}X%RsDwLwqC7rRiySyK2(Bmr|Q1-&H7Iu z7n%<2&olF8-psuB=Dm4)jg3wO<@3M1duz54p?}gwt=OuSy&l?ayc<${1lOpO@D}X$dQum?yc#jEw1THm?EI zH%9ws3yPM>;;Wg}jFPG_si39W*gFrEyXYn&jDUzmV2D+)5VjK?{1Z5V4WnBOu?e;? zB6h(J*dR0zZpj*T815O8OBYg_x#lx^Lc8D&W|xZDDz!8m&sXLB&w zqZYDRiL8m0cGPLGDwY?C3sxm=3Sa|>cqn)&G*R$TXr|x?P^p0UdNFPeXxaRL3_xM{n=@D;KI_AbiKEhxj{<6=?0a?B-?Wtg+V+_ffb zZkP%4>V{C8g;0_isd9UHhGI!t-lz_gj={)RuWN^O8*bE1DQT(kgPm}P?xb#F|Ibbe zAd&oVlAU$D7+tFgj@o+ly-pvxS7(wrgo~@jWv4%dLG zGkO7DDoN%By$ogn={Jp3EhcG6T9d5cJ8M&Hpe=e$UMeuUIccjo#UE(gF8Gsn4|>xg zv;Z$Q$-GkHf%;inV>YGX5W5J%E?-zh!z($3YMjn7HSMM0Ea1-DYLpxFikszyW$o*G zjZM_Jy!IsF7%Q|NqP2!z#GyEo_{_wQO-!4Z;85b<@ckXWd+>!ut&2>wBe5Qu9FNV+ zUb~bWpA*O86Nx!dm>Zj&o48~&FBH^_f>l*aLAc2&5NQm~^fFpbUH6;rDpvJ6F;$ZY zjYy9A*mZs%=d0s0;`p`1+{FAG{9d^-F-}+TRU9%_3$g}_Kp0#19b}tL<&6fcNDDGf zA7D5;7Oyj$otT@QoVX4QJ?UJkkfov0J zZ4sWrQGl1%h5%7`1pU`@sjc7fTMzj{mN$2Yd93DfDkI4mI5v;7d`&9-Jh*Emr}CIL zL(A5KoEqaNR`YV^g97c#@{7_sCVXxY6POC~BrUT7JWLv1#i;@al9#p~Z2bwVW@jp= zEM`cSc72wM@ktf)X(O;YBffz+% zidn4$K47ZhRn2hICU}{)l9TK7J!~3kFVsGQuuFhPU0DBC>Q_B)4 z&ATkA;t!5cwF<*?a69Y4gBC>XQN|ihL&WApoP~gxNf`myy)5IDCYmN#6NgpC3eX5V z7czJSi&Q#w+7i}`);z&^iQwuhpicBG6f$xedQ}ycR|ySYMl;0UmE8MSgm;K&zA(}T zy8v&HC`Vf80GlZx!@YkGq!%azf*3NmIy+M^_NoRspw-?GBtQpRNkK81_Ny%hJ)J;O zkAjnW1`%HOH2)&|^Q`Xf)7?J3@#wuDZM$Q7>xmr)@^r z#UrC%*h)vvmxJdw-2ZR|_2$-3PX6ko-ZZ2)1$1BU=c Qs3xy(-}Q{5*Yb;nC|xg zqec(Z06gf#K>7Hk;_*vgCQHYs%e~V@?&yX~cLmF?_M)r()5)@{ujuMK!1-z4P`U4P zvG4TuXuQ<-jvjfl92qS}Mz_yQmLgMncdXn!RO}vl^j4|+;%)=$Yu<69_SlZkT;D<0 zchL1Igpt=@_C$-G=;pDKXRyo-ZgYcA{lT)oujr?ecoH6lv+P)qZ``_Pv$lJ7IMjAb z5B2D6$ADu?1o*T#H@KZv6zV7kW5r->v+zjya=8?|R`y-nus;n&HtqLTw_6AG@NvC8 zqWin_NdJSi`)k{$rb?0NU6;MtxnbXFf}KvVdOXk&^*KJHp7hX)sMkc zJ`BZu%&(ct=h)xS+8>xWV*hvoYLB~^xRrg()85Btn7EyNe0HoG8e8p5+`(>jP{P(Q z6L+y&Bco9Ns-KBBvR|Ei+X0QQPcv~Z`}Jr8)c=eaidGZtCOS-Xnb>Hem*Rn!CA~}^ z!MAr2;D5a=4}YJ~xR_vR%p>#-4uPtr?g6N0sb2D^#k`p0ER0d;N$DT%fHSh!q21GNr&s**mh$jTF5j->WoT_%P7Pe8P0FpV~T^sH2iH zn7M?REtq+OnM;^y1btb&NHU^aqFNxyfmgmFWih<+ouZh9Yz*>4(Qu0*ga)}v@rojZ z5LJ^QoGUpXvy?zuDc5rp%;N<_(_?2+qNIWUTYxl46~%{$gy<0JXv_d%0(~y2LjXTR zx~uK>CAfnApxzRq|DI;u2ks%Dd;NN_{o6o`?d0?RF~-*V-DL}7J6&N%LrnrAMhj#x zG1FtK^eq8tE#zNS_qmS*?u@3mgyw{mUA#kU^_>~qTa~k)C-A05G*3{&06SKOVZK2> W{ss+xYk!YnPH+2rzeBWQ&iyZ)h1q!k literal 0 HcmV?d00001 diff --git a/templates/agent_template_backend/app/agents/__pycache__/prompting.cpython-313.pyc b/templates/agent_template_backend/app/agents/__pycache__/prompting.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b5bdcfbaf2061f77f872a3191524f116a42cdb09 GIT binary patch literal 1174 zcma)5%S#(U7@vJ4k;L89BGL-NB2rw9NgoIzeISL}hxA|?&_h|KyOXeXU(8Iv=q1N` zvWFJ(2lSw)7WxkqytKDS4hakGrH9g+AiejSWK&QS`oYf3H^1-k`+dxwYFZY7efYMy z^-f0Ur*MV_zA))@64v*qjLOn8^i-mffy!!Go_2s!3%7%)4@ z77$VfpLDt`iAoi;4*Pj4ON+_vJ!v)po!bZ*m&MB$#6wpD-TeRhNqitzFAC!gUn3*7X+_3t_>r`MrcHo zRNd{+a0nbjKjMZR235CKr`Q1$QxSw~B;Xb&e&k_J3`RWC&v(LNHv14eIEio_BMPCC z12qGe8b09wa$IB$2HDo2nL+%dNvU6$6&u+1ipCSh!mN)2est(3WSHTy&;uq6n)-%Q zk384L6e!t8pB=XbyaDQ0AXS=$6Q2DrdxcRBkSy<^VtbSSV7S@gnYhSHS;U%DLY&TbX z`HB7Az1`MwCx4@xFC0uaANTZ$eQ(ceJ?QB3UH#Sp?P^QM+EPbbZa(^v8|&q#+9Ojv zef-VF+ZU~3`?}fA&9&9JQ{aG}n`t@Y5S_h`Tu4a3M%!R_AlW}4f~JuR5i}UETGrfNxa4m4nA^ix`Xl@YY5Xac zwhJ~|*$G$Jy2m9`Jl>l(^9H6K40>Q|_x`ZHCIH`!IF!>8QlDA!3Uio~DMSRxsNv4k ziClCe5534oe+1O_ZV7f)Zs`3qt06IcE~V03r%INgYmP2gLh8Fp>NFR#bel>uoQbmJ zD{)n&SuD^D_wGbVEw=h53`~!)q|y}%#?ZC7=ETlR%@X9fcv83-W1EPcb7M5*t}=0f1AE545z1o=zb+x<6HfoSKo;Z!?!S7ImjL_L`Ul&;ZhQa$ literal 0 HcmV?d00001 diff --git a/templates/agent_template_backend/app/agents/__pycache__/support_agent.cpython-313.pyc b/templates/agent_template_backend/app/agents/__pycache__/support_agent.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..379742536e06d52ad1735afa5b07327a7f0e86dd GIT binary patch literal 3932 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 literal 0 HcmV?d00001 diff --git a/templates/agent_template_backend/app/examples/__pycache__/ic_examples.cpython-313.pyc b/templates/agent_template_backend/app/examples/__pycache__/ic_examples.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5ee30f0c935aca469b74aa40ed1d12bd13bfb740 GIT binary patch literal 1786 zcmdUv&u<$=6vt4cs+JD*&puA zI>a0hg1@1MoYDgaasweD_e3L3y#+<0)}D|!_2wv42u{3N+og)AK-?HsOm|bVP%uLR5O(yYkvo?xV}aGmKSndr?_0g7jZeF zz6eTT5V6ps1;Z$pIA*lPD5pV0d`vgN4q5`*{NY>24GZ|XM>!^*>kn$mK_g^7fmA@e zMOj*>WK4(-@<*0#5nzY% zg6r%`amES#Rv19bt{kVR??S5+D@_{Z6$YpyCXh{9xwuk{aa=7%eyiw#uyDONSg+-g zNU&pvj#~s6MNIiHIg0HXaW-gBFOpWPINC?ib?lK}Xm#RQI)K}55MY4VaB$xCB2p)8 z8a5!##3{%l^cm_IlMjvDk4CPSKJ#;KekZ@3-(Ps;Aa~_qZl!zq`=xtJ_p1k2S9-H& zc4oI{?_NEaE%s)f-^pxe?p`>Ux!B7s9A>nP(H}!oxIclibA6P_>c63krXLCUoln9_ zpPTb$UfO#>GG9{n&g$UDS>hxgA^^B!Ioxo>RgFdloCMS!IR7_LD+-{h{X0}A09CVO z-V~sgZdA(GOeW5LjJv#cY=S)}icSd`7ji<#Nf1ExFXWySaWMznVKxO$d}_o8aY!iW zaJ;q)aLhpRG(3C_sqS0fe42#&tM3o&2V`Iy>yJeln}-$u1mgFbE&u=k literal 0 HcmV?d00001 diff --git a/templates/agent_template_backend/app/examples/__pycache__/mcp_examples.cpython-313.pyc b/templates/agent_template_backend/app/examples/__pycache__/mcp_examples.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..223d915372cbc36477791d2922b7e8936780f8ce GIT binary patch literal 1928 zcmb_d&2Jk;6rcUHy>{HjjzZQ2x@iLHLSrM^iUufBl(bSxl9ntv070wG?l#?Iy|bMi zm!v9E1b+dC3VLcPIFv)BaH2=5^iXb&e6Y3RP>EA-j-m=7F1&HPG@u#@iIwKf`K=5&D%0$(QRgdl!JYi3~)L!5RGf+y$QS8Zv}M-Vk9(i^3wz zr+5kc40%yBQh(E=!Im>JCA1NsXBjiOx zZ=mPqMCtAS-y;~=aq*vL2;?5Q%l#j@`y`dz#e!_~8R&HPmW+H^>X}n{6s6JJ7K}?U zl(&S-oPKC7J`8OD^%q&x!-2W#uE$CQmCFRrxn6*-g7z{$ym)cOoSc}MIX49*>IPOo z!5pV8)@{J)B4HIt`WVSG!XRGHxoZ}*sT-A{;h+IBHm-&^fHiTd+kWxkrCQ_#5He{< zohMMAC_apdx9kO$pBS#^0nI@qJYLnM=LIH3*gzL!VcCU9Zq);fb}ZVK@5d?7S(Fkz zMc~OogjE`m8DWq~nh|yu)^<_Nrx8A%Kqs?-=dc>shZgKDwI7- z(8y8On8@amWv{w{Q?bIZl4PaAo}ol2hwE|C-GA21?n}eoDN=&??3s;DKqEgv8>rPk z__g|#T0b(@=r1?>r?+1DQU5`|Kht<~dgH=lrO;GLJ4&gQe(`Z>w57g$TfL=jzSB@E zt)Z80XKrOSUuz7FwbWy~87ZT*bEr7n?nl`pZIsE%zoCpI0|#Uf4IFJ2M|O%Mt#p4Y zeW+DBdRxDxZ%#K#Tz6^50OA(Sg@% zJ_gbjIF8%pMNWL0L);5(d6+BQJ=sPu-5=>pKRJ(1)-M^4&O8V@a@}(FXB^e(XZ7_P Yb$Vk5f8Nox5t`)B^H14wqQUlm0#fF{Z2$lO literal 0 HcmV?d00001 diff --git a/templates/agent_template_backend/app/examples/__pycache__/noc_examples.cpython-313.pyc b/templates/agent_template_backend/app/examples/__pycache__/noc_examples.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..683dcc407edb6ef24e5afdc9295ac2af101361c0 GIT binary patch literal 1875 zcmdUv&2Jk;6u@V_AGX(aC`}2q(~l`GkS$;b+O&!aNLEyd58+B#j8w_6Y_es9LNEQ-pq+cy}$t}D2JLkA#v)>m8lS%c(e9WR8hf!3nT5D_vXEQ z^Y+beXIv=g2*&5%U26ZRAoQ~^l98V(<13(CM<$}kl+4td(i7QglQ`YX$8qUa)+qG8hg27?5x?6B8FmS- zUtFo^I!H`Oml8&T$i@-r5T8WUvvJrZ)ONfuusaoe5nm1I`dTNviro;RAd4Ll+`j!8 zCcX!n$hNrn;`1UQC`&k5I$OaPSVR~m$=-g*B5M1XY{HgMi|u;xHXg$pJ%Vk_HhQ*; zyP->Tk=&!O2bgiNLvMc>c}~bG`d!gTL*jBR=<`f?nGw24C|6korSY`g^{gK4@SL!F z%yJ+f*G1WZ)3= z(XU7g(?$1j1eqQYFDLPj@GJk|SO4^<8gc_jsc2@}%q+{V16x<1!cFPATvzi@K?`M2 zT|FnSNk+EL<;BI9_yH#jpli&I;H0f}(&suOj0wYYc@~;w2a!-Sb_50Ol<42?W~i) zt=ipgHL0o!n_G!d>Gt`dDXEqq$O|^@j_2C61l#9C9qSd=ZfCc$H%{#zeqlIw zbXa|ip%ZNt%+Vq!d@dnl0=H)aP=OQDZe)1cvRq)=;#$=2dO?edYl9epVZZh@(h>TG5z(V#Ie7ClP!4ZKgOyvegV%0;6q|eRT$;?S P_GAv(eGx{Vfdc*jsKvb2 literal 0 HcmV?d00001 diff --git a/templates/agent_template_backend/app/examples/__pycache__/observer_examples.cpython-313.pyc b/templates/agent_template_backend/app/examples/__pycache__/observer_examples.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f59adf5482212fcdb33402abf7b65fdea9cb66f4 GIT binary patch literal 1384 zcmbtU&2Jk;6rb5$uh*MY-~?JF4$@XoV5_n&A$*B~6dS9!q;};YRST9@oAuP))ZSTU z))DRjRqz*Zs5m2WpeNcBIg)ZI7t|<+SaU++)SIKILfm*0yrPQQa^Okx_I>@{d-LAd z`$vx02jIgM)$B6?dRMq6(iZEZ=*K@7+3!6@xx=68_6~X%sT2!<^=tpnQXJCuBTfJV>Jr=GpeDpbix& zbprnR08HQpV90ub1K=d;1Wpq0z^PYHIgMu3q0ZUXif2Dijk;Ls%kfZJY?52%L>i=2 znw+Ks9ydZwg-s|A->>29U&RQ$h#a`zDsF*%m0u)JrQ?sSWMoK^^E3i;d{?i-)UQI04SZ8?g|i5!@V0&hyVDK%Y{+6*ynvOn*uO@H(iT(*r9_?UGxe%hyHq=WVWsIe zYG<3(dQ)0Km&Pf0U5yu@N3LG%^&m3i8apl~Ry10iOa)a5;Z8mbw! zjDsXXt=e%MxmHg5Nx27%v-R>n%_qGey~23kXJJ%MDKri*Q%szh?ym*mH5zxyL6VfS z>y@+X`I*H_hVrOtDB?cdfV(mQNCCer!Jmg7)QrS$z#gC*XjDA;U1u{Gl=`8{0mjIA>#LB9JkuADaRzG5ob!-}6)+%;w1_d6`~eQBn{B#OIS zZA%)RC`_d;QhqSGTWBAwh(D)%)|o!*40`kfz5uHF4-l^d8|xV3Jx#~PLmS~^V{;ap eKfEzU@VT9CR)3z|{OHb?2kO0-9x5v@0qSpRm`YLr literal 0 HcmV?d00001 diff --git a/templates/agent_template_backend/app/observability/__pycache__/__init__.cpython-313.pyc b/templates/agent_template_backend/app/observability/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f8eaf367d427427ece782c1a27d6492ec1301d35 GIT binary patch literal 191 zcmey&%ge<81WQgvW`XF(AOZ#$p^VQgK*m&tbOudEzm*I{OhDdekkqX>{gT`Q{Tv`s zl%J)an4X$f5?_#$Sdx}slpCL)oT*=unhWBm7DFXr{P?8Ap;#a1~EP|Gcqz3F#}lue3vyM literal 0 HcmV?d00001 diff --git a/templates/agent_template_backend/app/observability/__pycache__/telemetry_observer.cpython-313.pyc b/templates/agent_template_backend/app/observability/__pycache__/telemetry_observer.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2b6330fdfbbe9dfab9bb0fd0d972318506e8065b GIT binary patch literal 4802 zcmdT|O>7j|5q__`=YQ-m{=qgexQ!jd7(90PTa1=vjj>oT49MdlAaCfjXL`nM#@*(1 z5A2W}&_;?J}`{W zrd+RH)%&Se^}T9V>*|68+LwR5G&33|I^V|a6rwvHmeG=CP@#tB1!7WexZj% zrMTlu#>e94ejJZVuf$U$u~*XY$zh=JbI6|@KNDjCn0!HWM9XH52Z%oB1T9--wsl>% zrx&OrrOdQu;a+8DdcdBY8^{4+=FEVa)eKvi%c*w8WV4Ey(g$p97W08i_I0BM#X?R)tr7qQ}mRAdtlA5hdTIK zM}XWUk3;qIb~z&7DlCV(%AsAQ(5~fB^yZ0`rq;X1ZXYXlA1^nJl$u874-}>3(*7lT zDYMi8>?&B3|G8@u)~-#AGe7K*g^=KR*%lz~4X`F)ou)UOW0+O%w8BUu{8op6yw^MT z50#txN=<#}oTH09i}qq>u?|?)0@nWr&H=#q4$jH4oEtjZKph>#+K{|;M$AB-x4#4C z4(8li=7ONwUT5rD&Vg1IE^bq8@vRI+ZRx*gdp6bfW_?kAJSy-kWeAs!pr?3bI3nCU zLpL}g9Mkb=*B~3Clr?||uqnvsg63Zz>Blx=w|e)*6B#v>F(GN*GNLCL|~m69SVG zVnT}1M4gM$GXnDfvezfP@Xr?$b^y+=|Cfj$Ue8D~!VCp;uTL~20=!R|kN1Mro}c$Z z)m|VG24wd}w>TXWHKaL?V3Zz4_&Ue2NMlcXVXNxT;EGN#bFN=EWGfFtwrpnPb&4`a zva~!^kk{smabL%R!4swouZBFApUUahv}{i^GoPK7hYdAXu=SL6-W|}GJgqS;_IjXZ zq8nLxRxQX=S}$L8`j+`TPAA2Myu-p=xZvIb;E*Pb7R;? zHm3!db~UeAw#@BgK_}uqDH*2DYS&G61>)FinR%AdQq@}?DY-qLJk4H8PK(|)!Bt1!QLWp?`-wxaZV%df^jxXSbcCmF5A)PlkSv3F> z;*fX>ioPsSMU>Dqm!?a zAxFhdlA#}n8H%--Xsg+%#|iMHm;hYn5mD`*fW&pADKlMQ{jeOAqZkbq$skR&2oiM^--U;A(ql}p>>Sui@meI;L zrxtRin&xY|^X@~rgh9*D+`@~7)Zop{4@sNVZKvy)qJxSh=yc z)YyCf#-9sI+m??_6dQYsjmdI2SqvvDe$vn~AAS}fbrD{k#y0Y zE=AJct|Eqf-qTP2ME6J!Jm0U^w!>Z~*fF3`jd7pxa`IykY;cjEy9kQ2_dFjpwev8= z>RV3Qw|HQ=^YB-@if6Q?&wvy!T`vAKT{@$olQQKrf%BY1f@cJ`>!^=1H~JWbZ- zb#3`9d{bz|;y|Jy2kHp}>KUjff{HS0rt>-E!-{e>ujZ;F5k<-9%(8P(pBW|$`xGS& zUv>)HgER4f8%Ayv$tfUCi=x1HI#iX4YQtyyR30inMOkpaZg{q`Zd68sp0ln3`6YQK z_&okfBN;kPUlPHyy;4v5VijMjr==1kdj?jvcRj0v&XeZWiU);W(zvTO+_bwALRpw> z`9TG>z&P0UA_Cltebl4T7hdYg(wCe*BgpeQi0=XZ58@4{aR;IMKq|u@_ljOiL+Gqb z9_|yI#YEIVByt}+FMQQN0m-DKVM2F*L$*Hm3iJYf9u(+DRpO&fFPmdDOaDob@_GLQg(~-- literal 0 HcmV?d00001 diff --git a/templates/agent_template_backend/app/state.py b/templates/agent_template_backend/app/state.py index db5369b..754c71c 100644 --- a/templates/agent_template_backend/app/state.py +++ b/templates/agent_template_backend/app/state.py @@ -33,3 +33,6 @@ class AgentState(TypedDict, total=False): supervisor_handover_reason: str output_supervisor_results: list[dict[str, Any]] output_guardrails_already_applied: bool + long_term_memories: list[dict[str, Any]] + long_term_memory_context: str + long_term_memory_write_result: dict[str, Any] diff --git a/templates/agent_template_backend/app/workflows/__pycache__/agent_graph.cpython-313.pyc b/templates/agent_template_backend/app/workflows/__pycache__/agent_graph.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9ba65e93349baf76d20a87c67b27fdc13bd2b4f6 GIT binary patch literal 44723 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 RUdd2C7>?ARAB~_k{{eUE0rCI< delta 5071 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 L*H5-qY= 3 + second = {'tenant_id':'default','agent_id':'memory_test','session_id':'b','context':{'business_context':{'customer_key':'MEM-001'}}} + values = {item.key:item.value for item in await manager.load(second)} + assert values['preferred_name'].lower() == 'cris' + assert values['preferred_language'].lower() == 'python' + assert values['current_project'].lower() == 'atlas' + isolated = {'tenant_id':'default','agent_id':'memory_test','session_id':'c','context':{'business_context':{'customer_key':'MEM-002'}}} + assert await manager.load(isolated) == [] + print('OK: persistência, recuperação entre sessões e isolamento validados') + +asyncio.run(main()) diff --git a/templates/agent_template_backend_day_zero/.env b/templates/agent_template_backend_day_zero/.env index f62a8f5..ffeb4f9 100644 --- a/templates/agent_template_backend_day_zero/.env +++ b/templates/agent_template_backend_day_zero/.env @@ -175,3 +175,17 @@ MCP_GATEWAY_TIMEOUT_SECONDS=60 # MCP_GATEWAY_TOKEN= MCP_GATEWAY_AGENT_ID=telecom_contas MCP_GATEWAY_TENANT_ID=default + +############################################################################### +# LONG-TERM MEMORY +############################################################################### +ENABLE_LONG_TERM_MEMORY=true +LONG_TERM_MEMORY_PROVIDER=sqlite +LONG_TERM_MEMORY_SQLITE_PATH=./data/agent_framework.db +LONG_TERM_MEMORY_TABLE=agentfw_long_term_memory +# For Autonomous/Oracle, defaults to ${ADB_TABLE_PREFIX}_LONG_TERM_MEMORY +# LONG_TERM_MEMORY_ORACLE_TABLE=AGENTFW_LONG_TERM_MEMORY +LONG_TERM_MEMORY_MAX_CONTEXT_ITEMS=20 +LONG_TERM_MEMORY_MIN_CONFIDENCE=0.70 +LONG_TERM_MEMORY_AUTO_EXTRACT=true +LONG_TERM_MEMORY_INJECT_CONTEXT=true diff --git a/templates/agent_template_backend_day_zero/app/__pycache__/__init__.cpython-313.pyc b/templates/agent_template_backend_day_zero/app/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..eafedeeb7fe800a4d544ac4403d9f6b173ee6671 GIT binary patch literal 186 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 RPjd6!8pB@~<+zQ8{}-oiu!#Tw literal 0 HcmV?d00001 diff --git a/templates/agent_template_backend_day_zero/app/__pycache__/mcp_gateway_client_factory.cpython-313.pyc b/templates/agent_template_backend_day_zero/app/__pycache__/mcp_gateway_client_factory.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a8f0280db78b0089151ee0234a0faefa1c84a8e4 GIT binary patch literal 1034 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 N!@|C22>v4n{{V^SoSpyx literal 0 HcmV?d00001 diff --git a/templates/agent_template_backend_day_zero/app/agents/__pycache__/orders_agent.cpython-313.pyc b/templates/agent_template_backend_day_zero/app/agents/__pycache__/orders_agent.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cbe2d5df6826d1114751028c10bea6eb013b306e GIT binary patch literal 2459 zcmb7G&2JM&6rZ(sosI1{Uv`{00TyV8+=3GX1{EPh3ONA9pe#nAMVfBxiP^9pW@eq@ zo)Ab?4mtD^wU<_+tyHLlRH^?!QBK?vrBzm|Rzm8DOXW0h;MO<0YZIzoI`%%lnfK<+ z+xLF&jdyx_;t0m~&u%O}>q6*HK4}jrWOhyfvx-zikt(Q>OTxtnjVPRtUW#56si>e~ zbO5Ph4yn>ju^0{ZQK=TocQw9)S^WI;4dtVm*(=Jmnafu{oW3@r98s>&Dxqw;Ml2^D zpJ8_z#2}8Xm}^fg-L#d(wTDW@HtU+R_GF0|oLA1-))MT|9No6mhG}ZFd6}3tZ5HBc zW1cy>)6f)KsZ-eJC`8c|N@`S7szk9!ZSCiZZi5|b!KNkwJw_Ue#?L2(_zUiIUhr~S zy>2wIzl_1qf^HE03kE^j@4GwOupHeam-V~4RS8};ppkI0a|W1IbQ4hlq83pF8dW1y z+>74+s4|b)qCh28${`w4V?evqE-Kfe`ED<zN)cIwew-H3J8?->A*V9b4C$Zfuc z9t$P#H)W0@I%a*+ z07C7>$uPx+=5RbJ@meFh{_n;7tvQP|HFF;w}0u(F> ztI?8_g#ECigNn93LS^CO4kpTiDilK>%8_!kEUJAmD--Fv4ZptylzQ_XV1y#f{^bjHLilf0t7 zYZwj-kPKbKA$J`w$zcs}*Q%PMQC=!uFEl&Mn;o7$ckROU87~_^7>9d0>UqhFX%_nw zP#?Fz7-kM&kB)L9QLsz*0S8J$bxP{+3SlXP2hB$#>B-mrMR3Ued44&?7nq61s;jjc=x=w&baGdFuIKc5Cp^#^510+5da) zxZ9U;`{s9~=xC}XqmglU_@Fy7-s(r`i5BWk$Cu^S5E>ra$`&@Vg-4B_%wOg4smo}IHmf(rY;RpsMWSY4i5)&clDIe84U7?DEXGnUSqbVIu<( z+{_fWdW-9Faih2Rn(=meKeJc(QWz7zlJ*Mu?x3amz1HuheqZ%_t>0#ObA8i<^KvO1 za0x*7vow=H_lsj}+SLYJpV&)c43%btl3t7#bjlnfxPg$1@t1lZ?)1Sr2c z184YOr8f?G^64N1XXq&q@Se+fQMaOkAZ$k@L41`$!s$QI)XUgO;l%pD!B+@=eFOgh DGN67X literal 0 HcmV?d00001 diff --git a/templates/agent_template_backend_day_zero/app/agents/__pycache__/product_agent.cpython-313.pyc b/templates/agent_template_backend_day_zero/app/agents/__pycache__/product_agent.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d394a5bffdf0cb7a16d3842fc00bdf991d7d38d0 GIT binary patch literal 2564 zcmb7GO>7%Q6rT0&IvdAvk~)s_*D~}kG6W}8n^sg+)lhP1OVg-ZqwpgQoAt!qvOi{K zH`JUekWddfaH-$|5(3l~2?+^t2l>RUQ9)U)K&r&47vvIp;KrNXwVNU?jJ?k@^WMC9 zJMVqpcxP}hhoF7=(`(meHH3Z_gKksP&dzb@+(J5HNSE}?1?hZ-Ww7YW7v%E_Q!pAw z`;o3pB3*q`S&)-;Ol@QEg;Oi&vKL3wVWj^}zHO8`Sl4 zxh4KiKyB)K*lB;`T8@XWwLizE=QIt!{mlloMIWE_+zraO;ai@o2aaQ~)mA`F^7U3rvWy^{$>Hd$=fC}lMZ%HL+2KH6EO*nEu%|J)-z1m zho1iEnmDv;5>s_`60xkF1=*+fF|8q2`lI~rBYKzzmr))f>7ClL87D8@Q|v8{ey0iI z7Fr#)Qw2h+*p|A3)p@%cC0adWr^y1F6G36?l?hl8h_4Na568X9KV&6NkYDg(wIyB*d@~BSP#Eq6mVE1H!!)(K+8~&e$2Z#noD3}5xk_Sd+57Y9y?XiRyN znwDW(+&3_F1S!-5-{Z3wGVFRB=pnAg_HB<>@zV9CZ2<+roP*a5NHO--DTAf34)|sR ze8f6>otlAfFl@AMx4)-6o)ny7F-J^&;Z<>sQ(QOdu*b!-EBKwjaO)nfTfFJHg3Byk zttinTZ*eHElv|2*KK3*Z2tjJg5)3NS`PK&V6zDp@>x<=m@F zQ8@t#N!RquD{7QAT>dWLD(8YT0*3)$oDxoC@XMZo9vF=^8EqPjreN#(i3)3hW!GVr zb3hAcqAa34P~98e+mry@NetwABsZvE>0_fpug051`566aL~I%~Y_CvjJ#QF>T+~a7 zP*xIpnFVe%{1k9t=8&-c3dBP|&4cp9&3rgEd2=8Xy-pSl6}JnOtwQC_q5FmTZEgOZ zHvgbl-Yy>ADjp8=Bfm}_3x`Xc3>rBr@2K)rp`)RRL*e*=aNQ=dWC-~O=;p+YJrR|YRH?t4Mro+kS!>O6D_)Iu6d%N{{>)w&`_h&9V8psak zIw;$pd$bqzA9*y1_N+*M^JgK__oo&Yq)(;ABg$tdp!Z`*T0E-!I5sW%MnqxF+TgAMj&%)(0>|bEaRAzIO_L~32*AxwHA92|EV*x6 zE_FRnD?-7>gxBYUsDg+}Bu0g062teIwFb8pC*&?-XW0p8_GHtHj!?wJtKC|o1e z_#Wa_AR73S0k>g_vRx{ufANQdi5ofzM*Jsx5@4jAN{-+(I|&Lw>|7wbBTJI>D5FZs Z;{uXS{f6fM%ASyp-zy$`jNmtR@DC@@r=I`- literal 0 HcmV?d00001 diff --git a/templates/agent_template_backend_day_zero/app/agents/__pycache__/prompting.cpython-313.pyc b/templates/agent_template_backend_day_zero/app/agents/__pycache__/prompting.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d96c6e4c1af487e002014819ffbfc640032fcfa0 GIT binary patch literal 1183 zcma)5%WD%s7@vJ4DVy%bN-0_>ETu@(ril*$--(-hbWN zek&vNLpUP?UzpUNhV>mPqq6h_J(j3sqOw|+r_r`b<(iUD{Ssk$DbC^`2s!3%7%)4@ z77$W~pVVKKM5PMag#E0QrRC)Ijx?Wu&P{~Oi{j;T;-RaFuK$1iBtDQU^0}Opi+co0 zML9P|y(p)g+7y#GW6Oyjs1^0EZs1js=1@g7P&r+Z40KDGL-|boCj_rKt_>r`W@tu~ zRNbA>bO;3^QF8dccH1Q{Qyz zk>}c&0ww$SljGKaH$a0bgqegzzzcisuE;2Au|6aY!m6%E;tWXRWQWINBVw`s53?BU z%9@s!W2HuToMN21Q9dOOAUifd7Os07WQakgN26M zL#dV+U^S$^720lTG*Fg+Bj0+7?N=mlEC)BNT|&bWj-q5%rD1b7s1>6|JPm-?u>O&m z(4!MTM1_wvVQHe%ag>`paQEF-u4Bx14g0Y2Ve4q?W23XQ)?HfvyxA>moD?>`Se?QP zK-<`EuJ>|N2YdT_t<_HMS~piXoNYer8B+(|zSp|fF&4YVjYHbiS5EYmj=tJ_@I5=x z%gwaMW_rft>*sHtwTkVl`F3`ptuFL|1A3ZI({b2i72Y4Gx;QE@Y6nvYze2|cUjkdw=K(c>81Wh9sB4|*sdaSukxa4m4c(;d%oj<~VVB=4* zv|X^#%1*e#);%tH#W1roZys#f?{~q?>-+uck^y{IVn2BGsg8<7o>AZ3bN>nXp@UzKG4=_^e;r_V>;rDUy5r|I^_=w&GKz9R literal 0 HcmV?d00001 diff --git a/templates/agent_template_backend_day_zero/app/agents/__pycache__/support_agent.cpython-313.pyc b/templates/agent_template_backend_day_zero/app/agents/__pycache__/support_agent.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9dee7059da020e37fd2a806601d770b8999a8d6d GIT binary patch literal 2461 zcmb7G&2JM&6rZ(sosHu-AGYJf4zLA6uxo1eC=nv{ciLGcg&TOo~#nX zj>o60I}Xu^ZA$w3Q$y8FX=(kTR5A6MVy{14CK}_V^QN&(sHNDdX~>SQE3|%@=q9Zf zMd za!ReH)v>>f!O)_r5%voPLE7iLJL?#>s*}s=UDc=rZyV4^IBA{*W)0m$l!LHEWR6DV z2<7*qy&qX*L0jUeAPYG}V{!~=hulHMYP8VlC3c7CMFLEE-Bu8_z`vP_k6w5vJDkP7 zSp%|$=6bbIfY6+zajnVRgw}Es&7IRiv_R*o#`PJxtbPhuiw)kOM)@?xhA&w zDtf}rfX7iy)4fg`fYgai>t2^_ni{62V-xCiQKgD4LYLJF@q~&}xkWtDBDSp>Rm&62 zc_stBnC?UBC9MEq9Lx)GkGF`nNW0)v%A!HL8A&qI!$>b9eT?)ok^*9}07>oPk#SqE zjcY)txilVvtSL6j2y9l=ahvEhm=h}$w&r+VsoW;U0$xz+_)|j7ambTE#~R-QO0ib= zM2uBKwK4WGyD==bmYKWO2_W}SV-TgYtD-w}a5d@XkNheQhBT8~jk|-=YIi^ehF81X z{bRq0`6lc`l{SVjVK_X1r}-CZyLZabve4=b|31%% zm18mwR`@bE+xcc!i<5S~QD!RxOI%b?{P`qwd*1wn5pGb}#mNYAR z00vqNVeGO($r7{%1=@j4&5AMxtIMY5tUp@+)`VVB1qqb3QY`RLW_1gY59{9R(knGg ziREav6{ImV5u4n#y#&i?$aQ5wu@%bd#Or}(hgq}3ljpBpygub+gZ#zep0;{H@M4N# zeF7;TH^3OSY)CwsXGWr6mmUBPlm=>)fZHUjgO(jHIq#_20<2mVB(g;rVqQNa-m-a{ zV5mgw*Vuv=trEM?LDNhw_=k82zc53O=rBm=J|-61)+Cr(49c*0Xokt7)G@r?Jvj|4 zBTV-jATJ=HpJ#_w67E25rORbpBZhiX+sVRavha9hD><<(PHc!1&r{j$)RE295jWBI zd+vnWn{j(TY6{VOvLT|O5qI#AJ2cYhL+Q~5>P*L1#Kr&`9Nx|rH?zgZ&JV^y0;1tTVo|4|N{f$)WzM+-vJC zaPxkCvc!GGO&;UFJ_X#f3^#e4e>O18_~RVVJ=MkH`T(BhIUqrG{oji6bCzfMN?AjK zizE#&Y8t87nB`UoL?PmXL`Ol#V#Os%LD(~G?cKO}2loK+pE~a`Qw(1Qjhh+T&J;H@ z5W=lYX}hPiA(l3KO0O-}P48#+b6;@7{FlOhuFx5DRKL~w&D3wJeyjDnEbFardT>FU z2?yK((EAKUC(!%i80+SO1NSHP5*Xic6fKnWV7#bO%hptb7$%5$CZGe%>f4MIfp{70 zmxNluifvPM9&Ql}<0ps?G8LAOe#QBOy@53WNF>u42-eLohih;4vZJf3OJO8_FnYw2dfy|{%}i$CALch$($gSd4cB62-H37=w-XUK{#6ojXZ}DFFJq^;lN+hSuMqtD G2L1svB7o}v literal 0 HcmV?d00001 diff --git a/templates/agent_template_backend_day_zero/app/observability/__pycache__/__init__.cpython-313.pyc b/templates/agent_template_backend_day_zero/app/observability/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..359dee6e45c3f33c435089d7c5c4141ca2ed9494 GIT binary patch literal 200 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 CR{LZC literal 0 HcmV?d00001 diff --git a/templates/agent_template_backend_day_zero/app/state.py b/templates/agent_template_backend_day_zero/app/state.py index db5369b..754c71c 100644 --- a/templates/agent_template_backend_day_zero/app/state.py +++ b/templates/agent_template_backend_day_zero/app/state.py @@ -33,3 +33,6 @@ class AgentState(TypedDict, total=False): supervisor_handover_reason: str output_supervisor_results: list[dict[str, Any]] output_guardrails_already_applied: bool + long_term_memories: list[dict[str, Any]] + long_term_memory_context: str + long_term_memory_write_result: dict[str, Any] diff --git a/templates/agent_template_backend_day_zero/app/workflows/__pycache__/agent_graph.cpython-313.pyc b/templates/agent_template_backend_day_zero/app/workflows/__pycache__/agent_graph.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a39b5852a026f642b44beac29cda2ef94d232c11 GIT binary patch literal 44732 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%0Ds~o2y=2OXu@oh-$T(Akr_139d4f~| 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 P-!t*~_c(%VTKWF~9eL!V literal 0 HcmV?d00001 diff --git a/templates/agent_template_backend_day_zero/app/workflows/agent_graph.py b/templates/agent_template_backend_day_zero/app/workflows/agent_graph.py index 0e58ed3..bdc8e13 100644 --- a/templates/agent_template_backend_day_zero/app/workflows/agent_graph.py +++ b/templates/agent_template_backend_day_zero/app/workflows/agent_graph.py @@ -21,6 +21,7 @@ from app.state import AgentState from agent_framework.rag.rag_service import RagService from agent_framework.rag.embedding_provider import create_embedding_provider from agent_framework.cache.cache import create_cache +from agent_framework.memory.long_term_memory import create_long_term_memory_manager class LegacyOutputGuardrailRail: @@ -94,6 +95,7 @@ class AgentWorkflow: self.settings = settings self.tool_router = tool_router self.summary_memory = summary_memory + self.long_term_memory_manager = create_long_term_memory_manager(settings, telemetry=telemetry) self.guardrails = GuardrailPipeline( observer=self.observer, enable_parallel=bool(getattr(settings, "ENABLE_PARALLEL_GUARDRAILS", True)), @@ -121,6 +123,11 @@ class AgentWorkflow: self.product = ProductAgent(llm, **agent_kwargs) self.orders = OrdersAgent(llm, **agent_kwargs) self.support = SupportAgent(llm, **agent_kwargs) + + # The existing agent constructors intentionally keep their stable API. + # Long-term memory is injected as a runtime capability after creation. + for agent in (self.billing, self.product, self.orders, self.support): + agent.long_term_memory_manager = self.long_term_memory_manager self.graph = self._build_graph() def _node(self, name, fn): @@ -143,6 +150,7 @@ class AgentWorkflow: builder.add_node("output_guardrails", self._node("output_guardrails", self.output_guardrails)) builder.add_node("judge", self._node("judge", self.judge)) builder.add_node("supervisor_review", self._node("supervisor_review", self.supervisor_review)) + builder.add_node("persist_long_term_memory", self._node("persist_long_term_memory", self.persist_long_term_memory)) builder.add_node("persist", self._node("persist", self.persist)) builder.add_edge(START, "input_guardrails") @@ -172,7 +180,8 @@ class AgentWorkflow: builder.add_edge("output_supervisor", "output_guardrails") builder.add_edge("output_guardrails", "judge") builder.add_edge("judge", "supervisor_review") - builder.add_edge("supervisor_review", "persist") + builder.add_edge("supervisor_review", "persist_long_term_memory") + builder.add_edge("persist_long_term_memory", "persist") builder.add_edge("persist", END) return builder.compile(checkpointer=create_langgraph_checkpointer(self.settings)) @@ -598,6 +607,10 @@ class AgentWorkflow: ) return {"final_answer": answer if ok else answer} + async def persist_long_term_memory(self, state): + result = await self.long_term_memory_manager.persist_turn(state) + return {"long_term_memory_write_result": result} + async def persist(self, state): async with self.telemetry.span( "workflow.persist", diff --git a/templates/agent_template_backend_day_zero/scripts/test_long_term_memory.py b/templates/agent_template_backend_day_zero/scripts/test_long_term_memory.py new file mode 100644 index 0000000..52e2a8d --- /dev/null +++ b/templates/agent_template_backend_day_zero/scripts/test_long_term_memory.py @@ -0,0 +1,29 @@ +import asyncio +import tempfile +from types import SimpleNamespace +from agent_framework.memory.long_term_memory import create_long_term_memory_manager + +async def main(): + with tempfile.TemporaryDirectory() as d: + settings = SimpleNamespace( + ENABLE_LONG_TERM_MEMORY=True, + LONG_TERM_MEMORY_PROVIDER='sqlite', + LONG_TERM_MEMORY_SQLITE_PATH=f'{d}/memory.db', + LONG_TERM_MEMORY_TABLE='agentfw_long_term_memory', + LONG_TERM_MEMORY_MAX_CONTEXT_ITEMS=20, + LONG_TERM_MEMORY_MIN_CONFIDENCE=0.70, + LONG_TERM_MEMORY_AUTO_EXTRACT=True, + ) + manager = create_long_term_memory_manager(settings) + first = {'tenant_id':'default','agent_id':'memory_test','session_id':'a','user_text':'Me chame de Cris. Minha linguagem preferida é Python. Meu projeto atual se chama Atlas.','context':{'business_context':{'customer_key':'MEM-001'}}} + assert (await manager.persist_turn(first))['saved'] >= 3 + second = {'tenant_id':'default','agent_id':'memory_test','session_id':'b','context':{'business_context':{'customer_key':'MEM-001'}}} + values = {item.key:item.value for item in await manager.load(second)} + assert values['preferred_name'].lower() == 'cris' + assert values['preferred_language'].lower() == 'python' + assert values['current_project'].lower() == 'atlas' + isolated = {'tenant_id':'default','agent_id':'memory_test','session_id':'c','context':{'business_context':{'customer_key':'MEM-002'}}} + assert await manager.load(isolated) == [] + print('OK: persistência, recuperação entre sessões e isolamento validados') + +asyncio.run(main()) diff --git a/tests/unit/test_long_term_memory_autonomous.py b/tests/unit/test_long_term_memory_autonomous.py new file mode 100644 index 0000000..59e4d90 --- /dev/null +++ b/tests/unit/test_long_term_memory_autonomous.py @@ -0,0 +1,44 @@ +from types import SimpleNamespace + +from agent_framework.memory.long_term_store import ( + InMemoryLongTermMemoryStore, + OracleAutonomousLongTermMemoryStore, + SQLiteLongTermMemoryStore, + create_long_term_memory_store, +) + + +def settings(provider: str): + return SimpleNamespace( + LONG_TERM_MEMORY_PROVIDER=provider, + LONG_TERM_MEMORY_SQLITE_PATH=":memory:", + LONG_TERM_MEMORY_TABLE="agentfw_long_term_memory", + LONG_TERM_MEMORY_ORACLE_TABLE=None, + ADB_USER="user", + ADB_PASSWORD="password", + ADB_DSN="service_high", + ADB_WALLET_LOCATION=None, + ADB_WALLET_PASSWORD=None, + ADB_TABLE_PREFIX="AGENTFW", + ) + + +def test_factory_memory(): + assert isinstance(create_long_term_memory_store(settings("memory")), InMemoryLongTermMemoryStore) + + +def test_factory_sqlite(): + assert isinstance(create_long_term_memory_store(settings("sqlite")), SQLiteLongTermMemoryStore) + + +def test_factory_autonomous(): + store = create_long_term_memory_store(settings("autonomous")) + assert isinstance(store, OracleAutonomousLongTermMemoryStore) + assert store.table == "AGENTFW_LONG_TERM_MEMORY" + + +def test_factory_oracle_alias(): + assert isinstance( + create_long_term_memory_store(settings("oracle")), + OracleAutonomousLongTermMemoryStore, + )