@event4u/agent-config 1.27.0 → 1.29.0
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.
- package/.agent-src/commands/research.md +142 -0
- package/.agent-src/contexts/contracts/frugality-charter.md +4 -3
- package/.agent-src/contexts/contracts/research-schema.md +117 -0
- package/.agent-src/rules/domain-adoption-policy.md +1 -1
- package/.agent-src/rules/no-roadmap-references.md +1 -1
- package/.agent-src/rules/no-unsolicited-rebase.md +1 -1
- package/.agent-src/rules/scope-control.md +6 -8
- package/.agent-src/skills/async-python-patterns/SKILL.md +147 -0
- package/.agent-src/skills/deep-reading-analyst/SKILL.md +192 -0
- package/.agent-src/skills/defense-in-depth/SKILL.md +152 -0
- package/.agent-src/skills/error-handling-patterns/SKILL.md +134 -0
- package/.agent-src/skills/mcp-builder/SKILL.md +108 -0
- package/.agent-src/skills/prompt-engineering-patterns/SKILL.md +145 -0
- package/.agent-src/skills/repomix/SKILL.md +135 -0
- package/.agent-src/skills/roadmap-writing/SKILL.md +3 -3
- package/.agent-src/skills/secrets-management/SKILL.md +142 -0
- package/.agent-src/skills/testing-anti-patterns/SKILL.md +145 -0
- package/.agent-src/templates/agent-settings.md +1 -1
- package/.claude-plugin/marketplace.json +11 -1
- package/CHANGELOG.md +57 -0
- package/README.md +3 -3
- package/docs/architecture.md +3 -3
- package/docs/catalog.md +20 -7
- package/docs/contracts/command-clusters.md +1 -0
- package/docs/contracts/file-ownership-matrix.json +1644 -165
- package/docs/contracts/package-self-orientation.md +1 -1
- package/docs/decisions/ADR-004-rule-governance-pruning.md +3 -3
- package/docs/getting-started.md +1 -1
- package/docs/guidelines/agent-infra/inversion-thinking.md +388 -0
- package/docs/guidelines/agent-infra/mcp-request-signing.md +11 -14
- package/docs/guidelines/agent-infra/mental-models.md +314 -0
- package/docs/guidelines/agent-infra/scqa-framework.md +526 -0
- package/package.json +1 -1
- package/scripts/schemas/skill.schema.json +15 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: research
|
|
3
|
+
cluster: research
|
|
4
|
+
description: "Preliminary research scaffolder — pick objects, define fields, emit `outline.yaml` + `fields.yaml` for downstream deep research. Use for surveys, benchmarks, tech selection, competitive scans."
|
|
5
|
+
disable-model-invocation: true
|
|
6
|
+
skills: [project-analyzer, deep-reading-analyst]
|
|
7
|
+
suggestion:
|
|
8
|
+
eligible: true
|
|
9
|
+
trigger_description: "research a topic, scan competitors, benchmark X, do a tech-selection survey"
|
|
10
|
+
trigger_context: "user names a research topic and wants a structured scaffold (objects + fields), not an immediate answer"
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# /research
|
|
14
|
+
|
|
15
|
+
Entry point for **preliminary research**: pick the objects to study, name
|
|
16
|
+
the fields to fill, and emit a YAML scaffold that a downstream deep-research
|
|
17
|
+
run will populate. Use this when the user names a topic and wants a
|
|
18
|
+
structured plan, not an immediate answer.
|
|
19
|
+
|
|
20
|
+
Routes thinking-framework support to
|
|
21
|
+
[`deep-reading-analyst`](../skills/deep-reading-analyst/SKILL.md) (SCQA
|
|
22
|
+
for narrative structure, mental-models lens for object selection).
|
|
23
|
+
|
|
24
|
+
## Trigger
|
|
25
|
+
|
|
26
|
+
`/research <topic>`
|
|
27
|
+
|
|
28
|
+
## Workflow
|
|
29
|
+
|
|
30
|
+
### Step 1 — Initial framework from model knowledge
|
|
31
|
+
|
|
32
|
+
Generate, from the model's existing knowledge, the candidate object list
|
|
33
|
+
and field framework for the topic:
|
|
34
|
+
|
|
35
|
+
- **Objects / items** — entities, products, methods, datasets to compare.
|
|
36
|
+
- **Field framework** — dimensions to fill per item (basic info, technical
|
|
37
|
+
features, evidence, etc.).
|
|
38
|
+
|
|
39
|
+
Output `{step1_output}` and confirm with the user via numbered options
|
|
40
|
+
(per [`user-interaction`](../rules/user-interaction.md) Iron Law):
|
|
41
|
+
|
|
42
|
+
1. Add or remove items?
|
|
43
|
+
2. Field framework adequate?
|
|
44
|
+
|
|
45
|
+
### Step 2 — Web-search supplement
|
|
46
|
+
|
|
47
|
+
Ask one numbered question for the time range (e.g., last 6 months,
|
|
48
|
+
since 2024, unlimited). Use the agent's native web-search tool — do
|
|
49
|
+
**not** spawn a separate `web-search-agent` persona.
|
|
50
|
+
|
|
51
|
+
Search prompt template (variables in `{xxx}` only — do not modify
|
|
52
|
+
structure):
|
|
53
|
+
|
|
54
|
+
```text
|
|
55
|
+
Research topic: {topic}
|
|
56
|
+
Current date: {YYYY-MM-DD}
|
|
57
|
+
Time range: {time_range}
|
|
58
|
+
|
|
59
|
+
Existing framework:
|
|
60
|
+
{step1_output}
|
|
61
|
+
|
|
62
|
+
Goals:
|
|
63
|
+
1. Verify existing items are not missing important objects.
|
|
64
|
+
2. Supplement items based on missing objects.
|
|
65
|
+
3. Continue searching for {topic}-related items within {time_range}.
|
|
66
|
+
4. Supplement new fields where helpful.
|
|
67
|
+
|
|
68
|
+
Output (return inline, do not write files):
|
|
69
|
+
|
|
70
|
+
### Supplementary items
|
|
71
|
+
- item_name: brief explanation (why it should be added)
|
|
72
|
+
|
|
73
|
+
### Recommended supplementary fields
|
|
74
|
+
- field_name: field description (why this dimension is needed)
|
|
75
|
+
|
|
76
|
+
### Sources
|
|
77
|
+
- [Source 1](url)
|
|
78
|
+
- [Source 2](url)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Step 3 — Existing fields merge
|
|
82
|
+
|
|
83
|
+
Ask via numbered options whether the user has an existing field-definition
|
|
84
|
+
file. If yes, read the file and merge into the framework before Step 4.
|
|
85
|
+
|
|
86
|
+
### Step 4 — Generate outline (two files)
|
|
87
|
+
|
|
88
|
+
Merge `{step1_output}`, `{step2_output}`, and any user-provided fields,
|
|
89
|
+
then write two files into `$PROJECT_ROOT/agents/research/{topic_slug}/`:
|
|
90
|
+
|
|
91
|
+
**`outline.yaml`** (items + execution config):
|
|
92
|
+
|
|
93
|
+
- `topic`: research topic
|
|
94
|
+
- `items`: research-objects list
|
|
95
|
+
- `execution`: `batch_size`, `items_per_agent`, `output_dir`
|
|
96
|
+
(defaults: `./results`; confirm with the user via numbered options)
|
|
97
|
+
|
|
98
|
+
**`fields.yaml`** (field definitions):
|
|
99
|
+
|
|
100
|
+
- field categories + definitions
|
|
101
|
+
- per field: `name`, `description`, `detail_level`
|
|
102
|
+
(`brief` → `moderate` → `detailed`)
|
|
103
|
+
- `uncertain`: list reserved for the deep-research phase
|
|
104
|
+
|
|
105
|
+
YAML structure validation: see
|
|
106
|
+
[`research-schema`](../contexts/contracts/research-schema.md) for the
|
|
107
|
+
project-local JSON-Schema reference (no runtime Python validator; the
|
|
108
|
+
agent reads the schema and self-validates).
|
|
109
|
+
|
|
110
|
+
### Step 5 — Output + confirm
|
|
111
|
+
|
|
112
|
+
Create `agents/research/{topic_slug}/` if absent, write both YAML files,
|
|
113
|
+
and present a summary block to the user:
|
|
114
|
+
|
|
115
|
+
- Topic + slug.
|
|
116
|
+
- Item count + field count.
|
|
117
|
+
- Path to the two files.
|
|
118
|
+
- Next-step pointer: deep-research orchestration is a follow-up port;
|
|
119
|
+
use the YAML scaffold as input when that lands.
|
|
120
|
+
|
|
121
|
+
## Output paths
|
|
122
|
+
|
|
123
|
+
```text
|
|
124
|
+
$PROJECT_ROOT/agents/research/{topic_slug}/
|
|
125
|
+
├── outline.yaml # items list + execution config
|
|
126
|
+
└── fields.yaml # field definitions
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Out of scope (Phase 2)
|
|
130
|
+
|
|
131
|
+
`/research-deep`, `/research-add-items`, `/research-add-fields`, and the
|
|
132
|
+
Python `validate_json.py` validator are **not** ported in Phase 1 — they
|
|
133
|
+
are queued as follow-up cluster sub-commands.
|
|
134
|
+
|
|
135
|
+
## ADOPT citation
|
|
136
|
+
|
|
137
|
+
Adopted from [`Weizhena/Deep-Research-skills`](https://github.com/Weizhena/Deep-Research-skills)
|
|
138
|
+
@ commit `dc18cf4` · upstream file research/SKILL.md inside skills/research-en/ · MIT License.
|
|
139
|
+
Refactored: dropped `web-search-agent` persona (portability), dropped
|
|
140
|
+
Pydantic validator (replaced with JSON-Schema reference), repathed
|
|
141
|
+
`./` → `$PROJECT_ROOT/agents/research/`, deferred `/research-deep` +
|
|
142
|
+
`/research-add-*` to Phase 2.
|
|
@@ -21,9 +21,10 @@ Cite the source rule in writer artifacts; do **not** restate it here.
|
|
|
21
21
|
|
|
22
22
|
## Confirmation taxonomy
|
|
23
23
|
|
|
24
|
-
Iron-Law / Routine / Contextual classification with carve-outs
|
|
25
|
-
|
|
26
|
-
Charter does not duplicate
|
|
24
|
+
Iron-Law / Routine / Contextual classification with carve-outs is
|
|
25
|
+
canonical in the active token-frugality plate under `agents/roadmaps/`
|
|
26
|
+
(or `agents/roadmaps/archive/` once closed). Charter does not duplicate
|
|
27
|
+
the table.
|
|
27
28
|
|
|
28
29
|
## Settings hooks
|
|
29
30
|
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# research-schema
|
|
2
|
+
|
|
3
|
+
Project-local JSON-Schema reference for the
|
|
4
|
+
[`/research`](../../commands/research.md) command's two output files —
|
|
5
|
+
`outline.yaml` and `fields.yaml`. The agent reads the schemas below and
|
|
6
|
+
self-validates the YAML before writing; **no runtime Python validator
|
|
7
|
+
ships in this package** — the Pydantic validator from upstream was
|
|
8
|
+
dropped at adoption time and replaced with this reference contract.
|
|
9
|
+
|
|
10
|
+
## `outline.yaml` schema
|
|
11
|
+
|
|
12
|
+
```yaml
|
|
13
|
+
# JSON-Schema (YAML form) — outline.yaml
|
|
14
|
+
type: object
|
|
15
|
+
required: [topic, items, execution]
|
|
16
|
+
properties:
|
|
17
|
+
topic:
|
|
18
|
+
type: string
|
|
19
|
+
description: Research topic, free-form.
|
|
20
|
+
topic_slug:
|
|
21
|
+
type: string
|
|
22
|
+
pattern: "^[a-z0-9][a-z0-9-]*$"
|
|
23
|
+
description: Lower-kebab slug used as the directory name.
|
|
24
|
+
items:
|
|
25
|
+
type: array
|
|
26
|
+
minItems: 1
|
|
27
|
+
items:
|
|
28
|
+
type: object
|
|
29
|
+
required: [name]
|
|
30
|
+
properties:
|
|
31
|
+
name: { type: string }
|
|
32
|
+
explanation: { type: string }
|
|
33
|
+
source: { type: string, format: uri }
|
|
34
|
+
execution:
|
|
35
|
+
type: object
|
|
36
|
+
required: [batch_size, items_per_agent, output_dir]
|
|
37
|
+
properties:
|
|
38
|
+
batch_size:
|
|
39
|
+
type: integer
|
|
40
|
+
minimum: 1
|
|
41
|
+
description: Number of parallel agents in the deep-research phase.
|
|
42
|
+
items_per_agent:
|
|
43
|
+
type: integer
|
|
44
|
+
minimum: 1
|
|
45
|
+
description: Items each agent processes per batch.
|
|
46
|
+
output_dir:
|
|
47
|
+
type: string
|
|
48
|
+
default: "./results"
|
|
49
|
+
description: Path (relative to the topic dir) for deep-research output.
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## `fields.yaml` schema
|
|
53
|
+
|
|
54
|
+
```yaml
|
|
55
|
+
# JSON-Schema (YAML form) — fields.yaml
|
|
56
|
+
type: object
|
|
57
|
+
required: [categories]
|
|
58
|
+
properties:
|
|
59
|
+
categories:
|
|
60
|
+
type: array
|
|
61
|
+
minItems: 1
|
|
62
|
+
items:
|
|
63
|
+
type: object
|
|
64
|
+
required: [name, fields]
|
|
65
|
+
properties:
|
|
66
|
+
name:
|
|
67
|
+
type: string
|
|
68
|
+
description: Category label (e.g., "Basic info", "Technical features").
|
|
69
|
+
fields:
|
|
70
|
+
type: array
|
|
71
|
+
minItems: 1
|
|
72
|
+
items:
|
|
73
|
+
type: object
|
|
74
|
+
required: [name, description, detail_level]
|
|
75
|
+
properties:
|
|
76
|
+
name: { type: string }
|
|
77
|
+
description: { type: string }
|
|
78
|
+
detail_level:
|
|
79
|
+
type: string
|
|
80
|
+
enum: [brief, moderate, detailed]
|
|
81
|
+
uncertain:
|
|
82
|
+
type: array
|
|
83
|
+
description: Reserved field, populated during deep-research phase.
|
|
84
|
+
items:
|
|
85
|
+
type: object
|
|
86
|
+
properties:
|
|
87
|
+
item: { type: string }
|
|
88
|
+
field: { type: string }
|
|
89
|
+
reason: { type: string }
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Self-validation procedure
|
|
93
|
+
|
|
94
|
+
1. Generate the YAML in memory.
|
|
95
|
+
2. Walk the schema above against the candidate object.
|
|
96
|
+
3. On mismatch, fix the YAML before writing — do not write invalid
|
|
97
|
+
files and rely on a downstream check.
|
|
98
|
+
4. Validation diagnostics surface to the user inline (file path,
|
|
99
|
+
field path, expected vs actual). No external dependencies.
|
|
100
|
+
|
|
101
|
+
## Why no Pydantic / runtime validator
|
|
102
|
+
|
|
103
|
+
Upstream (`Weizhena/Deep-Research-skills`) shipped a `validate_json.py`
|
|
104
|
+
Pydantic-based validator that assumed `~/.claude/` paths and a Python
|
|
105
|
+
runtime in the consumer environment. Both are
|
|
106
|
+
`augment-portability` violations for this package (zero-runtime-Python
|
|
107
|
+
goal, host-agnostic distribution). The schema reference above lets the
|
|
108
|
+
agent validate by reading; consumers needing programmatic validation
|
|
109
|
+
can pipe the YAML through any JSON-Schema validator they prefer
|
|
110
|
+
(`ajv`, `python-jsonschema`, `check-jsonschema`, etc.).
|
|
111
|
+
|
|
112
|
+
## Cross-references
|
|
113
|
+
|
|
114
|
+
- [`/research`](../../commands/research.md) — the command this schema
|
|
115
|
+
validates.
|
|
116
|
+
- Future `/research:deep` and `/research:report` sub-commands will
|
|
117
|
+
reference this same schema once ported.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
type: "auto"
|
|
3
3
|
tier: "2b"
|
|
4
4
|
alwaysApply: false
|
|
5
|
-
description: "Adopting a new domain track (mobile, ML, blockchain,
|
|
5
|
+
description: "Adopting a new domain track (mobile, ML, blockchain, IoT, gaming) — gates import on demand, ownership, CI fit, Sunset compatibility BEFORE harvest"
|
|
6
6
|
source: package
|
|
7
7
|
triggers:
|
|
8
8
|
- intent: "adopt new domain"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
type: "auto"
|
|
3
3
|
tier: "mechanical-already"
|
|
4
|
-
description: "Linking transient files (agents/roadmaps/, agents/council
|
|
4
|
+
description: "Linking transient files (agents/roadmaps/, agents/council-*/) from a stable artifact — both layers expire; promote findings"
|
|
5
5
|
alwaysApply: false
|
|
6
6
|
source: package
|
|
7
7
|
triggers:
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
type: "auto"
|
|
3
3
|
tier: "2a"
|
|
4
4
|
alwaysApply: false
|
|
5
|
-
description: "Working with git history — never rewrite, rebase, squash, fixup, or amend without explicit user request;
|
|
5
|
+
description: "Working with git history — never rewrite, rebase, squash, fixup, or amend without explicit user request; shape is the user's call, not tidiness"
|
|
6
6
|
source: package
|
|
7
7
|
triggers:
|
|
8
8
|
- intent: "rebase the branch"
|
|
@@ -28,15 +28,15 @@ The user decides the git shape. Never improvise. Commit specifics: canonical [`c
|
|
|
28
28
|
- NEVER create / switch / delete a branch without explicit permission — includes spike, scratch, throwaway, worktree branches.
|
|
29
29
|
- NEVER create, close, reopen, or change the target of a pull request without permission.
|
|
30
30
|
- NEVER push a tag or create a release without permission.
|
|
31
|
-
- NEVER include version numbers, target releases, deprecation dates, release-tied milestones, or git tags in roadmaps, plans, tickets, or any planning artifact. Roadmaps plan **work**; releases / tags are a separate decision
|
|
31
|
+
- NEVER include version numbers, target releases, deprecation dates, release-tied milestones, or git tags in roadmaps, plans, tickets, or any planning artifact. Roadmaps plan **work**; releases / tags are a separate decision. User pins by saying so explicitly.
|
|
32
32
|
- Task seems to need a separate branch / PR → STOP and **brief before asking** ([`scope-mechanics § Brief-before-asking`](../contexts/authority/scope-mechanics.md)).
|
|
33
|
-
- BEFORE the first commit on related work, **inventory** existing branches and open PRs
|
|
33
|
+
- BEFORE the first commit on related work, **inventory** existing branches and open PRs. Plausible base beyond the current branch → STOP and ask with numbered options — never improvise. Commands + 4-option template + diverging-stack failure mode: [`scope-mechanics § Branch-base inventory`](../contexts/authority/scope-mechanics.md).
|
|
34
34
|
|
|
35
35
|
"Explicit permission" = user said so **this turn or in a standing instruction not yet revoked**. Earlier permission for a different operation does not carry over.
|
|
36
36
|
|
|
37
37
|
## Production, infrastructure, bulk-destructive — Hard Floor
|
|
38
38
|
|
|
39
|
-
A subset is **never** autonomous
|
|
39
|
+
A subset is **never** autonomous, regardless of standing autonomy. Canonical: [`non-destructive-by-default`](non-destructive-by-default.md). Triggers (prod-branch merges, deploys, prod data / infra, bulk-destructive) + this-turn-only clarification: [`scope-mechanics § Production, infrastructure, bulk-destructive`](../contexts/authority/scope-mechanics.md).
|
|
40
40
|
|
|
41
41
|
## Kernel-rule edits — slow-rollout guarantee
|
|
42
42
|
|
|
@@ -44,11 +44,11 @@ Each kernel-rule edit ships in **its own PR**, ≥ 24 h between merges. Autonomo
|
|
|
44
44
|
|
|
45
45
|
## Decline = silence — no re-asking on the same task
|
|
46
46
|
|
|
47
|
-
After the user **declines** a proposal (branch switch, PR creation, tag/release
|
|
47
|
+
After the user **declines** a proposal (branch switch, PR creation, tag/release, separate worktree, version pinning), do **not** raise it again on the same task. Decline stands until reopened. Timing: [`scope-mechanics § Decline = silence`](../contexts/authority/scope-mechanics.md).
|
|
48
48
|
|
|
49
49
|
## Fenced step — user-set review gates
|
|
50
50
|
|
|
51
|
-
User explicitly fences off the next step
|
|
51
|
+
User explicitly fences off the next step (*"plan only"*, *"review first"*, *"don't implement yet"*, German equivalents) — reply is **deliverable + handoff**, never deliverable + *"shall we start?"*.
|
|
52
52
|
|
|
53
53
|
```
|
|
54
54
|
USER FENCED OFF EXECUTION → DELIVER + HAND BACK.
|
|
@@ -57,6 +57,4 @@ NO "READY TO IMPLEMENT?" RE-ASK.
|
|
|
57
57
|
NO "STARTEN WIR MIT PHASE 1?" PIVOT.
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
-
Fence stands until
|
|
61
|
-
|
|
62
|
-
Failure-mode catalog (Option 1 = "start now", re-asking after delivery, hand-off-to-execution drift, inferring acceptance from a thumbs-up) and explicit bypass phrases: [`scope-mechanics § Fenced step`](../contexts/authority/scope-mechanics.md).
|
|
60
|
+
Fence stands until reopened (like `Decline = silence`). Follow-ups cover **the deliverable** (scope, wording, sections), never its execution. Failure modes + bypass phrases: [`scope-mechanics § Fenced step`](../contexts/authority/scope-mechanics.md).
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: async-python-patterns
|
|
3
|
+
description: "Use when writing Python asyncio code — picking between gather / TaskGroup / wait, structured concurrency, timeouts, cancellation, sync-bridging — decision framework only, cookbook externalized."
|
|
4
|
+
source: package
|
|
5
|
+
status: active
|
|
6
|
+
refresh_trigger: "Python ships a new structured-concurrency primitive (post-TaskGroup), OR ≥30% of cited upstream cookbook examples become deprecated, OR the cited libraries (aiohttp, httpx, anyio, trio) cut a major version with breaking async surface changes."
|
|
7
|
+
sunset_criterion: "When `https://docs.python.org/3/library/asyncio.html` ships an in-tree decision framework AND consumer projects no longer cite this skill in PR reviews for two consecutive review cycles."
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# async-python-patterns
|
|
11
|
+
|
|
12
|
+
Decision framework for picking the right Python asyncio primitive. **The pattern cookbook lives upstream** (links in § Provenance) — this skill is the predicate, not the recipe library. Sunset-policy compliant: the 600+ lines of language-specific cookbook stay in authoritative Python docs.
|
|
13
|
+
|
|
14
|
+
## When to use
|
|
15
|
+
|
|
16
|
+
- Designing a new async I/O-bound service (FastAPI, aiohttp, async DB client).
|
|
17
|
+
- Reviewing a diff that introduces `asyncio.gather`, `asyncio.create_task`, `TaskGroup`, `as_completed`, or `wait_for`.
|
|
18
|
+
- Mixing sync and async code (calling sync libs from async context, or vice versa).
|
|
19
|
+
- Diagnosing event-loop blocking, never-awaited warnings, or cancellation leaks.
|
|
20
|
+
|
|
21
|
+
Do NOT use when:
|
|
22
|
+
|
|
23
|
+
- The work is CPU-bound — async will not help; route to multiprocessing or threadpool.
|
|
24
|
+
- The runtime is not Python — read the host runtime's concurrency guide.
|
|
25
|
+
- The fix is a single missing `await` — read the upstream tutorial directly.
|
|
26
|
+
|
|
27
|
+
## Decision framework
|
|
28
|
+
|
|
29
|
+
### Step 1 — Verify async is the right tool
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
Workload is:
|
|
33
|
+
I/O-bound, many concurrent waits → async fits (network, disk, IPC).
|
|
34
|
+
CPU-bound (parsing, math, crypto) → async is wrong; use ProcessPoolExecutor.
|
|
35
|
+
Mixed → async shell + run_in_executor for CPU bursts.
|
|
36
|
+
Single sequential call → don't introduce async; sync is simpler.
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Step 2 — Pick the concurrency primitive
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
Run N independent coroutines, ALL must complete:
|
|
43
|
+
Same trust level, exceptions cancel siblings → asyncio.TaskGroup (3.11+; preferred).
|
|
44
|
+
Pre-3.11 OR exceptions must NOT cancel peers → asyncio.gather(*, return_exceptions=...).
|
|
45
|
+
|
|
46
|
+
Run N coroutines, react to results as they finish:
|
|
47
|
+
→ asyncio.as_completed (yields completed futures in finish order).
|
|
48
|
+
|
|
49
|
+
Run N coroutines, race to first success / failure:
|
|
50
|
+
→ asyncio.wait(..., return_when=FIRST_COMPLETED) + cancel pending.
|
|
51
|
+
|
|
52
|
+
Schedule fire-and-forget background work:
|
|
53
|
+
→ asyncio.create_task + keep a strong reference (else GC eats it).
|
|
54
|
+
Forgetting the reference is the #1 silent-failure source.
|
|
55
|
+
|
|
56
|
+
Bound the wait time:
|
|
57
|
+
→ asyncio.wait_for(coro, timeout=...) → raises TimeoutError on expiry.
|
|
58
|
+
→ asyncio.timeout(...) context manager (3.11+; preferred when many awaits share a deadline).
|
|
59
|
+
|
|
60
|
+
Bound concurrency (rate-limit, connection pool):
|
|
61
|
+
→ asyncio.Semaphore(n); acquire around the awaitable.
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Step 3 — Bridge sync ↔ async correctly
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
Async code calls sync, blocking, function:
|
|
68
|
+
Short pure-CPU → fine, accept the block (microseconds).
|
|
69
|
+
Long, blocking, or I/O-sync → await loop.run_in_executor(None, fn, *args).
|
|
70
|
+
Library has async sibling → switch the library (httpx vs requests, aiosqlite vs sqlite3).
|
|
71
|
+
|
|
72
|
+
Sync code calls async function:
|
|
73
|
+
Top-level entrypoint → asyncio.run(coro()).
|
|
74
|
+
Inside running loop → never asyncio.run; create_task + await it.
|
|
75
|
+
Test suite → pytest-asyncio fixture; never raw run() in tests.
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Step 4 — Cancellation discipline
|
|
79
|
+
|
|
80
|
+
Every long-running coroutine MUST be cancellation-safe:
|
|
81
|
+
|
|
82
|
+
- Catch `asyncio.CancelledError`, perform cleanup, **re-raise**. Swallowing it silently breaks the propagation chain.
|
|
83
|
+
- Use `try / finally` (or `async with`) around resource acquisition so cancellation cannot leak file handles, DB connections, locks.
|
|
84
|
+
- Detached `create_task` without a strong reference is undefined behavior; either store the task or use a TaskGroup.
|
|
85
|
+
|
|
86
|
+
### Step 5 — Don't block the event loop
|
|
87
|
+
|
|
88
|
+
A single blocking call (sync I/O, time.sleep, CPU-heavy parse, large JSON load) freezes every coroutine. Audit every leaf function under `async def`:
|
|
89
|
+
|
|
90
|
+
- Sleep → `await asyncio.sleep`, never `time.sleep`.
|
|
91
|
+
- HTTP → `httpx.AsyncClient` / `aiohttp`, never `requests`.
|
|
92
|
+
- DB → `asyncpg` / `aiosqlite` / `motor`, never the sync driver.
|
|
93
|
+
- File → `aiofiles` for hot-paths, or `run_in_executor` for one-shots.
|
|
94
|
+
|
|
95
|
+
## Procedure: Apply to a new async feature
|
|
96
|
+
|
|
97
|
+
1. Run Step 1; reject if work is CPU-bound.
|
|
98
|
+
2. Sketch the call graph; tag each `await` site with its primitive (Step 2).
|
|
99
|
+
3. Mark every sync↔async boundary; pick the bridge per Step 3.
|
|
100
|
+
4. For each long-running coroutine, write the cancel-safety contract (Step 4).
|
|
101
|
+
5. Grep the leaf calls for blocking sins (Step 5); replace or push to executor.
|
|
102
|
+
6. Hand the sketch to a reviewer **before** coding; cite this skill.
|
|
103
|
+
|
|
104
|
+
## Output format
|
|
105
|
+
|
|
106
|
+
1. Call-graph table: coroutine · concurrency primitive · timeout · cancel-safety note.
|
|
107
|
+
2. Sync↔async boundary list: site · bridge · justification.
|
|
108
|
+
3. Blocking-call audit: leaf function · status (async / executor / accepted-block + reason).
|
|
109
|
+
4. Cancel-safety contract for each background task.
|
|
110
|
+
|
|
111
|
+
## Gotcha
|
|
112
|
+
|
|
113
|
+
- "It works in my REPL" — `asyncio.run` inside an already-running loop (Jupyter, FastAPI startup) raises `RuntimeError`. Use `await` directly or `nest_asyncio` (last resort).
|
|
114
|
+
- `asyncio.gather` swallows the second exception silently; use `return_exceptions=True` and inspect, or use `TaskGroup` (cancels all on first error, surfaces the group).
|
|
115
|
+
- `create_task` results that nobody awaits look fine until the program exits and Python prints `Task was destroyed but it is pending!`. Always `await` or use a TaskGroup.
|
|
116
|
+
- `wait_for` on a non-cancellation-safe coroutine leaks resources; the timeout cancels the task but cleanup never runs.
|
|
117
|
+
- Libraries that "support async" via thread pools (e.g. `requests-async`) often re-block the loop under load; verify with the cited upstream library docs, not the README.
|
|
118
|
+
|
|
119
|
+
## Do NOT
|
|
120
|
+
|
|
121
|
+
- Do NOT call `asyncio.run` from a running loop.
|
|
122
|
+
- Do NOT swallow `CancelledError` without re-raising.
|
|
123
|
+
- Do NOT call sync blocking I/O from async paths without `run_in_executor`.
|
|
124
|
+
- Do NOT spawn `create_task` without storing the reference (or using TaskGroup).
|
|
125
|
+
- Do NOT inline the asyncio cookbook into this skill — externalize per Sunset Policy.
|
|
126
|
+
|
|
127
|
+
## Auto-trigger keywords
|
|
128
|
+
|
|
129
|
+
- asyncio
|
|
130
|
+
- async / await
|
|
131
|
+
- gather / TaskGroup / wait_for
|
|
132
|
+
- event loop blocking
|
|
133
|
+
- cancellation
|
|
134
|
+
- sync to async bridge
|
|
135
|
+
|
|
136
|
+
## Provenance
|
|
137
|
+
|
|
138
|
+
- Adopted from: `Microck/ordinary-claude-skills@8f5c83174f7aa683b4ddc7433150471983b93131:skills_all/async-python-patterns/SKILL.md` (MIT, © 2025 Microck) — **Sunset Policy applied**: 694-line cookbook source reduced to a ~140-line decision framework; pattern catalogues externalized to upstream docs below.
|
|
139
|
+
- Externalized cookbook:
|
|
140
|
+
- asyncio core: https://docs.python.org/3/library/asyncio.html · https://docs.python.org/3/library/asyncio-task.html
|
|
141
|
+
- TaskGroup (3.11+): https://docs.python.org/3/library/asyncio-task.html#task-groups
|
|
142
|
+
- Structured concurrency: https://anyio.readthedocs.io · https://trio.readthedocs.io
|
|
143
|
+
- Async HTTP: https://www.python-httpx.org/async/ · https://docs.aiohttp.org/en/stable/
|
|
144
|
+
- Async DB: https://magicstack.github.io/asyncpg/ · https://aiosqlite.omnilib.dev/
|
|
145
|
+
- Cross-linked: [`error-handling-patterns`](../error-handling-patterns/SKILL.md), [`mcp-builder`](../mcp-builder/SKILL.md), [`api-design`](../api-design/SKILL.md), [`performance`](../performance/SKILL.md).
|
|
146
|
+
- Provenance registry: `agents/contexts/skills-provenance.yml` (entry: `async-python-patterns`).
|
|
147
|
+
- Iron-Law floor: `verify-before-complete`, `skill-quality`, `non-destructive-by-default`.
|