An AI-agent specializing in compliance to review job postings for policy violations. Using OpenAI's language models and vector search, PolicyKit acts as your intelligent compliance officer, ensuring job postings on your platform meet all of your platform's policies while learning from previous reviews to improve efficiency.
- AI Compliance Agent: An intelligent system that understands and enforces complex policy requirements, detecting violations with high accuracy.
- Retrieval-Augmented Generation (RAG): Uses vector embeddings to find and reuse results from similar job postings for efficiency and consistency. Successful classifications are embedded and added to the database for quicker classification in subsequent requests.
- Vector Database: Uses Chroma, a dedicated vector database, for efficient similarity search and storage of job posting embeddings.
- Flexible Policy Schema: Supports both
StandardViolationandSafetyViolationtypes for nuanced violation reporting. - Async FastAPI Backend: High-performance, async API for real-time job posting checks.
- Seeding & Testing: Includes scripts to seed the database with example job postings and policies.
The PolicyKit AI agent follows the following design:
For a detailed, interactive view of the system architecture, you can open the PolicyKit.drawio file in draw.io
I began by validating that the input is indeed a job posting and safe to process. This included checking for prompt injection or other potentially malicious input. I implemented a gating mechanism to enforce this safety, inspired by the gating design pattern:
FYI, this pattern is described in Anthropic’s article on Building Effective Agents, which I have used as a building block for everything I've since learned about AI agents
I used ChromaDB as a vector database — not for traditional RAG in the usual sense, but as a semantic cache.
Initially, I considered using RAG to retrieve relevant policies. However, I realized that this approach assumes the input (job posting) is semantically similar to the policies it's meant to comply with. That’s often not the case — especially when a job post violates a policy. For example, a job offering “420-themed rewards” is unlikely to be semantically close to “mentions of illegal substances are prohibited.”
Therefore:
- I embedded edge cases like job postings that subtly violate policies
- I stored classified job postings in the vector DB. Since most postings follow consistent patterns, this allows the vector DB to function as a semantic cache. The more data the system sees, the faster and smarter it becomes.
- This enables fast short-circuiting: if a new posting is similar to a previously classified one, we can retrieve that result instantly without running the full classification pipeline again.
There are currently two concerns with this RAG approach that can be improved:
- In the current implementation, the Agent’s response is immediately stored in the VectorDB. This introduces a potential flaw: if a job posting is misclassified once, similar future postings may also be misclassified due to reliance on that incorrect embedding. Given more time, I would modify this by queuing results for potential human review. Only classifications confirmed to be correct would then be added to the VectorDB.
- Job posting embeddings are static, but policies can evolve. A job that was compliant yesterday might violate new rules today. To address this, I would store either a timestamp or a version tag alongside each embedding. This would allow us to reclassify or invalidate older embeddings when policy changes are introduced.
If the vector search doesn't return a confident classification (i.e., it’s a new or rare case), the system falls back to a multi-agent architecture using an Orchestrator-Worker pattern:
Here’s how it works:
- The Orchestrator is given a high-level overview: a summary of each policy category and what it covers.
- Based on the job posting, it dynamically selects relevant categories and spawns Workers to investigate each one.
- Each Worker receives the full list of policies within their category and performs a detailed compliance check.
- All Workers run concurrently using
asyncio. - The individual results are aggregated and returned to the client with the policy categories and potential violations clearly identified.
- Create and activate a Python virtual environment:
python3 -m venv venv source venv/bin/activate - Install Python dependencies:
pip install -r requirements.txt
- Install PostgreSQL:
brew install postgresql
- Ensure PostgreSQL is running and create the database:
createdb policykit
- Run migrations:
alembic upgrade head
The project includes several seeding scripts to populate the database with initial data:
To seed the vector database with policy categories and their respective policies:
python -m app.scripts.seed_policiesThis script includes policies that cover:
- Discrimination
- Legal Compliance
- Workplace Standards
- Compensation
- Privacy and Security
This script populates the database with example job postings and their embeddings for RAG:
python -m app.scripts.seed_job_postingsThe script includes examples of:
- Gender and age discrimination
- Illegal activities
- Copyright infringement
- Academic misconduct
- Privacy violations
- Multiple violation types
You can verify the seeded data using PostgreSQL:
# Check policy categories
psql policykit -c "SELECT * FROM policy_categories;"
# Check policies
psql policykit -c "SELECT p.id, p.title, c.name as category FROM policies p JOIN policy_categories c ON p.category_id = c.id;"Send a POST request to /api/v1/check-posting:
curl -X POST http://localhost:8000/api/v1/check-posting \
-H "Content-Type: application/json" \
-d '{"job_description": "Looking for a young, energetic female candidate to join our team. Must be under 30 years old."}'{
"has_violations": true,
"violations": [
{
"category": "Discrimination",
"policy": ["No Gender Discrimination", "No Age Discrimination"],
"reasoning": "Job posting specifies gender and age requirements",
"content": "Looking for a young, energetic female candidate to join our team. Must be under 30 years old."
}
],
"metadata": null
}- StandardViolation: Used for most policy violations (discrimination, legal, privacy, academic, etc.)
- SafetyViolation: Used for prompt injection, or other safety-related issues
- When a new job posting is checked, its embedding is generated and compared to existing embeddings in Chroma.
- If a similar posting is found (above a similarity threshold), its result is reused for efficiency.
- Otherwise, the posting is checked against all policies and the result is stored in Chroma for future RAG.
- Chroma provides efficient similarity search using HNSW (Hierarchical Navigable Small World) algorithm.
- Add new policies and categories in the database.
- Update the seeding script (
app/scripts/seed_job_postings.py) to add more edge cases or new violation types.
- Run tests with pytest:
python -m pytest tests/api/test_policy_api.py -v -s
- Example test cases are provided for all major violation types and edge cases.
- If you encounter errors related to missing fields or database issues, ensure migrations are up to date and the database is seeded.
- For vector search issues, verify that:
- The Chroma database is properly initialized in the
.chromadirectory - The job posting embeddings collection exists
- Your virtual environment is activated
- All dependencies are installed
- The Chroma database is properly initialized in the
MIT



