diff --git a/README.md b/README.md index e839366f..7610f065 100644 --- a/README.md +++ b/README.md @@ -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/) · @@ -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. | diff --git a/docs/api/models.md b/docs/api/models.md index ae695cd7..4022fcbc 100644 --- a/docs/api/models.md +++ b/docs/api/models.md @@ -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 diff --git a/docs/capabilities.md b/docs/capabilities.md index 73aa1673..208eb8b1 100644 --- a/docs/capabilities.md +++ b/docs/capabilities.md @@ -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 diff --git a/docs/concepts/checkpointers.md b/docs/concepts/checkpointers.md index 8bf61a32..e1a88467 100644 --- a/docs/concepts/checkpointers.md +++ b/docs/concepts/checkpointers.md @@ -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 diff --git a/docs/concepts/hooks.md b/docs/concepts/hooks.md index 4adb9d28..63203979 100644 --- a/docs/concepts/hooks.md +++ b/docs/concepts/hooks.md @@ -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"))], ) ``` diff --git a/docs/concepts/memory-manager.md b/docs/concepts/memory-manager.md index 0be62ebc..4b08cdb7 100644 --- a/docs/concepts/memory-manager.md +++ b/docs/concepts/memory-manager.md @@ -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 @@ -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` @@ -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 diff --git a/docs/concepts/memory.md b/docs/concepts/memory.md new file mode 100644 index 00000000..4850c304 --- /dev/null +++ b/docs/concepts/memory.md @@ -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. diff --git a/docs/concepts/models.md b/docs/concepts/models.md index 89b6134e..5e83a3a3 100644 --- a/docs/concepts/models.md +++ b/docs/concepts/models.md @@ -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 ``` @@ -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 @@ -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 @@ -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 @@ -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), @@ -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 diff --git a/docs/concepts/oracle-26ai.md b/docs/concepts/oracle-26ai.md new file mode 100644 index 00000000..e0705b2f --- /dev/null +++ b/docs/concepts/oracle-26ai.md @@ -0,0 +1,85 @@ +--- +title: Oracle Database 26ai +description: "Every Oracle Database 26ai primitive in Locus — vector search, in-database chunking and embeddings, table ingestion, durable and versioned threads, long-term memory and Oracle agent memory — with the notebook that runs each one." +--- + +# Oracle Database 26ai + +Locus treats Oracle Database 26ai as the place where an agent's state lives: +vectors for retrieval, threads for continuity, memories for what it has +learned, in the same database as the data it acts on. Each primitive is a +first-class Locus class on the `oracledb` driver, with no LangChain layer in +between, and it works with the same `Agent` as everything else. + +```bash +pip install "locus-sdk[oci,oracledb]" +``` + +## What ships + +| Need | Class | Notebook | +|---|---|---| +| Vector search for RAG on native `VECTOR` columns | `OracleVectorStore` | [38 — Oracle 26ai RAG](../notebooks/notebook_38_oracle_26ai_rag.md) | +| Ingest rows from your own tables as documents | `OracleADBLoader` | [39 — ADB loader](../notebooks/notebook_39_oracle_adb_loader.md) | +| Chunk text inside the database | `OracleInDBChunker` | [40 — In-database chunker](../notebooks/notebook_40_oracle_indb_chunker.md) | +| Embed inside the database with an ONNX model | `OracleInDBEmbeddings` | [41 — In-database embeddings](../notebooks/notebook_41_oracle_indb_embeddings.md) | +| Durable conversation threads | `oracle_checkpointer()` | [42 — Oracle 26ai checkpointer](../notebooks/notebook_42_oracle_26ai_checkpointer.md) | +| Every version of a thread, resume from any point | `OracleCheckpointSaver` | [43 — Versioned saver](../notebooks/notebook_43_oracle_versioned_saver.md) | +| Long-term memory store (`BaseStore`) | `OracleStore` | [37 — Oracle store](../notebooks/notebook_37_oracle_store.md) | +| Oracle's agent-memory service, in-database extraction and recall | `OracleAgentMemoryManager` | [44 — Oracle agent memory](../notebooks/notebook_44_oracle_agent_memory.md) | + +Every class has a synchronous twin (`OracleSyncVectorStore`, +`OracleSyncInDBEmbeddings`, `OracleSyncInDBChunker`, `OracleSyncADBLoader`, +`OracleSyncStore`, `OracleSyncCheckpointSaver`) for code that is not async. + +## Connecting + +All of them take the same connection arguments: `dsn`, `user`, `password`, +and for Autonomous Database `wallet_location` and `wallet_password`; or +`host`, `port` and `service_name` for a plain listener. The notebooks and the +integration tests read them from `ORACLE_DSN`, `ORACLE_USER`, +`ORACLE_PASSWORD`, `ORACLE_WALLET` and `ORACLE_WALLET_PASSWORD`. + +## Retrieval + +The agent's own documents on a `VECTOR` column, searched with +`VECTOR_DISTANCE`, exposed to the model as a tool. + +```python +import os + +from locus.agent import Agent +from locus.rag import OCIEmbeddings, OracleVectorStore, RAGRetriever + +db = dict( + 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", ""), +) +retriever = RAGRetriever( + embedder=OCIEmbeddings(model_id="cohere.embed-v4.0"), + store=OracleVectorStore(table_name="runbooks", dimension=1536, **db), +) +agent = Agent(model="oci:openai.gpt-5.5", tools=[retriever.as_tool()]) +``` + +When the text must not leave the database, chunk and embed inside it: +`OracleInDBChunker` splits with `DBMS_VECTOR_CHAIN`, `OracleInDBEmbeddings` +runs an ONNX model loaded into the database, and `OracleVectorStore` keeps +the result, so no document content goes to a hosted embedding API. + +## Threads and memory + +`oracle_checkpointer()` gives every thread a durable home; +`OracleCheckpointSaver` keeps the full history of each thread; `OracleStore` +and `OracleAgentMemoryManager` hold what the agent learns across threads. +[Memory: short-term and long-term](memory.md) explains the three layers +and shows each one on Oracle Database 26ai. + +## Where to go next + +- [OCI Generative AI](providers/oci.md) for the model side of the same stack. +- [Dedicated AI Clusters](../how-to/oci-dac.md) for your own model endpoints. +- [RAG](rag.md), [Checkpointers](checkpointers.md) and [Long-term memory manager](memory-manager.md) for the general surfaces these classes plug into. diff --git a/docs/concepts/prompts.md b/docs/concepts/prompts.md index 616f7112..c551936f 100644 --- a/docs/concepts/prompts.md +++ b/docs/concepts/prompts.md @@ -103,7 +103,7 @@ charges fewer tokens on cache hits — pass `prompt_cache=True`: from locus.models import AnthropicModel agent = Agent( - model=AnthropicModel(model="claude-sonnet-4-6", prompt_cache=True), + model=AnthropicModel(model="claude-sonnet-5-5", prompt_cache=True), system_prompt=very_long_prompt, ) ``` diff --git a/docs/concepts/providers/anthropic.md b/docs/concepts/providers/anthropic.md index 6676652e..4e4c6e36 100644 --- a/docs/concepts/providers/anthropic.md +++ b/docs/concepts/providers/anthropic.md @@ -33,12 +33,12 @@ export ANTHROPIC_API_KEY=sk-ant-... ```python from locus.agent import Agent agent = Agent( - model="anthropic:claude-sonnet-4-6", + model="anthropic:claude-sonnet-5-5", system_prompt="You are a support agent.", ) ``` -The string `"anthropic:claude-sonnet-4-6"` tells the SDK the +The string `"anthropic:claude-sonnet-5-5"` tells the SDK the provider (`anthropic:`) and the exact model id. Any model id Anthropic accepts, the SDK accepts. @@ -62,10 +62,10 @@ examples, not a maintained roster: | Model | When to pick it | |---|---| -| `claude-opus-4-8` | Hardest problems — timeline reconstruction, complex multi-step plans | -| `claude-sonnet-5` | Current-generation everyday workhorse | -| `claude-sonnet-4-6` | Everyday workhorse — fast enough, smart enough, cheap enough for routine work | -| `claude-haiku-4-5` | High-volume classification and routing — cheap calls, log summaries | +| `claude-fable-5-1` | Hardest problems — long multi-step plans, timeline reconstruction | +| `claude-opus-5-5` | Complex reasoning where cost matters less than quality | +| `claude-sonnet-5-5` | Everyday workhorse — fast enough, smart enough, cheap enough for routine work | +| `claude-haiku-5-5` | High-volume classification and routing — cheap calls, log summaries | ### Real SSE streaming @@ -103,7 +103,7 @@ class Triage(BaseModel): needs_human: bool agent = Agent( - model="anthropic:claude-sonnet-4-6", + model="anthropic:claude-sonnet-5-5", output_schema=Triage, ) result = agent.run_sync("Customer 4821 reports a duplicate charge on ord-4821.") @@ -124,10 +124,9 @@ Opt in with `prompt_cache=True` on `AnthropicModel`. The SDK then sends the system prompt as a block list with `cache_control: ephemeral` and tags the last entry of the tool catalog the same way (Anthropic walks markers in order — the last tag anchors the cache point). -These markers are applied on the non-streaming path only: -`AnthropicModel.stream()`, which `agent.run(..., stream_tokens=True)` -uses, sends the system prompt and tool catalog untagged, so nothing is -cached while streaming tokens. +The same markers go on the streaming path: `AnthropicModel.stream()`, +which `agent.run(..., stream_tokens=True)` uses, builds its request through +the same code as `complete()`, so streaming turns hit the cache too. ```python from locus.agent import Agent @@ -135,7 +134,7 @@ from locus.models.native.anthropic import AnthropicModel agent = Agent( model=AnthropicModel( - model="claude-sonnet-4-6", + model="claude-sonnet-5-5", prompt_cache=True, ), tools=[...], @@ -189,7 +188,7 @@ async for event in agent.run("...", stream_tokens=True): | `429 overloaded_error` | Anthropic capacity; the `ModelRetryHook` re-tries with backoff if installed | | Prompt caching not visible in usage stats | Cache window expired (5 min ephemeral) or prompt below the threshold | | `ThinkEvent`s never fire | The turn produced no assistant `message.content` (e.g. tool-call-only turn) | -| `temperature` appears to have no effect | Anthropic deprecated `temperature` from `claude-opus-4-7` onward and across the Claude 5 family. The provider matches those ids by prefix (`claude-opus-4-7`, `claude-opus-4-8`, `claude-opus-4-9`, `claude-sonnet-5`, `claude-opus-5`, `claude-haiku-5`, `claude-fable-5`, `claude-mythos-5`) and omits the parameter rather than taking a 400 — it is dropped silently, with no warning, even though `AnthropicConfig.temperature` defaults to `0.7`. Separately, token streaming (`agent.run(..., stream_tokens=True)`) goes through `AnthropicModel.stream()`, which sends no `temperature` for any Claude model, so while streaming the setting has no effect on `claude-sonnet-4-6` or `claude-haiku-4-5` either. Steer with the system prompt instead. | +| `temperature` appears to have no effect | Anthropic deprecated `temperature` from `claude-opus-4-7` onward and across the Claude 5 family. The provider matches those ids by prefix (`claude-opus-4-7`, `claude-opus-4-8`, `claude-opus-4-9`, `claude-sonnet-5`, `claude-opus-5`, `claude-haiku-5`, `claude-fable-5`, `claude-mythos-5`) and omits the parameter rather than taking a 400 — it is dropped silently, with no warning, even though `AnthropicConfig.temperature` defaults to `0.7`. Separately, token streaming (`agent.run(..., stream_tokens=True)`) goes through `AnthropicModel.stream()`, which sends no `temperature` for any Claude model, so while streaming the setting has no effect on `claude-sonnet-5-5` or `claude-haiku-4-5` either. Steer with the system prompt instead. | ## Source diff --git a/docs/concepts/providers/resilience.md b/docs/concepts/providers/resilience.md index 19db1b68..c0ff7ebb 100644 --- a/docs/concepts/providers/resilience.md +++ b/docs/concepts/providers/resilience.md @@ -55,7 +55,7 @@ pool = CredentialPool( def build(credential: Credential) -> AnthropicModel: return AnthropicModel( - model="claude-sonnet-4-6", + model="claude-sonnet-5-5", api_key=credential.api_key.get_secret_value(), ) diff --git a/docs/concepts/safety.md b/docs/concepts/safety.md index 8f30177a..b266c006 100644 --- a/docs/concepts/safety.md +++ b/docs/concepts/safety.md @@ -123,7 +123,7 @@ from locus.hooks.builtin.steering import SteeringHook # The steering model is a model instance — it can be a smaller/cheaper # model than the agent's main one. -judge = AnthropicModel(model="claude-sonnet-4-6") +judge = AnthropicModel(model="claude-sonnet-5-5") agent = Agent( model="{{ locus_example_model }}", diff --git a/docs/how-to/environment-variables.md b/docs/how-to/environment-variables.md index b1f27c97..d8e07fb1 100644 --- a/docs/how-to/environment-variables.md +++ b/docs/how-to/environment-variables.md @@ -26,7 +26,7 @@ the notebooks run offline against a bundled model — setting only | Variable | Used by | |---|---| | `LOCUS_MODEL_PROVIDER` | `examples/config.py:get_model` — `oci` (auto-detected when `~/.oci/config` has a usable profile), `mock` (default otherwise), `openai`, `anthropic` | -| `LOCUS_MODEL_ID` | Model id for the selected provider (e.g. `gpt-4o`, `claude-sonnet-4-6`) | +| `LOCUS_MODEL_ID` | Model id for the selected provider (e.g. `gpt-4o`, `claude-sonnet-5-5`) | | `LOCUS_MODEL_ID_B` | Secondary "model B" slot — comparison notebooks | | `LOCUS_MODEL_ID_C` | Tertiary "model C" slot | | `LOCUS_RESEARCH_MODEL` | Overrides the model for the research/eval scripts | diff --git a/docs/how-to/litellm-gateway.md b/docs/how-to/litellm-gateway.md index 6dc7777f..eaf52fc0 100644 --- a/docs/how-to/litellm-gateway.md +++ b/docs/how-to/litellm-gateway.md @@ -71,7 +71,7 @@ if any of them is unset. The gateway listens on `http://localhost:4000` and exposes the model aliases declared in `config.yaml`. The sample ships six: -`gpt-4o`, `gpt-4o-mini`, `gpt-5-mini`, `claude-sonnet-4-6`, +`gpt-4o`, `gpt-4o-mini`, `gpt-5-mini`, `claude-sonnet-5-5`, `claude-haiku`, and `text-embedding-3-small`. Add more by extending `model_list`. @@ -281,7 +281,7 @@ Highlights: - **`general_settings.master_key`** — the admin key that creates per-team virtual keys via `/key/generate`. - **`router_settings.fallbacks`** — fallback chains across model - aliases (e.g. `[{"gpt-5-mini": ["claude-sonnet-4-6"]}]`). + aliases (e.g. `[{"gpt-5-mini": ["claude-sonnet-5-5"]}]`). - **`litellm_settings.callbacks`** — observability hooks (Langfuse, OTel, Datadog, …). - **`litellm_settings.cache`** — Redis / S3 / Qdrant caching config. diff --git a/docs/index.md b/docs/index.md index 13d3a261..858a40c2 100644 --- a/docs/index.md +++ b/docs/index.md @@ -8,8 +8,6 @@ description: "Oracle Generative AI Agentic Harness. Locus is a Python agent harn
Oracle Generative AIAgentic Harness
- # Multi-agent workflows built for production. One `Agent` class, eight ways to combine agents, Oracle Cloud underneath, and the runtime around the model: tools, memory, RAG, observability, guardrails, an admission gate. @@ -77,7 +75,7 @@ print(agent.run_sync("What is the capital of France?").text) # → Paris ``` -Swap `oci:openai.gpt-5.5` for `openai:gpt-4o` or `anthropic:claude-sonnet-4-6`; +Swap `oci:openai.gpt-5.5` for `openai:gpt-4o` or `anthropic:claude-sonnet-5-5`; the call stays the same. With an OCI profile in `~/.oci/config`, every notebook runs against OCI Generative AI with no further setup. @@ -228,6 +226,7 @@ retriever = RAGRetriever( # Oracle Database 26ai agent = Agent(model="oci:openai.gpt-5.5", tools=[retriever.as_tool()]) ``` +[Oracle Database 26ai, every primitive →](concepts/oracle-26ai.md) · [Which OCI models work →](how-to/oci-models.md#model-support-on-oci) · [Dedicated AI Clusters →](how-to/oci-dac.md) · [Oracle Database 26ai notebooks →](notebooks/index.md#oracle-cloud-and-database-26ai) @@ -242,11 +241,12 @@ agent = Agent(model="oci:openai.gpt-5.5", tools=[retriever.as_tool()]) `@tool` on any function, typed arguments coerced from whatever the model sends, idempotent execution, MCP servers in or out. -- :material-database:{ .lg .middle } **[Memory and RAG](concepts/checkpointers.md)** +- :material-database:{ .lg .middle } **[Memory and RAG](concepts/memory.md)** --- - Durable threads on 10 backends, long-term memory, six vector stores, - rerankers, and `retriever.as_tool()`. + Four layers: working memory, the thread (10 checkpoint backends), + long-term memory across threads, and knowledge through RAG (six vector + stores, rerankers, `retriever.as_tool()`). All four on Oracle Database 26ai. - :material-shield-lock:{ .lg .middle } **[Grounding, guardrails, admission gate](concepts/control-layer.md)** diff --git a/docs/stylesheets/locus.css b/docs/stylesheets/locus.css index dda12d39..54304e59 100644 --- a/docs/stylesheets/locus.css +++ b/docs/stylesheets/locus.css @@ -388,47 +388,6 @@ html { border-radius: 50%; pointer-events: none; } -/* Hero eyebrow — a pill badge: red dot · "Oracle Generative AI" (ink) · - hairline · "Agentic Harness" (Oracle red). Compound - `.locus-hero p.locus-product-name` (specificity 0,3,1) beats - `.md-typeset .locus-hero p` (0,2,1), so the eyebrow size actually wins - inside the hero. Second selector covers usages outside the hero. */ -.md-typeset .locus-hero p.locus-product-name, -.md-typeset p.locus-product-name { - display: inline-flex; - align-items: center; - gap: 0.6rem; - margin: 0 0 1.1rem; - padding: 0.34rem 0.9rem 0.34rem 0.75rem; - border: 1px solid color-mix(in srgb, var(--tl-red) 36%, transparent); - border-radius: 999px; - background: color-mix(in srgb, var(--tl-red) 8%, transparent); - font-family: var(--locus-sans); - font-size: 0.66rem !important; - line-height: 1.2; - font-weight: 600; - letter-spacing: 0.1em; - text-transform: uppercase; - color: var(--locus-ink); - opacity: 1; -} -.md-typeset p.locus-product-name::before { - content: ""; - flex: none; - width: 0.45rem; - height: 0.45rem; - border-radius: 50%; - background: var(--tl-red); - box-shadow: 0 0 0 3px color-mix(in srgb, var(--tl-red) 22%, transparent); -} -.md-typeset p.locus-product-name .locus-product-name__brand { - opacity: 0.78; -} -.md-typeset p.locus-product-name .locus-product-name__name { - color: var(--tl-red); - padding-left: 0.6rem; - border-left: 1px solid color-mix(in srgb, var(--tl-red) 40%, transparent); -} .md-typeset .locus-hero h1:first-of-type { font-family: var(--locus-sans); font-size: 2.9rem; @@ -985,11 +944,6 @@ form.md-header__option { margin-top: 0; padding-top: 0.2rem; } -[data-md-color-scheme="slate"] .md-nav--primary .md-nav__item--section > .md-nav__link, -[data-md-color-scheme="slate"] .md-nav--primary .md-nav__item--section > .md-nav__link:not(.md-nav__link--active) { - color: var(--tl-rose-tint) !important; - border-top-color: var(--tl-rule); -} .md-nav__link--active, .md-nav__link[aria-current="true"] { @@ -1637,24 +1591,6 @@ form.md-header__option { } /* The one-line pill is ~400px; on phones stack brand over name inside a rounded tag instead of overflowing the column. */ - .md-typeset .locus-hero p.locus-product-name, - .md-typeset p.locus-product-name { - display: inline-grid; - grid-template-columns: auto 1fr; - column-gap: 0.6rem; - row-gap: 0.15rem; - align-items: center; - border-radius: 0.75rem; - font-size: 0.6rem !important; - letter-spacing: 0.06em; - } - .md-typeset p.locus-product-name::before { - grid-row: 1 / span 2; - } - .md-typeset p.locus-product-name .locus-product-name__name { - padding-left: 0; - border-left: 0; - } /* At 0.78rem the one-liner spills past the copy button; at 0.7rem the whole command fits a 390px column with no scroll. */ .md-typeset .locus-hero__copy pre { diff --git a/examples/README.md b/examples/README.md index 7bb44732..42f5ab25 100644 --- a/examples/README.md +++ b/examples/README.md @@ -52,7 +52,7 @@ need no provider at all. from locus.agent import Agent from locus.models import get_model -model = get_model("openai:gpt-4o") # or "anthropic:claude-sonnet-4-6" +model = get_model("openai:gpt-4o") # or "anthropic:claude-sonnet-5-5" agent = Agent( model=model, diff --git a/examples/config.py b/examples/config.py index 4b9d9f83..27e1482d 100644 --- a/examples/config.py +++ b/examples/config.py @@ -636,7 +636,7 @@ def _get_anthropic_model(**kwargs: Any) -> Any: """Get Anthropic model.""" from locus.models.native.anthropic import AnthropicModel - model_id = kwargs.pop("model_id", os.environ.get("LOCUS_MODEL_ID", "claude-sonnet-4-6")) + model_id = kwargs.pop("model_id", os.environ.get("LOCUS_MODEL_ID", "claude-sonnet-5-5")) api_key = os.environ.get("ANTHROPIC_API_KEY") if not api_key: diff --git a/examples/notebook_11_model_providers.py b/examples/notebook_11_model_providers.py index 96838bab..3cb230bd 100644 --- a/examples/notebook_11_model_providers.py +++ b/examples/notebook_11_model_providers.py @@ -4,9 +4,9 @@ """Notebook 11: Model providers — one cloud-ops agent, swap models with a string. The Gateway is the seam every AI request in the org flows through: one -cloud-operations agent codebase, any backing model. Locus supports OpenAI -and Anthropic as first-class providers, and the same agent code runs -against either — only the model object changes, so a platform team can +cloud-operations agent codebase, any backing model. Locus supports OCI +Generative AI, OpenAI and Anthropic as first-class providers, and the same +agent code runs against any of them — only the model object changes, so a platform team can re-platform a provider (for cost, latency, data residency, or an outage failover) without rewriting a single runbook. The provider abstraction is also a control point: routing, rate limits, and audit live at the Gateway, @@ -14,8 +14,9 @@ Provider matrix: - OpenAI — GPT-4o, o1, o3, gpt-5.x against the direct API. - Anthropic — Claude models (opus / sonnet / haiku). + OCI — oci: