RuleStack

Configs

Stacks

Compare

Diff

RuleStack

Configs

Stacks

Compare

Diff

Read API

RuleStack

Configs

Stacks

Compare

Diff

Read API

Diff/jleist-clemson-acheron-claude ↔ jleist-clemson-acheron-cursor-rules-background-tasks

Comparison

A · CLAUDE.md · jleist-clemson/acheronB · Cursor rules · jleist-clemson/acheron
What each file covers, counted
DimensionSharedOnly in AOnly in BOverlap
Sections0160%
Commands000—
Section tags0110%

What each file covers

Sections

0 shared · 1 only in A · 6 only in B
  • − Claude Code
  • + Background Task Lifecycle
  • + Drain vs. cancel on `stop()` (a correctness decision)
  • + Loop invariants
  • + ✅ wakes immediately on stop
  • + ❌ asyncio.sleep(self._interval) # ignores the stop signal
  • + Shutdown ordering in `app/main.py` lifespan

Commands

neither file has any

Section tags

0 shared · 1 only in A · 1 only in B
  • − agent-behaviour
  • + monorepo

Line diff

+42 added−14 removed4 unchanged8.7% identical
jleist-clemson/acheron · CLAUDE.md
@@ −1 @@
1@AGENTS.md
 
 
 
 
2 
3@.claude/rules/python-standards.md
4@.claude/rules/api-routes.md
5@.claude/rules/testing.md
6@.claude/rules/elasticsearch-mapping.md
7@.claude/rules/background-tasks.md
8 
9## Claude Code
 
 
 
10 
11Shared, vendor-neutral project context lives in `AGENTS.md` (imported above).
12Project conventions live in `.claude/rules/` and are **imported above** so they
13load every session — Claude Code has no glob-scoped rules directory, so each
14file simply names the paths it applies to in an "Applies to" line. They mirror
15the Cursor rules in `.cursor/rules/` (which *are* glob-scoped via `globs:`); if
16you change a convention, update **both** mirrors and the relevant section of
17`ARCHITECTURE.md` so the tools and docs don't drift.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18 
jleist-clemson/acheron · .cursor/rules/background-tasks.mdc
@@ +1 @@
1---
2description: Background task lifecycle, shutdown ordering, and loop invariants
3globs: app/worker/**/*.py,app/main.py
4alwaysApply: false
5---
6 
7# Background Task Lifecycle
 
 
 
 
8 
9Background tasks (`WorkerPool`, `EsIndexer`, `RollupScheduler`) follow a
10`start()` / `stop()` shape with an `asyncio.Event` stop signal and a single
11`asyncio.create_task(..., name=...)`. The skeleton matters less than the
12decisions below — get these wrong and you lose data or hang a deploy.
13 
14## Drain vs. cancel on `stop()` (a correctness decision)
15 
16- If the task holds **un-acked, un-persisted work**, drain it before cancelling.
17 `WorkerPool.stop()` does `await queue.join()` with a timeout, *then* cancels.
18- If the task's work is **idempotent / replayable** (the outbox re-indexes, the
19 next tick re-aggregates), set the stop event and cancel directly —
20 `EsIndexer` / `RollupScheduler`.
21 
22When adding a task, ask: would cancelling mid-flight lose anything? That answer
23picks the variant.
24 
25## Loop invariants
26 
27- Idle interruptibly — never block shutdown for a full interval:
28 
29```python
30# ✅ wakes immediately on stop
31try:
32 await asyncio.wait_for(self._stop_event.wait(), timeout=self._interval)
33except asyncio.TimeoutError:
34 pass # interval elapsed
35# ❌ asyncio.sleep(self._interval) # ignores the stop signal
36```
37 
38- The loop body **catches and logs**, never lets an exception kill the task.
39- Consumers must call `task_done()` for every `get()`, even on error.
40 
41## Shutdown ordering in `app/main.py` lifespan
42 
43`ingestion.stop_accepting()` → `worker.stop()` (drain) → `es_indexer.stop()` →
44`rollup.stop()` → **then** close Mongo/ES/Redis clients. Never close a client a
45running task still uses.
46 
@@ −1 +1 @@
1−@AGENTS.md
1+---
2+description: Background task lifecycle, shutdown ordering, and loop invariants
3+globs: app/worker/**/*.py,app/main.py
4+alwaysApply: false
5+---
26  
3−@.claude/rules/python-standards.md
4−@.claude/rules/api-routes.md
5−@.claude/rules/testing.md
6−@.claude/rules/elasticsearch-mapping.md
7−@.claude/rules/background-tasks.md
7+# Background Task Lifecycle
88  
9−## Claude Code
9+Background tasks (`WorkerPool`, `EsIndexer`, `RollupScheduler`) follow a
10+`start()` / `stop()` shape with an `asyncio.Event` stop signal and a single
11+`asyncio.create_task(..., name=...)`. The skeleton matters less than the
12+decisions below — get these wrong and you lose data or hang a deploy.
1013  
11−Shared, vendor-neutral project context lives in `AGENTS.md` (imported above).
12−Project conventions live in `.claude/rules/` and are **imported above** so they
13−load every session — Claude Code has no glob-scoped rules directory, so each
14−file simply names the paths it applies to in an "Applies to" line. They mirror
15−the Cursor rules in `.cursor/rules/` (which *are* glob-scoped via `globs:`); if
16−you change a convention, update **both** mirrors and the relevant section of
17−`ARCHITECTURE.md` so the tools and docs don't drift.
14+## Drain vs. cancel on `stop()` (a correctness decision)
15+ 
16+- If the task holds **un-acked, un-persisted work**, drain it before cancelling.
17+ `WorkerPool.stop()` does `await queue.join()` with a timeout, *then* cancels.
18+- If the task's work is **idempotent / replayable** (the outbox re-indexes, the
19+ next tick re-aggregates), set the stop event and cancel directly —
20+ `EsIndexer` / `RollupScheduler`.
21+ 
22+When adding a task, ask: would cancelling mid-flight lose anything? That answer
23+picks the variant.
24+ 
25+## Loop invariants
26+ 
27+- Idle interruptibly — never block shutdown for a full interval:
28+ 
29+```python
30+# ✅ wakes immediately on stop
31+try:
32+ await asyncio.wait_for(self._stop_event.wait(), timeout=self._interval)
33+except asyncio.TimeoutError:
34+ pass # interval elapsed
35+# ❌ asyncio.sleep(self._interval) # ignores the stop signal
36+```
37+ 
38+- The loop body **catches and logs**, never lets an exception kill the task.
39+- Consumers must call `task_done()` for every `get()`, even on error.
40+ 
41+## Shutdown ordering in `app/main.py` lifespan
42+ 
43+`ingestion.stop_accepting()` → `worker.stop()` (drain) → `es_indexer.stop()` →
44+`rollup.stop()` → **then** close Mongo/ES/Redis clients. Never close a client a
45+running task still uses.
1846  
RuleStack

Built by

Kynth Studio

Directory

Configs
Stacks
Compare formats
Diff two configs
Best AGENTS.md examples

Formats

AGENTS.md
CLAUDE.md
Cursor rules
Copilot instructions

Reference

Read API
Corpus health
Privacy Policy
Terms

RuleStack

RuleStack

Built by

Kynth Studio

Directory

Configs
Stacks
Compare formats
Diff two configs
Best AGENTS.md examples

Formats

AGENTS.md
CLAUDE.md
Cursor rules
Copilot instructions

Reference

Read API
Corpus health
Privacy Policy
Terms

RuleStack

RuleStack

Built by

Kynth Studio

Directory

Configs
Stacks
Compare formats
Diff two configs
Best AGENTS.md examples

Formats

AGENTS.md
CLAUDE.md
Cursor rules
Copilot instructions

Reference

Read API
Corpus health
Privacy Policy
Terms

RuleStack