langrove 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.
- langrove-0.1.0/.claude/agents/architect.md +58 -0
- langrove-0.1.0/.claude/agents/implementer.md +52 -0
- langrove-0.1.0/.claude/agents/reviewer.md +59 -0
- langrove-0.1.0/.claude/journals/README.md +30 -0
- langrove-0.1.0/.claude/journals/implementer.md +7 -0
- langrove-0.1.0/.claude/journals/planner.md +7 -0
- langrove-0.1.0/.claude/journals/reviewer.md +7 -0
- langrove-0.1.0/.claude/rules/api-design.md +64 -0
- langrove-0.1.0/.claude/rules/architecture.md +65 -0
- langrove-0.1.0/.claude/rules/auth.md +63 -0
- langrove-0.1.0/.claude/rules/database.md +56 -0
- langrove-0.1.0/.claude/rules/pipeline.md +23 -0
- langrove-0.1.0/.claude/rules/streaming.md +74 -0
- langrove-0.1.0/.claude/rules/testing.md +37 -0
- langrove-0.1.0/.claude/rules/worker-tasks.md +73 -0
- langrove-0.1.0/.claude/scheduled-tasks.md +186 -0
- langrove-0.1.0/.claude/settings.json +66 -0
- langrove-0.1.0/.claude/settings.local.json +22 -0
- langrove-0.1.0/.github/ISSUE_TEMPLATE/bug.yml +28 -0
- langrove-0.1.0/.github/ISSUE_TEMPLATE/feature.yml +31 -0
- langrove-0.1.0/.github/scripts/setup-labels.sh +28 -0
- langrove-0.1.0/.github/workflows/auto-merge.yml.disabled +18 -0
- langrove-0.1.0/.github/workflows/ci.yml.disabled +47 -0
- langrove-0.1.0/.github/workflows/claude-backlog.yml.disabled +65 -0
- langrove-0.1.0/.github/workflows/claude-implement.yml.disabled +69 -0
- langrove-0.1.0/.github/workflows/claude-maintenance.yml.disabled +32 -0
- langrove-0.1.0/.github/workflows/claude-planner.yml.disabled +42 -0
- langrove-0.1.0/.github/workflows/claude-review.yml.disabled +24 -0
- langrove-0.1.0/.github/workflows/claude.yml.disabled +25 -0
- langrove-0.1.0/.github/workflows/publish.yml +27 -0
- langrove-0.1.0/.gitignore +51 -0
- langrove-0.1.0/CLAUDE.md +88 -0
- langrove-0.1.0/Dockerfile +13 -0
- langrove-0.1.0/LICENSE +21 -0
- langrove-0.1.0/PKG-INFO +478 -0
- langrove-0.1.0/README.md +436 -0
- langrove-0.1.0/alembic.ini +36 -0
- langrove-0.1.0/docker-compose.yml +59 -0
- langrove-0.1.0/examples/README.md +76 -0
- langrove-0.1.0/examples/custom-auth/.env.example +6 -0
- langrove-0.1.0/examples/custom-auth/README.md +140 -0
- langrove-0.1.0/examples/custom-auth/agent.py +36 -0
- langrove-0.1.0/examples/custom-auth/auth.py +51 -0
- langrove-0.1.0/examples/custom-auth/auth_private.py +62 -0
- langrove-0.1.0/examples/custom-auth/client.py +109 -0
- langrove-0.1.0/examples/custom-auth/langgraph.json +15 -0
- langrove-0.1.0/examples/helios-video-agent/.env.example +10 -0
- langrove-0.1.0/examples/helios-video-agent/AGENTS.md +253 -0
- langrove-0.1.0/examples/helios-video-agent/README.md +229 -0
- langrove-0.1.0/examples/helios-video-agent/agent.py +151 -0
- langrove-0.1.0/examples/helios-video-agent/client.py +200 -0
- langrove-0.1.0/examples/helios-video-agent/frontend/index.html +21 -0
- langrove-0.1.0/examples/helios-video-agent/frontend/package-lock.json +2182 -0
- langrove-0.1.0/examples/helios-video-agent/frontend/package.json +25 -0
- langrove-0.1.0/examples/helios-video-agent/frontend/src/App.tsx +250 -0
- langrove-0.1.0/examples/helios-video-agent/frontend/src/components/ChatPanel.tsx +352 -0
- langrove-0.1.0/examples/helios-video-agent/frontend/src/components/PreviewPanel.tsx +638 -0
- langrove-0.1.0/examples/helios-video-agent/frontend/src/components/ReviewBar.tsx +160 -0
- langrove-0.1.0/examples/helios-video-agent/frontend/src/components/ToolProgress.tsx +95 -0
- langrove-0.1.0/examples/helios-video-agent/frontend/src/hooks/useVideoAgent.ts +186 -0
- langrove-0.1.0/examples/helios-video-agent/frontend/src/lib/store.ts +116 -0
- langrove-0.1.0/examples/helios-video-agent/frontend/src/main.tsx +10 -0
- langrove-0.1.0/examples/helios-video-agent/frontend/tsconfig.json +21 -0
- langrove-0.1.0/examples/helios-video-agent/frontend/vite.config.ts +24 -0
- langrove-0.1.0/examples/helios-video-agent/helios_tools.py +749 -0
- langrove-0.1.0/examples/helios-video-agent/langgraph.json +12 -0
- langrove-0.1.0/examples/helios-video-agent/pyproject.toml +37 -0
- langrove-0.1.0/examples/helios-video-agent/skills/helios-skills/core/SKILL.md +447 -0
- langrove-0.1.0/examples/helios-video-agent/skills/helios-skills/examples/canvas/SKILL.md +75 -0
- langrove-0.1.0/examples/helios-video-agent/skills/helios-skills/examples/chartjs/SKILL.md +67 -0
- langrove-0.1.0/examples/helios-video-agent/skills/helios-skills/examples/d3/SKILL.md +60 -0
- langrove-0.1.0/examples/helios-video-agent/skills/helios-skills/examples/framer-motion/SKILL.md +79 -0
- langrove-0.1.0/examples/helios-video-agent/skills/helios-skills/examples/gsap/SKILL.md +68 -0
- langrove-0.1.0/examples/helios-video-agent/skills/helios-skills/examples/lottie/SKILL.md +59 -0
- langrove-0.1.0/examples/helios-video-agent/skills/helios-skills/examples/p5/SKILL.md +67 -0
- langrove-0.1.0/examples/helios-video-agent/skills/helios-skills/examples/pixi/SKILL.md +61 -0
- langrove-0.1.0/examples/helios-video-agent/skills/helios-skills/examples/podcast-visualizer/SKILL.md +88 -0
- langrove-0.1.0/examples/helios-video-agent/skills/helios-skills/examples/react/SKILL.md +98 -0
- langrove-0.1.0/examples/helios-video-agent/skills/helios-skills/examples/signals/SKILL.md +50 -0
- langrove-0.1.0/examples/helios-video-agent/skills/helios-skills/examples/solid/SKILL.md +162 -0
- langrove-0.1.0/examples/helios-video-agent/skills/helios-skills/examples/svelte/SKILL.md +203 -0
- langrove-0.1.0/examples/helios-video-agent/skills/helios-skills/examples/tailwind/SKILL.md +52 -0
- langrove-0.1.0/examples/helios-video-agent/skills/helios-skills/examples/threejs/SKILL.md +136 -0
- langrove-0.1.0/examples/helios-video-agent/skills/helios-skills/examples/vanilla/SKILL.md +88 -0
- langrove-0.1.0/examples/helios-video-agent/skills/helios-skills/examples/vue/SKILL.md +124 -0
- langrove-0.1.0/examples/helios-video-agent/skills/helios-skills/getting-started/SKILL.md +247 -0
- langrove-0.1.0/examples/helios-video-agent/skills/helios-skills/guided/explainer-video/SKILL.md +229 -0
- langrove-0.1.0/examples/helios-video-agent/skills/helios-skills/guided/launch-announcement/SKILL.md +235 -0
- langrove-0.1.0/examples/helios-video-agent/skills/helios-skills/guided/motion-design-rules/SKILL.md +144 -0
- langrove-0.1.0/examples/helios-video-agent/skills/helios-skills/guided/product-demo/SKILL.md +243 -0
- langrove-0.1.0/examples/helios-video-agent/skills/helios-skills/guided/promo-video/SKILL.md +208 -0
- langrove-0.1.0/examples/helios-video-agent/skills/helios-skills/guided/social-clip/SKILL.md +243 -0
- langrove-0.1.0/examples/helios-video-agent/skills/helios-skills/guided/testimonial-video/SKILL.md +234 -0
- langrove-0.1.0/examples/helios-video-agent/skills/helios-skills/player/SKILL.md +191 -0
- langrove-0.1.0/examples/helios-video-agent/skills/helios-skills/renderer/SKILL.md +236 -0
- langrove-0.1.0/examples/helios-video-agent/skills/helios-skills/studio/SKILL.md +93 -0
- langrove-0.1.0/examples/helios-video-agent/skills/helios-skills/workflows/create-composition/SKILL.md +120 -0
- langrove-0.1.0/examples/helios-video-agent/skills/helios-skills/workflows/render-video/SKILL.md +105 -0
- langrove-0.1.0/examples/helios-video-agent/skills/helios-skills/workflows/visualize-data/SKILL.md +69 -0
- langrove-0.1.0/examples/helios-video-agent/uv.lock +2291 -0
- langrove-0.1.0/examples/multi-agent-store/.env.example +6 -0
- langrove-0.1.0/examples/multi-agent-store/README.md +186 -0
- langrove-0.1.0/examples/multi-agent-store/client.py +241 -0
- langrove-0.1.0/examples/multi-agent-store/langgraph.json +12 -0
- langrove-0.1.0/examples/multi-agent-store/researcher.py +68 -0
- langrove-0.1.0/examples/multi-agent-store/writer.py +50 -0
- langrove-0.1.0/examples/quickstart/.env.example +6 -0
- langrove-0.1.0/examples/quickstart/README.md +112 -0
- langrove-0.1.0/examples/quickstart/agent.py +37 -0
- langrove-0.1.0/examples/quickstart/client.py +109 -0
- langrove-0.1.0/examples/quickstart/langgraph.json +14 -0
- langrove-0.1.0/pyproject.toml +87 -0
- langrove-0.1.0/src/langrove/__init__.py +3 -0
- langrove-0.1.0/src/langrove/__main__.py +5 -0
- langrove-0.1.0/src/langrove/api/__init__.py +0 -0
- langrove-0.1.0/src/langrove/api/agents.py +68 -0
- langrove-0.1.0/src/langrove/api/assistants.py +109 -0
- langrove-0.1.0/src/langrove/api/crons.py +72 -0
- langrove-0.1.0/src/langrove/api/dead_letter.py +79 -0
- langrove-0.1.0/src/langrove/api/deps.py +166 -0
- langrove-0.1.0/src/langrove/api/health.py +99 -0
- langrove-0.1.0/src/langrove/api/runs.py +328 -0
- langrove-0.1.0/src/langrove/api/store.py +256 -0
- langrove-0.1.0/src/langrove/api/threads.py +162 -0
- langrove-0.1.0/src/langrove/app.py +172 -0
- langrove-0.1.0/src/langrove/auth/__init__.py +11 -0
- langrove-0.1.0/src/langrove/auth/base.py +102 -0
- langrove-0.1.0/src/langrove/auth/custom.py +153 -0
- langrove-0.1.0/src/langrove/auth/middleware.py +76 -0
- langrove-0.1.0/src/langrove/auth/noop.py +17 -0
- langrove-0.1.0/src/langrove/cli.py +174 -0
- langrove-0.1.0/src/langrove/config.py +108 -0
- langrove-0.1.0/src/langrove/db/__init__.py +0 -0
- langrove-0.1.0/src/langrove/db/assistant_repo.py +176 -0
- langrove-0.1.0/src/langrove/db/cron_repo.py +130 -0
- langrove-0.1.0/src/langrove/db/langgraph_pools.py +60 -0
- langrove-0.1.0/src/langrove/db/pool.py +80 -0
- langrove-0.1.0/src/langrove/db/run_repo.py +138 -0
- langrove-0.1.0/src/langrove/db/store_repo.py +113 -0
- langrove-0.1.0/src/langrove/db/thread_repo.py +130 -0
- langrove-0.1.0/src/langrove/exceptions.py +42 -0
- langrove-0.1.0/src/langrove/graph/__init__.py +0 -0
- langrove-0.1.0/src/langrove/graph/loader.py +54 -0
- langrove-0.1.0/src/langrove/graph/registry.py +116 -0
- langrove-0.1.0/src/langrove/migrations/env.py +51 -0
- langrove-0.1.0/src/langrove/migrations/script.py.mako +24 -0
- langrove-0.1.0/src/langrove/migrations/versions/001_initial_schema.py +190 -0
- langrove-0.1.0/src/langrove/models/__init__.py +0 -0
- langrove-0.1.0/src/langrove/models/assistants.py +78 -0
- langrove-0.1.0/src/langrove/models/common.py +37 -0
- langrove-0.1.0/src/langrove/models/crons.py +52 -0
- langrove-0.1.0/src/langrove/models/runs.py +65 -0
- langrove-0.1.0/src/langrove/models/store.py +58 -0
- langrove-0.1.0/src/langrove/models/threads.py +70 -0
- langrove-0.1.0/src/langrove/queue/__init__.py +0 -0
- langrove-0.1.0/src/langrove/queue/consumer.py +140 -0
- langrove-0.1.0/src/langrove/queue/publisher.py +61 -0
- langrove-0.1.0/src/langrove/queue/recovery.py +97 -0
- langrove-0.1.0/src/langrove/services/__init__.py +0 -0
- langrove-0.1.0/src/langrove/services/assistant_service.py +124 -0
- langrove-0.1.0/src/langrove/services/cron_service.py +61 -0
- langrove-0.1.0/src/langrove/services/run_service.py +282 -0
- langrove-0.1.0/src/langrove/services/store_service.py +72 -0
- langrove-0.1.0/src/langrove/services/thread_service.py +224 -0
- langrove-0.1.0/src/langrove/settings.py +41 -0
- langrove-0.1.0/src/langrove/streaming/__init__.py +0 -0
- langrove-0.1.0/src/langrove/streaming/broker.py +190 -0
- langrove-0.1.0/src/langrove/streaming/executor.py +248 -0
- langrove-0.1.0/src/langrove/streaming/formatter.py +67 -0
- langrove-0.1.0/src/langrove/worker.py +252 -0
- langrove-0.1.0/tests/__init__.py +0 -0
- langrove-0.1.0/tests/conftest.py +19 -0
- langrove-0.1.0/tests/fixtures/__init__.py +0 -0
- langrove-0.1.0/tests/fixtures/echo_graph.py +22 -0
- langrove-0.1.0/tests/fixtures/langgraph.json +7 -0
- langrove-0.1.0/tests/test_auth.py +61 -0
- langrove-0.1.0/tests/test_config.py +114 -0
- langrove-0.1.0/tests/test_streaming.py +103 -0
- langrove-0.1.0/tests/test_thread_copy.py +146 -0
- langrove-0.1.0/uv.lock +2328 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: architect
|
|
3
|
+
description: Plans implementation approach for complex features
|
|
4
|
+
model: claude-opus-4-6
|
|
5
|
+
tools: ["Read", "Glob", "Grep", "WebSearch"]
|
|
6
|
+
---
|
|
7
|
+
You are the Langrove architect agent. Your job is to analyze
|
|
8
|
+
requirements and create a detailed implementation plan. You do NOT
|
|
9
|
+
write code — only plans. Follow CLAUDE.md principles strictly.
|
|
10
|
+
|
|
11
|
+
## Before Planning
|
|
12
|
+
1. Read `.claude/rules/` files relevant to the area you're planning for
|
|
13
|
+
2. Read CLAUDE.md for project principles and structure
|
|
14
|
+
3. Read the issue description and acceptance criteria carefully
|
|
15
|
+
4. Identify ALL files that need to change — read them to understand current state
|
|
16
|
+
|
|
17
|
+
## Plan Format
|
|
18
|
+
Post the plan as a **GitHub issue comment** using this exact structure:
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
## Implementation Plan
|
|
22
|
+
|
|
23
|
+
### Approach
|
|
24
|
+
(1-3 sentences describing the strategy)
|
|
25
|
+
|
|
26
|
+
### Files to Modify
|
|
27
|
+
- `path/to/file.py` — what changes and why
|
|
28
|
+
|
|
29
|
+
### Files to Create
|
|
30
|
+
- `path/to/new.py` — purpose and responsibility
|
|
31
|
+
|
|
32
|
+
### Test Plan
|
|
33
|
+
- What scenarios to test
|
|
34
|
+
- Which existing tests may need updates
|
|
35
|
+
|
|
36
|
+
### Estimated Diff Size
|
|
37
|
+
- Lines added/removed estimate
|
|
38
|
+
- Complexity assessment
|
|
39
|
+
|
|
40
|
+
### Risk Assessment
|
|
41
|
+
- low / medium / high — and why
|
|
42
|
+
- Any areas requiring extra caution
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Complexity Estimation
|
|
46
|
+
- **small** (<100 lines diff, single file or module)
|
|
47
|
+
- **medium** (100-300 lines, multiple files in same module)
|
|
48
|
+
- **large** (>300 lines, cross-module changes)
|
|
49
|
+
|
|
50
|
+
If estimated as large: add labels `complexity:large` + `blocked` instead of `planned`.
|
|
51
|
+
Only proceed with `planned` label for small/medium issues.
|
|
52
|
+
|
|
53
|
+
## After Planning
|
|
54
|
+
- Remove `triage` label, add `planned` + appropriate `complexity:*` label
|
|
55
|
+
- Append new learnings to the most relevant `.claude/rules/` file:
|
|
56
|
+
- Date prefix: `- YYYY-MM-DD: <concise learning>`
|
|
57
|
+
- Only add genuinely new insights, not duplicates of existing entries
|
|
58
|
+
- **Do NOT commit or push** — you only read files and post issue comments
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: implementer
|
|
3
|
+
description: Implements features following the architect's plan
|
|
4
|
+
model: claude-opus-4-6
|
|
5
|
+
tools: ["Read", "Write", "Edit", "Bash", "Glob", "Grep"]
|
|
6
|
+
---
|
|
7
|
+
You are the Langrove implementation agent. You receive a plan
|
|
8
|
+
from the architect and implement it precisely. Follow CLAUDE.md strictly.
|
|
9
|
+
|
|
10
|
+
## Before Implementing
|
|
11
|
+
1. Read `.claude/rules/` files relevant to the files you'll touch
|
|
12
|
+
2. Read CLAUDE.md for project principles and structure
|
|
13
|
+
3. Read the **Implementation Plan** comment on the issue (posted by architect)
|
|
14
|
+
4. Follow the plan exactly unless you find a technical reason not to
|
|
15
|
+
|
|
16
|
+
## Implementation Process
|
|
17
|
+
1. **Never commit or push directly to `main`.**
|
|
18
|
+
2. Create a new branch from `main`: `git checkout main && git pull && git checkout -b claude/{issue-number}-{short-slug}`
|
|
19
|
+
3. Implement changes following the architect's plan
|
|
20
|
+
4. Write tests for all new/changed functionality
|
|
21
|
+
5. Run the full quality check:
|
|
22
|
+
```bash
|
|
23
|
+
uv run ruff check . && uv run ruff format . && uv run pytest
|
|
24
|
+
```
|
|
25
|
+
6. Fix any failures (up to 3 retry cycles)
|
|
26
|
+
7. Commit all changes on the branch with a message referencing the issue
|
|
27
|
+
|
|
28
|
+
## Diff Size Guard
|
|
29
|
+
- If your changes exceed **500 lines**, STOP
|
|
30
|
+
- Add labels `blocked` + `human-review-required` to the issue
|
|
31
|
+
- Post a comment explaining why the diff is larger than expected
|
|
32
|
+
- Do NOT open a PR
|
|
33
|
+
|
|
34
|
+
## PR Convention
|
|
35
|
+
- Branch: `claude/{issue-number}-{short-slug}` — always branched off `main`, never commit to `main` directly
|
|
36
|
+
- Push the branch and open a PR targeting `main`
|
|
37
|
+
- PR description must include: `Closes #{issue-number}`
|
|
38
|
+
- If you deviated from the architect's plan, explain why in the PR description
|
|
39
|
+
- Labels: remove `in-progress`, add `review`
|
|
40
|
+
|
|
41
|
+
## Code Standards (from CLAUDE.md)
|
|
42
|
+
- KISS, YAGNI, SOLID, OOP
|
|
43
|
+
- Raw asyncpg SQL — no ORM
|
|
44
|
+
- Services receive dependencies via constructor injection
|
|
45
|
+
- API handlers are thin — delegate to services
|
|
46
|
+
- No speculative abstractions
|
|
47
|
+
|
|
48
|
+
## After Implementing
|
|
49
|
+
- Append new learnings to the most relevant `.claude/rules/` file:
|
|
50
|
+
- Date prefix: `- YYYY-MM-DD: <concise learning>`
|
|
51
|
+
- Only genuinely new insights (implementation gotchas, patterns discovered)
|
|
52
|
+
- Commit rules/ updates alongside implementation changes
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: reviewer
|
|
3
|
+
description: Reviews code for quality, security, and correctness
|
|
4
|
+
model: claude-opus-4-6
|
|
5
|
+
tools: ["Read", "Glob", "Grep", "Bash"]
|
|
6
|
+
---
|
|
7
|
+
You are the Langrove code review agent. Review code changes thoroughly
|
|
8
|
+
but concisely. Follow CLAUDE.md principles as the quality bar.
|
|
9
|
+
|
|
10
|
+
## Before Reviewing
|
|
11
|
+
1. Read `.claude/rules/` files relevant to the changed files
|
|
12
|
+
2. Read CLAUDE.md for project principles
|
|
13
|
+
|
|
14
|
+
## Review Checklist
|
|
15
|
+
For every PR, check:
|
|
16
|
+
|
|
17
|
+
### Security
|
|
18
|
+
- [ ] All SQL queries use `$N` parameterized syntax — no f-strings or string interpolation
|
|
19
|
+
- [ ] No hardcoded secrets or credentials
|
|
20
|
+
- [ ] Auth bypass not possible via missing middleware checks
|
|
21
|
+
- [ ] JSONB inputs properly cast with `::jsonb`
|
|
22
|
+
|
|
23
|
+
### Async Correctness
|
|
24
|
+
- [ ] All I/O operations properly awaited
|
|
25
|
+
- [ ] No blocking calls in async context (no `time.sleep`, `requests.get`)
|
|
26
|
+
- [ ] Connection pools properly acquired/released (context managers)
|
|
27
|
+
- [ ] `asyncio.aclosing()` used for astream() generators
|
|
28
|
+
|
|
29
|
+
### CLAUDE.md Adherence
|
|
30
|
+
- [ ] KISS — no unnecessary complexity
|
|
31
|
+
- [ ] YAGNI — no speculative features or abstractions
|
|
32
|
+
- [ ] SOLID — single responsibility, dependency injection
|
|
33
|
+
- [ ] Raw asyncpg — no ORM usage
|
|
34
|
+
- [ ] Thin API handlers — logic in services, not routes
|
|
35
|
+
|
|
36
|
+
### Test Coverage
|
|
37
|
+
- [ ] New code paths have corresponding tests
|
|
38
|
+
- [ ] Edge cases considered (None, empty, error states)
|
|
39
|
+
- [ ] Tests actually assert meaningful behavior
|
|
40
|
+
|
|
41
|
+
### SSE Format (if streaming changes)
|
|
42
|
+
- [ ] Wire format compliance: `event: {name}\ndata: {json}\n\n`
|
|
43
|
+
- [ ] Event sequence: metadata first, end last
|
|
44
|
+
- [ ] Subgraph namespace uses pipe delimiter
|
|
45
|
+
|
|
46
|
+
## Decision Tree
|
|
47
|
+
- **APPROVE** if: tests pass, no security issues, follows CLAUDE.md, reasonable coverage
|
|
48
|
+
- **REQUEST_CHANGES** if: security vulnerability, missing tests for critical paths, CLAUDE.md violations, broken async patterns
|
|
49
|
+
|
|
50
|
+
## Review Cycle Limit
|
|
51
|
+
- Track how many review cycles have occurred (check existing review comments)
|
|
52
|
+
- After **2 review cycles** with unresolved issues: add `human-review-required` label and stop
|
|
53
|
+
|
|
54
|
+
## After Reviewing
|
|
55
|
+
- On approve: remove `review` label, add `done`
|
|
56
|
+
- On request changes: remove `review` label, add `in-progress`
|
|
57
|
+
- Append new learnings to the most relevant `.claude/rules/` file:
|
|
58
|
+
- Date prefix: `- YYYY-MM-DD: <concise learning>`
|
|
59
|
+
- Focus on recurring patterns (what keeps coming up in reviews)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Agent Journals
|
|
2
|
+
|
|
3
|
+
This directory previously contained per-role journal files (planner.md, implementer.md, reviewer.md).
|
|
4
|
+
|
|
5
|
+
## Migration to `.claude/rules/`
|
|
6
|
+
|
|
7
|
+
Agent learnings are now stored in **topic-based `.claude/rules/` files** instead of per-role journals.
|
|
8
|
+
This follows the industry-standard pattern (Lore Protocol, Letta Context Repositories, Cline Memory Bank)
|
|
9
|
+
and uses Claude Code's native path-scoped rules for automatic context loading.
|
|
10
|
+
|
|
11
|
+
### How it works now
|
|
12
|
+
|
|
13
|
+
- Knowledge organized by domain: `database.md`, `streaming.md`, `auth.md`, etc.
|
|
14
|
+
- Files have `globs:` frontmatter — Claude auto-loads relevant rules when editing matching files
|
|
15
|
+
- All agents share all knowledge (no role silos)
|
|
16
|
+
- Agents append new learnings to the most relevant rules/ file after completing work
|
|
17
|
+
- Weekly maintenance consolidates and deduplicates entries
|
|
18
|
+
|
|
19
|
+
### Rules files
|
|
20
|
+
|
|
21
|
+
| File | Scope |
|
|
22
|
+
|------|-------|
|
|
23
|
+
| `rules/database.md` | asyncpg, JSONB, pools, migrations |
|
|
24
|
+
| `rules/streaming.md` | SSE format, pub/sub, event replay |
|
|
25
|
+
| `rules/worker-tasks.md` | Redis Streams, consumer groups, recovery |
|
|
26
|
+
| `rules/auth.md` | middleware, authorization, AuthUser |
|
|
27
|
+
| `rules/api-design.md` | FastAPI patterns, services, error handling |
|
|
28
|
+
| `rules/testing.md` | pytest conventions, fixtures, CI |
|
|
29
|
+
| `rules/architecture.md` | system design, graph loading, lifecycle |
|
|
30
|
+
| `rules/pipeline.md` | automation meta-learnings |
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Implementer Agent Journal
|
|
2
|
+
|
|
3
|
+
> **Migrated to `.claude/rules/`** — Agent learnings are now stored by topic
|
|
4
|
+
> (database.md, streaming.md, etc.) instead of by role. See `.claude/rules/` for all knowledge.
|
|
5
|
+
>
|
|
6
|
+
> This file is kept for backwards compatibility. New learnings go into the
|
|
7
|
+
> most relevant `.claude/rules/` file.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Planner Agent Journal
|
|
2
|
+
|
|
3
|
+
> **Migrated to `.claude/rules/`** — Agent learnings are now stored by topic
|
|
4
|
+
> (database.md, streaming.md, etc.) instead of by role. See `.claude/rules/` for all knowledge.
|
|
5
|
+
>
|
|
6
|
+
> This file is kept for backwards compatibility. New learnings go into the
|
|
7
|
+
> most relevant `.claude/rules/` file.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Reviewer Agent Journal
|
|
2
|
+
|
|
3
|
+
> **Migrated to `.claude/rules/`** — Agent learnings are now stored by topic
|
|
4
|
+
> (database.md, streaming.md, etc.) instead of by role. See `.claude/rules/` for all knowledge.
|
|
5
|
+
>
|
|
6
|
+
> This file is kept for backwards compatibility. New learnings go into the
|
|
7
|
+
> most relevant `.claude/rules/` file.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: FastAPI route patterns, dependency injection, service layer, error handling, and request/response models
|
|
3
|
+
globs:
|
|
4
|
+
- "src/langrove/api/**/*.py"
|
|
5
|
+
- "src/langrove/models/**/*.py"
|
|
6
|
+
- "src/langrove/services/**/*.py"
|
|
7
|
+
- "src/langrove/exceptions.py"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# API Design
|
|
11
|
+
|
|
12
|
+
## Route Handler Pattern
|
|
13
|
+
- **Thin handlers** — delegate ALL business logic to services
|
|
14
|
+
- Handlers only: parse request, inject dependencies, call service, return response
|
|
15
|
+
- One router per resource: agents, assistants, threads, runs, store, crons, health, dead_letter
|
|
16
|
+
|
|
17
|
+
## Dependency Injection
|
|
18
|
+
- FastAPI `Depends()` functions in `api/deps.py`
|
|
19
|
+
- App-level resources: `request.app.state.{db_pool, redis, graph_registry, checkpointer, store}`
|
|
20
|
+
- Request-scoped: `request.state.{user, auth}`
|
|
21
|
+
- Service factory pattern:
|
|
22
|
+
```python
|
|
23
|
+
def _get_service(db=Depends(get_db), registry=Depends(get_graph_registry)):
|
|
24
|
+
return ServiceClass(Repository(db), registry)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Service Layer
|
|
28
|
+
- Services instantiated fresh per-request (not cached)
|
|
29
|
+
- Constructor injection: `__init__(self, repo, registry, ...)`
|
|
30
|
+
- Repositories are lightweight, stateless data-access objects
|
|
31
|
+
- One service per domain: AssistantService, RunService, ThreadService, StoreService, CronService
|
|
32
|
+
|
|
33
|
+
## Error Hierarchy
|
|
34
|
+
```
|
|
35
|
+
LangroveError (base)
|
|
36
|
+
├── NotFoundError(resource: str, resource_id: str) → 404
|
|
37
|
+
├── ConflictError(message: str) → 409
|
|
38
|
+
├── AuthError(message: str) → 401
|
|
39
|
+
└── ForbiddenError(message: str) → 403
|
|
40
|
+
```
|
|
41
|
+
- Response format: `JSONResponse({"code": "error_type", "message": "..."})`
|
|
42
|
+
- Exception handlers registered in `app.py`
|
|
43
|
+
|
|
44
|
+
## Request/Response Models
|
|
45
|
+
- Response models: Pydantic `BaseModel` with UUID + datetime fields
|
|
46
|
+
- Create models: optional `if_exists` field ("raise" | "do_nothing")
|
|
47
|
+
- Search models: optional filters + `limit`/`offset` pagination
|
|
48
|
+
- Patch/Update: all fields optional, `model_dump(exclude_none=True)` for partial updates
|
|
49
|
+
|
|
50
|
+
## Key Patterns
|
|
51
|
+
- `if_exists="do_nothing"` returns existing record instead of ConflictError
|
|
52
|
+
- Thread state is dual-sourced: record (metadata, status) from repo + state (values, next, tasks) from checkpointer
|
|
53
|
+
- Ephemeral threads: auto-deleted after stream completes if `on_completion="delete"`
|
|
54
|
+
- Run cancellation: DB status → "interrupted" + Redis cancel key + thread reset to "idle"
|
|
55
|
+
|
|
56
|
+
## Three Run Execution Modes
|
|
57
|
+
1. `stream_run()` — foreground SSE streaming (same process, asyncio.Queue)
|
|
58
|
+
2. `wait_run()` — foreground blocking, returns final state
|
|
59
|
+
3. `background_run()` — dispatches to Redis Streams queue, returns Run immediately
|
|
60
|
+
|
|
61
|
+
## Gotchas
|
|
62
|
+
- Services are NOT singletons — new instance per request
|
|
63
|
+
- `metadata_` → `metadata` rename happens in repository layer, not service/API layer
|
|
64
|
+
- Auth user injected into graph config as `langgraph_auth_user` key in configurable dict
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: System architecture, graph loading, app lifecycle, and cross-cutting patterns
|
|
3
|
+
globs:
|
|
4
|
+
- "src/langrove/**/*.py"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Architecture
|
|
8
|
+
|
|
9
|
+
## Graph Instance Per Request
|
|
10
|
+
- Base graphs loaded at startup into `GraphRegistry` — cached without checkpointer (immutable, shared)
|
|
11
|
+
- Per-request: `graph.copy(update={...})` injects checkpointer + store
|
|
12
|
+
- Config deep-copied per request via `deepcopy(config)` to prevent concurrent mutations
|
|
13
|
+
- Fallback to shallow copy if deepcopy fails (e.g., non-serializable objects like LangfuseResourceManager)
|
|
14
|
+
|
|
15
|
+
## Graph Loading (graph/loader.py)
|
|
16
|
+
- Dynamic module loading: `importlib.util.spec_from_file_location()` from `"./path/to/module.py:attribute"` specs
|
|
17
|
+
- Parent directory auto-added to `sys.path` for transitive imports
|
|
18
|
+
- Relative paths resolve against config file directory, NOT cwd
|
|
19
|
+
|
|
20
|
+
## App Factory (app.py)
|
|
21
|
+
- `create_app()` with `@asynccontextmanager` for async lifespan
|
|
22
|
+
- **Startup order:** load .env → asyncpg pool → Redis → load graphs → setup checkpointer (psycopg) → setup store (psycopg) → auto-create assistants
|
|
23
|
+
- **Shutdown:** close all pools (checkpointer, store, db, redis)
|
|
24
|
+
- State on `app.state`: `db_pool`, `redis`, `graph_registry`, `checkpointer`, `store`, `settings`, `config`
|
|
25
|
+
|
|
26
|
+
## Middleware Stack
|
|
27
|
+
1. CORS (configured from `config.http.cors`)
|
|
28
|
+
2. Auth (if `config.auth.path` set) — see `.claude/rules/auth.md`
|
|
29
|
+
|
|
30
|
+
## Two Database Pool Types
|
|
31
|
+
- **asyncpg** (`db/pool.py`): app-level queries (threads, runs, assistants, store, crons)
|
|
32
|
+
- **psycopg** (`db/langgraph_pools.py`): LangGraph checkpointer + store (separate pools, autocommit=True)
|
|
33
|
+
|
|
34
|
+
## Streaming Architecture
|
|
35
|
+
- **Foreground:** `asyncio.Queue` per run_id — publish/subscribe within same process
|
|
36
|
+
- **Background:** Redis pub/sub (live events) + Redis Streams (replay/reconnection)
|
|
37
|
+
- See `.claude/rules/streaming.md` for details
|
|
38
|
+
|
|
39
|
+
## Thread State Lifecycle
|
|
40
|
+
- Status: `idle` → `busy` → `success|error` → `idle`
|
|
41
|
+
- Thread `values` and `interrupts` are NOT stored in threads table — derived from LangGraph checkpointer at read time
|
|
42
|
+
|
|
43
|
+
## Ephemeral Threads
|
|
44
|
+
- Created when no `thread_id` provided and `on_completion="delete"`
|
|
45
|
+
- Auto-deleted in generator `finally` block after stream completes
|
|
46
|
+
|
|
47
|
+
## Config File (langgraph.json)
|
|
48
|
+
- Parsed by `config.py` with Pydantic models
|
|
49
|
+
- Fallback loading: `langgraph.json` → `aegra.json` → defaults
|
|
50
|
+
- `.env` path in config resolves relative to config file directory
|
|
51
|
+
- Graphs: `{"graph_id": "./path/to/module.py:attribute"}`
|
|
52
|
+
|
|
53
|
+
## CLI Commands (cli.py)
|
|
54
|
+
- `langrove serve` — FastAPI via uvicorn (default port 8123)
|
|
55
|
+
- `langrove worker` — Redis Streams consumer + recovery monitor
|
|
56
|
+
- `langrove init` — scaffold langgraph.json + agent.py
|
|
57
|
+
|
|
58
|
+
## Settings (settings.py)
|
|
59
|
+
- Pydantic BaseSettings with `env_prefix="LANGROVE_"` and `.env` file support
|
|
60
|
+
- Key defaults: DB pool 2-10, checkpointer pool 5, store pool 5, worker concurrency 5, task timeout 900s, event TTL 86400s (24h)
|
|
61
|
+
|
|
62
|
+
## Gotchas
|
|
63
|
+
- Multiple workers each spawn all pools — can exhaust PostgreSQL max_connections quickly
|
|
64
|
+
- Auth user injected into graph configurable as `langgraph_auth_user` key
|
|
65
|
+
- `asyncio.aclosing()` required for astream() generator cleanup (releases checkpoint locks)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Authentication middleware, authorization handlers, AuthUser protocol, and custom auth loading
|
|
3
|
+
globs:
|
|
4
|
+
- "src/langrove/auth/**/*.py"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Authentication & Authorization
|
|
8
|
+
|
|
9
|
+
## Two-Stage Auth
|
|
10
|
+
1. **Authenticate** (request-level via middleware): validates credentials → returns AuthUser
|
|
11
|
+
2. **Authorize** (per-operation via deps.py): checks if user can perform action on resource
|
|
12
|
+
|
|
13
|
+
## Auth Handler Resolution (Priority Order)
|
|
14
|
+
1. Exact match: `(resource, action)` — e.g., `("assistants", "create")`
|
|
15
|
+
2. Resource-level: `(resource, *)` — e.g., `("assistants", "*")`
|
|
16
|
+
3. Global: catches all
|
|
17
|
+
|
|
18
|
+
## AuthUser Protocol
|
|
19
|
+
- Implements `langgraph_sdk.auth.types.BaseUser`
|
|
20
|
+
- Properties: `identity`, `display_name`, `permissions`, `is_authenticated`
|
|
21
|
+
- Dict-like access: `user[key]`, `key in user`, `iter(user)`
|
|
22
|
+
- `to_dict()` serializes for graph configurable injection as `langgraph_auth_user`
|
|
23
|
+
- Uses `__slots__` for memory efficiency
|
|
24
|
+
|
|
25
|
+
## CustomAuthHandler (auth/custom.py)
|
|
26
|
+
- Loads from `module.py:handler` spec in `langgraph.json` auth config
|
|
27
|
+
- Supports two modes:
|
|
28
|
+
- Plain async function: `async def handler(headers) -> dict`
|
|
29
|
+
- `langgraph_sdk.Auth` instance with `@auth.authenticate` decorator
|
|
30
|
+
|
|
31
|
+
### Parameter Injection (signature-based)
|
|
32
|
+
Handler parameters are inspected and injected:
|
|
33
|
+
- `headers` → full headers dict
|
|
34
|
+
- `authorization` → extracted from `headers["authorization"]`
|
|
35
|
+
- `method` → HTTP method string
|
|
36
|
+
- `path` → request path string
|
|
37
|
+
- Unknown params → falls back to headers as first positional arg
|
|
38
|
+
|
|
39
|
+
### Return Type Handling
|
|
40
|
+
- `None` → 401 (reject)
|
|
41
|
+
- `str` → `AuthUser(identity=string)`
|
|
42
|
+
- `dict` → must have `identity` key, optional `display_name`, `permissions`
|
|
43
|
+
- Object with `.identity` → extracted as MinimalUser protocol
|
|
44
|
+
|
|
45
|
+
## AuthMiddleware (auth/middleware.py)
|
|
46
|
+
- Extends `BaseHTTPMiddleware`
|
|
47
|
+
- Skip paths: `/ok`, `/health`, `/info`, `/docs`, `/openapi.json`, `/redoc`
|
|
48
|
+
- Skips OPTIONS (CORS preflight)
|
|
49
|
+
- Stores result: `request.state.user` (AuthUser) + `request.state.auth` (langgraph_sdk.Auth or None)
|
|
50
|
+
|
|
51
|
+
## Authorization in API Endpoints (deps.py)
|
|
52
|
+
- `authorize(request, resource, action, value_dict)` — for write operations
|
|
53
|
+
- Handler can return `False` (reject → 403), `True/None` (accept), or modified dict (rewrite values)
|
|
54
|
+
- `authorize_read(request, resource, metadata)` — for read operations
|
|
55
|
+
- Validates fetched resource against filter operators: `$eq`, `$contains`
|
|
56
|
+
|
|
57
|
+
## NoopAuthHandler (auth/noop.py)
|
|
58
|
+
- Development mode: always returns `AuthUser(identity="anonymous", permissions=("authenticated",))`
|
|
59
|
+
|
|
60
|
+
## Gotchas
|
|
61
|
+
- Auth is null-safe: no `request.state.auth` = no-op passthrough
|
|
62
|
+
- `request.state.auth` is the `langgraph_sdk.Auth` instance (for authorization rules), not the AuthUser
|
|
63
|
+
- Store namespace can be rewritten by auth handler via authorization result
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: asyncpg query patterns, JSONB handling, connection pools, and migration conventions
|
|
3
|
+
globs:
|
|
4
|
+
- "src/langrove/db/**/*.py"
|
|
5
|
+
- "migrations/**/*.py"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Database
|
|
9
|
+
|
|
10
|
+
## asyncpg JSONB Handling
|
|
11
|
+
- asyncpg returns JSONB as JSON-encoded strings. The custom codec in `db/pool.py` uses `orjson.loads()` which may need two passes (first unwraps outer quotes, second extracts the object)
|
|
12
|
+
- Encoder: `orjson.dumps(v).decode()` — must decode bytes to string for PostgreSQL
|
|
13
|
+
- Codec registration uses "text" format (not "binary") via `init` callback on pool creation
|
|
14
|
+
|
|
15
|
+
## Query Patterns
|
|
16
|
+
- ALL queries use `$N` positional parameters — never f-strings or string interpolation (SQL injection prevention)
|
|
17
|
+
- JSONB inserts require explicit `::jsonb` cast: `VALUES ($1::jsonb)`
|
|
18
|
+
- Dynamic WHERE building: track `idx` counter, append conditions + args in lockstep
|
|
19
|
+
```python
|
|
20
|
+
conditions.append(f"{key} = ${idx}")
|
|
21
|
+
args.append(value)
|
|
22
|
+
idx += 1
|
|
23
|
+
```
|
|
24
|
+
- JSONB containment operator: `metadata_ @> $N::jsonb` with `orjson.dumps(dict).decode()`
|
|
25
|
+
- Only interpolate known field names into SQL — parameters always use $N placeholders
|
|
26
|
+
|
|
27
|
+
## Field Naming
|
|
28
|
+
- DB column `metadata_` (reserved word escape) → normalized to `metadata` in Python dicts
|
|
29
|
+
- All repositories call `_normalize()` to handle this rename
|
|
30
|
+
- Config/JSON columns use `::jsonb` cast on INSERT/UPDATE
|
|
31
|
+
|
|
32
|
+
## Store Namespace
|
|
33
|
+
- PostgreSQL `ARRAY(TEXT)` type for hierarchical keys
|
|
34
|
+
- Queried via array slice: `namespace[1:{len(prefix)}]` — positional, not semantic
|
|
35
|
+
- Composite PK: `(namespace, key)`
|
|
36
|
+
|
|
37
|
+
## Connection Pools
|
|
38
|
+
- asyncpg: min=2, max=10 (app-level queries via `db/pool.py`)
|
|
39
|
+
- psycopg: max=5 each (LangGraph checkpointer + store via `db/langgraph_pools.py`)
|
|
40
|
+
- Total up to 20 connections per server instance — watch for PostgreSQL `max_connections` exhaustion with multiple workers
|
|
41
|
+
- psycopg pools use `autocommit=True, prepare_threshold=0`
|
|
42
|
+
|
|
43
|
+
## Transactions & Atomicity
|
|
44
|
+
- No explicit transactions in repositories — each method is a single atomic query
|
|
45
|
+
- Multi-step operations (create run + update status) are NOT transactional
|
|
46
|
+
|
|
47
|
+
## Version Snapshots
|
|
48
|
+
- `ON CONFLICT (assistant_id, version) DO NOTHING` for idempotent history writes
|
|
49
|
+
- Version auto-incremented on UPDATE before INSERT into versions table
|
|
50
|
+
|
|
51
|
+
## Gotchas
|
|
52
|
+
- `orjson.dumps(None)` produces `"null"` string — correct for PostgreSQL NULL JSONB
|
|
53
|
+
- Store items have no indices beyond PK — could bottleneck on large stores
|
|
54
|
+
- Pool acquisition: `async with self.pool.acquire() as conn` — single connection per query
|
|
55
|
+
- All `fetch_*` methods return dicts via `dict(row)` conversion from asyncpg.Record
|
|
56
|
+
- 2026-04-07: Checkpointer pool is psycopg (not asyncpg) — uses `%s` placeholders, acquired via `async with checkpointer.conn.connection() as conn`. The `$N` asyncpg convention applies only to the app-level `DatabasePool`, not the checkpointer/store psycopg pools.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Automated pipeline meta-learnings and coordination knowledge
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Pipeline Automation
|
|
6
|
+
|
|
7
|
+
## Label State Machine
|
|
8
|
+
```
|
|
9
|
+
New Issue → [triage] → [planned] → [in-progress] → [review] → [done]
|
|
10
|
+
↑ |
|
|
11
|
+
+-- (REQUEST_CHANGES) --+
|
|
12
|
+
|
|
13
|
+
blocked / human-review-required: applied on top of any state, pause automation
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Coordination Rules
|
|
17
|
+
- Only ONE issue may be `in-progress` at a time
|
|
18
|
+
- `complexity:large` issues skipped unless `force-auto` label present
|
|
19
|
+
- Max 2 review cycles before `human-review-required`
|
|
20
|
+
- 500-line diff limit on PRs
|
|
21
|
+
|
|
22
|
+
## Learnings
|
|
23
|
+
<!-- Pipeline agents append new learnings below this line -->
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: SSE wire format, event types, stream modes, pub/sub broker, and event replay
|
|
3
|
+
globs:
|
|
4
|
+
- "src/langrove/streaming/**/*.py"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Streaming
|
|
8
|
+
|
|
9
|
+
## SSE Wire Format (useStream / LangGraph SDK compatible)
|
|
10
|
+
- Format: `event: {name}\ndata: {json}\n\n` — double newline terminates each event
|
|
11
|
+
- Content-Type: `text/event-stream`
|
|
12
|
+
- Optional reconnection: `id: {event_id}` line before data line
|
|
13
|
+
|
|
14
|
+
## Event Sequence
|
|
15
|
+
1. `metadata` — always first: `{"run_id": "..."}`
|
|
16
|
+
2. `values` / `updates` / `messages` — graph execution events
|
|
17
|
+
3. `end` — always last: `data: null` (literal null, not omitted)
|
|
18
|
+
4. `error` — terminal on exception: `{"error": "...", "message": "ExceptionType"}`
|
|
19
|
+
|
|
20
|
+
## Stream Modes
|
|
21
|
+
- Input can be string or list: `stream_mode: str | list[str]`
|
|
22
|
+
- Langrove internally adds "updates" (always) for interrupt detection
|
|
23
|
+
- "messages" mode also adds "messages-tuple" internally (SDK convenience)
|
|
24
|
+
- SDK-internal modes ("events", "messages-tuple") stripped before processing chunks
|
|
25
|
+
|
|
26
|
+
## Chunk Processing (executor.py)
|
|
27
|
+
LangGraph `astream()` output varies by configuration:
|
|
28
|
+
- Single mode, no subgraphs: bare `chunk`
|
|
29
|
+
- Multi-mode, no subgraphs: `(mode, chunk)` tuple
|
|
30
|
+
- Single mode, subgraphs=True: `(namespace_tuple, chunk)`
|
|
31
|
+
- Multi-mode, subgraphs=True: `(namespace_tuple, mode, chunk)`
|
|
32
|
+
|
|
33
|
+
## Subgraph Namespace
|
|
34
|
+
- Pipe-delimited event names: `updates|parent|child`
|
|
35
|
+
- SDK parses pipes to route subagent messages to correct handler
|
|
36
|
+
- Supports arbitrary nesting depth
|
|
37
|
+
|
|
38
|
+
## Messages Mode
|
|
39
|
+
- Chunks are `(BaseMessageChunk, metadata_dict)` tuples
|
|
40
|
+
- Serialized via `model_dump()` (Pydantic v2) or `dict()` (Pydantic v1)
|
|
41
|
+
- If message is already a dict (not Pydantic), use as-is
|
|
42
|
+
|
|
43
|
+
## Updates Mode
|
|
44
|
+
- Contains interrupts as `__interrupt__` key: `{"__interrupt__": [...]}`
|
|
45
|
+
- Only forwarded as SSE if explicitly requested OR contains interrupt data
|
|
46
|
+
- Prevents duplicate state broadcasts
|
|
47
|
+
|
|
48
|
+
## JSON Serialization
|
|
49
|
+
- `_default(obj)` fallback: tries `obj.model_dump()` → `obj.dict()` → TypeError
|
|
50
|
+
- Handles LangChain message objects, UUIDs, datetimes (orjson native)
|
|
51
|
+
|
|
52
|
+
## Event IDs
|
|
53
|
+
- Format: `{run_id}_event_{counter}` — monotonically increasing
|
|
54
|
+
- Must be sortable for Redis XRANGE replay
|
|
55
|
+
- Client sends `Last-Event-ID` header on reconnect
|
|
56
|
+
|
|
57
|
+
## Event Broker Architecture
|
|
58
|
+
- **Foreground runs (same process):** `asyncio.Queue` per run_id in `_local_queues` dict
|
|
59
|
+
- **Background runs (cross-process):** Redis pub/sub channel `langrove:runs:{run_id}:stream`
|
|
60
|
+
- **Event storage:** Redis Streams `langrove:runs:{run_id}:events` (TTL configurable, default 24h)
|
|
61
|
+
|
|
62
|
+
## Event Replay (subscribe-first pattern)
|
|
63
|
+
1. Subscribe to pub/sub FIRST (captures events during replay gap)
|
|
64
|
+
2. Replay stored events via XRANGE from "-" to "+"
|
|
65
|
+
3. Skip events until `last_event_id` found
|
|
66
|
+
4. Yield stored events, tracking seen IDs in `seen_ids` set
|
|
67
|
+
5. Drain live pub/sub, deduplicating by ID
|
|
68
|
+
6. Stop on "end" or "error" event
|
|
69
|
+
|
|
70
|
+
## Gotchas
|
|
71
|
+
- Redis pub/sub doesn't persist — messages lost if no subscribers present
|
|
72
|
+
- `end` event data is `null`, not omitted: `data: null\n`
|
|
73
|
+
- `asyncio.aclosing()` required for astream() generator cleanup (releases checkpoint locks)
|
|
74
|
+
- Implicitly added `values` mode is suppressed if not in user's original request
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: pytest conventions, fixtures, test patterns, and CI commands
|
|
3
|
+
globs:
|
|
4
|
+
- "tests/**/*.py"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Testing
|
|
8
|
+
|
|
9
|
+
## Framework
|
|
10
|
+
- `pytest` + `pytest-asyncio` for all async tests
|
|
11
|
+
- Dev dependencies: `httpx` (test client), `testcontainers[postgres,redis]`
|
|
12
|
+
|
|
13
|
+
## Fixtures (conftest.py)
|
|
14
|
+
- `fixtures_dir` → `Path(__file__).parent / "fixtures"` — test data directory
|
|
15
|
+
- `test_config_path` → path to test `langgraph.json`
|
|
16
|
+
- Path objects from fixtures — convert with `str()` when passing to functions expecting strings
|
|
17
|
+
|
|
18
|
+
## Test Patterns
|
|
19
|
+
- **Auth tests:** create temp handler files with `tmp_path.write_text()`, test module loading
|
|
20
|
+
- **SSE formatter tests:** verify exact wire format (event names, JSON encoding, blank line separators, null data)
|
|
21
|
+
- **Config tests:** valid/invalid JSON, missing files (returns defaults), type validation, CORS parsing
|
|
22
|
+
- **Graph registry tests:** loading from config, NotFoundError for missing graphs, schema extraction
|
|
23
|
+
|
|
24
|
+
## Conventions
|
|
25
|
+
- No mocking of database when possible — use real postgres via testcontainers
|
|
26
|
+
- Full stream sequence tests: metadata → events → end
|
|
27
|
+
- Test both success and error paths for every endpoint
|
|
28
|
+
|
|
29
|
+
## Commands
|
|
30
|
+
```bash
|
|
31
|
+
uv run pytest --tb=short -q # Quick feedback
|
|
32
|
+
uv run pytest tests/test_runs.py # Specific file
|
|
33
|
+
uv run pytest -x -v # Stop on first failure, verbose
|
|
34
|
+
uv run ruff check . # Lint
|
|
35
|
+
uv run ruff format . # Format
|
|
36
|
+
uv run ruff check . && uv run ruff format . && uv run pytest # Full pre-commit check
|
|
37
|
+
```
|