Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ agent = Agent(model="oci:openai.gpt-5.5", tools=[retriever.as_tool()])

→ [OCI GenAI models](https://locusagents.ai/how-to/oci-models/) ·
[Dedicated AI Clusters](https://locusagents.ai/how-to/oci-dac/) ·
[Oracle Database 26ai](https://locusagents.ai/concepts/oracle-26ai/) ·
[Notebook 01 — OCI transports](https://locusagents.ai/notebooks/notebook_01_oci_transports/) ·
[Notebook 38 — Oracle 26ai RAG](https://locusagents.ai/notebooks/notebook_38_oracle_26ai_rag/) ·
[Notebook 42 — Oracle 26ai checkpointer](https://locusagents.ai/notebooks/notebook_42_oracle_26ai_checkpointer/) ·
Expand All @@ -150,7 +151,7 @@ agent = Agent(model="oci:openai.gpt-5.5", tools=[retriever.as_tool()])
| | |
|---|---|
| **[Tools and MCP](https://locusagents.ai/concepts/tools/)** | `@tool` on any function; arguments coerced to the declared types whatever the model sends; `@tool(idempotent=True)` dedupes on `(name, args)`; `MCPClient` consumes MCP servers and `LocusMCPServer` exposes yours. |
| **[Memory](https://locusagents.ai/concepts/checkpointers/)** | 10 checkpoint backends (Oracle Database 26ai and its versioned saver, OCI Object Storage, PostgreSQL, MySQL, Redis, OpenSearch, S3-compatible, in-memory, file, HTTP); `OracleStore`, Oracle agent memory and the portable `LLMMemoryManager` for long-term memory. |
| **[Memory](https://locusagents.ai/concepts/memory/)** | 10 checkpoint backends (Oracle Database 26ai and its versioned saver, OCI Object Storage, PostgreSQL, MySQL, Redis, OpenSearch, S3-compatible, in-memory, file, HTTP); `OracleStore`, Oracle agent memory and the portable `LLMMemoryManager` for long-term memory. |
| **[RAG](https://locusagents.ai/concepts/rag/)** | Six vector stores (Oracle 26ai, pgvector, Qdrant, Chroma, OpenSearch, in-memory); OCI, in-database, OpenAI and Cohere embeddings; OCI Cohere, local and Cohere rerankers; `retriever.as_tool()`. |
| **[Grounded reasoning](https://locusagents.ai/concepts/gsar/)** | GSAR partitions every claim as grounded, ungrounded, contradicted or complementary against typed evidence; below threshold the agent regenerates, replans or abstains. Reflexion scores every turn. |
| **[Guardrails and the admission gate](https://locusagents.ai/concepts/control-layer/)** | Input, output and tool-call guardrails, steering (LLM-as-judge), trace hygiene, and `admit()` / `approve()`: allow, hold for a human, or deny, on a hash-chained audit trail. |
Expand Down
2 changes: 1 addition & 1 deletion docs/api/models.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ For the concepts, start with [Model providers](../concepts/models.md).
## Registry

String factory — routes `"openai:gpt-4o"`,
`"anthropic:claude-sonnet-4-6"`, etc. to the right client.
`"anthropic:claude-sonnet-5-5"`, etc. to the right client.

::: locus.models.registry.get_model
::: locus.models.registry.list_providers
Expand Down
4 changes: 2 additions & 2 deletions docs/capabilities.md
Original file line number Diff line number Diff line change
Expand Up @@ -274,8 +274,8 @@ await ctx.actions.execute(
|---|---|---|
| OCI Generative AI | OpenAI, Meta Llama, xAI Grok, Mistral, Google Gemini and Cohere models served on OCI (on-demand or Dedicated AI Cluster) | `locus.models.providers.oci` · [OCI Generative AI](concepts/providers/oci.md) |
| OpenAI | All commercial models (gpt-5.5, o-series, etc) | `locus.models.native.openai` · [OpenAI](concepts/providers/openai.md) |
| Anthropic | Claude 4.x (e.g. `claude-sonnet-4-6`) — direct API | `locus.models.native.anthropic` · [Anthropic](concepts/providers/anthropic.md) |
| Auto-routing | `get_model("anthropic:claude-sonnet-4-6")` picks transport from id | `locus.models.registry.get_model` |
| Anthropic | Claude 5.x (e.g. `claude-sonnet-5-5`) — direct API | `locus.models.native.anthropic` · [Anthropic](concepts/providers/anthropic.md) |
| Auto-routing | `get_model("anthropic:claude-sonnet-5-5")` picks transport from id | `locus.models.registry.get_model` |
| Resilience helpers | Failover classification, credential pooling, prompt-cache breakpoints, rate-limit buckets | `locus.models.failover` · `locus.models.pooled` · `locus.models.caching` · `locus.models.rate_limits` · [Resilience](concepts/providers/resilience.md) |

## Skills + Playbooks
Expand Down
26 changes: 26 additions & 0 deletions docs/concepts/checkpointers.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,32 @@ lifecycle policies handle retention, IAM handles auth.

## Getting started

### Oracle Database 26ai: `oracle_checkpointer()`

Durable threads in the same database as the data the agent acts on. The
factory returns a ready checkpointer; `OracleCheckpointSaver` is the
versioned alternative that keeps every checkpoint of a thread (see
[Oracle Database 26ai](oracle-26ai.md)).

```python
import os

from locus.agent import Agent
from locus.memory.backends import oracle_checkpointer

cp = oracle_checkpointer(
dsn=os.environ["ORACLE_DSN"],
user=os.environ["ORACLE_USER"],
password=os.environ["ORACLE_PASSWORD"],
wallet_location=os.environ["ORACLE_WALLET"], # Autonomous Database wallet
wallet_password=os.environ.get("ORACLE_WALLET_PASSWORD", ""),
table_name="locus_checkpoints",
)
agent = Agent(model="oci:openai.gpt-5.5", checkpointer=cp)
agent.run_sync("My name is Alice.", thread_id="t1")
agent.run_sync("What's my name?", thread_id="t1") # resumes from the database
```

### Local: `FileCheckpointer`

```python
Expand Down
2 changes: 1 addition & 1 deletion docs/concepts/hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ from locus.models import AnthropicModel

agent = Agent(
...,
hooks=[SteeringHook(model=AnthropicModel(model="claude-sonnet-4-6"))],
hooks=[SteeringHook(model=AnthropicModel(model="claude-sonnet-5-5"))],
)
```

Expand Down
34 changes: 32 additions & 2 deletions docs/concepts/memory-manager.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ async def main():
)

# Session 1 — agent learns a standing rule
async for event in agent.run("Refunds over $500 need manager approval — page the duty manager first."):
async for event in agent.run("Never refund over $500 without manager approval — page the duty manager first."):
...

# Session 2 — the agent already knows
Expand Down Expand Up @@ -172,9 +172,9 @@ The SDK exposes memory along a spectrum, from "works everywhere" to

| Path | Manager class | When to pick it |
|---|---|---|
| Oracle-native | `OracleAgentMemoryManager` ([`oracleagentmemory`](https://pypi.org/project/oracleagentmemory/)) | You run on Oracle Database 26ai and want Oracle's official agent-memory client — context-card summaries, scoped retrieval by user / agent / thread, LongMemEval-benchmarked extraction |
| Portable / multi-backend | `LLMMemoryManager` + any `BaseStore` (`InMemoryStore`, `HolographicStore`, `PgMemory`, or a custom subclass) | You need backend portability, an LLM-free extractor, or a test-friendly path |
| Managed | `Mem0MemoryManager` ([`mem0ai`](https://pypi.org/project/mem0ai/)) | You want a managed memory layer with LLM-tuned recall and scoped retrieval by `user_id` without writing your own extractor |
| Oracle-native | `OracleAgentMemoryManager` ([`oracleagentmemory`](https://pypi.org/project/oracleagentmemory/)) | You run on Oracle Database 26ai and want Oracle's official agent-memory client — context-card summaries, scoped retrieval by user / agent / thread, LongMemEval-benchmarked extraction |

### Managed memory — `Mem0MemoryManager`

Expand Down Expand Up @@ -205,6 +205,36 @@ pip install 'locus-sdk[mem0]'
When you need a fully-deterministic LLM-free extractor or a custom
backend, fall back to the portable path below.

### Oracle Database 26ai: `OracleStore` and `OracleAgentMemoryManager`

Two ways to keep long-term memory in Oracle Database 26ai. `OracleStore` is a
`BaseStore`, so it slots under `LLMMemoryManager` with the portable
extractor and per-user scoping; `OracleAgentMemoryManager` hands the whole
job to Oracle's own `oracleagentmemory` client (extraction, context cards,
retrieval scoped by user, agent and thread, all inside the database).

```python
import os

from locus.agent import Agent
from locus.memory import LLMMemoryManager
from locus.memory.store_backends import OracleStore

store = OracleStore(
dsn=os.environ["ORACLE_DSN"],
user=os.environ["ORACLE_USER"],
password=os.environ["ORACLE_PASSWORD"],
wallet_location=os.environ["ORACLE_WALLET"],
wallet_password=os.environ.get("ORACLE_WALLET_PASSWORD", ""),
)
agent = Agent(model="oci:openai.gpt-5.5", memory_manager=LLMMemoryManager(store=store))
```

Notebooks [37](../notebooks/notebook_37_oracle_store.md) and
[44](../notebooks/notebook_44_oracle_agent_memory.md) run both against a
live database; [Oracle Database 26ai](oracle-26ai.md) lists the rest of the
stack.

### Portable path (any BaseStore backend)

`LLMMemoryManager` works against any `BaseStore` implementation. Use
Expand Down
118 changes: 118 additions & 0 deletions docs/concepts/memory.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
---
title: Memory — short-term and long-term
description: "The three memories of a Locus agent — working memory, thread memory and long-term memory — plus knowledge retrieval, and how Oracle Database 26ai backs each of them."
---

# Memory: short-term and long-term

An agent remembers at three different time scales, and Locus keeps them as
three separate surfaces so you can choose a backend for each. Oracle
Database 26ai can back all of them, in the same database as the business
data the agent acts on.

| Layer | What it holds | Lifetime | Locus surface | Oracle Database 26ai |
|---|---|---|---|---|
| **Working memory** | The messages of the current run: prompt, tool calls, observations | One run | `Agent(conversation_manager=…)`: `SlidingWindowManager`, `SummarizingManager`, `ContextCompactor` | In-process, nothing to persist |
| **Short-term memory** (the thread) | The full state of one conversation: messages, tool results, iteration, so a thread can resume in another process or after a crash | The thread | `Agent(checkpointer=…)` and `thread_id=` on every call | `oracle_checkpointer()` for durable threads; `OracleCheckpointSaver` keeps every version for history and resume-from-any-point |
| **Long-term memory** | Facts that outlive any thread: preferences, standing rules, what was learned handling earlier cases | Until you delete it | `Agent(memory_manager=…)`: `LLMMemoryManager(store=…)` or `OracleAgentMemoryManager` | `OracleStore` as the store; `OracleAgentMemoryManager` over Oracle's `oracleagentmemory` client |
| **Knowledge** (RAG) | Documents the agent retrieves rather than remembers | Your corpus | `RAGRetriever(...).as_tool()` | `OracleVectorStore`, `OracleInDBChunker`, `OracleInDBEmbeddings`, `OracleADBLoader` |

## Working memory

Working memory is the context window. It is not stored anywhere; the
question is what to drop when a long run no longer fits. A
`ConversationManager` decides: `SlidingWindowManager` keeps the last *n*
messages, `SummarizingManager` folds older turns into a summary, and
`ContextCompactor` archives tool output and replaces it with a reference.
See [the agent loop](agent-loop.md) for where it runs.

```python
from locus.agent import Agent
from locus.memory.conversation import SlidingWindowManager

agent = Agent(model="oci:openai.gpt-5.5", conversation_manager=SlidingWindowManager(window_size=20))
```

## Short-term memory: the thread

A checkpointer saves the agent's state under a `thread_id` and loads it back
on the next call, from any process. That is what makes a conversation
survive a restart, a deploy, or a hand-off to the next shift.

```python
import os

from locus.agent import Agent
from locus.memory.backends import oracle_checkpointer

cp = oracle_checkpointer(
dsn=os.environ["ORACLE_DSN"],
user=os.environ["ORACLE_USER"],
password=os.environ["ORACLE_PASSWORD"],
wallet_location=os.environ["ORACLE_WALLET"],
wallet_password=os.environ.get("ORACLE_WALLET_PASSWORD", ""),
table_name="support_threads",
)
agent = Agent(model="oci:openai.gpt-5.5", checkpointer=cp)
agent.run_sync("My name is Alice.", thread_id="case-100")
agent.run_sync("What is my name?", thread_id="case-100") # another process, same thread
```

`oracle_checkpointer()` keeps the latest state of each thread.
`OracleCheckpointSaver` keeps every version, so you can list a thread's
history and resume from any earlier checkpoint. Ten backends ship in total;
[Checkpointers](checkpointers.md) compares them.

## Long-term memory

Long-term memory is what the agent keeps *across* threads: Alice's
preferences should be known in her next case, not only in this one. A
`MemoryManager` runs two hooks around every run: before the first model
call it retrieves relevant memories into the system prompt; after the run
it extracts durable facts and persists them.

```python
import os

from locus.agent import Agent
from locus.memory import LLMMemoryManager
from locus.memory.store_backends import OracleStore

store = OracleStore(
dsn=os.environ["ORACLE_DSN"],
user=os.environ["ORACLE_USER"],
password=os.environ["ORACLE_PASSWORD"],
wallet_location=os.environ["ORACLE_WALLET"],
wallet_password=os.environ.get("ORACLE_WALLET_PASSWORD", ""),
)
agent = Agent(model="oci:openai.gpt-5.5", memory_manager=LLMMemoryManager(store=store))

agent.run_sync("I'm Alice. I always want quotes in EUR, never in USD.", thread_id="case-100")
agent.run_sync("Which currency do I want my quotes in?", thread_id="case-200") # new thread: EUR
```

Two managers ship. `LLMMemoryManager` works over any `BaseStore`, with
Oracle Database 26ai through `OracleStore`, and lets you scope memories per
user or tenant; out of the box it extracts with a small heuristic (standing
rules such as "always … never …", who the user is, project context, links),
and you pass `extract_fn` to let a model decide what is worth keeping. `OracleAgentMemoryManager` wraps Oracle's own agent-memory
client (`oracleagentmemory`): extraction, context-card summaries and
retrieval scoped by user, agent and thread, all inside the database.
[Long-term memory manager](memory-manager.md) covers both.

## Knowledge is not memory

What the agent should *look up* belongs in retrieval, not in memory: policy
pages, runbooks, past resolutions. [RAG](rag.md) covers embedders, vector
stores and `retriever.as_tool()`; on Oracle Database 26ai the whole pipeline
can run inside the database.

## Why one database

Threads, memories and vectors can each live in a different system; on
Oracle Database 26ai they live in one, next to the tables the agent reads
and writes through its tools. That means one set of credentials, one backup
and retention policy, one place to audit what the agent knew when it acted,
and no copy of customer data in a separate memory service.
[Oracle Database 26ai](oracle-26ai.md) lists every primitive;
notebooks 37 to 44 run each one.
31 changes: 16 additions & 15 deletions docs/concepts/models.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ OpenAI-compatible prefixes such as `ollama:`, `vllm:`, `groq:` or

```python
# tools, system_prompt, and other kwargs are the same across providers
Agent(model="oci:openai.gpt-5", tools=[lookup_order, issue_refund]) # OCI Generative AI
Agent(model="oci:openai.gpt-5.5", tools=[lookup_order, issue_refund]) # OCI Generative AI
Agent(model="openai:gpt-4o", tools=[lookup_order, issue_refund]) # OpenAI direct
Agent(model="anthropic:claude-sonnet-4-6", tools=[lookup_order, issue_refund]) # Anthropic direct
Agent(model="anthropic:claude-sonnet-5-5", tools=[lookup_order, issue_refund]) # Anthropic direct
Agent(model="openrouter:provider/model-id", tools=[lookup_order, issue_refund]) # OpenRouter
Agent(model="together:organization/model-id", tools=[lookup_order, issue_refund]) # Together.ai
```
Expand Down Expand Up @@ -56,10 +56,11 @@ locus.models
│
├── oci: ── OCI Generative AI · auto-routed transport
│ ├─ V1 (default) — OCIChatCompletionsModel on /openai/v1:
│ │ OpenAI · Meta · xAI · Mistral · Gemini ·
│ │ Cohere (non-R) — SSE streaming, day-0 models
│ ├─ SDK — OCIModel for cohere.command-r* and
│ │ Dedicated AI Cluster endpoint OCIDs
│ │ OpenAI · Meta · xAI · Google — SSE
│ │ streaming, new models as OCI lists them
│ ├─ SDK — OCIModel for every cohere.* id (Command R
│ │ and Command A; OCI's /openai/v1 rejects
│ │ them) and Dedicated AI Cluster endpoint OCIDs
│ └─ Responses (opt-in) — OCIResponsesModel, server-stateful
│
├── openai: ── OpenAI direct · OpenAIModel
Expand All @@ -68,12 +69,12 @@ locus.models
│
├── anthropic: ── Anthropic direct · AnthropicModel
│ ├─ Claude family — opus · sonnet · haiku
│ └─ prompt caching — opt-in (prompt_cache=True), non-streaming
│ calls only: marks the system prompt + tool
│ catalog; once that prefix passes the model's
│ minimum cacheable length, later turns read
│ it at ~1/10th input cost while the ~5-min
│ cache holds
│ └─ prompt caching — opt-in (prompt_cache=True), on complete()
│ and stream() alike: marks the system prompt
│ + tool catalog; once that prefix passes the
│ model's minimum cacheable length, later
│ turns read it at ~1/10th input cost while
│ the ~5-min cache holds
│
├── OpenAI-compatible prefixes ── OpenAIModel at a routed base_url
│ ├─ self-hosted — ollama · vllm · lmstudio · llamacpp
Expand Down Expand Up @@ -131,7 +132,7 @@ register_provider("audited", lambda model_id, **kw: AuditedModel(model_id, **kw)
agent = Agent(model="audited:internal-llm", tools=[lookup_order, issue_refund])
```

Source: [`register_provider` in `models/registry.py:21`](https://github.com/oracle/locus/blob/main/src/locus/models/registry.py#L21).
Source: [`register_provider` in `models/registry.py`](https://github.com/oracle/locus/blob/main/src/locus/models/registry.py).

## Credential pooling & rotation

Expand All @@ -152,7 +153,7 @@ pool = CredentialPool([
])

def _build(cred: Credential) -> AnthropicModel:
return AnthropicModel(model="claude-sonnet-4-6", api_key=cred.api_key)
return AnthropicModel(model="claude-sonnet-5-5", api_key=cred.api_key)

agent = Agent(
model=CredentialPoolModel(pool=pool, build_model=_build),
Expand All @@ -171,7 +172,7 @@ Source:
## Notebook

[`notebook_11_model_providers.py`](https://github.com/oracle/locus/blob/main/examples/notebook_11_model_providers.py)
runs the same agent — a security-operations triage example — against OpenAI and
runs the same cloud-operations agent against OCI Generative AI, OpenAI and
Anthropic by swapping one string.

## Source
Expand Down
Loading
Loading