Junction is where your infrastructure repo and your coding agent meet. One command turns any
repo into an agent-operated repo: answer a short set of junction questions (or accept the
defaults) and get a schema-validated six-agent graph scaffolded for your coding agent of
choice — GitHub Copilot, Claude Code, Cursor, Continue.dev, or anything that reads
AGENTS.md.
discovery answers ──► agent-graph.yml (validated) ──► 6 agent files + skills + guardrails + knowledge + MCP wiring
Instead of dropping one general-purpose assistant into your infra repo and hoping it stays in its lane, Junction gives you a graph of specialists — each with its own zone, tools, model and risk gate — plus the guardrails, knowledge base and phased build plan that keep them from drifting.
| Agent | Role | Access | Risk gate | Default model |
|---|---|---|---|---|
orchestrator |
Dispatch + context assembly | read-only | none | claude-opus-5 (planner) |
iac-validator |
Correctness gate | read-only | none | claude-sonnet-5 (validator) |
terraform-builder |
IaC code generation | read-write | MEDIUM | claude-opus-5 (builder) |
sre-observer |
Post-deploy health + triage | read-only | none | claude-sonnet-5 (observer) |
knowledge-curator |
OKF knowledge maintenance | read-write (docs) | LOW | claude-sonnet-5 (curator) |
migration-executor |
V1 → V2 state machine | read-write | HIGH | claude-opus-5 (planner) |
New to the idea? Start with the Graph Agent Engineering Wiki — it covers the bootstrap idea, the agent graph, the discovery engine, governance, and the production learnings baked into the defaults.
Requires Python 3.11+.
# From GitHub (works today)
pip install "git+https://github.com/egen/junction"
# From a local clone
git clone https://github.com/egen/junction
cd junction && pip install .
# From a built wheel
python -m build && pip install dist/junction-*.whl
# Once published to PyPI
pip install junction-sdk # or: pipx install junction-sdkThis installs the junction command (the PyPI distribution is named junction-sdk since junction was already taken; the CLI and the importable package are both still junction). python -m junction does the same thing.
# End-to-end, interactive: 17 junction questions, phase by phase
junction --discover --target ./my-infra-repo
# End-to-end, no prompts (CI or a demo): defaults for everything not preset
junction --discover --defaults \
--agent claude-code --domain payments \
--target ./my-infra-repo --yes
# Let your local coding agent look at an existing repo and propose answers first
junction --discover-with-agent --agent claude-code --target ./existing-repo
# Replay a saved answers file (every run writes one)
junction --answers .github/config/discovery-answers.yml --target ./another-repo
# Validate a graph after hand edits
junction --validate-graph ./my-infra-repo/.github/config/agent-graph.ymlThe run prints every decision, the phased plan, and the validated agent graph. Then it writes the files.
| Flag | Description | Default |
|---|---|---|
--discover |
Run the discovery engine end to end: answers → agent graph → scaffold | off |
--answers FILE |
YAML/JSON answers to preset (implies --discover). Unanswered questions are asked |
none |
--defaults |
Take the default for every question not preset (implies --discover) |
off |
--discover-with-agent |
Experimental: before asking, have your local claude CLI (headless, read-only) inspect --target and propose answers for what it can infer, so there's less to answer by hand. Requires the claude binary on PATH and an existing --target repo. Implies --discover |
off |
--domain NAME |
Business domain used in resource names (presets domain_name) |
prompted |
--services a,b,... |
Comma-separated service names to generate Terraform for (GCP only). Default: one service named after --domain |
one service |
--model TIER=MODEL |
Override a model tier (planner, builder, validator, observer, curator). Repeatable |
see table |
--validate-graph FILE |
Validate an agent-graph.yml against schema v2.0 and the safety invariants, then exit |
— |
--agent |
copilot | claude-code | cursor | continue | agnostic |
prompted |
--cloud |
aws | gcp | azure | multi |
prompted |
--iac |
terraform | opentofu | pulumi | cdk | cloudformation |
prompted |
--config FILE |
Pre-filled platform.yml (environments, accounts, MCP backends) |
none |
--target DIR |
Target repo root | . |
--overwrite |
Replace existing files | off |
--dry-run |
List files without writing | off |
--yes, -y |
Don't prompt to create the target directory (existing files still need --overwrite) |
off |
--generate-iac-with-agent |
Experimental: after scaffolding, drive the scaffolded terraform-builder subagent itself (via the claude CLI) to generate infra/, instead of the deterministic template. Requires --agent claude-code, claude/terraform on PATH, and real API usage |
off |
--version |
Print the version | — |
Without --discover, the classic platform wizard (or --config / --agent --cloud --iac) runs, and the default six-agent graph is generated for that platform. You always get all six agents.
Any subset of question ids, plus optional environments (full records with account ids) and models:
agent: claude-code
domain_name: payments
env_names: dev,staging,prod
jira_required: yes
has_msk: yes
models:
validator: claude-haiku-4-5
environments: # optional: overrides env_names with real accounts
- {name: dev, branch: dev, account_id: "111111111111", risk_tier: LOW}
- {name: staging, branch: stg, account_id: "222222222222", risk_tier: MEDIUM}
- {name: prod, branch: main, account_id: "333333333333", risk_tier: HIGH}A full example is in examples/discovery-answers.example.yml.
For every agent:
.github/
├── config/
│ ├── agent-graph.yml # six nodes, edges, gates, failure policy, evals (schema v2.0)
│ ├── implementation-plan.md # 7 gated phases + the decisions you made
│ ├── discovery-answers.yml # replayable answers (discovery runs)
│ └── platform.yml # environments, compute, MCP backends
├── skills/_core/ # self-healing-deploy, env-promotion, drift-detection, post-push-monitor,
│ # knowledge-curation, v1-v2-migration
├── skills/_triage/ # iac-triage, sre-log-fetching
├── skills/_domain/_TEMPLATE.md
├── instructions/ # iac-standards, compute-patterns, prod-guardrails
├── knowledge/ # service-registry, deployment-policies (OKF)
├── mcp-servers/README.md # MCP interface contracts
├── copilot-instructions.md
└── .env-private.example
.vscode/mcp.json
Plus the six agents in the chosen agent's native format:
--agent |
Agent files | Extras |
|---|---|---|
copilot |
.github/agents/<agent>.agent.md ×6: tools, agents, and graph edges as handoffs |
— |
claude-code |
.claude/agents/<agent>.md ×6 subagents: tools, model alias, disallowedTools on read-only agents; the orchestrator gets Agent(...) |
CLAUDE.md, /deploy, /promote, /drift-check, /sre-triage, /health-check, /migrate |
cursor |
.cursor/rules/<agent>.mdc ×6 (orchestrator always applied) |
iac-standards.mdc |
continue |
.continue/rules/<agent>.md ×6 |
config.json slash commands, context file |
agnostic |
.agents/<agent>.md ×6 |
AGENTS.md |
For --cloud gcp, junction/terraform_generator.py turns the same discovery answers into real,
terraform validate-clean HCL under infra/:
infra/
├── versions.tf / providers.tf / variables.tf / locals.tf
├── store-firestore.tf / store-pubsub.tf / store-gcs.tf / store-secret.tf # only if selected
├── platform-run.tf # service accounts, IAM bindings, Cloud Run (for_each var.services)
├── service-<name>.tf # one per service, outputs only (url, secret id if any)
└── tfvars/<env>.tfvars # one per environment
Services scale up with for_each over a services map, never count. Use --services api,worker,...
to generate more than the single default service; each gets independent image/cpu/memory/replica/public/secret
settings in the generated tfvars. Public exposure (allUsers on Cloud Run) is opt-in per service, and IAM
grants are scoped to specific resources, never project-wide roles/editor/roles/owner.
AWS and Azure don't generate Terraform yet — generate_terraform() returns None for those clouds so the
gap stays visible rather than silently producing nothing.
The generator above is a deterministic Python template — fast, free, and reliably valid, but not actually
driven by the terraform-builder agent's own persona or context. --generate-iac-with-agent is the real
alternative: it skips the template and shells out to the claude CLI in headless mode
(claude -p --agent terraform-builder ...), running it inside the freshly scaffolded repo so it's grounded
in platform.yml, service-registry.md, and the iac-standards/compute-patterns/prod-guardrails
instructions the same way an interactive session would be. It loops terraform init/validate, feeding
errors back to the agent, up to 3 attempts.
This is opt-in and not the default: it requires --agent claude-code (the only adapter with a headless CLI
this module can drive today), the claude and terraform binaries on PATH, real API usage per bootstrap
run, and produces non-deterministic output — unlike the template, two runs on the same input can generate
different, though both potentially valid, Terraform. See junction/agent_iac_generator.py.
agent-graph.yml is validated every time it is generated or written, and again on demand with --validate-graph. Validation uses JSON Schema (junction/schemas/agent-graph.schema.json) plus safety invariants the schema can't express:
- all six nodes are present, and the orchestrator can route to each of them
iac-validatorandsre-observerhave no edit/write toolsmigration-executorisHIGHrisk withretry: 0- the
terraform-builder → iac-validator"must pass" gate exists, and the fix loop hasmax_cycles - the Jira gate edge and
governance.jira_gate.enabledagree - token budgets are ordered: warn < hard ≤ work-graph hard
An invalid graph is never written. The CLI exits 1 and lists every violation.
See Agent Graph and Governance, Safety and Evals for the full model.
Reasoning-heavy tiers default to claude-opus-5, and read-only tiers to claude-sonnet-5. Override any tier with --model, models: in an answers file, or generate_agent_graph(answers, models={...}). Claude Code subagents get the family alias (opus / sonnet / haiku), so they keep tracking the latest model.
from junction import Bootstrap
# End-to-end from discovery answers (missing answers take defaults)
b = Bootstrap.from_discovery(
{"agent": "claude-code", "domain_name": "payments", "env_names": "dev,staging,prod"},
target_dir="./my-infra-repo",
models={"validator": "claude-haiku-4-5"},
)
b.graph # the validated agent graph (dict)
b.validate() # paths that already exist
b.dry_run() # {path: content}, no writes
b.run() # write everything, return the paths writtenfrom junction import Bootstrap, Platform, generate_agent_graph, validate_agent_graph
platform = Platform.from_yaml("./my-platform.yml", agent="cursor")
graph = generate_agent_graph({"domain_name": "savings", "jira_required": False})
validate_agent_graph(graph) # raises AgentGraphValidationError
Bootstrap(platform, target_dir="./repo", graph=graph).run()from junction import run_discovery, print_implementation_plan
result = run_discovery(prefilled={"agent": "copilot"}, use_defaults=False) # interactive
print_implementation_plan(result)| API | Description |
|---|---|
Bootstrap(platform, target_dir=".", overwrite=False, graph=None, answers=None) |
Scaffolder. Generates the default graph if graph is omitted |
Bootstrap.from_discovery(answers, target_dir, overwrite, models) |
Answers → platform + graph → scaffolder |
.validate() / .dry_run() / .run() / .graph / .answers |
Conflicts, preview, write, graph, answers |
Platform.from_answers(dict) / Platform.from_yaml(path, agent) |
Platform config |
run_discovery(prefilled=None, use_defaults=False) |
Junction questions → DiscoveryResult(answers, phase_plan) |
generate_agent_graph(answers, models=None) |
Validated graph dict |
write_agent_graph(graph, path) |
Validate + write YAML |
validate_agent_graph(graph) |
Raises AgentGraphValidationError (.errors lists every violation) |
# .github/workflows/bootstrap.yml
name: Bootstrap IaC Agent Graph
on:
workflow_dispatch:
inputs:
agent:
type: choice
default: copilot
options: [copilot, claude-code, cursor, continue, agnostic]
jobs:
bootstrap:
runs-on: ubuntu-latest
permissions: {contents: write}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: {python-version: "3.12"}
- run: pip install "git+https://github.com/egen/junction"
- run: |
junction --answers .github/config/discovery-answers.yml \
--agent ${{ inputs.agent }} --target . --overwrite --yes
- run: junction --validate-graph .github/config/agent-graph.yml
- run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add . && git commit -m "chore: scaffold six-agent graph (${{ inputs.agent }})" || echo "No changes"
git pushThe Graph Agent Engineering Wiki is the deep-dive companion to this README:
| If you want to… | Read |
|---|---|
| Understand the idea and why it's a graph | The Bootstrap Idea |
See the six agents, their edges and the agent-graph.yml schema |
Agent Graph |
| Know which questions the bootstrap asks, and why then | Discovery Engine |
| Follow the build from empty repo to first agent-built service | Implementation Phases |
| Review risk tiers, gates, failure policy and token budgets | Governance, Safety and Evals |
| Learn the anti-patterns the framework prevents | Production Learnings |
| Install and run it, step by step | Getting Started |
| Map the idea to files in this repo | Architecture and Repo Map |
| See what's done and what's still open | Roadmap and Open Items |
pip install -e ".[dev]"
pytest # unit + end-to-end CLI tests
python scripts/sync_framework.py # after editing .github/ skills/instructions/knowledge
python -m build # sdist + wheel in dist/
./scripts/local-e2e-check.sh # optional: discovery → graph → agents → real `terraform validate` (needs terraform)The skills, instructions and knowledge templates that junction copies are shipped inside the package (junction/framework/), so a pip install works without a repo checkout. scripts/sync_framework.py mirrors them from .github/, and a test fails if the two copies drift.
To add an adapter: subclass AgentAdapter, implement generate_files(platform, graph=None) using self.agents(platform, graph) (the six specs with rendered bodies), register it in junction/agent_adapters/__init__.py, and add it to AGENT_CHOICES.
See CONTRIBUTING.md. Bug reports, new coding-agent adapters, and new domain skills are especially welcome.
See CODE_OF_CONDUCT.md.