Skip to main content
Integrations

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}")

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"
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

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 like economy.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.md for 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 openclaw block in tooling/schema.json with 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:

  1. Open a discussion describing the production shape and at least one reference implementation.
  2. File an issue with the proposed JSON Schema patch.
  3. Submit a PR that adds the new block to tooling/schema.json, updates employee.md, adds at least one new example under examples/, and adds the example to both the strict schema-check whitelist in .github/workflows/validate.yml and the validate and validate-strict targets in the Makefile.

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

Additional Resources