langclaw 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- langclaw-0.1.0/.cursor/rules/extending-the-framework.mdc +48 -0
- langclaw-0.1.0/.cursor/rules/testing.mdc +26 -0
- langclaw-0.1.0/.env.example +249 -0
- langclaw-0.1.0/.github/workflows/ci.yml +63 -0
- langclaw-0.1.0/.github/workflows/publish.yml +54 -0
- langclaw-0.1.0/.gitignore +38 -0
- langclaw-0.1.0/.pre-commit-config.yaml +20 -0
- langclaw-0.1.0/.python-version +1 -0
- langclaw-0.1.0/AGENTS.md +49 -0
- langclaw-0.1.0/LICENSE +21 -0
- langclaw-0.1.0/PKG-INFO +311 -0
- langclaw-0.1.0/README.md +232 -0
- langclaw-0.1.0/docs/ARCHITECTURE.md +44 -0
- langclaw-0.1.0/examples/README.md +51 -0
- langclaw-0.1.0/examples/__init__.py +0 -0
- langclaw-0.1.0/examples/echo_bot.py +38 -0
- langclaw-0.1.0/examples/gmail_assistant.py +151 -0
- langclaw-0.1.0/examples/knowledge_base_bot.py +191 -0
- langclaw-0.1.0/examples/nobel_assistant.py +230 -0
- langclaw-0.1.0/examples/research_assistant.py +191 -0
- langclaw-0.1.0/examples/websocket_guard.py +125 -0
- langclaw-0.1.0/langclaw/__init__.py +34 -0
- langclaw-0.1.0/langclaw/agents/__init__.py +3 -0
- langclaw-0.1.0/langclaw/agents/builder.py +274 -0
- langclaw-0.1.0/langclaw/agents/defaults/AGENTS.md +41 -0
- langclaw-0.1.0/langclaw/agents/defaults/skills/cron/SKILL.md +106 -0
- langclaw-0.1.0/langclaw/agents/defaults/skills/skill-creator/LICENSE.txt +202 -0
- langclaw-0.1.0/langclaw/agents/defaults/skills/skill-creator/SKILL.md +357 -0
- langclaw-0.1.0/langclaw/agents/defaults/skills/skill-creator/references/output-patterns.md +82 -0
- langclaw-0.1.0/langclaw/agents/defaults/skills/skill-creator/references/workflows.md +28 -0
- langclaw-0.1.0/langclaw/agents/defaults/skills/skill-creator/scripts/init_skill.py +299 -0
- langclaw-0.1.0/langclaw/agents/defaults/skills/skill-creator/scripts/package_skill.py +111 -0
- langclaw-0.1.0/langclaw/agents/defaults/skills/skill-creator/scripts/quick_validate.py +124 -0
- langclaw-0.1.0/langclaw/agents/subagents.py +156 -0
- langclaw-0.1.0/langclaw/agents/tools/__init__.py +111 -0
- langclaw-0.1.0/langclaw/agents/tools/cron.py +221 -0
- langclaw-0.1.0/langclaw/agents/tools/gmail.py +466 -0
- langclaw-0.1.0/langclaw/agents/tools/gmail_auth.py +134 -0
- langclaw-0.1.0/langclaw/agents/tools/web_fetch.py +194 -0
- langclaw-0.1.0/langclaw/agents/tools/web_search.py +200 -0
- langclaw-0.1.0/langclaw/app.py +512 -0
- langclaw-0.1.0/langclaw/bus/__init__.py +43 -0
- langclaw-0.1.0/langclaw/bus/asyncio_bus.py +59 -0
- langclaw-0.1.0/langclaw/bus/base.py +123 -0
- langclaw-0.1.0/langclaw/bus/kafka_bus.py +99 -0
- langclaw-0.1.0/langclaw/bus/rabbitmq_bus.py +108 -0
- langclaw-0.1.0/langclaw/checkpointer/__init__.py +32 -0
- langclaw-0.1.0/langclaw/checkpointer/base.py +51 -0
- langclaw-0.1.0/langclaw/checkpointer/postgres.py +52 -0
- langclaw-0.1.0/langclaw/checkpointer/sqlite.py +40 -0
- langclaw-0.1.0/langclaw/cli/__init__.py +3 -0
- langclaw-0.1.0/langclaw/cli/app.py +416 -0
- langclaw-0.1.0/langclaw/cli/utils.py +34 -0
- langclaw-0.1.0/langclaw/config/__init__.py +19 -0
- langclaw-0.1.0/langclaw/config/schema.py +456 -0
- langclaw-0.1.0/langclaw/context.py +19 -0
- langclaw-0.1.0/langclaw/cron/__init__.py +270 -0
- langclaw-0.1.0/langclaw/cron/scheduler.py +417 -0
- langclaw-0.1.0/langclaw/cron/utils.py +8 -0
- langclaw-0.1.0/langclaw/gateway/__init__.py +4 -0
- langclaw-0.1.0/langclaw/gateway/base.py +117 -0
- langclaw-0.1.0/langclaw/gateway/commands.py +191 -0
- langclaw-0.1.0/langclaw/gateway/discord.py +493 -0
- langclaw-0.1.0/langclaw/gateway/manager.py +392 -0
- langclaw-0.1.0/langclaw/gateway/telegram.py +455 -0
- langclaw-0.1.0/langclaw/gateway/utils.py +133 -0
- langclaw-0.1.0/langclaw/gateway/websocket.py +266 -0
- langclaw-0.1.0/langclaw/heartbeat/__init__.py +3 -0
- langclaw-0.1.0/langclaw/heartbeat/watcher.py +169 -0
- langclaw-0.1.0/langclaw/middleware/__init__.py +12 -0
- langclaw-0.1.0/langclaw/middleware/channel_context.py +37 -0
- langclaw-0.1.0/langclaw/middleware/guardrails.py +90 -0
- langclaw-0.1.0/langclaw/middleware/permissions.py +73 -0
- langclaw-0.1.0/langclaw/middleware/rate_limit.py +73 -0
- langclaw-0.1.0/langclaw/providers/__init__.py +0 -0
- langclaw-0.1.0/langclaw/py.typed +0 -0
- langclaw-0.1.0/langclaw/session/__init__.py +3 -0
- langclaw-0.1.0/langclaw/session/manager.py +106 -0
- langclaw-0.1.0/langclaw/utils.py +43 -0
- langclaw-0.1.0/pyproject.toml +106 -0
- langclaw-0.1.0/tests/__init__.py +0 -0
- langclaw-0.1.0/tests/test_config.py +308 -0
- langclaw-0.1.0/tests/test_gateway.py +301 -0
- langclaw-0.1.0/tests/test_gmail_tools.py +315 -0
- langclaw-0.1.0/tests/test_subagents.py +486 -0
- langclaw-0.1.0/uv.lock +4574 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: When adding new tools, channels, middleware, message bus backends, or checkpointer backends to the langclaw framework
|
|
3
|
+
alwaysApply: false
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Extending Langclaw
|
|
7
|
+
|
|
8
|
+
## Adding a Tool
|
|
9
|
+
|
|
10
|
+
Tools are async functions decorated with `@tool` from `langchain_core.tools` or registered via `@app.tool()` on the `Langclaw` instance. Follow the pattern in `langclaw/agents/tools/`.
|
|
11
|
+
|
|
12
|
+
- Return a dict on failure: `{"error": f"Failed to ...: {exc}"}` — never raise
|
|
13
|
+
- Add type hints to all parameters — LangChain uses them for the tool schema
|
|
14
|
+
- The docstring becomes the tool description the LLM sees — make it actionable
|
|
15
|
+
- Register in `langclaw/agents/tools/__init__.py` if it's a built-in tool
|
|
16
|
+
|
|
17
|
+
## Adding a Channel
|
|
18
|
+
|
|
19
|
+
Extend `BaseChannel` in `langclaw/gateway/base.py`. See `telegram.py`, `discord.py`, `websocket.py` for reference.
|
|
20
|
+
|
|
21
|
+
- Implement `start()`, `stop()`, `send()`, and `send_ai_message()`
|
|
22
|
+
- The channel publishes `InboundMessage` to the bus — it never talks to the agent directly
|
|
23
|
+
- Add the optional dependency to `pyproject.toml` under `[project.optional-dependencies]`
|
|
24
|
+
- Guard the import in `Langclaw._build_all_channels()` with `try/except ImportError`
|
|
25
|
+
|
|
26
|
+
## Adding a Bus or Checkpointer Backend
|
|
27
|
+
|
|
28
|
+
Follow the abstract-base + factory pattern:
|
|
29
|
+
|
|
30
|
+
1. Create the implementation in the respective package (e.g. `bus/redis_bus.py`)
|
|
31
|
+
2. Extend the abstract base (`bus/base.py` or `checkpointer/base.py`)
|
|
32
|
+
3. Implement the async context manager protocol (`__aenter__` / `__aexit__`)
|
|
33
|
+
4. Register in the factory function (`make_message_bus` or `make_checkpointer_backend`)
|
|
34
|
+
5. Add config options to `config/schema.py` following the existing nested pattern
|
|
35
|
+
|
|
36
|
+
## Adding Middleware
|
|
37
|
+
|
|
38
|
+
Middleware uses the `@wrap_model_call` pattern from deepagents. See `middleware/permissions.py` for the canonical example.
|
|
39
|
+
|
|
40
|
+
- Middleware filters or transforms the tool list **before** the LLM sees it
|
|
41
|
+
- Order matters — middleware is composed in `agents/builder.py`
|
|
42
|
+
- Accept `LangclawConfig` in the factory, return the middleware callable
|
|
43
|
+
|
|
44
|
+
## Config Changes
|
|
45
|
+
|
|
46
|
+
When adding config, update both:
|
|
47
|
+
1. `langclaw/config/schema.py` — add the Pydantic model/field
|
|
48
|
+
2. `.env.example` — document the new env var with the `LANGCLAW__` prefix
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Testing conventions for langclaw
|
|
3
|
+
globs: tests/**/*.py
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Testing
|
|
8
|
+
|
|
9
|
+
## Setup
|
|
10
|
+
|
|
11
|
+
- Framework: pytest + pytest-asyncio (`asyncio_mode = "auto"` — no need for `@pytest.mark.asyncio`)
|
|
12
|
+
- Run: `uv run pytest tests/ -v`
|
|
13
|
+
|
|
14
|
+
## Patterns
|
|
15
|
+
|
|
16
|
+
- Use `monkeypatch.setenv()` for env var overrides — never mutate `os.environ` directly
|
|
17
|
+
- Use factory functions (`make_message_bus`, `make_checkpointer_backend`) for integration tests
|
|
18
|
+
- Class-based grouping for related tests (e.g. `TestSplitMessage`, `TestIsAllowed`)
|
|
19
|
+
- Prefer real implementations over mocks when the component is fast and deterministic (buses, config)
|
|
20
|
+
- For async tests, just define `async def test_...` — the auto mode handles the rest
|
|
21
|
+
|
|
22
|
+
## Assertions
|
|
23
|
+
|
|
24
|
+
- Assert tool error responses with `assert result == {"error": ...}`
|
|
25
|
+
- For bus tests, use `async with bus:` and break after first consumed message
|
|
26
|
+
- For config tests, use `monkeypatch.setenv("LANGCLAW__...", value)` then call `load_config()`
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
# ============================================================
|
|
2
|
+
# langclaw — environment configuration
|
|
3
|
+
# Copy this file to .env and fill in your values.
|
|
4
|
+
# All variables use the LANGCLAW__ prefix with __ as the
|
|
5
|
+
# nested delimiter (matches pydantic-settings).
|
|
6
|
+
# ============================================================
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# ------------------------------------------------------------
|
|
10
|
+
# LLM Providers — set at least one
|
|
11
|
+
# ------------------------------------------------------------
|
|
12
|
+
# Langclaw uses LangChain's init_chat_model() which reads standard
|
|
13
|
+
# provider env vars directly. Set the key for your chosen provider.
|
|
14
|
+
|
|
15
|
+
# Anthropic (default model: claude-sonnet-4-5-20250929)
|
|
16
|
+
# ANTHROPIC_API_KEY=sk-ant-...
|
|
17
|
+
|
|
18
|
+
# OpenAI
|
|
19
|
+
# OPENAI_API_KEY=sk-...
|
|
20
|
+
|
|
21
|
+
# Google Gemini
|
|
22
|
+
# GOOGLE_API_KEY=...
|
|
23
|
+
|
|
24
|
+
# OpenRouter (routes to any model, useful as a fallback)
|
|
25
|
+
# OPENROUTER_API_KEY=...
|
|
26
|
+
|
|
27
|
+
# Azure OpenAI
|
|
28
|
+
# AZURE_OPENAI_API_KEY=...
|
|
29
|
+
# AZURE_OPENAI_ENDPOINT=https://<resource>.openai.azure.com/
|
|
30
|
+
# OPENAI_API_VERSION=2025-01-01-preview
|
|
31
|
+
# Set model to azure_openai:<your-deployment-name>
|
|
32
|
+
# The part after the colon is the Azure *deployment* name (set in Azure AI Foundry),
|
|
33
|
+
# NOT the underlying model name. Examples:
|
|
34
|
+
# LANGCLAW__AGENTS__MODEL=azure_openai:gpt-4o-prod
|
|
35
|
+
# LANGCLAW__AGENTS__MODEL=azure_openai:my-claude-deployment
|
|
36
|
+
|
|
37
|
+
# Ollama (local — no key needed)
|
|
38
|
+
# OLLAMA_BASE_URL=http://localhost:11434
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# ------------------------------------------------------------
|
|
42
|
+
# Agent
|
|
43
|
+
# ------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
# Model string format: "<provider>:<model-name>"
|
|
46
|
+
# Examples: anthropic:claude-sonnet-4-5-20250929 | openai:gpt-4.1
|
|
47
|
+
LANGCLAW__AGENTS__MODEL=anthropic:claude-sonnet-4-5-20250929
|
|
48
|
+
|
|
49
|
+
# Requests per minute per user before rate-limit kicks in
|
|
50
|
+
LANGCLAW__AGENTS__RATE_LIMIT_RPM=60
|
|
51
|
+
|
|
52
|
+
# Comma-separated keywords that will be blocked before reaching the LLM
|
|
53
|
+
# LANGCLAW__AGENTS__BANNED_KEYWORDS=
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# ------------------------------------------------------------
|
|
57
|
+
# Permissions (per-user tool RBAC)
|
|
58
|
+
# ------------------------------------------------------------
|
|
59
|
+
|
|
60
|
+
# Enable per-user tool permission filtering.
|
|
61
|
+
# When disabled (default), all users get access to every tool.
|
|
62
|
+
# LANGCLAW__PERMISSIONS__ENABLED=false
|
|
63
|
+
|
|
64
|
+
# Role assigned to users not listed in any channel's USER_ROLES.
|
|
65
|
+
# LANGCLAW__PERMISSIONS__DEFAULT_ROLE=viewer
|
|
66
|
+
|
|
67
|
+
# Role definitions (role -> allowed tools) are best set in
|
|
68
|
+
# ~/.langclaw/config.json because nested dicts don't map
|
|
69
|
+
# cleanly to env vars. Example config.json snippet:
|
|
70
|
+
#
|
|
71
|
+
# {
|
|
72
|
+
# "permissions": {
|
|
73
|
+
# "enabled": true,
|
|
74
|
+
# "default_role": "viewer",
|
|
75
|
+
# "roles": {
|
|
76
|
+
# "admin": { "tools": ["*"] },
|
|
77
|
+
# "editor": { "tools": ["web_search", "web_fetch",
|
|
78
|
+
# "gmail_read", "gmail_send"] },
|
|
79
|
+
# "viewer": { "tools": ["web_search", "web_fetch"] }
|
|
80
|
+
# }
|
|
81
|
+
# }
|
|
82
|
+
# }
|
|
83
|
+
#
|
|
84
|
+
# Use ["*"] to grant a role access to all tools (including
|
|
85
|
+
# deepagents built-ins like ls, read_file, write_file, etc.).
|
|
86
|
+
# Per-channel user -> role mappings live alongside ALLOW_FROM
|
|
87
|
+
# in each channel section below.
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# ------------------------------------------------------------
|
|
91
|
+
# Channels — Telegram (default/recommended)
|
|
92
|
+
# ------------------------------------------------------------
|
|
93
|
+
|
|
94
|
+
# Get a token from @BotFather on Telegram
|
|
95
|
+
LANGCLAW__CHANNELS__TELEGRAM__ENABLED=true
|
|
96
|
+
LANGCLAW__CHANNELS__TELEGRAM__TOKEN=<your-telegram-bot-token>
|
|
97
|
+
|
|
98
|
+
# Whitelist of Telegram user IDs or @usernames (leave empty to allow everyone)
|
|
99
|
+
# LANGCLAW__CHANNELS__TELEGRAM__ALLOW_FROM=123456789,@yourusername
|
|
100
|
+
|
|
101
|
+
# Per-user role mapping (format: user_id:role,user_id:role)
|
|
102
|
+
# Only used when PERMISSIONS__ENABLED=true
|
|
103
|
+
# LANGCLAW__CHANNELS__TELEGRAM__USER_ROLES=123456789:admin,@yourusername:editor
|
|
104
|
+
|
|
105
|
+
# Discord (disabled by default — requires langclaw[discord])
|
|
106
|
+
# LANGCLAW__CHANNELS__DISCORD__ENABLED=false
|
|
107
|
+
# LANGCLAW__CHANNELS__DISCORD__TOKEN=
|
|
108
|
+
# LANGCLAW__CHANNELS__DISCORD__USER_ROLES=123456:admin,789012:viewer
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
# ------------------------------------------------------------
|
|
112
|
+
# Message Bus
|
|
113
|
+
# ------------------------------------------------------------
|
|
114
|
+
|
|
115
|
+
# asyncio — default, single-process (dev / single-instance)
|
|
116
|
+
# rabbitmq — multi-process production (requires langclaw[rabbitmq])
|
|
117
|
+
# kafka — high-throughput (requires langclaw[kafka])
|
|
118
|
+
LANGCLAW__BUS__BACKEND=asyncio
|
|
119
|
+
|
|
120
|
+
# RabbitMQ (only used when BUS__BACKEND=rabbitmq)
|
|
121
|
+
# LANGCLAW__BUS__RABBITMQ__AMQP_URL=amqp://guest:guest@localhost/
|
|
122
|
+
# LANGCLAW__BUS__RABBITMQ__QUEUE_NAME=langclaw.inbound
|
|
123
|
+
|
|
124
|
+
# Kafka (only used when BUS__BACKEND=kafka)
|
|
125
|
+
# LANGCLAW__BUS__KAFKA__BOOTSTRAP_SERVERS=localhost:9092
|
|
126
|
+
# LANGCLAW__BUS__KAFKA__TOPIC=langclaw.inbound
|
|
127
|
+
# LANGCLAW__BUS__KAFKA__GROUP_ID=langclaw
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
# ------------------------------------------------------------
|
|
131
|
+
# Checkpointer (conversation state persistence)
|
|
132
|
+
# ------------------------------------------------------------
|
|
133
|
+
|
|
134
|
+
# sqlite — default, zero-config (dev / single-process)
|
|
135
|
+
# postgres — production multi-instance (requires langclaw[postgres])
|
|
136
|
+
LANGCLAW__CHECKPOINTER__BACKEND=sqlite
|
|
137
|
+
|
|
138
|
+
# SQLite database file path (tilde expansion supported)
|
|
139
|
+
LANGCLAW__CHECKPOINTER__SQLITE__DB_PATH=~/.langclaw/state.db
|
|
140
|
+
|
|
141
|
+
# PostgreSQL DSN (only used when CHECKPOINTER__BACKEND=postgres)
|
|
142
|
+
# LANGCLAW__CHECKPOINTER__POSTGRES__DSN=postgresql://user:pass@localhost:5432/langclaw
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
# ------------------------------------------------------------
|
|
146
|
+
# Tools (web search & fetch — requires langclaw[search])
|
|
147
|
+
# ------------------------------------------------------------
|
|
148
|
+
|
|
149
|
+
# Search backend: brave | tavily | duckduckgo (default: brave)
|
|
150
|
+
# LANGCLAW__TOOLS__SEARCH_BACKEND=brave
|
|
151
|
+
|
|
152
|
+
# Brave Search API key (required when SEARCH_BACKEND=brave)
|
|
153
|
+
# Get one at https://api.search.brave.com/app/dashboard
|
|
154
|
+
# LANGCLAW__TOOLS__BRAVE_API_KEY=BSA...
|
|
155
|
+
|
|
156
|
+
# Tavily Search API key (required when SEARCH_BACKEND=tavily)
|
|
157
|
+
# Get one at https://app.tavily.com
|
|
158
|
+
# LANGCLAW__TOOLS__TAVILY_API_KEY=tvly-...
|
|
159
|
+
|
|
160
|
+
# duckduckgo requires no API key
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
# ------------------------------------------------------------
|
|
164
|
+
# Tools — Gmail (requires langclaw[gmail])
|
|
165
|
+
# ------------------------------------------------------------
|
|
166
|
+
|
|
167
|
+
# Enable Gmail tools (read, search, send, draft, reply, labels)
|
|
168
|
+
# Requires a Google Cloud project with the Gmail API enabled and
|
|
169
|
+
# OAuth 2.0 Desktop credentials.
|
|
170
|
+
# Setup guide: https://console.cloud.google.com/apis/credentials
|
|
171
|
+
# LANGCLAW__TOOLS__GMAIL__ENABLED=true
|
|
172
|
+
|
|
173
|
+
# OAuth 2.0 client_id and client_secret from your credentials JSON
|
|
174
|
+
# LANGCLAW__TOOLS__GMAIL__CLIENT_ID=123456789-xxxxxxx.apps.googleusercontent.com
|
|
175
|
+
# LANGCLAW__TOOLS__GMAIL__CLIENT_SECRET=GOCSPX-xxxxxxxxxxxxxxxx
|
|
176
|
+
|
|
177
|
+
# When true, only read_email and search_emails are registered.
|
|
178
|
+
# Set to false to also enable send, draft, reply, and label management.
|
|
179
|
+
# LANGCLAW__TOOLS__GMAIL__READONLY=true
|
|
180
|
+
|
|
181
|
+
# Path to the persisted OAuth token (created automatically on first auth)
|
|
182
|
+
# LANGCLAW__TOOLS__GMAIL__TOKEN_PATH=~/.langclaw/gmail_token.json
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
# ------------------------------------------------------------
|
|
186
|
+
# Cron
|
|
187
|
+
# ------------------------------------------------------------
|
|
188
|
+
|
|
189
|
+
LANGCLAW__CRON__ENABLED=true
|
|
190
|
+
LANGCLAW__CRON__TIMEZONE=UTC
|
|
191
|
+
|
|
192
|
+
# ── Local dev (default) ──────────────────────────────────────────────────────
|
|
193
|
+
# SQLite + local broker: persistent, zero-config, single-process.
|
|
194
|
+
# Requires: uv add sqlalchemy aiosqlite
|
|
195
|
+
#
|
|
196
|
+
# Jobs survive gateway restarts and are visible to `langclaw cron list`.
|
|
197
|
+
# Both the gateway and the CLI connect directly to the same .db file.
|
|
198
|
+
|
|
199
|
+
# Data store — where job schedules are persisted
|
|
200
|
+
# sqlite — default, persistent local file (requires sqlalchemy + aiosqlite)
|
|
201
|
+
# postgres — persistent shared DB (requires sqlalchemy + asyncpg)
|
|
202
|
+
# memory — in-process only, lost on restart
|
|
203
|
+
LANGCLAW__CRON__DATA_STORE__BACKEND=sqlite
|
|
204
|
+
|
|
205
|
+
# SQLite data store path (only used when DATA_STORE__BACKEND=sqlite)
|
|
206
|
+
# LANGCLAW__CRON__DATA_STORE__SQLITE__DB_PATH=~/.langclaw/cron.db
|
|
207
|
+
|
|
208
|
+
# ── Production ───────────────────────────────────────────────────────────────
|
|
209
|
+
# PostgreSQL data store + asyncpg or psycopg event broker.
|
|
210
|
+
# Required when running multiple gateway instances (shared scheduler state).
|
|
211
|
+
# Requires: uv add sqlalchemy asyncpg (or psycopg[binary] for psycopg broker)
|
|
212
|
+
#
|
|
213
|
+
# LANGCLAW__CRON__DATA_STORE__BACKEND=postgres
|
|
214
|
+
# LANGCLAW__CRON__DATA_STORE__POSTGRES__DSN=postgresql+asyncpg://user:pass@localhost/langclaw
|
|
215
|
+
|
|
216
|
+
# ── Production + Redis pub/sub ───────────────────────────────────────────────
|
|
217
|
+
# Use when you prefer Redis over PostgreSQL pub/sub for scheduler events.
|
|
218
|
+
# Requires: uv add sqlalchemy asyncpg redis
|
|
219
|
+
#
|
|
220
|
+
# LANGCLAW__CRON__DATA_STORE__BACKEND=postgres
|
|
221
|
+
# LANGCLAW__CRON__DATA_STORE__POSTGRES__DSN=postgresql+asyncpg://user:pass@localhost/langclaw
|
|
222
|
+
# LANGCLAW__CRON__EVENT_BROKER__BACKEND=redis
|
|
223
|
+
# LANGCLAW__CRON__EVENT_BROKER__REDIS__HOST=localhost
|
|
224
|
+
# LANGCLAW__CRON__EVENT_BROKER__REDIS__PORT=6379
|
|
225
|
+
|
|
226
|
+
# Event broker — how scheduler events are fanned out across instances
|
|
227
|
+
# local — default, in-process only (single gateway instance)
|
|
228
|
+
# asyncpg — PostgreSQL pub/sub via asyncpg (multi-instance)
|
|
229
|
+
# psycopg — PostgreSQL pub/sub via psycopg3 (multi-instance)
|
|
230
|
+
# redis — Redis pub/sub (multi-instance)
|
|
231
|
+
LANGCLAW__CRON__EVENT_BROKER__BACKEND=local
|
|
232
|
+
|
|
233
|
+
# asyncpg event broker DSN (only used when EVENT_BROKER__BACKEND=asyncpg)
|
|
234
|
+
# LANGCLAW__CRON__EVENT_BROKER__ASYNCPG__DSN=postgresql+asyncpg://user:pass@localhost/langclaw
|
|
235
|
+
|
|
236
|
+
# psycopg event broker DSN (only used when EVENT_BROKER__BACKEND=psycopg)
|
|
237
|
+
# LANGCLAW__CRON__EVENT_BROKER__PSYCOPG__DSN=postgresql+psycopg://user:pass@localhost/langclaw
|
|
238
|
+
|
|
239
|
+
# Redis event broker (only used when EVENT_BROKER__BACKEND=redis)
|
|
240
|
+
# LANGCLAW__CRON__EVENT_BROKER__REDIS__HOST=localhost
|
|
241
|
+
# LANGCLAW__CRON__EVENT_BROKER__REDIS__PORT=6379
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
# ------------------------------------------------------------
|
|
245
|
+
# Heartbeat (proactive condition-based agent wake-up)
|
|
246
|
+
# ------------------------------------------------------------
|
|
247
|
+
|
|
248
|
+
LANGCLAW__HEARTBEAT__ENABLED=false
|
|
249
|
+
LANGCLAW__HEARTBEAT__INTERVAL_SECONDS=60
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
tags: ["**"]
|
|
7
|
+
pull_request: {}
|
|
8
|
+
|
|
9
|
+
env:
|
|
10
|
+
UV_FROZEN: "1"
|
|
11
|
+
|
|
12
|
+
permissions:
|
|
13
|
+
contents: read
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
lint:
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v4
|
|
20
|
+
|
|
21
|
+
- uses: astral-sh/setup-uv@v5
|
|
22
|
+
with:
|
|
23
|
+
python-version: "3.12"
|
|
24
|
+
enable-cache: true
|
|
25
|
+
cache-suffix: lint
|
|
26
|
+
|
|
27
|
+
- name: Install dependencies
|
|
28
|
+
run: uv sync --group lint
|
|
29
|
+
|
|
30
|
+
- name: Run pre-commit
|
|
31
|
+
run: uv run pre-commit run --all-files --verbose
|
|
32
|
+
env:
|
|
33
|
+
SKIP: no-commit-to-branch
|
|
34
|
+
|
|
35
|
+
test:
|
|
36
|
+
runs-on: ubuntu-latest
|
|
37
|
+
strategy:
|
|
38
|
+
matrix:
|
|
39
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
40
|
+
steps:
|
|
41
|
+
- uses: actions/checkout@v4
|
|
42
|
+
|
|
43
|
+
- uses: astral-sh/setup-uv@v5
|
|
44
|
+
with:
|
|
45
|
+
python-version: ${{ matrix.python-version }}
|
|
46
|
+
enable-cache: true
|
|
47
|
+
cache-suffix: test
|
|
48
|
+
|
|
49
|
+
- name: Install dependencies
|
|
50
|
+
run: uv sync --all-extras
|
|
51
|
+
|
|
52
|
+
- name: Test
|
|
53
|
+
run: uv run pytest tests/ -v
|
|
54
|
+
|
|
55
|
+
check:
|
|
56
|
+
if: always()
|
|
57
|
+
needs: [lint, test]
|
|
58
|
+
runs-on: ubuntu-latest
|
|
59
|
+
steps:
|
|
60
|
+
- name: Decide whether the needed jobs succeeded or failed
|
|
61
|
+
uses: re-actors/alls-green@release/v1
|
|
62
|
+
with:
|
|
63
|
+
jobs: ${{ toJSON(needs) }}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
build:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
|
|
14
|
+
- uses: actions/setup-python@v5
|
|
15
|
+
with:
|
|
16
|
+
python-version: "3.13"
|
|
17
|
+
|
|
18
|
+
- name: Install build tools
|
|
19
|
+
run: pip install build
|
|
20
|
+
|
|
21
|
+
- name: Build package
|
|
22
|
+
run: python -m build
|
|
23
|
+
|
|
24
|
+
- uses: actions/upload-artifact@v4
|
|
25
|
+
with:
|
|
26
|
+
name: dist
|
|
27
|
+
path: dist/
|
|
28
|
+
|
|
29
|
+
publish:
|
|
30
|
+
needs: build
|
|
31
|
+
runs-on: ubuntu-latest
|
|
32
|
+
environment: pypi
|
|
33
|
+
permissions:
|
|
34
|
+
id-token: write
|
|
35
|
+
steps:
|
|
36
|
+
- uses: actions/download-artifact@v4
|
|
37
|
+
with:
|
|
38
|
+
name: dist
|
|
39
|
+
path: dist/
|
|
40
|
+
|
|
41
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
42
|
+
|
|
43
|
+
github-release:
|
|
44
|
+
needs: publish
|
|
45
|
+
runs-on: ubuntu-latest
|
|
46
|
+
permissions:
|
|
47
|
+
contents: write
|
|
48
|
+
steps:
|
|
49
|
+
- uses: actions/checkout@v4
|
|
50
|
+
|
|
51
|
+
- name: Create GitHub Release
|
|
52
|
+
env:
|
|
53
|
+
GH_TOKEN: ${{ github.token }}
|
|
54
|
+
run: gh release create "${{ github.ref_name }}" --generate-notes
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.pyo
|
|
5
|
+
*.pyd
|
|
6
|
+
.Python
|
|
7
|
+
*.egg-info/
|
|
8
|
+
dist/
|
|
9
|
+
build/
|
|
10
|
+
*.egg
|
|
11
|
+
|
|
12
|
+
# Virtual environment
|
|
13
|
+
.venv/
|
|
14
|
+
venv/
|
|
15
|
+
env/
|
|
16
|
+
|
|
17
|
+
# Secrets — never commit these
|
|
18
|
+
.env
|
|
19
|
+
*.env
|
|
20
|
+
!.env.example
|
|
21
|
+
|
|
22
|
+
# langclaw runtime data
|
|
23
|
+
~/.langclaw/
|
|
24
|
+
state.db
|
|
25
|
+
*.db
|
|
26
|
+
workspace/**
|
|
27
|
+
|
|
28
|
+
# IDE
|
|
29
|
+
.idea/
|
|
30
|
+
.vscode/
|
|
31
|
+
*.swp
|
|
32
|
+
*.swo
|
|
33
|
+
.DS_Store
|
|
34
|
+
|
|
35
|
+
# pytest / coverage
|
|
36
|
+
.pytest_cache/
|
|
37
|
+
.coverage
|
|
38
|
+
htmlcov/
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
3
|
+
rev: v5.0.0
|
|
4
|
+
hooks:
|
|
5
|
+
# - id: no-commit-to-branch
|
|
6
|
+
- id: check-yaml
|
|
7
|
+
args: ["--unsafe"]
|
|
8
|
+
- id: check-toml
|
|
9
|
+
- id: end-of-file-fixer
|
|
10
|
+
- id: trailing-whitespace
|
|
11
|
+
- id: check-added-large-files
|
|
12
|
+
args: ["--maxkb=512"]
|
|
13
|
+
exclude: uv\.lock
|
|
14
|
+
|
|
15
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
16
|
+
rev: v0.9.2
|
|
17
|
+
hooks:
|
|
18
|
+
- id: ruff-format
|
|
19
|
+
- id: ruff
|
|
20
|
+
args: ["--fix"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.13
|
langclaw-0.1.0/AGENTS.md
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Langclaw
|
|
2
|
+
|
|
3
|
+
Multi-channel AI agent **framework** (not an app) built on LangChain, LangGraph, and deepagents. Developers `pip install langclaw` and build on top of it — think Flask/FastAPI for agentic systems. Python 3.11+.
|
|
4
|
+
|
|
5
|
+
## Package Map
|
|
6
|
+
|
|
7
|
+
| Package | Purpose |
|
|
8
|
+
|---|---|
|
|
9
|
+
| `app.py` | `Langclaw` class — developer's primary interface (decorators, lifecycle, wiring) |
|
|
10
|
+
| `agents/` | LangGraph agent construction, tool wiring, subagent delegation |
|
|
11
|
+
| `gateway/` | Channel orchestration (`GatewayManager`), command routing, message dispatch |
|
|
12
|
+
| `bus/` | Message bus abstraction — asyncio (dev), RabbitMQ, Kafka (prod) |
|
|
13
|
+
| `middleware/` | Request pipeline: RBAC, rate limit, content filter, PII redaction |
|
|
14
|
+
| `config/` | Pydantic Settings with `LANGCLAW__` env var prefix (nested `__` delimiter) |
|
|
15
|
+
| `cron/` | Scheduled jobs via APScheduler v4 |
|
|
16
|
+
| `session/` | Maps (channel, user, context) → LangGraph thread IDs |
|
|
17
|
+
| `checkpointer/` | Conversation state persistence — SQLite (dev), Postgres (prod) |
|
|
18
|
+
| `providers/` | LLM model resolution via `init_chat_model` |
|
|
19
|
+
| `cli/` | Typer CLI: `langclaw gateway`, `langclaw agent`, `langclaw cron`, `langclaw status` |
|
|
20
|
+
|
|
21
|
+
## Development
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
uv sync --group dev # Install all deps
|
|
25
|
+
uv run pytest tests/ -v # Run tests
|
|
26
|
+
uv run ruff check . && uv run ruff format . # Lint + format
|
|
27
|
+
uv run pre-commit run --all-files # Full pre-commit suite
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Architecture
|
|
31
|
+
|
|
32
|
+
Read `docs/ARCHITECTURE.md` for design principles and rationale. Critical invariants:
|
|
33
|
+
|
|
34
|
+
- **Message flow:** Channel → `InboundMessage` → Bus → `GatewayManager` → Middleware → Agent → `OutboundMessage` → Channel
|
|
35
|
+
- **Commands** (`/start`, `/reset`, `/help`) bypass the bus and LLM — handled by `CommandRouter` in `gateway/commands.py`
|
|
36
|
+
- **Cron jobs** publish `InboundMessage` to the same bus, flowing through the identical agent pipeline
|
|
37
|
+
- **Pluggable backends** always follow: abstract `base.py` + factory function (`make_message_bus`, `make_checkpointer_backend`)
|
|
38
|
+
- **Middleware order matters** — see `agents/builder.py` for the stack composition
|
|
39
|
+
- **Explicit registration** over auto-discovery — tools, channels, middleware are registered on the `Langclaw` app object
|
|
40
|
+
|
|
41
|
+
## Conventions
|
|
42
|
+
|
|
43
|
+
- `from __future__ import annotations` in every module
|
|
44
|
+
- `TYPE_CHECKING` guard for import-only types
|
|
45
|
+
- Modern type syntax: `list[T]`, `dict[K, V]`, `str | None` — never `typing.List`, `Optional`
|
|
46
|
+
- `loguru.logger` for all application logging (not stdlib `logging`)
|
|
47
|
+
- Google-style docstrings (Args/Returns/Raises)
|
|
48
|
+
- Tools return `{"error": "..."}` dicts on failure — never raise into the agent
|
|
49
|
+
- Ruff handles formatting and linting — see `[tool.ruff]` in `pyproject.toml`
|
langclaw-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-2026 langclaw contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|