What Is the Expected Contract for Portal Skills in the AI Job Search Framework?
Portal skills must expose search and detail CLI commands that return standardized JSON outputs containing mandatory fields like date and description, handle errors via stderr with non-zero exit codes, and support automatic discovery through a configured SKILL.md manifest.
The AI Job Search Framework (MadsLorentzen/ai-job-search) treats portal skills as portable adapters that normalize any job board—whether scraped HTML or REST API—into a uniform interface. To maintain this interoperability, every skill must obey a strict contract documented in CONTRIBUTING.md and enforced by tests/test_scrape_contract.py.
Core CLI Commands: search and detail
Every portal skill must implement two entry points as command-line interfaces. According to the CONTRIBUTING.md portal-skill contract, these commands are:
search: Queries the job board and returns a list of postings.detail: Accepts a job identifier and fetches the full description for a single listing.
Both commands must support the --format json|table|plain flag. When invoked with --format json, the output must be machine-readable JSON suitable for pipeline processing. For example:
bun run search --format json "software engineer"
bun run detail --format json abc123
Input and Output Specifications
The contract strictly defines the JSON schema for both commands to ensure the core scraper (/scrape) can parse results uniformly regardless of the underlying technology.
Search Output Contract
The search command must emit a JSON array where each object contains the following mandatory fields as validated by tests/test_scrape_contract.py:
| Field | Type | Description |
|---|---|---|
id |
string | Unique identifier for the job posting. |
title |
string | Job title. |
company |
string | Hiring organization. |
location |
string | Geographic location. |
date |
string | ISO-8601 posting date (required for chronological sorting). |
url |
string | Direct link to the posting. |
Additional portal-specific metadata may be included, but the six fields above are non-negotiable for the ranking workflow.
Detail Output Contract
The detail command must return a single JSON object containing at least:
description: Full job text.deadline: Application deadline (ISO-8601 ornull).employment_type: Contract type (e.g., "Full-time", "Contract").hours: Working hours specification.apply_link: Direct URL for submitting applications.
As documented in .agents/skills/jobindex-search/SKILL.md, these fields allow the /rank module to evaluate fit against your resume.
Error Handling and Resilience
Portal skills must implement defensive HTTP behavior and structured error reporting.
Error Format: All errors are written to stderr as JSON payloads and exit with a non-zero status code. The standard format is:
{ "error": "API_ERROR", "code": 1 }
Rate Limiting: Skills must implement exponential back-off when encountering HTTP 429 (Too Many Requests) or any 5xx server error before retrying. This politeness policy prevents the framework from being blocked by job board defenses.
Runtime Dependencies: By default, skills should rely solely on the bundled Bun runtime. Avoid external npm packages unless the specific portal integration absolutely requires them.
Discovery and Configuration
The framework auto-discovers skills without manual registration. Any folder under .agents/skills/ that contains a SKILL.md file following the contract is automatically loaded by the /scrape command.
Each SKILL.md must declare an enabled: flag. The framework only executes skills marked enabled: true (or those explicitly activated via /setup). To add a new job board, place your skill folder in .agents/skills/, ensure the contract is met, and toggle the flag.
Example directory structure:
.agents/skills/
├── jobindex-search/
│ ├── SKILL.md # Contains enabled: true
│ └── index.ts # Implements search/detail commands
└── linkedin-search/
└── ...
Validation and Testing
Before a skill enters the pipeline, it must pass validation in tests/test_scrape_contract.py. This test suite verifies that:
- Both
searchanddetailcommands exit successfully with valid JSON. - Search results contain the mandatory
datefield in ISO-8601 format. - Detail responses include the required metadata (
description,deadline, etc.). - Error conditions trigger non-zero exit codes with JSON stderr output.
Run the contract tests to verify compliance:
python tests/test_scrape_contract.py
Summary
- Portal skills are CLI adapters living in
.agents/skills/that normalize job board access. - They must implement
search(list jobs) anddetail(fetch specific job) commands. - Output must support
--format jsonwith mandatory fields:id,title,company,location,date,urlfor search; anddescription,deadline,employment_type,hours,apply_linkfor detail. - Errors go to stderr as JSON with non-zero exits; rate-limiting requires exponential back-off.
- Discovery is automatic based on
SKILL.mdfiles containing anenabled:flag. - Compliance is enforced by
tests/test_scrape_contract.py.
Frequently Asked Questions
What happens if a portal skill doesn't include the mandatory date field?
The framework's scraper will reject the output. According to tests/test_scrape_contract.py, the date field is required for search results to enable chronological sorting and filtering. Missing this field causes the contract validation to fail, preventing the skill from being used in the /scrape workflow.
How does the framework handle rate limiting from job boards?
Each skill must implement exponential back-off for HTTP 429 and 5xx responses as specified in CONTRIBUTING.md. The skill is responsible for retry logic with progressive delays before surfacing a final error. This ensures the framework remains respectful to job board infrastructure while maintaining data flow stability.
Can I use external npm packages in my portal skill?
The contract strongly recommends zero runtime dependencies beyond the bundled Bun runtime. You should avoid extra npm packages unless the specific job board API absolutely requires them. This constraint keeps the framework lightweight and reduces supply-chain security risks across the skill ecosystem.
How do I enable or disable a specific portal skill?
Set the enabled: boolean in the skill's SKILL.md file located in .agents/skills/<portal-name>/. When enabled: true, the /scrape command automatically discovers and executes the skill. You can also manage activation states via the /setup command interface.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →