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.
Files changed (86) hide show
  1. langclaw-0.1.0/.cursor/rules/extending-the-framework.mdc +48 -0
  2. langclaw-0.1.0/.cursor/rules/testing.mdc +26 -0
  3. langclaw-0.1.0/.env.example +249 -0
  4. langclaw-0.1.0/.github/workflows/ci.yml +63 -0
  5. langclaw-0.1.0/.github/workflows/publish.yml +54 -0
  6. langclaw-0.1.0/.gitignore +38 -0
  7. langclaw-0.1.0/.pre-commit-config.yaml +20 -0
  8. langclaw-0.1.0/.python-version +1 -0
  9. langclaw-0.1.0/AGENTS.md +49 -0
  10. langclaw-0.1.0/LICENSE +21 -0
  11. langclaw-0.1.0/PKG-INFO +311 -0
  12. langclaw-0.1.0/README.md +232 -0
  13. langclaw-0.1.0/docs/ARCHITECTURE.md +44 -0
  14. langclaw-0.1.0/examples/README.md +51 -0
  15. langclaw-0.1.0/examples/__init__.py +0 -0
  16. langclaw-0.1.0/examples/echo_bot.py +38 -0
  17. langclaw-0.1.0/examples/gmail_assistant.py +151 -0
  18. langclaw-0.1.0/examples/knowledge_base_bot.py +191 -0
  19. langclaw-0.1.0/examples/nobel_assistant.py +230 -0
  20. langclaw-0.1.0/examples/research_assistant.py +191 -0
  21. langclaw-0.1.0/examples/websocket_guard.py +125 -0
  22. langclaw-0.1.0/langclaw/__init__.py +34 -0
  23. langclaw-0.1.0/langclaw/agents/__init__.py +3 -0
  24. langclaw-0.1.0/langclaw/agents/builder.py +274 -0
  25. langclaw-0.1.0/langclaw/agents/defaults/AGENTS.md +41 -0
  26. langclaw-0.1.0/langclaw/agents/defaults/skills/cron/SKILL.md +106 -0
  27. langclaw-0.1.0/langclaw/agents/defaults/skills/skill-creator/LICENSE.txt +202 -0
  28. langclaw-0.1.0/langclaw/agents/defaults/skills/skill-creator/SKILL.md +357 -0
  29. langclaw-0.1.0/langclaw/agents/defaults/skills/skill-creator/references/output-patterns.md +82 -0
  30. langclaw-0.1.0/langclaw/agents/defaults/skills/skill-creator/references/workflows.md +28 -0
  31. langclaw-0.1.0/langclaw/agents/defaults/skills/skill-creator/scripts/init_skill.py +299 -0
  32. langclaw-0.1.0/langclaw/agents/defaults/skills/skill-creator/scripts/package_skill.py +111 -0
  33. langclaw-0.1.0/langclaw/agents/defaults/skills/skill-creator/scripts/quick_validate.py +124 -0
  34. langclaw-0.1.0/langclaw/agents/subagents.py +156 -0
  35. langclaw-0.1.0/langclaw/agents/tools/__init__.py +111 -0
  36. langclaw-0.1.0/langclaw/agents/tools/cron.py +221 -0
  37. langclaw-0.1.0/langclaw/agents/tools/gmail.py +466 -0
  38. langclaw-0.1.0/langclaw/agents/tools/gmail_auth.py +134 -0
  39. langclaw-0.1.0/langclaw/agents/tools/web_fetch.py +194 -0
  40. langclaw-0.1.0/langclaw/agents/tools/web_search.py +200 -0
  41. langclaw-0.1.0/langclaw/app.py +512 -0
  42. langclaw-0.1.0/langclaw/bus/__init__.py +43 -0
  43. langclaw-0.1.0/langclaw/bus/asyncio_bus.py +59 -0
  44. langclaw-0.1.0/langclaw/bus/base.py +123 -0
  45. langclaw-0.1.0/langclaw/bus/kafka_bus.py +99 -0
  46. langclaw-0.1.0/langclaw/bus/rabbitmq_bus.py +108 -0
  47. langclaw-0.1.0/langclaw/checkpointer/__init__.py +32 -0
  48. langclaw-0.1.0/langclaw/checkpointer/base.py +51 -0
  49. langclaw-0.1.0/langclaw/checkpointer/postgres.py +52 -0
  50. langclaw-0.1.0/langclaw/checkpointer/sqlite.py +40 -0
  51. langclaw-0.1.0/langclaw/cli/__init__.py +3 -0
  52. langclaw-0.1.0/langclaw/cli/app.py +416 -0
  53. langclaw-0.1.0/langclaw/cli/utils.py +34 -0
  54. langclaw-0.1.0/langclaw/config/__init__.py +19 -0
  55. langclaw-0.1.0/langclaw/config/schema.py +456 -0
  56. langclaw-0.1.0/langclaw/context.py +19 -0
  57. langclaw-0.1.0/langclaw/cron/__init__.py +270 -0
  58. langclaw-0.1.0/langclaw/cron/scheduler.py +417 -0
  59. langclaw-0.1.0/langclaw/cron/utils.py +8 -0
  60. langclaw-0.1.0/langclaw/gateway/__init__.py +4 -0
  61. langclaw-0.1.0/langclaw/gateway/base.py +117 -0
  62. langclaw-0.1.0/langclaw/gateway/commands.py +191 -0
  63. langclaw-0.1.0/langclaw/gateway/discord.py +493 -0
  64. langclaw-0.1.0/langclaw/gateway/manager.py +392 -0
  65. langclaw-0.1.0/langclaw/gateway/telegram.py +455 -0
  66. langclaw-0.1.0/langclaw/gateway/utils.py +133 -0
  67. langclaw-0.1.0/langclaw/gateway/websocket.py +266 -0
  68. langclaw-0.1.0/langclaw/heartbeat/__init__.py +3 -0
  69. langclaw-0.1.0/langclaw/heartbeat/watcher.py +169 -0
  70. langclaw-0.1.0/langclaw/middleware/__init__.py +12 -0
  71. langclaw-0.1.0/langclaw/middleware/channel_context.py +37 -0
  72. langclaw-0.1.0/langclaw/middleware/guardrails.py +90 -0
  73. langclaw-0.1.0/langclaw/middleware/permissions.py +73 -0
  74. langclaw-0.1.0/langclaw/middleware/rate_limit.py +73 -0
  75. langclaw-0.1.0/langclaw/providers/__init__.py +0 -0
  76. langclaw-0.1.0/langclaw/py.typed +0 -0
  77. langclaw-0.1.0/langclaw/session/__init__.py +3 -0
  78. langclaw-0.1.0/langclaw/session/manager.py +106 -0
  79. langclaw-0.1.0/langclaw/utils.py +43 -0
  80. langclaw-0.1.0/pyproject.toml +106 -0
  81. langclaw-0.1.0/tests/__init__.py +0 -0
  82. langclaw-0.1.0/tests/test_config.py +308 -0
  83. langclaw-0.1.0/tests/test_gateway.py +301 -0
  84. langclaw-0.1.0/tests/test_gmail_tools.py +315 -0
  85. langclaw-0.1.0/tests/test_subagents.py +486 -0
  86. 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
@@ -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.