A Node.js prototype of a two-agent pipeline built on Gemini function calling. It reads a resume, has one agent plan a portfolio page, creates a GitHub repository, has a second agent write a single-file HTML portfolio, and pushes the result to that repository.
Status: learning project exploring multi-agent orchestration. It has no automated tests.
- Input. The resume is read from
./resume.txtin the project root. - Planning agent (
src/agents/planning.js). The model reads the resume with areadFiletool and writes a markdown blueprint to./output/portfolio_blueprint.mdwith awriteFiletool. Its instructions focus the plan on backend engineering work. Temperature 0.2. - Repository setup (
src/tools/github_actions.js, plain code, no model involved). Creates a public repository namedautonomous-portfolio-<4 digits>through the GitHub API (Octokit) with an initial commit, then clones it into./workspace/<repo name>. - Coding agent (
src/agents/coding.js). The model reads the blueprint, writes a single-fileindex.htmlthat uses the Tailwind CSS CDN into the cloned workspace, then calls apushToGitHubtool. That tool runsgit add .,git commit,git branch -M mainandgit push -u origin main --force. Temperature 0.1. - Result. The script prints whether the run completed and the name of the new repository.
The run state is tracked by PipelineState in src/core/state.js, a small state machine with the steps IDLE, PLANNING, PLANNED, CODING, COMPLETED and FAILED. Errors are recorded with a timestamp and the step they happened in.
The split of work: models do the planning and the page writing. Creating and cloning the repository is plain code that runs between the two agents.
- Node.js 18 or newer
- Git installed, and able to push to GitHub over HTTPS from your machine. The code does not pass
GITHUB_TOKENto git, so push credentials must already be configured (for example through a credential helper). - A Google Gemini API key
- A GitHub token that can create repositories on your account (for example a classic personal access token with the
reposcope)
npm installCreate a .env file in the project root:
GEMINI_API_KEY=your_gemini_api_key
GITHUB_TOKEN=your_github_token
# Optional. Both default to gemini-2.5-flash in the code.
PLANNING_MODEL=
CODING_MODEL=Model names are retired over time. If the default model is not available to your key, set PLANNING_MODEL and CODING_MODEL. See Gemini API deprecations.
Put your resume as plain text in ./resume.txt. The .gitignore excludes *.txt, *.pdf and every .md file except README.md, so the resume and the generated blueprint are not committed.
npm startThe script exits immediately if GEMINI_API_KEY or GITHUB_TOKEN is missing.
The new repository is public. The pipeline does not turn on GitHub Pages, so to publish the page you enable Pages in the repository settings yourself.
index.js orchestrates the phases and prints the result
src/core/state.js PipelineState: run phases and error log
src/agents/planning.js planning agent: resume to markdown blueprint
src/agents/coding.js coding agent: blueprint to index.html, then push
src/tools/file_system.js readFile / writeFile tools and a resume parser
src/tools/github_actions.js create repository, clone, commit and push
- The resume path is fixed to
./resume.txt.parseResumeFileinfile_system.jscan read.txt,.mdand.pdffiles, but nothing calls it, so PDF resumes are not supported. - The
readFileandwriteFiletools accept any path. The resume text is passed to a model that can read and write files, so text inside a resume could steer the model to read or overwrite other files on the machine. - The run is marked
COMPLETEDwhen the coding agent stops calling tools. It does not check thatindex.htmlexists or that the push succeeded. - The model loops have no maximum number of iterations.
- The push uses
--force. That is only safe because the repository was just created. - The
workspace/folder, where repositories are cloned, is not listed in.gitignore. - No automated tests.
Planned change that turns the coding agent from a fixed sequence into a feedback loop, so the model's next action depends on a check it did not write itself.
The coding agent must not push a page that fails validation. After writing index.html, it validates the file, reads the errors, fixes them and validates again, until the file passes or the turn limit is reached.
- New tool
validateHtml(src/tools/validate.js, plain code, no model involved). It returns{ valid, errors }and combines:- generic HTML checks with the
html-validatepackage - project checks: the Tailwind CDN script is present, no placeholder text, a minimum page length, and at least three
<section>blocks from the blueprint - the error list is capped at 10 entries to keep the model's context small
- generic HTML checks with the
- Gate in code, not in the prompt. The coding agent loop keeps
state.validated. AnywriteFilecall resets it tofalse, and a passingvalidateHtmlcall sets it totrue.pushToGitHubreturns an error unlessstate.validatedistrue. - Turn limit. The coding loop stops after
MAX_TURNS(8) iterations. If the page was not validated and pushed by then, the run is markedFAILEDinstead ofCOMPLETED. - Prompt update. The coding agent's instructions tell it to write the file, call
validateHtml, fix every listed error and rewrite the whole file, and only callpushToGitHubafter a result ofvalid: true.
-
npm install html-validate - Add
src/tools/validate.jswithvalidateHtmland its declaration - Register the tool in
src/agents/coding.jsand add thestate.validatedgate - Add
MAX_TURNSto the coding loop, and to the planning loop (which also needs its undefinedturnvariable fixed) - Set
state.pushedafter a successful push, and markCOMPLETEDonly when it istrue - Update the coding agent's system prompt
- Log each validation result during testing to see how many fix rounds the model needs
- A page with a deliberately broken tag is rejected, fixed by the model and then pushed
pushToGitHubrefuses to run when the last write has not been validated- A run that never produces a valid page ends as
FAILEDwith the errors recorded