Real adapters for real frameworks.
Every recipe on this page is built against a framework whose format we independently verified against its
upstream documentation (linked inline). The SKILL.md exporter itself is unit-tested in
tests/unit/test_skill_export.py and the contract-enforcement loop those recipes rely on is covered
by tests/integration/test_agent_loop.py. If we couldn't verify a project actually exists
in the form claimed, it's not on this page.
SKILL.md export — Anthropic Claude Skills
Anthropic Claude Skills package a capability as a folder with a SKILL.md at the root. The frontmatter
requires name (≤64 chars, kebab-case) and description (≤1024 chars), with optional
allowed-tools and disable-model-invocation for Claude Code. The runtime uses
progressive disclosure:
metadata is loaded at session start, the body is loaded when the skill matches user intent, and bundled
scripts/, references/, assets/ are loaded on demand.
employee.md ships a converter so the same contract can power a Claude Skill:
from runtime import Employee
from runtime.skill_export import to_skill_md
employee = Employee.from_file("employee.md")
skill_md = to_skill_md(employee)
# Write to your Claude Skill directory
import pathlib
skill_dir = pathlib.Path("~/.claude/skills/my-agent").expanduser()
skill_dir.mkdir(parents=True, exist_ok=True)
(skill_dir / "SKILL.md").write_text(skill_md)
Source: runtime/skill_export.py · Tests: tests/unit/test_skill_export.py
CrewAI — agents.yaml
CrewAI agents are defined in src/<project>/config/agents.yaml with required fields
role, goal, backstory and optional llm, tools,
allow_delegation. Map these from employee.md:
import yaml
from runtime import Employee
emp = Employee.from_file("employee.md")
d = emp.data
agents_yaml = {
emp.data["identity"]["agent_id"]: {
"role": d["role"]["title"],
"goal": d["mission"]["purpose"],
"backstory": "\n".join(d["mission"].get("objectives", [])),
"tools": d.get("permissions", {}).get("tool_access", []),
"allow_delegation": bool(
d.get("delegation", {}).get("sub_delegation", False)
),
}
}
with open("src/myproject/config/agents.yaml", "w") as f:
yaml.safe_dump(agents_yaml, f, sort_keys=False)
Wire CrewAI to refuse out-of-scope tasks by checking employee.is_in_scope() in your
before_kickoff_callbacks hook.
Reference: docs.crewai.com/concepts/agents
LangGraph — state initialization + tool gating
LangGraph nodes operate on a typed state object. Initialize the state from your contract and gate tool calls against the contract's permissions:
from typing import TypedDict, List
from langgraph.graph import StateGraph
from runtime import Employee, ContractError
employee = Employee.from_file("employee.md")
class AgentState(TypedDict):
messages: List[dict]
spent_usd: float
def tool_node(state: AgentState, tool_name: str, cost: float):
# Refuse anything outside the contract's tool_access list
if not employee.is_action_allowed(tool_name):
raise ContractError(f"Tool '{tool_name}' not in permissions.tool_access")
# Refuse anything that would exceed the per-task spend cap
cap = employee.data.get("guardrails", {}).get("max_spend_per_task")
if cap and cost > cap:
raise ContractError(f"Cost {cost} exceeds max_spend_per_task={cap}")
state["spent_usd"] += cost
# ... invoke the tool, append to messages ...
return state
graph = StateGraph(AgentState)
graph.add_node("system_prompt",
lambda s: {**s, "messages": [{"role":"system","content": employee.system_prompt()}]})
AutoGen — AssistantAgent role config
Microsoft AutoGen's AssistantAgent takes name, system_message, and
llm_config. Drive all three from the contract:
from autogen import AssistantAgent
from runtime import Employee
employee = Employee.from_file("employee.md")
ai = employee.data.get("ai_settings", {})
agent = AssistantAgent(
name=employee.data["identity"]["agent_id"],
system_message=employee.system_prompt(),
llm_config={
"model": ai.get("model_preference", "gpt-4"),
"temperature": ai.get("generation_params", {}).get("temperature", 0.0),
"max_tokens": ai.get("token_limits", {}).get("max_tokens", 4096),
},
)
Model Context Protocol — server prompt
Expose the rendered system prompt as an MCP prompts resource so any MCP-aware client can pick up
the contract:
from mcp.server import Server
from runtime import Employee
employee = Employee.from_file("employee.md")
server = Server("employee-md")
@server.list_prompts()
async def list_prompts():
return [{
"name": "employee_contract",
"description": f"Active contract for {employee.data['identity']['agent_id']}",
}]
@server.get_prompt()
async def get_prompt(name, arguments):
if name != "employee_contract":
raise ValueError(f"Unknown prompt: {name}")
return {
"messages": [
{"role": "system", "content": {"type": "text", "text": employee.system_prompt()}}
]
}
Plain Python — works everywhere
The runtime SDK has zero framework dependencies. The simplest possible loop:
from runtime import Employee, ContractError
employee = Employee.from_file("employee.md")
def run_action(name: str, cost: float = 0.0):
if not employee.is_action_allowed(name):
return {"ok": False, "reason": f"action '{name}' is prohibited"}
cap = employee.data.get("guardrails", {}).get("max_spend_per_task")
if cap is not None and cost > cap:
return {"ok": False, "reason": f"cost {cost} > max_spend_per_task {cap}"}
return {"ok": True}
print(run_action("deploy_to_production", cost=0)) # {'ok': False, 'reason': ...}
print(run_action("write_unit_test", cost=10)) # {'ok': True}
The full end-to-end loop (load → render → enforce → loop) is exercised in tests/integration/test_agent_loop.py — that test is the proof that an agent will actually follow the contract.
A note on integrations we don't list
Earlier drafts of this doc named "OpenClaw" and "HermesAgent" as integration targets. When we tried to verify those projects against primary sources for v1.0.0, we couldn't find independently confirmable evidence they exist in the form previously described — the search results we got back were inconsistent (future-dated commits, implausible star counts) and the URLs didn't resolve to the canonical projects we'd hoped for. Rather than pretend, we removed them from this page. If either project ships a real, public spec we can verify, we'll add it. The forward-looking placeholder shapes still live in INTEGRATION.md under "Experimental / Planned" with explicit caveats.
Full integration guide
Integrate employee.md with your AI agent systems, including LangChain, AutoGen, CrewAI, and custom Python/TypeScript runtimes.
employee.md is part of the open Agentic Web ecosystem, designed to work alongside:
- AGENTS.md: For repository-level context and codebase instructions
- MCP: For connecting tools and data sources
- SOUL.md: For defining agent personality and values
Python Integration¶
Basic PyYAML Parsing¶
The simplest way to parse employee.md:
import yaml
with open('employee.md', 'r') as f:
config = yaml.safe_load(f)
# Access fields
agent_id = config['identity']['agent_id']
role_title = config['role']['title']
status = config['lifecycle']['status']
print(f"Agent {agent_id} is a {role_title} with status {status}")
Pydantic Validation (Recommended)¶
Type-safe validation for robust agents:
from pydantic import BaseModel, Field
from typing import List, Optional, Dict, Any, Literal
from enum import Enum
import yaml
class Level(str, Enum):
JUNIOR = "junior"
MID = "mid"
SENIOR = "senior"
LEAD = "lead"
class Role(BaseModel):
title: str
level: Level
department: Optional[str] = None
capabilities: List[str] = []
class Identity(BaseModel):
agent_id: str
display_name: Optional[str] = None
version: Optional[str] = None
class Lifecycle(BaseModel):
status: Literal["onboarding", "active", "suspended", "terminated"]
class Employee(BaseModel):
spec: Dict[str, Any]
identity: Optional[Identity] = None
role: Role
lifecycle: Lifecycle
# Load and validate
with open('employee.md', 'r') as f:
data = yaml.safe_load(f)
employee = Employee(**data)
print(f"Validated Agent: {employee.role.title} ({employee.role.level})")
Advanced Python Integration¶
import yaml
from dataclasses import dataclass
from typing import Optional, List
@dataclass
class AgentConfig:
"""Typed configuration wrapper for employee.md"""
agent_id: str
title: str
level: str
purpose: str
model: str
temperature: float
@classmethod
def from_file(cls, path: str) -> "AgentConfig":
with open(path, 'r') as f:
data = yaml.safe_load(f)
return cls(
agent_id=data['identity']['agent_id'],
title=data['role']['title'],
level=data['role']['level'],
purpose=data['mission']['purpose'],
model=data['ai_settings']['model_preference'],
temperature=data['ai_settings']['generation_params']['temperature']
)
def can_access(self, resource: str) -> bool:
"""Check if agent has access to a resource"""
# Implementation based on permissions
pass
# Usage
config = AgentConfig.from_file('employee.md')
TypeScript Integration¶
Using js-yaml¶
import yaml from 'js-yaml';
import * as fs from 'fs';
interface EmployeeConfig {
identity?: {
agent_id: string;
display_name?: string;
};
role: {
title: string;
level: 'junior' | 'mid' | 'senior' | 'lead';
};
mission?: {
purpose: string;
};
lifecycle: {
status: string;
};
}
const file = fs.readFileSync('employee.md', 'utf8');
const config = yaml.load(file) as EmployeeConfig;
console.log(`Agent Role: ${config.role.title}`);
Using Zod (Runtime Validation)¶
import { z } from 'zod';
import yaml from 'js-yaml';
import * as fs from 'fs';
const EmployeeSchema = z.object({
spec: z.object({
name: z.literal('employee.md'),
version: z.string(),
kind: z.literal('agent-employment'),
}),
identity: z.object({
agent_id: z.string(),
display_name: z.string().optional(),
}).optional(),
role: z.object({
title: z.string(),
level: z.enum(['junior', 'mid', 'senior', 'lead']),
}),
lifecycle: z.object({
status: z.enum(['onboarding', 'active', 'suspended', 'terminated']),
}),
});
type EmployeeConfig = z.infer<typeof EmployeeSchema>;
const file = fs.readFileSync('employee.md', 'utf8');
const data = yaml.load(file);
const employee = EmployeeSchema.parse(data);
console.log(employee.role.title);
AI Framework Integration¶
LangChain¶
Inject employee.md context into your system prompt:
from langchain.agents import AgentExecutor, create_openai_functions_agent
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
import yaml
# Load employee config
with open('employee.md', 'r') as f:
config = yaml.safe_load(f)
# Create dynamic system prompt
system_prompt = f"""You are {config['identity'].get('display_name', 'an AI agent')}.
ROLE: {config['role']['title']} ({config['role']['level']})
MISSION: {config['mission']['purpose']}
CAPABILITIES:
{chr(10).join(f"- {cap}" for cap in config['role'].get('capabilities', []))}
GUARDRAILS (STRICTLY ENFORCED):
- NEVER: {', '.join(config['guardrails'].get('prohibited_actions', []))}
- REQUIRE APPROVAL FOR: {', '.join(config['guardrails'].get('required_approval', []))}
SCOPE:
- IN SCOPE: {', '.join(config['scope'].get('in_scope', []))}
- OUT OF SCOPE: {', '.join(config['scope'].get('out_of_scope', []))}
Respond according to your role and mission while respecting all guardrails.
"""
# Create agent with configured settings
llm = ChatOpenAI(
model=config['ai_settings'].get('model_preference', 'gpt-4'),
temperature=config['ai_settings'].get('generation_params', {}).get('temperature', 0.7)
)
prompt = ChatPromptTemplate.from_messages([
("system", system_prompt),
("human", "{input}"),
("placeholder", "{agent_scratchpad}"),
])
AutoGen¶
Configure an AutoGen assistant using employee.md:
import autogen
import yaml
with open('employee.md', 'r') as f:
config = yaml.safe_load(f)
# Create assistant agent from config
assistant = autogen.AssistantAgent(
name=config['identity']['agent_id'],
system_message=f"""You are a {config['role']['title']} ({config['role']['level']}).
Your mission: {config['mission']['purpose']}
Capabilities: {', '.join(config['role'].get('capabilities', []))}
Constraints:
- Never: {', '.join(config['guardrails'].get('prohibited_actions', []))}
- Confidence threshold: {config['guardrails'].get('confidence_threshold', 0.8)}
Reply TERMINATE when the task is complete.
""",
llm_config={
"config_list": [
{
"model": config['ai_settings'].get('model_preference', 'gpt-4'),
"api_key": "your-api-key"
}
],
"temperature": config['ai_settings'].get('generation_params', {}).get('temperature', 0.7),
}
)
# Create user proxy agent
user_proxy = autogen.UserProxyAgent(
name="user",
human_input_mode="NEVER",
max_consecutive_auto_reply=10,
code_execution_config={"work_dir": "coding"},
)
# Start conversation
user_proxy.initiate_chat(assistant, message="Hello!")
CrewAI¶
from crewai import Agent, Task, Crew
import yaml
# Load config
with open('employee.md', 'r') as f:
config = yaml.safe_load(f)
# Create agent from employee.md
coding_agent = Agent(
role=config['role']['title'],
goal=config['mission']['purpose'],
backstory=f"A {config['role']['level']} level developer with expertise in {', '.join(config['role'].get('capabilities', []))}",
verbose=True,
allow_delegation=config['delegation'].get('sub_delegation', False) if 'delegation' in config else False,
llm=config['ai_settings'].get('model_preference', 'gpt-4') if 'ai_settings' in config else 'gpt-4'
)
# Create task
task = Task(
description="Write a Python function to calculate fibonacci numbers",
agent=coding_agent,
expected_output="A working Python function with docstring"
)
# Create and run crew
crew = Crew(agents=[coding_agent], tasks=[task])
result = crew.kickoff()
MCP Integration¶
The Model Context Protocol (MCP) enables secure connections between AI agents and external tools/data sources.
Quick Start with MCP¶
integration:
mcp_servers:
- name: "filesystem"
endpoint: "https://nosytlabs.github.io/employee-md:3000"
capabilities:
- "read_file"
- "write_file"
- "list_directory"
- name: "web-search"
endpoint: "https://nosytlabs.github.io/employee-md:3001"
capabilities:
- "search"
- "fetch_url"
Popular MCP Servers¶
| Server | Purpose | Capabilities |
|---|---|---|
| filesystem | Local file operations | read_file, write_file, list_directory |
| web-search | Internet search | search, fetch_url |
| github | GitHub integration | read_repo, create_pr, list_issues |
| database | SQL database access | query, execute, transaction |
| browser | Web browsing | navigate, screenshot, extract |
| code-execution | Sandbox code execution | execute_python, execute_javascript |
MCP Server Examples¶
Vector Database (Semantic Search)¶
integration:
mcp_servers:
- name: "vector-db"
endpoint: "https://nosytlabs.github.io/employee-md:6333"
capabilities:
- "vector_search"
- "embedding_queries"
- "similarity_search"
API Gateway¶
integration:
mcp_servers:
- name: "api-gateway"
endpoint: "https://nosytlabs.github.io/employee-md:8080"
capabilities:
- "http_requests"
- "rate_limiting"
- "circuit_breaker"
Browser Automation¶
integration:
mcp_servers:
- name: "browser-mcp"
endpoint: "https://nosytlabs.github.io/employee-md:9222"
capabilities:
- "web_navigation"
- "page_extraction"
- "screenshot_capture"
- "form_interaction"
Code Execution (Sandboxed)¶
integration:
mcp_servers:
- name: "code-exec-mcp"
endpoint: "https://nosytlabs.github.io/employee-md:5555"
capabilities:
- "python_execution"
- "javascript_execution"
- "sandbox_isolation"
- "resource_monitoring"
Knowledge Base¶
integration:
mcp_servers:
- name: "kb-mcp"
endpoint: "https://nosytlabs.github.io/employee-md:7373"
capabilities:
- "faq_lookup"
- "best_practices"
- "knowledge_graph"
- "document_retrieval"
Production Multi-Server Setup¶
integration:
mcp_servers:
# Primary gateway for tool routing
- name: "mcp-gateway"
endpoint: "https://nosytlabs.github.io/employee-md:18789"
capabilities:
- "tool_routing"
- "session_memory"
- "rate_limiting"
# Semantic search and embeddings
- name: "vector-db"
endpoint: "https://nosytlabs.github.io/employee-md:6333"
capabilities:
- "vector_search"
- "embedding_queries"
# External API management
- name: "api-gateway"
endpoint: "https://nosytlabs.github.io/employee-md:8080"
capabilities:
- "http_requests"
- "rate_limiting"
- "api_key_management"
# Web research capabilities
- name: "browser-mcp"
endpoint: "https://nosytlabs.github.io/employee-md:9222"
capabilities:
- "web_navigation"
- "page_extraction"
- "screenshot_capture"
# Secure code execution
- name: "code-exec-mcp"
endpoint: "https://nosytlabs.github.io/employee-md:5555"
capabilities:
- "python_execution"
- "sandbox_isolation"
# Internal documentation
- name: "docs-mcp"
endpoint: "https://nosytlabs.github.io/employee-md:8888"
capabilities:
- "doc_retrieval"
- "faq_lookup"
MCP Capabilities Reference¶
Common capabilities you may encounter:
| Capability | Description | Use Case |
|---|---|---|
tool_routing |
Route tools dynamically | Load tools from multiple sources |
session_memory |
Persistent session context | Remember conversation history |
semantic_search |
Vector-based similarity search | Find relevant documents |
document_retrieval |
Fetch documents by ID | Retrieve specific documents |
vector_search |
Query vector embeddings | Semantic search over embeddings |
embedding_queries |
Generate embeddings | Convert text to vectors |
http_requests |
Make HTTP requests | Call external APIs |
rate_limiting |
Enforce rate limits | Prevent API abuse |
circuit_breaker |
Fail-fast protection | Handle service failures |
web_navigation |
Browse web pages | Research and scraping |
page_extraction |
Extract page content | Get text from HTML |
python_execution |
Run Python code | Execute code snippets |
sandbox_isolation |
Isolated execution | Secure code execution |
sql_queries |
Execute SQL queries | Database operations |
transaction_management |
Handle DB transactions | Atomic operations |
Protocol Integration¶
A2A (Agent-to-Agent) Protocol¶
Enable agents to discover and communicate with each other:
import yaml
from datetime import datetime
with open('employee.md', 'r') as f:
config = yaml.safe_load(f)
if config.get('protocols', {}).get('a2a', {}).get('enabled'):
message = {
"protocol": "a2a/1.0",
"from": config['identity']['agent_id'],
"to": "target-agent-id",
"timestamp": datetime.utcnow().isoformat(),
"type": "task_request",
"payload": {
"task": "process_data",
"priority": "high",
"deadline": "2026-02-15T12:00:00Z"
}
}
# Send via your transport layer (HTTP, WebSocket, P2P)
x402 Payment Protocol¶
Handle crypto payments for agent services:
from web3 import Web3
import yaml
with open('employee.md', 'r') as f:
config = yaml.safe_load(f)
if config.get('protocols', {}).get('x402', {}).get('enabled'):
w3 = Web3(Web3.HTTPProvider('https://mainnet.infura.io/v3/YOUR_KEY'))
# Create payment transaction
transaction = {
'to': config['identity']['wallet'],
'value': w3.to_wei(config['economy']['rate'], 'ether'),
'gas': 2000000,
'gasPrice': w3.to_wei('50', 'gwei'),
}
Human Review Workflow¶
import yaml
with open('employee.md', 'r') as f:
config = yaml.safe_load(f)
human_review = config.get('protocols', {}).get('human_review', {})
if human_review.get('enabled'):
def request_human_approval(action: str, context: dict):
"""Request human approval for sensitive actions"""
triggers = human_review.get('review_triggers', [])
# Check if action matches any trigger
if any(trigger in action for trigger in triggers):
# Send notification to escalation contacts
contacts = human_review.get('escalation_contacts', [])
timeout = human_review.get('approval_timeout', 86400)
# Implementation: Send email/Slack notification
print(f"Approval required from {contacts} for: {action}")
return wait_for_approval(timeout)
Experimental / Planned Integrations¶
⚠️ Status: experimental / planned / unverified. The integrations below are not part of the stable v1.0.0 contract. The schema accepts them today via free-form sections (
integration.openclaw,custom_fields.*) and via existing fields likeeconomy.payment_method: "joulework", but no first-class validator, reference server, or production runtime ships with this repo yet.When researching v1.0.0 we attempted to verify the upstream projects named below against canonical sources (project websites, GitHub, peer-reviewed material). For OpenClaw and Hermes, the search results we got back were inconsistent with what a real, live project would look like (future-dated commits, implausible star counts, links that didn't resolve to a canonical repo). Rather than pretend, we are leaving these here only as a forward-looking design sketch: if the projects ship a real, public spec we can independently confirm, we'll promote them. Until then, treat these blocks as illustrative shapes, not real integrations. See
docs/RESEARCH_NOTES.mdfor the verification methodology.
OpenClaw — always-on personal agent runtime (UNVERIFIED upstream)¶
OpenClaw is described in earlier drafts of this repo as a runtime for
always-on autonomous agents (email, calendar, browsing, messaging).
employee.md would be a natural fit as the governance layer for such a
runtime: it answers "what is this agent allowed to do, with what budget,
under whose review?". The shape below is a placeholder; we could not
independently verify the upstream project for v1.0.0.
The integration is currently a placeholder shape. There is no published
openclaw namespace in tooling/schema.json; today you express it via
integration.mcp_servers plus a free-form integration.openclaw block:
integration:
# Stable, validated today:
mcp_servers:
- name: "openclaw-gateway"
endpoint: "http://127.0.0.1:18789"
capabilities:
- "tool_routing"
- "session_memory"
# Experimental — shape may change before being promoted into the schema:
openclaw:
enabled: true
channels: ["whatsapp", "telegram", "slack"]
capabilities:
- "email_management"
- "calendar_scheduling"
- "web_browsing"
heartbeat:
interval: 60 # seconds
timeout: 300 # seconds
What is not shipped yet:
- A published OpenClaw reference server in this repo.
- A first-class
openclawblock intooling/schema.jsonwith strict validation. - A conformance test suite for the heartbeat / channel contract.
If you need a stable surface today, define your OpenClaw gateway as a regular
MCP server under integration.mcp_servers and keep any OpenClaw-specific
fields under custom_fields until the shape stabilises.
Hermes — agent-to-agent message transport (UNVERIFIED upstream)¶
Hermes is a proposed transport layer for protocols.a2a messages
(discovery, authentication, delivery). Today the spec validates the policy
of A2A — whether it is enabled, the discovery method, the message format, the
encryption requirement — but does not bind to any specific transport
implementation:
protocols:
a2a:
enabled: true
discovery_method: "registry" # or "broadcast"
message_format: "json"
encryption: true
# Planned — not yet validated by the schema:
# transport: "hermes"
# hermes:
# relay: "https://hermes.example.com"
# identity: "did:web:agent.example.com"
A future minor release may add a first-class protocols.a2a.transport enum
and a protocols.a2a.hermes sub-block. Until then, anything Hermes-specific
should live under custom_fields to avoid coupling your config to an
unstable surface.
JouleWork — energy-denominated economy (experimental example)¶
The economy block already accepts payment_method: "joulework" and
currency: "ENERGY", and the schema validates them today. See
examples/zhc-worker.md for a worked example of an
energy-accounted, profit-tracked agent. The semantic meaning of these values
(how a "joule" is metered, how P&L is settled, how insolvency_policy
suspends an agent) is not standardised by this repo and is left to the
runtime that consumes the contract.
How to propose promotion¶
If you are running one of these integrations in production and want it promoted to a stable, validated section of the schema:
- Open a discussion describing the production shape and at least one reference implementation.
- File an issue with the proposed JSON Schema patch.
- Submit a PR that adds the new block to
tooling/schema.json, updatesemployee.md, adds at least one new example underexamples/, and adds the example to both the strictschema-checkwhitelist in.github/workflows/validate.ymland thevalidateandvalidate-stricttargets in theMakefile.
Validation Tools¶
Always validate your employee.md before deploying:
CLI Validation¶
# Install validator
pip install employee-md
# Basic validation
employee-validate employee.md
# Validate with JSON output
employee-validate employee.md --format json
# Validate multiple files
employee-validate examples/*.md
# Production mode (sanitized errors)
employee-validate employee.md --production
# With metrics
employee-validate employee.md --metrics prometheus
Python API¶
from tooling import validate_file
result = validate_file("employee.md")
if result.is_valid:
print("✅ Valid configuration")
else:
print("❌ Validation failed:")
for error in result.errors:
print(f" - {error.field}: {error.message}")
CI/CD Integration¶
# .github/workflows/validate-employee-md.yml
name: Validate employee.md
on:
push:
paths:
- 'employee.md'
- 'examples/*.md'
pull_request:
paths:
- 'employee.md'
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install validator
run: pip install employee-md
- name: Validate
run: employee-validate employee.md --format compact