In modern AI developer workflows, building autonomous agents that can analyze job descriptions, align candidate experience, and generate tailored, ATS-optimized resumes for specific job applications is becoming essential.
Flue (created by the team behind Astro) is an open-source agent framework designed for TypeScript developers under a “write once, deploy anywhere” paradigm.
This complete step-by-step guide walks through creating an AI-powered resume matcher agent using Flue, incorporating specialized resume-writing skills, and configuring OpenRouter for cost-effective LLM processing.
What is the Flue Framework?
Flue is a TypeScript agent framework built around familiar React-like hooks (useAgent, useModel, useSkill, useTool). Key characteristics include:
- Provider Agnostic: Bring your preferred LLM provider, such as OpenAI or Anthropic.
- Flexible Deployment: Target standard Node.js environments or serverless edge platforms like Cloudflare Workers via
Flue.config. - Efficient Token Usage: Load skills conditionally based on agent decision-making to optimize context usage.
For the initial POC, we’ll skip the need for a database, vector database, RAG pipeline, PDF parser, or external job-search API. Just to keep it simple and easy.
Step 1: Initializing the Project
To get started, using your coding agent. Paste the prompt below:
Read https://flueframework.com/start.md then help create my first agent...
Your agent will guide you through setting up an agent in a new or existing project, and help answer any questions you might have along the way.
# Create and navigate to the project foldercd resume-matcher-agent
# Install dependenciespnpm iOpen the project in Code editor. The project structure contains:
flue.config: Defines deployment targets (e.g., standard Node.js or Cloudflare Workers).agents.md: Provides context about Flu for coding assistants working on agent code.src/agents/: Directory where agent definitions live.skills/: Directory storing reusable writing guidance.src/tools/: Directory containing external tool integrations.
Next, set up your LLM credentials (such as OPENAI_API_KEY or ANTHROPIC_API_KEY) in an environment file, ensuring API keys are kept out of source control. For cost effective demonstration we will use OpenRouter, under free plan.
Step 2: Configurinig OpenRouter
Create an .env file:
OPENROUTER_API_KEY=your_openrouter_api_keyOPENROUTER_MODEL=openrouter/freePORT=3000The important configuration is:
OPENROUTER_MODEL=openrouter/free
This allows the application to use OpenRouter’s free-model routing option.
Keeping the model name in an environment variable also means you can change the model later without modifying your agent code.
Tip: Using lightweight or cheaper models during initial testing speeds up iteration cycles before scaling to heavier production models.
Step 3: Defining the Resume Matcher Agent
In Flue, every exported capitalized function inside a useAgent module defines an agent. Create src/agents/resume-agent.ts (renamed from hello.ts) and configure the model and baseline system prompt:
export function ResumeAgent() { let modelSpec = process.env.OPENROUTER_MODEL || 'openrouter/free';
if (modelSpec === 'openrouter/free' || modelSpec === 'free') { modelSpec = 'openrouter/openrouter/free'; }
useModel(modelSpec);
return `...`;}Step 4: Guiding Tone and Style with Resume Skills
Skills in Flue are written as Markdown files (skill.md) with YAML frontmatter containing metadata like name and description. Unlike static system prompts, Flu agents dynamically pull in skills only when required, reducing token consumption.One of the useful concepts in Flue is the ability to separate specialized instructions into reusable skills.
Create:
skills/resume-writing/SKILL.md
Add:
---name: resume-writingdescription: Provides professional resume analysis and optimization guidance.---
# Resume Writing Guidelines
You are an expert professional resume writer.
## Accuracy
Never invent information.
Only use information supplied by the candidate.
Do not add skills, technologies, companies, job titles, certifications,education, achievements, or metrics that are not supported by the resume.
## Job Alignment
Prioritize experience and skills that are relevant to the target job description.
Identify important keywords from the job description and determine whetherthe candidate actually demonstrates them.
## ATS Optimization
Use relevant job-description keywords naturally when they are supportedby the candidate's actual experience.
Do not keyword-stuff the resume.
## Writing Style
Use:
- Clear professional language- Concise bullet points- Strong action verbs- Achievement-oriented language- Consistent formatting- Easy-to-scan sections
## Metrics
Preserve existing metrics from the candidate's resume.
Never create fictional metrics.
If a measurable achievement is not provided, rewrite the statement clearlywithout fabricating numbers.
## Missing Skills
If a job requirement is not demonstrated in the resume, identify it as:
- Missing- Weakly demonstrated- Unclear
Do not present a missing skill as an existing candidate skill.
The benefit of separating this logic into a skill is that you can reuse it later with other agents.
For example:
Resume Matcher ↓Resume Writing Skill
Cover Letter Agent ↓Resume Writing Skill
LinkedIn Profile Agent ↓Professional Writing SkillStep 5: Making it less AI with Unslop skill
The goal is to make generated writing sound more natural and less AI-like. By stripping away repetitive AI clichés, buzzwords, and overly marketing-focused prose, the Unslop skill ensures that every rewritten resume reads crisply and authentically. This helps candidate profiles pass both automated screening tools and human recruiter checks without raising red flags for generic AI generation.
Create:
skills/unslop/SKILL.md
Add:
---name: unslopdescription: Removes AI clichés and flowery fluff from text---
# Unslop Guidelines
## CRITICAL RULES
1. REMOVE CLICHÉS
Examples:- "delve into"- "it is important to note"- "seamlessly integrate"- "testament to"- "game-changer"- "cutting-edge"- "tapestry of"- "in today's fast-paced world"- "leverage synergies"
2. NO WEASEL WORDS
Remove:- "basically"- "actually"- "in order to"- "that being said"- "it should be noted"- "as you can see"
3. ELIMINATE FLUFF
Remove sentences that add no value:- "This demonstrates a strong understanding of..."- "The candidate possesses a solid foundation in..."- "Overall, the candidate has a good profile."
4. BE DIRECT
Write like a professional human, not an AI:- Short sentences- Active voice- Strong verbs- Clear meaning
5. NO METAPHORICAL NONSENSE
No:- "tapestry"- "rollercoaster"- "journey"- "landscape"
---
## OUTPUT REQUIREMENT
Rewrite the text in:- Normal professional tone- Clear direct sentences- Human-like style- No self-referential AI language- No clichés
If the input is already good, return it unchanged.Step 6: Assembling the Resume Agent
'use agent';import { useModel, useSkill, setProvider } from '@flue/runtime';import { openrouterProvider } from '@earendil-works/pi-ai/providers/openrouter';import resumeWritingSkill from '../../skills/resume-writing/SKILL.md';import unslopSkill from '../../skills/unslop/SKILL.md';
// Configure OpenRouter provider to resolve OPENROUTER_API_KEY directly from environmentconst baseOpenRouter = openrouterProvider();
setProvider({ ...baseOpenRouter, auth: { ...baseOpenRouter.auth, apiKey: { name: 'OpenRouter API key', resolve: async () => ({ auth: { apiKey: process.env.OPENROUTER_API_KEY || '' }, }), }, },} as any);
export function ResumeAgent() { let modelSpec = process.env.OPENROUTER_MODEL || 'openrouter/free';
// Map 'openrouter/free' or 'free' specifiers to 'openrouter/openrouter/free' // so provider is 'openrouter' and model ID matches 'openrouter/free' in OpenRouter catalog if (modelSpec === 'openrouter/free' || modelSpec === 'free') { modelSpec = 'openrouter/openrouter/free'; }
useModel(modelSpec); useSkill(resumeWritingSkill); useSkill(unslopSkill);
return `...`;}
ResumeAgent.agentName = 'ResumeAgent';Step 7: API Reference & Testing
POST /resume-agent
Accepts a JSON payload with resume and jobDescription strings and returns structured markdown analysis.
Sample Request (cURL)
curl -X POST http://localhost:3000/resume-agent \ -H "Content-Type: application/json" \ -d '{ "resume": "John Doe\n\nBackend Engineer\n\n6 years of experience building web applications and APIs.\n\nSkills:\nNode.js\nTypeScript\nMongoDB\nPostgreSQL\nAWS\nDocker\nREST APIs", "jobDescription": "Senior Backend Engineer\n\nRequirements:\n- 5+ years backend development\n- Node.js\n- TypeScript\n- PostgreSQL\n- AWS\n- REST APIs\n- Microservices\n- Docker" }'Response Structure
200 OK:{ "success": true, "result": "# Resume Analysis\n\n## 1. Job Requirements\n..." }400 Bad Request: Returned if required JSON parameters are missing.500 Internal Server Error: Returned in case of processing or upstream API failures.
Step 8: Running & Interacting with the Agent
Development: Run pnpm dev to start the local server on http://localhost:3000 Production: Run pnpm build && pnpm start
Flue CLI Direct Execution
You can also run the agent directly from your terminal without launching an HTTP server:
npx flue run src/agents/resume-agent.ts --message "Candidate Resume: Senior Backend Engineer with 6 years experience... Job Description: Looking for Node.js developer..."References
Here is a collection of reading materials you can refer to learn more about AI agents:
Conclusion
The initia POC provides a strong foundation for the Resume Matcher Agent, but it can be extended with several powerful capabilities. Future phases can introduce PDF and DOCX resume upload support, followed by the ability to generate downloadable optimized resumes in DOCX and PDF formats.
The agent can also be enhanced to create tailored cover letters, perform job description web research and scraping, using Tavily, and provide an automated job matching score based on the candidate’s qualifications and the job requirements and more.
Have a question, feedback, or simply want to build production-grade AI agents? Shoot me a DM and I’ll do my best to get back to you.
Thank you!
I am available for new projects. Let’s collaborate and build something great together.