lauren-ai
llms.txt0 starsChanged 6 months agoMITView on GitHub
First-party AI/LLM companion for the Lauren web framework. lauren-ai integrates large-language-model completions, agentic loops, tool execution, memory management, and structured workflows into Lauren applications using the same decorator-first, DI-driven, module-scoped paradigm as Lauren itself. Rules: - Must be invoked with parentheses: @tool() not @tool - Schema is generated from type annotations + Google-style Args: docstring section - Any parameter annotated as ToolContext (or ToolContext | None) is injected by the runner and excluded from the JSON schema — the parameter…
- Reads credentials
- Installs packages
In this repository65 other files it gives its agents.
- AGENTS.mdAGENTS.md
- skills/building-agents/agents.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- llms-full.txtllms.txt
- adding-guardrailsSkill
- agent-health-timeoutSkill
- agent-permission-scopingSkill
- agent-session-isolationSkill
- agent-step-loggingSkill
- agent-supervisor-routerSkill
- agent-telemetrySkill
- async-agent-backgroundSkill
- building-agentsSkill
- building-teamsSkill
- building-toolsSkill
- calendar-scheduling-toolSkill
- callback-tracing-handlerSkill
- chain-of-thought-promptingSkill
- chat-model-configurationSkill
- code-execution-sandboxSkill
- common-agent-patternsSkill
- context-window-managementSkill
- conversation-memorySkill
- conversation-state-dbSkill
- database-query-toolSkill
- document-loadersSkill
- document-rerankingSkill
- email-notification-toolSkill
- embedding-model-ingestionSkill
- fallback-model-chainSkill
- few-shot-injectionSkill
- filesystem-toolSkill
- human-in-the-loopSkill
- hybrid-searchSkill
- inspecting-streamsSkill
- integrating-with-laurenSkill
- json-schema-enforcementSkill
- llm-provider-connectionSkill
- llm-rate-limitingSkill
- long-term-vector-memorySkill
- managing-memorySkill
- migrating-to-lauren-aiSkill
- multi-agent-delegationSkill
- multi-tool-orchestrationSkill
- multi-turn-tool-stateSkill
- output-guardrailsSkill
- plan-and-execute-agentSkill
- prompt-injection-defenseSkill
- prompt-versioningSkill
- rag-pipelineSkill
- react-agent-loopSkill
- response-validation-retrySkill
- rest-api-toolSkill
- securing-agentsSkill
- semantic-cacheSkill
- streaming-response-handlerSkill
- structured-output-parsingSkill
- sub-agent-spawningSkill
- system-prompt-templatingSkill
- testing-agentsSkill
- The first 60 shown.
The file
We split this llms.txt into the rules it gives an agent
3 rules. About 0 are common, 0 are distinctive, and 3 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈0
Distinctive · estimate
This author's own choices. Worth a look.
3
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 90
Must be invoked with parentheses: @tool() not @tool
- Very specific: line 168
If an agent omits model=..., AgentModule.for_root() fills in the model from the imported LLMConfig during module wiring.
- Very specific: line 322
No named AgentRunnerBase subclass needed.
≈ “Common” is an estimate: at least 1 in 5 of the 3,592 llms.txt files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
# lauren-ai
> First-party AI/LLM companion for the Lauren web framework.
`lauren-ai` integrates large-language-model completions, agentic loops, tool
execution, memory management, and structured workflows into Lauren applications
using the same decorator-first, DI-driven, module-scoped paradigm as Lauren itself.
## Installation
```bash
pip install "lauren-ai[anthropic]" # Anthropic Claude (default)
pip install "lauren-ai[openai]" # OpenAI / Ollama
pip install "lauren-ai[all]" # All providers + all extras
```
## Core Concepts
### LLMConfig — provider connection
```python
from lauren_ai import LLMConfig
# Direct construction (provider is required)
config = LLMConfig(
provider="anthropic", # "anthropic" | "openai" | "ollama" | "litellm"
model="claude-opus-4-6", # Model ID
api_key="sk-ant-...", # API key (or reads env var)
max_tokens=4096, # Max output tokens (default: 4096)
temperature=1.0, # Sampling temperature (default: 1.0)
timeout=60.0, # Request timeout in seconds (default: 60.0)
max_retries=3, # Retries on transient errors (default: 3)
)
# Convenience factories (recommended)
config = LLMConfig.for_anthropic(model="claude-opus-4-6") # reads ANTHROPIC_API_KEY
config = LLMConfig.for_openai(model="gpt-4o") # reads OPENAI_API_KEY
config = LLMConfig.for_ollama(model="llama3.2") # no key needed
# Test factory (zero network calls)
cfg, mock = LLMConfig.for_testing() # returns (LLMConfig, MockTransport)
```
### AgentConfig — runtime behaviour
```python
from lauren_ai import AgentConfig
config = AgentConfig(
system_prompt="You are a helpful assistant.", # Default system prompt
max_turns=10, # Max agentic loop turns (default: 10)
max_tokens_per_turn=4096, # Max output tokens per turn (default: 4096)
temperature=1.0, # Per-agent temperature override (default: 1.0)
memory_window_tokens=40_000, # Sliding memory window (default: 40_000)
max_cost_usd=None, # USD budget cap per run (None = unlimited)
parallel_tool_calls=False, # Execute tools in parallel (default: False)
tool_error_policy="return_error", # "return_error" | "raise" | "skip"
# Extended thinking (Anthropic only)
thinking=False,
thinking_budget_tokens=8_000,
# OpenAI reasoning models
reasoning_effort=None, # "low" | "medium" | "high" | None
)
```
### LLMModule — wiring into Lauren
```python
from lauren_ai import LLMModule, LLMConfig
LLMProvider = LLMModule.for_root(LLMConfig.for_anthropic())
```
### @tool() — define a tool for an agent
```python
from lauren_ai import tool, ToolContext
@tool()
async def get_weather(city: str, ctx: ToolContext | None = None) -> dict:
"""Return current weather for a city.
Args:
city: City name.
"""
return {"city": city, "temperature": 22, "condition": "sunny"}
```
Rules:
- Must be invoked with parentheses: `@tool()` not `@tool`
- Schema is generated from type annotations + Google-style `Args:` docstring section
- Any parameter annotated as `ToolContext` (or `ToolContext | None`) is injected by
the runner and excluded from the JSON schema — the parameter may have **any name**
- `from __future__ import annotations` is supported, but tool annotations still
need to resolve when `@tool()` builds the schema. Avoid unresolved forward
refs or circular imports in function-form tool files.
- Class-form tools get `@injectable(scope=SINGLETON)` applied automatically; use
`@tool() @injectable(scope=Scope.REQUEST)` to override the scope
- Subclasses of `@tool()`-decorated classes must re-apply `@tool()` themselves;
the registry raises `MetadataInheritanceError` otherwise
### @agent() + @use_tools() — declare an agent
```python
from lauren_ai import agent, use_tools
@agent(system="You are a helpful assistant.")
@use_tools(get_weather)
class WeatherAgent: ...
```
- `@agent()` is the **outermost** decorator; `@use_tools()` is below it (applied first)
- Mandatory ordering: `@agent` → `@remember` → `@use_guardrails` → `@use_tools`
- Agents are singletons in the DI container by default
### AgentRunner (Protocol) / AgentRunnerBase (concrete)
`AgentRunner` is a `@runtime_checkable Protocol`. The concrete implementation is
`AgentRunnerBase`. In production, runners are created by `AgentModule.for_root()`.
For testing, instantiate `AgentRunnerBase` directly:
```python
from lauren_ai import AgentRunner, AgentRunnerBase
runner = AgentRunnerBase(transport=transport)
isinstance(runner, AgentRunner) # True
response = await runner.run(
agent_instance, # @agent()-decorated class instance
"What's the weather?", # User message string
conversation_id="sess-1",
metadata={"user_id": "u1"},
execution_context=exec_ctx, # lauren ExecutionContext (optional, from route handler)
)
print(response.content) # str — final text output
print(response.turns) # int — agentic loop iterations
print(response.stop_reason) # "end_turn" | "max_turns" | "budget_exceeded" | "error"
print(response.total_usage) # TokenUsage — cumulative tokens
print(response.tool_calls_made) # list[ToolCall]
print(response.reasoning_traces) # list[str] — extended thinking traces (Anthropic)
```
**Rule: every `AgentModule` MUST have its own runner.**
Each `AgentModule.for_root()` call auto-generates a unique `AgentRunnerBase`
subclass and registers `AgentRunner[agent_cls]` for every agent in `agents=`.
- **Single module:** inject with `runner: AgentRunner` (structural Protocol scan).
- **Multiple modules:** inject with `runner: AgentRunner[MyAgent]` — a cached real
subclass used as an unambiguous DI token. No named `AgentRunnerBase` subclass
boilerplate needed.
```python
from lauren_ai import AgentRunner
class BankingController:
def __init__(
self,
crm_runner: AgentRunner[CRMAgent],
transfer_runner: AgentRunner[TransferAgent],
) -> None: ...
```
Both `@agent(memory=ShortTermMemory(...))` and `@agent(conversation_store=InMemoryConversationStore())`
declare per-agent state. `AgentModule.for_root()` **no longer accepts** `memory=` or
`conversation_store=` at the module level.
If an agent omits `model=...`, `AgentModule.for_root()` fills in the model from the
imported `LLMConfig` during module wiring.
### @use_guardrails() — attach guardrail instances to an agent
```python
from lauren_ai import use_guardrails, PromptInjectionFilter, PIIRedactor, LengthFilter, TopicFilter
@agent(model="claude-opus-4-6")
@use_guardrails(
input=[PromptInjectionFilter(), PIIRedactor()],
output=[LengthFilter(max_chars=8000)],
)
@use_tools(my_tool)
class SafeAgent: ...
```
Built-ins: `PromptInjectionFilter`, `PIIRedactor`, `LengthFilter`, `TopicFilter`, `LLMGuardrail`.
### @guardrail() — DI-injectable guardrail class
```python
from lauren_ai import guardrail, GuardrailDecision, GuardrailContext
@guardrail(kind="input") # also marks as @injectable(scope=Scope.SINGLETON)
class ProfanityFilter:
async def check(self, message: str, ctx: GuardrailContext) -> GuardrailDecision:
if "badword" in message.lower():
return GuardrailDecision(action="block", violation="Profanity",
guardrail_name="ProfanityFilter")
return GuardrailDecision(action="pass", guardrail_name="ProfanityFilter")
```
`kind` may be `"input"`, `"output"`, or `"any"` (default).
### @remember() — persistent user memory
```python
from lauren_ai import remember, InMemoryUserMemoryStore
_memory = InMemoryUserMemoryStore()
@agent(model="claude-opus-4-6")
@remember(store=_memory, extract=True, inject=True, top_k=5)
@use_guardrails(input=[PromptInjectionFilter()])
@use_tools(my_tool)
class PersonalAgent: ...
```
- Stack between `@agent()` and `@use_guardrails()`
- `extract=True`: facts extracted from each turn and stored
- `inject=True`: top-k relevant facts prepended to system prompt
### LLMService — low-level completions
```python
from lauren_ai import LLMService, Message
class MyController:
def __init__(self, llm: LLMService) -> None:
self._llm = llm
@get("/complete")
async def complete(self) -> dict:
result = await self._llm.complete(
[Message.user("Say hello!")]
)
return {"text": result.content}
```
### ToolContext — injected run context
```python
from lauren_ai import ToolContext
# Any parameter name works — annotation determines injection:
@tool()
async def my_tool(query: str, ctx: ToolContext | None = None) -> dict:
"""..."""
# ctx.agent_context — AgentContext for the running agent
# ctx.tool_use_id — Provider-assigned tool call ID
# ctx.turn — Current agentic loop iteration (0-based)
# ctx.request — Originating HTTP request (or None)
# ctx.execution_context — lauren ExecutionContext (route/handler metadata, or None)
# ctx.state — Mutable dict for per-call local storage
# ctx.get_metadata("key") — Read from agent_context.metadata
return {"result": query}
```
### Memory
```python
from lauren_ai import ShortTermMemory, InMemoryVectorStore, InMemoryConversationStore
mem = ShortTermMemory(max_tokens=40_000)
mem.add_user("Hello!")
store = InMemoryVectorStore()
conv = InMemoryConversationStore()
```
`ShortTermMemory` validates provider-neutral tool-call exchanges before every
provider request. Use `begin_tool_exchange()`, `commit_tool_exchange()`, and
`validate_tool_history()` for a batch: every non-empty unique assistant call ID
must have exactly one adjacent result. Cancellations, approval filtering, and
partial execution synthesize bounded error results for unresolved calls;
unknown, duplicate, empty, or non-adjacent IDs raise
`ToolConversationIntegrityError` before network I/O. Snapshots retain active
exchange state, and the lifecycle signals include
`ToolExchangeStarted`, `ToolExchangeResultRecorded`, `ToolExchangeCommitted`,
`ToolExchangeAborted`, and `ToolExchangeRepaired`.
### Signals — observability hooks
```python
from lauren_ai import SignalBus, ModelCallComplete, AgentRunComplete
bus = SignalBus()
@bus.on(ModelCallComplete)
async def on_complete(event: ModelCallComplete) -> None:
print(f"Cost: ${event.cost_usd:.6f}, tokens: {event.usage.total_tokens}")
@bus.on(AgentRunComplete)
async def on_run_done(event: AgentRunComplete) -> None:
print(f"Agent {event.agent_class.__name__} — turns={event.turns}, "
f"total_cost=${event.total_cost_usd:.6f}")
```
### @team() — multi-agent teams
```python
from lauren_ai import team, TeamRunner, TeamResult
@team(name="research", mode="coordinator", max_rounds=4, model="openai/gpt-4o-mini")
class ResearchTeam:
def __init__(self, researcher: ResearchAgent, writer: WriterAgent) -> None:
self.researcher = researcher
self.writer = writer
runner = TeamRunner(team_cls=ResearchTeam, llm=llm_service, agent_runner=agent_runner)
result: TeamResult = await runner.run("Summarise AI safety research.")
print(result.final_answer)
print(result.worker_outputs) # {"researcher": "...", "writer": "..."}
print(result.rounds)
```
Stream events via `runner.run_stream(...)`:
`TeamWorkerStarted`, `TeamWorkerFinished`, `TeamCoordinatorDecision`, `TeamFinalAnswer`.
### Delegation pattern — cross-module tool routing
The delegation tool lives in the **calling module's `tools=`**. The calling module
imports the target module so `AgentRunner[TargetAgent]` is visible to DI.
No named `AgentRunnerBase` subclass needed.
```python
from lauren_ai import AgentRunner, tool, ToolContext
@tool()
class DelegateToSpecialist:
"""Delegate a task to the SpecialistAgent.
Args:
task: Full description of the task.
"""
def __init__(
self,
agent: SpecialistAgent,
runner: AgentRunner[SpecialistAgent], # ← parameterized token — no boilerplate
) -> None:
self._agent = agent
self._runner = runner
async def run(self, ctx: ToolContext, task: str) -> dict:
response = await self._runner.run(self._agent, task,
execution_context=ctx.execution_context)
return {"result": response.content}
# Target module: AgentRunner[SpecialistAgent] is auto-registered
SpecialistMod = AgentModule.for_root(
agents=[SpecialistAgent],
tools=[SpecialistTool1],
imports=[LLMProvider],
)
# Calling module: owns the delegation tool; imports target module so
# AgentRunner[SpecialistAgent] is visible to DI when resolving DelegateToSpecialist
OrchestratorMod = AgentModule.for_root(
agents=[OrchestratorAgent],
tools=[DelegateToSpecialist], # ← delegation tool lives HERE
imports=[LLMProvider, SpecialistMod], # ← makes AgentRunner[SpecialistAgent] visible
)
```
**Rule:** The delegation tool belongs in the **calling module's `tools=`**. The
calling module imports the target module so the target runner token is visible.
### @traced() — tracing & observability
```python
from lauren_ai import traced, SpanKind, TraceStore, InMemoryTraceExporter, set_trace_store
exporter = InMemoryTraceExporter()
store = TraceStore()
store._exporters = [exporter]
set_trace_store(store)
@traced(name="my_handler", kind=SpanKind.AGENT)
async def my_handler(request): ...
```
### Cost & Rate Tracking
```python
from lauren_ai import CostTracker, TokenBudget, RateLimiter, default_pricing_table
tracker = CostTracker(pricing=default_pricing_table())
budget = TokenBudget(max_tokens_per_conversation=100_000)
# Raises BudgetExceededError when token limit hit
limiter = RateLimiter(requests_per_minute=60)
await limiter.acquire()
# Raises RateLimitExhaustedError when ceiling breached
```
### StructuredLLM — typed LLM outputs
```python
from lauren_ai import StructuredLLM, LLMService, Message
from pydantic import BaseModel
class Sentiment(BaseModel):
label: str
score: float
structured = llm_service.with_structured_output(Sentiment)
result: Sentiment = await structured.complete([Message.user("Great product!")])
```
### Prompt Templates & Chains
```python
from lauren_ai import PromptTemplate, ChatPromptTemplate, Chain, StrOutputParser
tmpl = PromptTemplate("Summarise: {text}")
chain = tmpl | llm_service | StrOutputParser()
result = await chain.invoke({"text": "Some long document..."})
```
### Output Parsers
```python
from lauren_ai import StrOutputParser, JSONOutputParser, PydanticOutputParser
parser = PydanticOutputParser(MyModel)
chain = prompt | llm_service | parser
```
Available: `StrOutputParser`, `JSONOutputParser`, `RegexParser`,
`CommaSeparatedListParser`, `MarkdownCodeBlockParser`, `PydanticOutputParser`,
`RetryOutputParser`.
### SemanticRouter
```python
from lauren_ai import SemanticRouter, Route, RouteMatch
router = SemanticRouter(
routes=[Route(name="weather", examples=["What's the weather?", "Is it raining?"])],
embed_fn=embed_fn,
min_confidence=0.7,
)
await router.compile()
match: RouteMatch = await router.route("Will it snow today?")
```
### Multimodal inputs
```python
from lauren_ai import ImageContent, AudioContent, DocumentContent, Message
msg = Message.from_multimodal("user", [
"Describe this image:",
ImageContent(url="https://example.com/img.png"),
])
```
## Decorator quick-reference
| Decorator | Purpose | Parentheses required |
|-----------|---------|---------------------|
| `@tool()` | Expose a function or class as an agent tool | Yes |
| `@agent()` | Declare an agent class | Yes |
| `@use_tools(t1, t2)` | Attach tools to an agent | Yes |
| `@use_knowledge_sources(ks, ...)` | Opt agent into specific KB sources (opt-in — no decorator = no KB tools) | Yes |
| `@use_guardrails(...)` | Attach guardrail instances to an agent | Yes |
| `@guardrail()` | Mark a class as a DI-injectable guardrail | Yes |
| `@remember()` | Enable persistent user memory | Yes |
| `@team()` | Declare a multi-agent team | Yes |
| `@traced()` | Wrap with tracing spans | Yes |
## Decorator ordering (mandatory)
```
@agent() ← outermost
@remember() ← optional
@use_guardrails() ← optional
@use_knowledge_sources() ← optional (KB opt-in; must be below @agent)
@use_tools() ← innermost
class Agent: ...
```
## Critical rule — tool annotations must resolve
`from __future__ import annotations` is supported in both agent and tool files, but
`@tool()` still resolves parameter types when the module is imported. Keep tool
annotations importable and avoid unresolved forward references or circular imports,
especially in function-form tools.
## Links
- Documentation: https://docs.lauren-framework.dev/lauren-ai/
- GitHub: https://github.com/lauren-framework/lauren-ai
- PyPI: https://pypi.org/project/lauren-ai/
- Changelog: https://github.com/lauren-framework/lauren-ai/blob/main/CHANGELOG.md
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

