Setting Up Your Platform Hub Repo
This post walks through setting up the GitHub repository that powers the IssueOps policy authoring workflow for Octopus Deploy Platform Hub. By the end, you’ll have a repo wired up as the GitOps source of truth for your Platform Hub — storing OCL policy files, process templates, and project templates — with two AI agents ready to author and publish policies on demand.
This is the foundation PoC for the IssueOps theme. The posts on generating policies with IssueOps and authoring locally with Copilot both assume this setup is in place.
Prerequisites
- Octopus Deploy Platform Hub configured and connected to a GitHub repository via the Platform Hub version control settings
- GitHub repository with Actions enabled — this will become your platform-hub repo
- GitHub Copilot with agent mode enabled on your account, or access to Claude Code (you need at least one)
- Octopus MCP server configured if you intend to use Claude Code for local authoring (the Policy Author agent uses it for what-if analysis)
- Familiarity with Octopus Platform Hub concepts — this post doesn’t cover Platform Hub basics
How the repo is structured
Before creating anything, it’s worth understanding the directory layout and what each part does, so the setup decisions make sense.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
.octopus/
policies/ # OCL policy files — source of truth for Platform Hub
process-templates/ # Shared deployment process templates
project-templates/ # Reusable project templates
octopus-agent/
output-<timestamp>/
reports/ # What-if reports produced by the Policy Author agent
.claude/
agents/ # Agent definitions for Claude Code
.github/
agents/ # Agent definitions for GitHub Copilot
ISSUE_TEMPLATE/ # Issue template for submitting policy requests
policies-documentation/
schema.md # OCL policy schema reference
best-practices.md # Authoring guidelines
examples.md # Example policies
troubleshooting.md # Common issues and fixes
The .octopus/ directory is what Platform Hub reads. Everything else supports the authoring workflow. The octopus-agent/ directory accumulates what-if reports over time — it’s worth committing these alongside the OCL files so there’s a permanent record of the impact analysis that preceded each policy.
Phase 1: Connect the repo to Platform Hub
Platform Hub needs to know which GitHub repository holds your OCL files. This is configured under Platform Hub’s version control settings.
TODO: Screenshot — Platform Hub version control settings page, showing the GitHub repository field, default branch, and policies directory path filled in
Set the policies directory to .octopus/policies. Set the default branch to main. Platform Hub will read from this branch when publishing — which is why the Policy Publisher agent hard-stops if your OCL file hasn’t been pushed there yet.
Once connected, create the directory structure manually or let it be created by the first agent run. Either works, but creating the skeleton upfront avoids confusion on the first run.
1
2
3
4
5
6
7
8
mkdir -p .octopus/policies
mkdir -p .octopus/process-templates
mkdir -p .octopus/project-templates
mkdir -p octopus-agent
mkdir -p .claude/agents
mkdir -p .github/agents
mkdir -p .github/ISSUE_TEMPLATE
mkdir -p policies-documentation
Commit and push to main.
Phase 2: Add the agent definition files
The two agents — Policy Author and Policy Publisher — are defined as Markdown files that tell Copilot or Claude Code how to behave. These files are the same regardless of whether you’re using GitHub Copilot Cloud, Claude Code locally, or Copilot in VS Code/JetBrains. The invocation method differs; the agent logic doesn’t.
Policy Author agent
Create .github/agents/octopus-deploy-policy-author.agent.md and .claude/agents/octopus-deploy-policy-author.md with the Policy Author agent definition. The agent runs four phases in order:
- Intent parsing — maps plain-English intent to specific
input.*fields in the Platform Hub schema - OCL authoring — checks for existing policies that cover the same ground before writing a new file; derives a snake_case package name; writes the OCL in the correct format with 12-space Rego indentation
- What-if analysis — calls the Octopus MCP server to resolve projects, fetch deployment processes, and evaluate each project statically against the authored policy
- Report generation — writes the what-if report to
octopus-agent/output-<timestamp>/reports/<package_name>.mdand commits the OCL file
The key constraints baked into the agent definition:
- Never uses
input.*fields that aren’t in the Platform Hub schema - Never combines two independent policy conditions into one OCL file
- Forces
violation_action = "warn"when scope covers more than one project - Never deletes OCL files — retires by setting
default evaluate := falsein scope
TODO: Code block or link — Add the full agent definition file here, or link to the file in the repo once it’s public. The definition is long (~400 lines) and is better read in context than reproduced inline.
Policy Publisher agent
Create .github/agents/octopus-deploy-policy-publish.agent.md and .claude/agents/octopus-deploy-policy-publish.md with the Policy Publisher agent definition.
The publisher runs these pre-flight checks before making any API calls:
- OCL file exists at
.octopus/policies/<slug>.ocl - Policy is not fully disabled (scope block has an
evaluate ifoverride) - File is committed, not just staged
- Commit has been pushed to the remote default branch — hard stop if not
Then it publishes in three steps: resolve the current version via GET /api/platformhub/policies/<slug>/versions/v2, increment the patch version, publish via POST /api/platformhub/main/policies/<slug>/publish, and activate via POST /api/platformhub/policies/<slug>/versions/<version>/modify-status.
The hard stop on unpushed commits is intentional and important. The Octopus publish API reads from the remote default branch, not your local files. If you run the publisher before pushing, it silently publishes the previous committed version — the policy activates but enforces the wrong content. The pre-flight check makes this failure mode impossible rather than just unlikely.
TODO: Code block or link — Add the full publisher agent definition here, or link to the repo.
Phase 3: Add the issue template
The GitHub issue template is what makes the IssueOps flow work — it structures the request so the Policy Author agent always receives the inputs it needs without having to ask clarifying questions.
Create .github/ISSUE_TEMPLATE/octopus-deploy-add-policy.yaml:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
name: 🐙 Add Octopus Deploy Policy
description: Add an Octopus Deploy platform hub policy
title: "[OD-Policy] <title>"
labels: ["🐙 policy", "🤖 copilot"]
body:
- type: textarea
id: policy-prompt
attributes:
label: Describe the policy
description: >
Please enter the policy you would like created. Policies are best when
they are direct — do not include multiple policies in one request.
Break those out to separate issues.
validations:
required: true
- type: input
id: projects-list
attributes:
label: Project(s) in scope
description: >
Enter the Octopus Deploy project in the format `project-slug` OR
`space/project-slug`. Enter multiple as comma-separated values.
placeholder: Default/project-one,SecondSpace/project-two
validations:
required: true
The two labels — 🐙 policy and 🤖 copilot — are what trigger the Copilot Coding Agent automatically. Without both labels, the issue is created but Copilot doesn’t pick it up.
TODO: Screenshot — GitHub repository issue templates page showing the
🐙 Add Octopus Deploy Policytemplate in the list
Phase 4: Add the policies documentation
The Policy Author agent uses the policies documentation as context when generating Rego. Without it, the agent produces syntactically valid OCL that may reference incorrect field names or miss Platform Hub-specific conventions.
The documentation lives in policies-documentation/ and covers:
schema.md— everyinput.*field the policy engine exposes, with types and conditional presence notesbest-practices.md— naming conventions, the warn-before-block principle, existence-and-skipping checksexamples.md— ready-to-use Rego organized by enforcement goal (required steps, production controls, process structure, tagging standards)troubleshooting.md— common evaluation problems and how to diagnose them
TODO: Note — The documentation files are sourced from the Octopus Platform Hub docs. Add links to the canonical docs pages here, or copy the content into the repo files if you want the agent to work offline or against a private Octopus instance.
The quality of the agent’s output is directly proportional to the quality of the documentation it has access to. The schema and examples files matter most — the schema tells it which fields exist, and the examples show it the patterns to follow.
Phase 5: Seed the repo with your first OCL file
Before the agents have run, the .octopus/policies/ directory is empty. The Policy Author agent checks for existing policies before creating new ones — if the directory is empty, it always creates a new file. Once you have a few policies in place, it will correctly identify cases where extending an existing policy scope is better than creating a duplicate.
A good first policy to add manually is a broad warn-mode check that gives you confidence the pipeline is working before you write stricter rules. For example, a policy that warns whenever a deployment to production happens without any manual intervention step, scoped to a single low-risk project:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
name = "Deploy - Require Manual Intervention for Production"
description = "Enforces that deployments to Production include at least one enabled manual intervention step."
violation_action = "warn"
violation_reason = "A manual intervention step is required for deployments to Production but is missing from the deployment process."
conditions {
rego = <<-EOT
package require_manual_intervention_production
default result := {"allowed": false}
targeting_production if {
input.Environment.Slug == "production"
}
has_manual_intervention if {
some step in input.Steps
step.ActionType == "Octopus.Manual"
step.Enabled == true
}
result := {"allowed": true} if {
not targeting_production
}
result := {"allowed": true} if {
targeting_production
has_manual_intervention
}
result := {
"allowed": false,
"reason": "Deployment to Production requires a manual intervention step. Add an enabled Octopus.Manual step to the deployment process.",
} if {
targeting_production
not has_manual_intervention
}
EOT
}
scope {
rego = <<-EOT
package require_manual_intervention_production
default evaluate := false
evaluate if {
input.Project.Slug == "your-project-slug"
not input.Runbook
}
EOT
}
Replace your-project-slug with a real slug from your Octopus instance. Commit and push this to main, then use the Policy Publisher agent to activate it. Seeing a policy publish successfully end-to-end confirms the repo-to-Platform Hub connection is working before you introduce the full IssueOps flow.
TODO: Screenshot — Platform Hub policies list showing the manually seeded policy in an active state after the first publisher run
Known limitations / what I’d do differently
The agent documentation context is manual to maintain. The policies-documentation/ files are copies of the Octopus docs at a point in time. When the Platform Hub schema evolves — new input.* fields, new action types, changes to the OCL format — the agent’s context goes stale silently. A better approach would be to fetch from the live docs at agent runtime, or set up a scheduled sync. For now, treating these files as versioned documentation with explicit update dates is the minimum viable approach.
Branch protection rules interact with the IssueOps flow. If your repo has branch protection requiring PR reviews before merging to main, the Copilot Coding Agent will open a PR but someone still needs to approve it before the publisher can run. That’s the intended governance model — but it means the fully automated end-to-end path (issue opened → policy activated, no human touch) isn’t achievable with branch protection in place. This is a feature, not a bug, but it’s worth being explicit about when setting expectations with stakeholders.
One policy per OCL file is a hard constraint. The agent enforces this, but it means your policies directory grows quickly if you have many narrow rules. This is a good thing for auditability and independent versioning, but it can feel like overhead when you’re managing dozens of files. A naming convention and periodic review of whether policies can be consolidated helps.
The Octopus MCP server must be running for what-if analysis. The Policy Author agent’s what-if phase calls the Octopus MCP server to fetch live project and deployment process data. If the MCP server isn’t connected when the agent runs, it will skip the what-if analysis and produce only the OCL file — noting the omission in its output. Always verify MCP connectivity before triggering the author agent on consequential policies.