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:.: the OCI Generative AI catalog, auto-routed. + OpenAI — GPT-4o, o3, gpt-5.x against the direct API. + Anthropic — Claude models (fable / opus / sonnet / haiku). Registry — get_model("provider:model_name") returns the right client. Run it @@ -67,7 +68,7 @@ async def example_providers(): print("\nUsage:") print(' model = get_model("openai:gpt-4o")') - print(' model = get_model("anthropic:claude-sonnet-4-6")') + print(' model = get_model("anthropic:claude-sonnet-5-5")') print() print("The provider prefix before the colon selects the client; the rest") print("is the model id that provider expects. See docs/concepts/models.md.") @@ -89,7 +90,7 @@ async def example_direct(): print("\nAnthropic (requires ANTHROPIC_API_KEY):") print(" from locus.models.native.anthropic import AnthropicModel") - print(' model = AnthropicModel(model="claude-sonnet-4-6")') + print(' model = AnthropicModel(model="claude-sonnet-5-5")') async def example_live_call() -> None: diff --git a/examples/notebook_67_litellm_gateway_cost.py b/examples/notebook_67_litellm_gateway_cost.py index 10b4129b..f58d5b3c 100644 --- a/examples/notebook_67_litellm_gateway_cost.py +++ b/examples/notebook_67_litellm_gateway_cost.py @@ -191,7 +191,7 @@ def main() -> None: # ----- Step 1: issue two virtual keys, one per payments team ----------- fraud_ops_key = issue_virtual_key(url, master_key, team="fraud-ops", models=["gpt-4o"]) dispute_ops_key = issue_virtual_key( - url, master_key, team="dispute-ops", models=["gpt-4o", "claude-sonnet-4-6"] + url, master_key, team="dispute-ops", models=["gpt-4o", "claude-sonnet-5-5"] ) print(" Virtual keys issued (each with a $5 hard budget):") print(f" fraud-ops (gpt-4o only): {fraud_ops_key[:24]}...") diff --git a/examples/projects/a2a-mesh/src/a2a_mesh/_model.py b/examples/projects/a2a-mesh/src/a2a_mesh/_model.py index 0ea24cba..921baacb 100644 --- a/examples/projects/a2a-mesh/src/a2a_mesh/_model.py +++ b/examples/projects/a2a-mesh/src/a2a_mesh/_model.py @@ -70,7 +70,7 @@ def _openai_model() -> Any: def _anthropic_model() -> Any: from locus.models.native.anthropic import AnthropicModel - return AnthropicModel(model=os.environ.get("LOCUS_MODEL_ID", "claude-sonnet-4-6")) + return AnthropicModel(model=os.environ.get("LOCUS_MODEL_ID", "claude-sonnet-5-5")) def get_model() -> Any: diff --git a/examples/projects/deep-research/README.md b/examples/projects/deep-research/README.md index bbae6e41..71857861 100644 --- a/examples/projects/deep-research/README.md +++ b/examples/projects/deep-research/README.md @@ -51,7 +51,7 @@ export OPENSEARCH_USERNAME= export OPENSEARCH_PASSWORD='...' export OPENAI_API_KEY=sk-... export ANTHROPIC_API_KEY=sk-ant-... -export LOCUS_RESEARCH_MODEL=anthropic:claude-sonnet-4-6 +export LOCUS_RESEARCH_MODEL=anthropic:claude-sonnet-5-5 python examples/projects/deep-research/demo_opensearch_multi_index.py ``` diff --git a/examples/projects/deep-research/demo_hello_world.py b/examples/projects/deep-research/demo_hello_world.py index 04d77ed6..284c6488 100755 --- a/examples/projects/deep-research/demo_hello_world.py +++ b/examples/projects/deep-research/demo_hello_world.py @@ -9,7 +9,7 @@ call both tools in one turn. Run: - LOCUS_MODEL=anthropic:claude-sonnet-4-6 \\ + LOCUS_MODEL=anthropic:claude-sonnet-5-5 \\ ANTHROPIC_API_KEY=sk-ant-... \\ python examples/projects/deep-research/demo_hello_world.py """ @@ -37,7 +37,7 @@ def fetch_advisory(vendor: str) -> str: def main() -> int: - model_id = os.environ.get("LOCUS_MODEL", "anthropic:claude-sonnet-4-6") + model_id = os.environ.get("LOCUS_MODEL", "anthropic:claude-sonnet-5-5") print("== locus hello-world deepagent ==") print(f" model : {model_id}") diff --git a/examples/projects/deep-research/demo_opensearch_multi_index.py b/examples/projects/deep-research/demo_opensearch_multi_index.py index 97763bd9..ebc3d041 100755 --- a/examples/projects/deep-research/demo_opensearch_multi_index.py +++ b/examples/projects/deep-research/demo_opensearch_multi_index.py @@ -102,7 +102,7 @@ async def main() -> int: password = os.environ["OPENSEARCH_PASSWORD"] verify_certs = os.environ.get("OPENSEARCH_VERIFY_CERTS", "true").lower() == "true" - model_id = os.environ.get("LOCUS_RESEARCH_MODEL", "anthropic:claude-sonnet-4-6") + model_id = os.environ.get("LOCUS_RESEARCH_MODEL", "anthropic:claude-sonnet-5-5") run_id = uuid.uuid4().hex[:8].lower() intel_index = f"locus_intel_{run_id}" diff --git a/examples/projects/deep-research/demo_smoke.py b/examples/projects/deep-research/demo_smoke.py index cb41ca2a..ac30ce34 100755 --- a/examples/projects/deep-research/demo_smoke.py +++ b/examples/projects/deep-research/demo_smoke.py @@ -89,7 +89,7 @@ async def main() -> None: from locus.models import get_model chat_model = get_model( - "anthropic:claude-sonnet-4-6", # reliable tool caller for smoke + "anthropic:claude-sonnet-5-5", # reliable tool caller for smoke ) agent = create_deepagent( diff --git a/mkdocs.yml b/mkdocs.yml index c6bd2b6e..02393770 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -163,6 +163,7 @@ nav: - A2A protocol: concepts/multi-agent/a2a.md - DeepAgent: concepts/deepagent.md - Production-readiness: concepts/multi-agent/production.md + - Oracle Database 26ai: concepts/oracle-26ai.md - Agents: - State: concepts/state.md - Prompts: concepts/prompts.md @@ -179,6 +180,7 @@ nav: - MCP: concepts/mcp.md - Multi-modal providers: concepts/multi-modal-providers.md - Memory and RAG: + - Memory — short-term and long-term: concepts/memory.md - Checkpointers & Store: concepts/checkpointers.md - Long-term memory manager: concepts/memory-manager.md - RAG: concepts/rag.md diff --git a/src/locus/__init__.py b/src/locus/__init__.py index d4387679..031f5854 100644 --- a/src/locus/__init__.py +++ b/src/locus/__init__.py @@ -22,7 +22,7 @@ def search(query: str) -> str: return "results..." agent = Agent( - model="openai:gpt-4o", # or anthropic:claude-sonnet-4-6 + model="openai:gpt-4o", # or anthropic:claude-sonnet-5-5 tools=[search], system_prompt="You are a helpful assistant.", ) diff --git a/src/locus/agent/agent.py b/src/locus/agent/agent.py index 465959ad..2a8b66bd 100644 --- a/src/locus/agent/agent.py +++ b/src/locus/agent/agent.py @@ -118,7 +118,7 @@ class Agent(AgentRuntimeMixin, BaseModel): Usage: agent = Agent( - model="openai:gpt-4o", # or anthropic:claude-sonnet-4-6 + model="openai:gpt-4o", # or anthropic:claude-sonnet-5-5 tools=[search, calculate], system_prompt="You are a helpful assistant.", ) diff --git a/src/locus/agent/config.py b/src/locus/agent/config.py index 8c8779c2..120d7b68 100644 --- a/src/locus/agent/config.py +++ b/src/locus/agent/config.py @@ -291,7 +291,7 @@ class AgentConfig(BaseModel): # Model specification model: str | Any = Field( ..., - description="Model string ('openai:gpt-4o' or 'anthropic:claude-sonnet-4-6') or ModelProtocol instance", + description="Model string ('openai:gpt-4o' or 'anthropic:claude-sonnet-5-5') or ModelProtocol instance", ) # Provider configuration that has to travel with the string form. diff --git a/src/locus/agent/specs.py b/src/locus/agent/specs.py index d174c425..b4ed2c58 100644 --- a/src/locus/agent/specs.py +++ b/src/locus/agent/specs.py @@ -14,7 +14,7 @@ --- name: reviewer description: Reviews a diff for bugs. Use after a change, before commit. - model: anthropic:claude-sonnet-4-6 + model: anthropic:claude-sonnet-5-5 tools: read, grep, glob # or a YAML list disallowed_tools: [bash] mode: subagent # primary | subagent | all diff --git a/src/locus/control/verification.py b/src/locus/control/verification.py index e2784288..8647d32e 100644 --- a/src/locus/control/verification.py +++ b/src/locus/control/verification.py @@ -265,7 +265,7 @@ class AdversarialSkeptic: finding, skeptics=[ EvidenceQualitySkeptic(), - AdversarialSkeptic("anthropic:claude-sonnet-4-6"), + AdversarialSkeptic("anthropic:claude-sonnet-5-5"), ], ) """ diff --git a/src/locus/memory/manager.py b/src/locus/memory/manager.py index 7ceb1596..681598b4 100644 --- a/src/locus/memory/manager.py +++ b/src/locus/memory/manager.py @@ -88,7 +88,7 @@ async def my_extractor(messages): agent = Agent( - model="anthropic:claude-sonnet-4-6", + model="anthropic:claude-sonnet-5-5", memory_manager=LLMMemoryManager(store=store, extract_fn=my_extractor), ) """ @@ -528,7 +528,7 @@ class LLMMemoryManager(BaseMemoryManager): namespace_prefix=("users", user_id), ) - agent = Agent(model="anthropic:claude-sonnet-4-6", memory_manager=manager) + agent = Agent(model="anthropic:claude-sonnet-5-5", memory_manager=manager) """ def __init__( diff --git a/src/locus/memory/managers/mem0.py b/src/locus/memory/managers/mem0.py index b500168a..14ae3136 100644 --- a/src/locus/memory/managers/mem0.py +++ b/src/locus/memory/managers/mem0.py @@ -20,7 +20,7 @@ from locus.memory.managers import Mem0MemoryManager manager = Mem0MemoryManager(user_id="alice") - agent = Agent(model="anthropic:claude-sonnet-4-6", memory_manager=manager) + agent = Agent(model="anthropic:claude-sonnet-5-5", memory_manager=manager) By default mem0 builds its own OpenAI-backed LLM + embedder + local vector store; pass ``config=`` to point it at other backends (a fully diff --git a/src/locus/models/__init__.py b/src/locus/models/__init__.py index 58513a25..2ded1de5 100644 --- a/src/locus/models/__init__.py +++ b/src/locus/models/__init__.py @@ -21,7 +21,7 @@ model = OpenAIModel(model="gpt-4o") from locus.models import AnthropicModel - model = AnthropicModel(model="claude-sonnet-4-6") + model = AnthropicModel(model="claude-sonnet-5-5") # OCI GenAI — V1 transport from locus.models import OCIChatCompletionsModel @@ -29,7 +29,7 @@ # String factory — "provider:model" from locus.models import get_model - model = get_model("anthropic:claude-sonnet-4-6") + model = get_model("anthropic:claude-sonnet-5-5") model = get_model("oci:openai.gpt-5", profile="DEFAULT") Provider failover — ``FallbackChain`` (see :mod:`locus.models.fallback`):: @@ -37,7 +37,7 @@ from locus.models import FallbackChain model = FallbackChain( - [get_model("anthropic:claude-sonnet-4-6"), get_model("openai:gpt-4o")] + [get_model("anthropic:claude-sonnet-5-5"), get_model("openai:gpt-4o")] ) """ diff --git a/src/locus/models/fallback.py b/src/locus/models/fallback.py index 96018a30..18dc15b1 100644 --- a/src/locus/models/fallback.py +++ b/src/locus/models/fallback.py @@ -14,7 +14,7 @@ model = FallbackChain( [ - get_model("anthropic:claude-sonnet-4-6"), + get_model("anthropic:claude-sonnet-5-5"), get_model("oci:meta.llama-3.3-70b-instruct"), get_model("openai:gpt-4o"), ], diff --git a/src/locus/models/native/anthropic.py b/src/locus/models/native/anthropic.py index 7655363f..24c66e4b 100644 --- a/src/locus/models/native/anthropic.py +++ b/src/locus/models/native/anthropic.py @@ -238,7 +238,7 @@ def _usage_dict(usage: Any) -> dict[str, int]: class AnthropicConfig(ModelConfig): """Configuration for Anthropic models.""" - model: str = "claude-sonnet-4-6" + model: str = "claude-sonnet-5-5" max_tokens: int = 4096 temperature: float = 0.7 top_p: float = 0.9 @@ -302,10 +302,11 @@ class AnthropicConfig(ModelConfig): class AnthropicModel(BaseModel): """Anthropic model provider. - Supports Claude 4.6, 4.5, 3.5 models with streaming and tool calling. + Supports the Claude 5 family (Fable 5.1, Opus / Sonnet / Haiku 5.5) and + earlier Claude models, with streaming and tool calling. Example: - >>> model = AnthropicModel(model="claude-sonnet-4-6") + >>> model = AnthropicModel(model="claude-sonnet-5-5") >>> response = await model.complete([Message.user("Hello!")]) """ @@ -325,7 +326,7 @@ def supports_structured_output(self) -> bool: def __init__( self, - model: str = "claude-sonnet-4-6", + model: str = "claude-sonnet-5-5", api_key: str | None = None, base_url: str | None = None, max_tokens: int = 4096, diff --git a/src/locus/models/pooled.py b/src/locus/models/pooled.py index 373e599d..3ab946df 100644 --- a/src/locus/models/pooled.py +++ b/src/locus/models/pooled.py @@ -26,7 +26,7 @@ def _build(cred: Credential) -> AnthropicModel: return AnthropicModel( - model="claude-sonnet-4-6", + model="claude-sonnet-5-5", api_key=cred.api_key, ... ) diff --git a/src/locus/models/profiles.py b/src/locus/models/profiles.py index 7caabc98..57cf8652 100644 --- a/src/locus/models/profiles.py +++ b/src/locus/models/profiles.py @@ -231,7 +231,7 @@ class ModelProfile(BaseModel): "default": {"prompt_variant": "default"}, } -#: A Claude id's tier and version: ``claude-sonnet-4-6`` → (4, 6), +#: A Claude id's tier and version: ``claude-sonnet-5-5`` → (5, 5), #: ``claude-opus-5`` → (5, None), ``claude-sonnet-4-5-20250929`` → (4, 5). _CLAUDE_VERSION: Final[re.Pattern[str]] = re.compile( r"claude-(?:opus|sonnet|haiku|fable|mythos)-(\d+)(?:-(\d{1,2}))?(?:$|[-@.])" diff --git a/src/locus/models/registry.py b/src/locus/models/registry.py index c3784922..1aaf7eae 100644 --- a/src/locus/models/registry.py +++ b/src/locus/models/registry.py @@ -37,7 +37,7 @@ def get_model(model_string: str, **kwargs: Any) -> ModelProtocol: Examples: - "openai:gpt-4o" - - "anthropic:claude-sonnet-4-6" + - "anthropic:claude-sonnet-5-5" Args: model_string: Model identifier in "provider:model" format diff --git a/src/locus/rogue/challenge.py b/src/locus/rogue/challenge.py index 4b7b29b4..05da8842 100644 --- a/src/locus/rogue/challenge.py +++ b/src/locus/rogue/challenge.py @@ -265,7 +265,7 @@ def build_agent(mode: str) -> Agent: temperature=0, ) elif mode == MODE_FRONTIER: - model = get_model("anthropic:claude-sonnet-4-6", api_key=os.environ["ANTHROPIC_API_KEY"]) + model = get_model("anthropic:claude-sonnet-5-5", api_key=os.environ["ANTHROPIC_API_KEY"]) else: model = CompromisedModel() return Agent(