Install
$ agentstack add skill-dungnotnull-workshop-air-pollution-monitoring-agent-skill-workshop-air-pollution-monitoring-agent-skill ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
SKILL.md — Skill Registry & Execution Contract
This document is the canonical registry documentation for the workshop-air-pollution-monitoring skill. It explains how skills, tools, and hooks are registered, resolved, executed, and validated, including the input/output JSON-Schema contracts that bind every component.
It is read by:
- contributors who add or modify a skill/tool/hook;
- the runtime (
tools/skill_registry.py,tools/tool_registry.py,
tools/hooks.py) which loads and validates the registry;
- the orchestrator (
skills/main.md) which dispatches sub-skills by name.
The machine-readable companion is assets/registry.json; every entry there validates against assets/schemas/registry-entry.schema.json.
1. Architecture at a glance
USER QUERY
│
▼
[pre_flight hooks] language · context budget · registry validate
│
▼
[skills/main.md] orchestrator + quality gate
│
▼
[skills/sub-router.md] chain-of-thought → plan
│
▼
[skills/sub-*.md] intake → evidence → core-analysis → knowledge → advisor
│ (each powered by tools/* via the tool registry, grounded by references/*)
▼
[pre_delivery hook] enforce U1–U6 + G1–G4
│
▼
[final report] conforms to assets/schemas/report.schema.json
│
▼
[post_delivery hook] queue gaps · metrics · structured log
See assets/diagrams/system-architecture.md for the full diagram and assets/registry.json for the manifest.
2. Skill registry
2.1 Registration
A skill is a Markdown file in skills/ with YAML frontmatter and the required sections. To register a new skill:
- Create
skills/sub-.mdwith frontmattername+description. - Include the required sections:
Role & Persona,Workflow,Output Format,
Quality Gates (sub-skills) — and additionally Sub-skills Available, Quality Gates table, Graceful Degradation, Output Format (orchestrator).
- Add an entry to
assets/registry.jsonunderskills[]withtype=sub_skill
(or orchestrator / router), the path, phase, depends_on, inputs, outputs, and output_schema (a URI to a schema in assets/schemas/).
- If the skill emits structured data, author the schema in
assets/schemas/and
reference it from output_schema.
- Re-run
python scripts/validate_project.py— it asserts the file exists, the
frontmatter is valid, the required sections are present, and the registry.json entry validates.
Frontmatter contract
---
name: sub- # required; matches filename stem
description: # required
---
Required sections (sub-skill)
| Section | Required content | |---------|------------------| | Role & Persona | Who the sub-skill is and its discipline. | | Workflow | Numbered steps: Receive Inputs → Execute Core Task → Emit Outputs. | | Tools | Tool names from the tool registry (or "conversation only"). | | Output Format | The exact block template the sub-skill emits. | | Quality Gates | Checkbox list of pass criteria. |
The orchestrator (main.md) additionally requires Harness Execution Protocol, Quality Gates (table), Graceful Degradation & Error Handling, Sub-skills Available, Tools, Output Format.
2.2 Resolution
tools/skill_registry.SkillRegistry.load() scans skills/, parses frontmatter, and builds an index keyed by name. Resolution by name is O(1):
from tools.skill_registry import SkillRegistry
reg = SkillRegistry.load()
sub = reg.get("sub-core-analysis") # -> SkillDefinition
The orchestrator resolves each sub-skill named in the router's plan through this index; an unknown name is a hard error (the pre_flight_registry_validate hook catches this at startup).
2.3 Execution
Sub-skills are dispatched by SkillRegistry.invoke(name, inputs, context), which:
- Resolves the name to a
SkillDefinition. - Runs the
pre_stephook (input validation against the skill's declared
inputs and the referenced input schema, if any).
- Hands the prompt + inputs to the model (or, for offline runs, to the
deterministic tool handlers).
- Captures the structured output.
- Runs the
post_stephook — validates the output againstoutput_schema
(tools/validate_schema), and on failure either re-invokes (up to skill_registry.max_retries_per_gate) or raises a ValidationError that triggers the graceful-degradation protocol.
2.4 Validation
Three layers:
- Static —
scripts/validate_project.pyvalidates frontmatter, sections,
and registry.json against registry-entry.schema.json.
- Load-time —
pre_flight_registry_validatehook validates every skill at
harness startup.
- Runtime —
post_stephook validates each structured output against its
JSON Schema (assets/schemas/*.schema.json).
3. Tool registry
3.1 Registration
A tool is a deterministic Python callable with a JSON input schema. Register it by:
- Implementing the handler in
tools/(e.g.tools/exposure_calc.py). - Adding an entry to
assets/registry.jsonundertools[]withname,
category, handler (dotted path), optional input_schema/output_schema, timeout_seconds, idempotent, and requires_feature_flag.
- The handler is auto-discovered by
tools/tool_registry.ToolRegistry.load().
3.2 Tool definition shape
// assets/schemas/tool.schema.json
{
"name": "exposure_assess",
"description": "Compute C_i/T_i ratios and the additive mixture index.",
"category": "computation",
"input_schema": { /* JSON Schema for the tool's args */ },
"output_schema": { /* JSON Schema for the tool's result */ },
"handler": "tools.exposure_calc.assess",
"timeout_seconds": 10,
"idempotent": true,
"requires_feature_flag": "enable_mixture_formula"
}
3.3 Resolution & execution
from tools.tool_registry import ToolRegistry
reg = ToolRegistry.load()
result = reg.invoke("exposure_assess", {
"pollutants": [
{"name": "CO", "concentration_ppm": 30, "limit_ppm": 50},
{"name": "NO2", "concentration_ppm": 3, "limit_ppm": 5}
],
"apply_mixture": True
})
ToolRegistry.invoke:
- Resolves the handler by dotted path.
- Checks
requires_feature_flagagainstconfig.FeatureFlags; if disabled,
returns a structured {"disabled": true, "reason": ...} result (never raises — graceful degradation).
- Validates
inputsagainstinput_schema(whenenforce_schemais on). - Calls the handler with a timeout; catches all exceptions and returns a
structured error envelope {"error": ..., "type": ...} so the orchestrator can route to a fallback instead of crashing.
3.4 Built-in tools
| Tool | Handler | Category | Schema | |------|---------|----------|--------| | web_search | tools.tool_registry.web_search | retrieval | tool schema | | web_fetch | tools.tool_registry.web_fetch | retrieval | tool schema | | knowledge_query | tools.tool_registry.knowledge_query | knowledge | — | | knowledge_append | tools.tool_registry.knowledge_append | knowledge | — | | exposure_assess | tools.exposure_calc.assess | computation | core-analysis | | ventilation_design | tools.ventilation_calc.design | computation | core-analysis | | context_budget | tools.context_manager.ContextManager | orchestration | — | | skill_invoke | tools.skill_registry.SkillRegistry.invoke | orchestration | — |
4. Hooks
4.1 Lifecycle phases
| Phase | Fires | Typical use | |-------|-------|-------------| | pre_flight | once, before Step 1 | language detect, context budget, registry validate | | pre_step | before each sub-skill | input validation, token-budget check | | post_step | after each sub-skill | output schema validation, gate check, metrics | | pre_delivery | before final report | enforce U1–U6 + G1–G4, auto-fix | | post_delivery | after final report | queue knowledge gaps, log, metrics |
4.2 Registration
Add an entry to assets/registry.json under hooks[]:
{
"name": "pre_flight_language",
"phase": "pre_flight",
"handler": "tools.hooks.pre_flight_language",
"priority": 10,
"fail_closed": false,
"requires_feature_flag": "enable_language_detection"
}
Hooks within a phase run in ascending priority order. If fail_closed is true and the handler raises, the harness halts; otherwise it logs and continues.
4.3 Execution contract
Every hook handler has the signature handler(context: dict, config: AppConfig) -> dict and returns a possibly-mutated context (plus optional metrics). See hooks/README.md and tools/hooks.py.
5. Input / Output JSON-Schema contracts
Every structured hand-off in the pipeline is bound to a JSON Schema (Draft 2020-12) in assets/schemas/. Validation is performed by tools/validate_schema.validate() (a stdlib-only validator; jsonschema is used when available for full Draft 2020-12 compliance).
| Artifact | Schema | Emitted by | |----------|--------|------------| | Requirements object | requirements.schema.json | sub-gather-requirements | | Evidence bundle | evidence-bundle.schema.json | sub-evidence-collector | | Core analysis scorecard | core-analysis.schema.json | sub-core-analysis | | Knowledge evidence | knowledge-evidence.schema.json | sub-knowledge-updater | | Advisor conclusion | advisor-conclusion.schema.json | sub-advisor | | Final report | report.schema.json | orchestrator | | Skill definition | skill.schema.json | skills/*.md (parsed) | | Tool definition | tool.schema.json | assets/registry.json tools[] | | Hook definition | hook.schema.json | assets/registry.json hooks[] | | Registry entry | registry-entry.schema.json | assets/registry.json |
Example — core-analysis output (excerpt)
{
"pollutants": [{"name": "Welding fume", "sources": ["MIG welding on mild steel"]}],
"exposure_assessment": [
{"pollutant": "CO", "concentration": 30, "unit": "ppm",
"limit_type": "OSHA PEL", "limit_value": 50, "ratio": 0.6, "status": "conditional"}
],
"mixture_index": 0.6,
"ventilation_lev": {"type": "LEV + dilution", "capture_velocity_m_s": 0.6,
"air_changes_per_hour": 6, "source": "ACGIH IV manual"},
"sensors_alerts": [{"parameter": "CO", "threshold": 25, "unit": "ppm",
"action": "warn-investigate-increase-LEV"}],
"control_hierarchy": [{"level": "engineering_control", "measure": "Portable LEV hood at weld point"}],
"scenarios": {"best": "...", "base": "...", "worst": "..."}
}
6. Context window & token management
tools/context_manager.ContextManager enforces the budgets in config.context:
- softbudgettokens / hardbudgettokens — never exceed the hard budget.
- reservetokensfor_output — always kept free for the final report.
- compactionthresholdtokens — when running usage crosses this, compact the
evidence bundle and reference chunks (keep highest-tier items, drop low-tier news) and log the compaction.
- Per-section caps:
evidence_bundle_max_items,knowledge_citations_max,
reference_chunk_max_chars.
Every sub-skill checks context.budget before fetching; the router downgrades a comparison plan to standard if the remaining budget cannot afford two core-analysis passes.
7. Error handling & graceful fallback
- Tools return structured error envelopes (never raise to the orchestrator).
- Sub-skills that fail schema validation are re-invoked up to
max_retries_per_gate, then degraded.
- LLM calls fall back to
llm.fallback_modelafterllm.max_retries. - Sources follow the fallback chain live → secondary → knowledge base →
DATA UNAVAILABLE, raising degradation_level.
- Hooks fail-closed or fail-open per their
fail_closedflag. - The orchestrator never fabricates a missing value; it emits
DATA UNAVAILABLE and, if the missing input is decisive, routes the verdict to Inconclusive.
8. Self-improving knowledge pipeline
tools/knowledge_updater.py (driven by config.knowledge) crawls ArXiv, Semantic Scholar, and RSS feeds, dedups by SHA-256 of DOI/URL, scores by recency + keyword relevance + citation count, and appends to SECOND-KNOWLEDGE-BRAIN.md Section 7. Schedule (documented in CLAUDE.md): weekly academic (Mondays 08:00) + daily news (07:00). Gaps flagged by sub-knowledge-updater become the next crawl's queries.
9. Validation & testing
| Command | What it checks | |---------|----------------| | python scripts/validate_project.py | 8-File Contract + new modular dirs, registry.json vs schema, all skill frontmatter/sections, schemas are valid JSON Schema. | | python scripts/run_pipeline.py --dry-run | End-to-end harness dry run with the typed tools + hooks (no network). | | python tools/test_knowledge_updater.py | Hash dedup, scoring, formatting. | | python tools/run_test_scenarios.py | Structural & content validator + scenario gate coverage. | | python -m pytest tools/tests/ | Unit tests for exposure/ventilation/config/schema tooling. |
10. Contributing checklist
- [ ] New skill →
skills/sub-*.md+assets/registry.jsonentry + schema. - [ ] New tool → handler in
tools/+assets/registry.jsonentry + input/output schema. - [ ] New hook → handler in
tools/hooks.py+assets/registry.jsonentry. - [ ]
python scripts/validate_project.pypasses. - [ ]
python tools/run_test_scenarios.pypasses. - [ ] No placeholders, TODOs, stubs, or empty handlers.
- [ ] Structured logging via
config.get_logger; no bareprintin library code.
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: dungnotnull
- Source: dungnotnull/workshop-air-pollution-monitoring-agent-skill
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.