Install
$ agentstack add skill-dungnotnull-herbal-essential-oil-extraction-agent-skill-herbal-essential-oil-extraction-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 — herbal-essential-oil-extraction (v2.0)
> Skill registry & runtime contract for the Essential Oil Extraction & > Aromatic Chemistry analysis harness.
This document is the authoritative reference for how skills (agents, tools, hooks) are registered, resolved, executed and validated in the v2.0 runtime. It supersedes the linear-only flow described in the original markdown skills while remaining backward-compatible with them (skills/*.md are now the human-readable manifests of the code under src/herbal_oil/).
1. Overview
The runtime is a small, dependency-free Python framework (src/herbal_oil/) that implements a modular skill-registry pattern:
- Agents are domain steps (intake, evidence, core analysis, knowledge,
advisor). Each owns a persona, a toolset, and a strict I/O JSON schema.
- Tools are the only side-effecting boundary (web, knowledge base,
computation). Each exposes an OpenAI-style function descriptor.
- Hooks are lifecycle callbacks (logging, state sync, event bus).
- Registry resolves components by name at runtime.
- Router decides the ordered plan from intent (chain-of-thought routing).
- Runner orchestrates the plan, fires hooks, runs quality gates, renders
the report.
Nothing is hardcoded in the orchestrator: registering a new agent/tool/hook extends the skill without touching the runner.
USER INPUT
-> Pre-Flight (language detect)
-> ChainOfThoughtRouter (intent -> ordered plan)
-> for step in plan: Registry.agent(name).solve(state) [hooks fire]
-> Quality Gates (U1-U6, G1-G4) with auto-fix + 2-retry budget
-> Markdown report + JSON state
2. Registration
Components are registered against SkillRegistry by name. Registration is idempotent-safe: duplicates raise ValueError (fail fast, no silent override). Every registered component is validated for the required attributes.
| Kind | Required attrs | Validator | |--------|---------------------------|----------------------------| | agent | name, solve, output_schema | BaseAgent subclass | | tool | name, parameters, run | JSON-schema on call args | | hook | name, events, dispatch | BaseHook subclass |
from herbal_oil.factory import build_registry # default wiring
from herbal_oil.core import SkillRegistry, BaseAgent, BaseTool, BaseHook
registry = SkillRegistry(settings=settings)
registry.register_tool(MyTool(settings=settings))
registry.register_agent(MyAgent(settings=settings, registry=registry))
registry.register_hook(MyHook())
The factory build_registry() (in src/herbal_oil/factory.py) is the canonical wiring: it instantiates all 5 agents, 6 tools and 5 hooks and binds the optional LLM client.
2.1 Resolution
Resolution is by-name and O(1):
agent = registry.agent("core-analysis") # raises AgentNotFoundError if missing
tool = registry.tool("gcms_profile") # raises ToolNotFoundError if missing
registry.invoke_tool("knowledge_query", keywords=["lavender"]) # executes + validates
registry.manifest() returns the full declarative manifest (OpenAI-style function descriptors for tools; agent names + tool lists; hook events).
3. Execution
The runner executes the router plan in order. For each step:
- Fire
on_step_starthooks. - Look up the agent by name.
- Call
agent.solve(state, **input_builder_kwargs). - On exception: retry up to 2 attempts; on final failure, mark the step
degraded and escalate the degradation level (graceful fallback — never crash the pipeline).
- Commit the result to
PipelineState(step_outputs[name], evidence
ledger, verdict, limitations).
- Fire
on_step_complete(oron_step_error) hooks.
After the plan completes, the runner runs the quality gates (see §5), escalates degradation if needed, and renders the Markdown report.
3.1 Deterministic-by-default
Every agent implements a deterministic solve path grounded in the knowledge base and tools. When an LLM client is bound (build_runner(llm_client=...)), agent.llm_call(prompt) enriches the narrative, with automatic model-fallback on failure. With no client, agents degrade gracefully (Level 2-3) and still produce a structured, limitation-flagged output.
3.2 Comparison / repeat steps
The router sets decision.repeats[agent_name] = N; the runner invokes the agent N times, passing iteration so the agent can tag the object variant (e.g. two herbs in a comparison).
4. Validation
Validation happens at three layers:
- Tool argument validation —
BaseTool.executevalidates call args
against the tool's parameters JSON schema (core/schemas.py validator). Invalid args raise ToolExecutionError (recoverable).
- Agent output validation —
BaseAgent._okvalidates the agent's output
against its output_schema (the canonical copies live under assets/schemas/*.schema.json).
- Quality gates — see §5.
The stdlib JSON-schema validator (core/schemas.py) supports the subset used by our schemas: type, required, properties, enum, items, additionalProperties, minimum, maximum, minLength, maxLength, minItems, pattern, oneOf. Unknown keywords are ignored (forward- compatible). validate(instance, schema, label=...) raises SchemaValidationError(errors=[...]) listing every violation.
5. Quality Gates
Ten gates: six universal (U1-U6) + four domain (G1-G4). Each gate is a callable (state) -> (passed: bool, detail: str, auto_fix: Callable | None).
| Gate | Check | Auto-fix | |------|--------------------------------------------------------|--------------------------------------------| | U1 | >=3 sources cited, >=1 tier<=2 | append a knowledge-base fallback source | | U2 | disclosure/limitations present before recommendation | prepend standard disclosure | | U3 | evidence hierarchy tier (1-4) stated per source | annotate tiers | | U4 | output language matches user preference | run pre-flight language detection | | U5 | verdict present | reformat | | U6 | every claim traceable or flagged | flag unsupported claims | | G1 | method + parameters set | set method/parameters | | G2 | GC-MS profile & yield analyzed | analyze profile/yield | | G3 | aroma preservation addressed | address aroma | | G4 | ISO/pharmacopoeia standardization | standardize |
Enforcement: each gate runs, on failure the auto-fix is invoked, then it retries up to pipeline.gate_retry_limit (default 2) more times. If still failing, the gate is fail-open: a limitation is recorded and degradation escalates to Level 2 — the report is still delivered with an explicit notice.
6. Hooks & Tools
Hooks (src/herbal_oil/hooks/)
| Hook | Events | Purpose | |-----------------------|---------------------------------------|----------------------------------| | LoggingHook | all lifecycle | structured JSON logging | | TimingHook | step start/complete | per-step ms timing | | EvidenceLedgerHook | onevidenceadded | tier-coerce + dedup | | StateCheckpointHook | step complete / run complete | disk checkpoint for replay | | EventEmitterHook | all lifecycle | in-memory pub/sub event bus |
Custom hooks subclass BaseHook, set events, override the matching handlers, and register via registry.register_hook(MyHook()). Hooks must never crash the pipeline (failures are logged and swallowed).
Tools (src/herbal_oil/tools/)
| Tool | Schema file | Purpose | |-------------------|--------------------------|-----------------------------------------------| | websearch | tool.parameters | public-web search (DuckDuckGo, no API key) | | webfetch | tool.parameters | fetch + clean URL text (bounded) | | knowledgequery | tool.parameters | tiered citations from SECOND-KNOWLEDGE-BRAIN | | gcmsprofile | tool.parameters | chemotype composition (literature-typical) | | yieldestimator | tool.parameters | yield % + aroma-preservation index | | knowledgeappend | tool.parameters | dedup-append to brain Section 7 |
Each tool's parameters is a JSON schema; descriptor().to_openai_schema() yields an OpenAI function-calling descriptor.
7. Input / Output JSON Schemas
Canonical schemas live in assets/schemas/. Each agent's output_schema mirrors the corresponding file. Inputs to the pipeline are the user message (string) + optional run_id; outputs are a PipelineResult:
{
"ok": true,
"report": "# Analysis Report ...",
"error": null,
"decision": {"plan": ["gather-requirements", ...], "intent": "standard", "repeats": {}, "skipped": []},
"state": { "run_id": "...", "evidence": [...], "gates": {...}, "verdict": "Optimal Extraction", ... }
}
See:
requirements.schema.json(gather-requirements output)evidence.schema.json(evidence-collector output)analysis.schema.json(core-analysis output)knowledge.schema.json(knowledge-updater output)advisor.schema.json(advisor output)report.schema.json(top-level pipeline result)
8. Configuration
config/settings.py is a pure-stdlib dataclass settings tree resolvable from environment variables or TOML (config/settings.example.toml):
LLM_*— provider, model, temperature, max_tokens, timeout, retries, fallbackFEATURE_*— boolean feature flags (cotrouter, structuredlogging,
qualitygates, degradationbanner, knowledgecrawl, webtools, cache, dry_run)
PIPELINE_*— maxsteps, gateretrylimit, degradationlevels, context budgetsKNOWLEDGE_*— crawl keywords, limits, scoring
get_settings() returns a process-cached instance; reset_settings_cache() clears it (for tests).
9. Adding a New Skill
- Implement a
BaseTool(setname,description,parameters,run). - Implement a
BaseAgent(setname,persona,tool_names,
output_schema, implement solve(state, **kwargs)).
- Add the JSON schema to
assets/schemas/if exposing a new output contract. - Register both in
factory.py(or callregister_tool/register_agent). - Optionally add a quality gate via
runner.register_gate("Gx", fn). - Add tests under
tests/.
No orchestrator edits are required.
10. Markdown skills (skills/*.md)
The skills/*.md files are the human-readable manifests / Claude Code skill definitions. They describe persona, workflow, tools, output format and quality gates for the same steps implemented in src/herbal_oil/agents/. When used inside Claude Code, the agent follows the markdown; the Python runtime is the deterministic, testable, offline-runnable implementation of the same contract.
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/herbal-essential-oil-extraction-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.