Getting started with agentic initiatives
This guide walks you through building the pattern described in Agentic initiatives: one entity represents a fix effort against a failing scorecard rule, and Port fans a coding agent out across every service that rule currently fails.
By the end, a platform engineer will be able to approve a Draft fix effort and watch Port create one tracked run per affected service, launch a Cursor agent against each repository, and roll every result back up into a single progress view.
Or implement it in one shot with your coding assistant


Connect Port MCP to your coding assistant, then copy the prompt below to implement this entire guide automatically against your Port account.
I want to set up a fleet-wide scorecard remediation pattern in Port.
Here is what I need you to do, step by step:
1. Create a blueprint with identifier "agentic_initiative", title "Agentic Initiative", icon "Automation":
- description (markdown): what this initiative fixes
- prompt (markdown): the instruction given to the coding agent for every target
- approval_policy (string, enum: auto-merge, owner-approval, pe-approval)
- status (string, enum: Draft, Running, Completed, Cancelled)
- target_blueprint (string): the blueprint identifier the targets belong to
- reference_context (array): supporting links or entity identifiers for the agent
- target_entities (array): JSON objects of {serviceIdentifier, serviceTitle, repoUrl}, populated before the run begins
Add aggregation properties targeting "run_target" entities, counting by status: total_targets (count all), targets_pending, targets_running, targets_pr_opened, targets_merged, targets_failed.
2. Create a blueprint with identifier "run_target", title "Run Target", icon "Branch":
- target_blueprint, target_identifier, target_title (strings)
- target_url (string, url)
- status (string, enum: Pending, Running, PR Opened, Merged, Failed, Skipped)
- pr_url (string, url)
- pr_ci_status (string, enum: pending, passing, failing)
- error_message (string)
- agent_log_url (string, url)
- team_notified (boolean)
Add relations: service (target "service", not required, not many), pull_request (target "githubPullRequest", not required, not many), agentic_initiative (target "agentic_initiative", not required, not many).
3. Create a Port workflow with identifier "agentic_initiative_executor", title "Kickoff initiative fixer agents":
- trigger (EVENT_TRIGGER): fires on ENTITY_UPDATED for agentic_initiative where status changed from Draft to Running.
- bulk_create_targets (WEBHOOK): POSTs to the run_target bulk entities endpoint with upsert=true, building one target entity per object in the initiative's target_entities array with status Pending.
- start_targets (WEBHOOK): bulk-upserts the same targets with status Running so the per-target executor's Pending-to-Running trigger fires reliably.
Connections: trigger to bulk_create_targets to start_targets.
4. Create a Port workflow with identifier "agentic_target_executor", title "Execute task on a single service":
- manual_trigger (SELF_SERVE_TRIGGER) on the service blueprint, with a required "prompt" text input and the triggering service as context.
- fetch_service (WEBHOOK, GET): reads the service entity to get its title, repo URL, and identifier.
- create_run_target (UPSERT_ENTITY): creates a run_target entity with status Running for the manual path.
- trigger (EVENT_TRIGGER): fires on ENTITY_UPDATED for run_target where status changed from Pending to Running.
- fetch_agentic_initiative (WEBHOOK, GET): reads the parent initiative to get its prompt, when the run came from the fan-out path.
- cursor_agent (CURSOR_AGENT): launches a Cursor agent using {{ .secrets["CURSOR_API_KEY"] }}, using the initiative prompt or the manual prompt, targeting the run target's repository, with autoCreatePr true.
- check_success (CONDITION): branches on whether the Cursor agent finished successfully.
- update_target_pr (UPSERT_ENTITY, success branch): writes pr_url and status "PR Opened" back to the run_target.
- update_target_failed (UPSERT_ENTITY, failure branch): writes status "Failed" and an error_message back to the run_target.
- link_pr_entity (AI): looks up the matching githubPullRequest entity by PR link and sets the run_target's pull_request relation.
Connections: both trigger paths converge on cursor_agent, which flows to check_success, which branches to update_target_pr (then link_pr_entity) or update_target_failed.
5. Create a Port workflow with identifier "begin_agentic_initiative", title "Begin Agentic Initiative":
- trigger (SELF_SERVE_TRIGGER): scoped to agentic_initiative entities where status equals Draft, with an ENTITY context on the initiative input so it appears in the bolt menu.
- start_initiative (UPSERT_ENTITY): sets the initiative status to Running.
Connection: trigger to start_initiative.
6. Create a Port workflow with identifier "sync_run_target_pr_merged", title "Sync PR merge status to run targets":
- trigger (EVENT_TRIGGER): fires on ENTITY_UPDATED for githubPullRequest where status changed to merged.
- find_run_target (WEBHOOK, POST search): finds run_target entities related to the merged pull request, falling back to a pr_url match if the relation isn't set yet.
- mark_merged (UPSERT_ENTITY): sets the related run_target status to Merged.
Connections: trigger to find_run_target to mark_merged.
7. Add the required secret in Port Settings > Secrets:
- CURSOR_API_KEY: the Cursor API key.
Use Port MCP to create each artifact. Confirm each step before moving to the next.
Prerequisites
- A Port account.
- Port's GitHub Ocean integration installed, with
serviceentities carrying arepo_urlproperty (mirrored from the repository). - A scorecard on the
serviceblueprint with at least one rule a coding agent can actually fix, such as a missing CODEOWNERS file or a missing PR template. See Ensure production readiness if you don't have one yet. - A Cursor API key.
This guide uses Cursor as the coding agent and GitHub as the source of repositories. The pattern is the same with any coding agent that accepts a prompt and a repository, and any Git provider Port ingests.
Data model
Two blueprints carry this pattern:
agentic_initiative- one entity per fix effort. Holds the prompt given to the agent, the approval policy, and the list of target services. Its aggregation properties (total_targets,targets_pending,targets_running,targets_pr_opened,targets_merged,targets_failed) count the relatedrun_targetentities by status, so the initiative always shows live progress without a separate dashboard build.run_target- one entity per service touched by an initiative. Tracks that single service's run: status, the pull request it opened, CI result, and a link back to its parent initiative.
agentic_initiative blueprint (click to expand)
{
"identifier": "agentic_initiative",
"title": "Agentic Initiative",
"icon": "Automation",
"schema": {
"properties": {
"description": {
"title": "Description",
"type": "string",
"format": "markdown"
},
"prompt": {
"title": "Agent Prompt",
"type": "string",
"format": "markdown"
},
"approval_policy": {
"title": "Approval Policy",
"type": "string",
"enum": ["auto-merge", "owner-approval", "pe-approval"],
"enumColors": {
"auto-merge": "yellow",
"owner-approval": "green",
"pe-approval": "blue"
}
},
"status": {
"title": "Status",
"type": "string",
"enum": ["Draft", "Running", "Completed", "Cancelled"],
"enumColors": {
"Draft": "lightGray",
"Running": "orange",
"Completed": "green",
"Cancelled": "red"
}
},
"target_blueprint": {
"title": "Target Blueprint",
"type": "string"
},
"reference_context": {
"title": "Reference Context",
"type": "array"
},
"target_entities": {
"title": "Target Entities",
"description": "JSON array of {serviceIdentifier, serviceTitle, repoUrl} objects, populated by Port AI before the run begins",
"type": "array"
}
},
"required": []
},
"aggregationProperties": {
"total_targets": {
"title": "Total Targets",
"type": "number",
"target": "run_target",
"calculationSpec": { "calculationBy": "entities", "func": "count" }
},
"targets_pending": {
"title": "Pending",
"type": "number",
"target": "run_target",
"query": {
"combinator": "and",
"rules": [{ "property": "status", "operator": "=", "value": "Pending" }]
},
"calculationSpec": { "calculationBy": "entities", "func": "count" }
},
"targets_running": {
"title": "Running",
"type": "number",
"target": "run_target",
"query": {
"combinator": "and",
"rules": [{ "property": "status", "operator": "=", "value": "Running" }]
},
"calculationSpec": { "calculationBy": "entities", "func": "count" }
},
"targets_pr_opened": {
"title": "PR Opened",
"type": "number",
"target": "run_target",
"query": {
"combinator": "and",
"rules": [{ "property": "status", "operator": "=", "value": "PR Opened" }]
},
"calculationSpec": { "calculationBy": "entities", "func": "count" }
},
"targets_merged": {
"title": "Merged",
"type": "number",
"target": "run_target",
"query": {
"combinator": "and",
"rules": [{ "property": "status", "operator": "=", "value": "Merged" }]
},
"calculationSpec": { "calculationBy": "entities", "func": "count" }
},
"targets_failed": {
"title": "Failed",
"type": "number",
"target": "run_target",
"query": {
"combinator": "and",
"rules": [{ "property": "status", "operator": "=", "value": "Failed" }]
},
"calculationSpec": { "calculationBy": "entities", "func": "count" }
}
},
"relations": {}
}
run_target blueprint (click to expand)
{
"identifier": "run_target",
"title": "Run Target",
"icon": "Branch",
"schema": {
"properties": {
"target_blueprint": {
"title": "Target Blueprint",
"type": "string"
},
"target_identifier": {
"title": "Target Identifier",
"type": "string"
},
"target_title": {
"title": "Target Title",
"type": "string"
},
"target_url": {
"title": "Target URL",
"type": "string",
"format": "url"
},
"status": {
"title": "Status",
"type": "string",
"enum": ["Pending", "Running", "PR Opened", "Merged", "Failed", "Skipped"],
"enumColors": {
"Pending": "lightGray",
"Running": "orange",
"PR Opened": "blue",
"Merged": "green",
"Failed": "red",
"Skipped": "darkGray"
}
},
"pr_url": {
"title": "PR URL",
"type": "string",
"format": "url"
},
"pr_ci_status": {
"title": "CI Status",
"type": "string",
"enum": ["pending", "passing", "failing"],
"enumColors": {
"pending": "yellow",
"passing": "green",
"failing": "red"
}
},
"error_message": {
"title": "Error Message",
"type": "string"
},
"agent_log_url": {
"title": "Agent Log URL",
"type": "string",
"format": "url"
},
"team_notified": {
"title": "Team Notified",
"type": "boolean"
}
},
"required": []
},
"relations": {
"service": {
"title": "Service",
"target": "service",
"required": false,
"many": false
},
"pull_request": {
"title": "Pull Request",
"target": "githubPullRequest",
"required": false,
"many": false
},
"agentic_initiative": {
"title": "Agentic Initiative",
"target": "agentic_initiative",
"required": false,
"many": false
}
}
}
Build it step by step
- Go to the Data model page in Port.
- Click + Blueprint, then
{...} Edit JSON, and paste theagentic_initiativeblueprint from Data model above. - Click Create.
- Repeat for the
run_targetblueprint.
The run_target blueprint's relations point back to service, githubPullRequest, and agentic_initiative. Create it after agentic_initiative exists, since the relation target must already be in your data model.
This workflow does two things when an initiative moves from Draft to Running: it bulk-creates a run_target entity for every service listed in that initiative's target_entities property, then flips each target from Pending to Running so the executor workflow in step 3 starts reliably.
- Go to the workflows page in Port.
- Click + Workflow.
- Fill out the Create new workflow form, then click Confirm.
- Click the
{...}button to open the JSON editor and paste the following JSON:
Kickoff workflow JSON (click to expand)
{
"identifier": "agentic_initiative_executor",
"title": "Kickoff initiative fixer agents",
"icon": "Agent",
"description": "Triggered when an Agentic Initiative status changes Draft to Running. Bulk-creates run_target entities for each confirmed service, then starts them.",
"nodes": [
{
"identifier": "trigger",
"config": {
"type": "EVENT_TRIGGER",
"event": {
"type": "ENTITY_UPDATED",
"blueprintIdentifier": "agentic_initiative"
},
"condition": {
"type": "JQ",
"combinator": "and",
"expressions": [
".diff.after.properties.status == \"Running\"",
".diff.before.properties.status == \"Draft\""
]
}
},
"variables": {},
"verbose": false
},
{
"identifier": "bulk_create_targets",
"title": "Bulk create Run Targets",
"icon": "Microservice",
"config": {
"type": "WEBHOOK",
"url": "https://api.port.io/v1/blueprints/run_target/entities/bulk?upsert=true&create_missing_related_entities=false",
"method": "POST",
"body": {
"entities": "{{ .outputs[\"trigger\"].diff.after as $init | $init.properties.target_entities | map({identifier: (\"target-\" + .serviceIdentifier + \"-\" + $init.identifier), title: .serviceTitle, properties: ({target_blueprint: \"service\"} + {target_identifier: .serviceIdentifier} + {target_title: .serviceTitle} + {target_url: .repoUrl} + {status: \"Pending\"}), relations: ({service: .serviceIdentifier} + {agentic_initiative: $init.identifier}) }) }}"
},
"onTimeout": "fail",
"onFailure": "terminate"
},
"variables": {},
"verbose": false
},
{
"identifier": "start_targets",
"title": "Start run targets",
"icon": "Play",
"config": {
"type": "WEBHOOK",
"url": "https://api.port.io/v1/blueprints/run_target/entities/bulk?upsert=true&create_missing_related_entities=false",
"method": "POST",
"body": {
"entities": "{{ .outputs[\"trigger\"].diff.after as $init | $init.properties.target_entities | map({identifier: (\"target-\" + .serviceIdentifier + \"-\" + $init.identifier), title: .serviceTitle, properties: ({target_blueprint: \"service\"} + {target_identifier: .serviceIdentifier} + {target_title: .serviceTitle} + {target_url: .repoUrl} + {status: \"Running\"}), relations: ({service: .serviceIdentifier} + {agentic_initiative: $init.identifier}) }) }}"
},
"onTimeout": "fail",
"onFailure": "terminate"
},
"variables": {},
"verbose": false
}
],
"connections": [
{ "sourceIdentifier": "trigger", "targetIdentifier": "bulk_create_targets" },
{ "sourceIdentifier": "bulk_create_targets", "targetIdentifier": "start_targets" }
]
}
- Click Apply changes.
The per-target workflow in step 3 listens for run_target entities moving from Pending to Running. Bulk-creating targets with status Running in a single step often records creation without that transition, so the executor never starts.
This workflow creates each target as Pending, then immediately bulk-upserts them to Running. That update emits ENTITY_UPDATED events the executor workflow relies on.
This workflow does the actual fix. It runs once per run_target, whether that target came from the fan-out in step 2 or from a platform engineer triggering a fix on a single service manually.
- Go to the workflows page in Port.
- Click + Workflow
- Fill out the Create new workflow form, then click Confirm.
- Click the
{...}button to open the JSON editor and paste the following JSON:
Per-target executor workflow JSON (click to expand)
{
"identifier": "agentic_target_executor",
"title": "Execute task on a single service",
"icon": "Branch",
"description": "Triggered when a Run Target status changes to Running, or manually from a service entity. Creates a run target and launches a Cursor agent against the service repo.",
"nodes": [
{
"identifier": "manual_trigger",
"config": {
"type": "SELF_SERVE_TRIGGER",
"userInputs": {
"properties": {
"prompt": {
"title": "What needs to be fixed?",
"description": "Describe the issue or improvement to fix in this service",
"type": "string",
"format": "multi-line"
},
"service": {
"title": "Service",
"type": "string",
"format": "entity",
"blueprint": "service"
}
},
"required": ["service", "prompt"]
},
"contexts": [{ "on": "ENTITY", "userInput": "service" }]
},
"variables": {},
"verbose": false
},
{
"identifier": "fetch_service",
"title": "Fetch service details",
"icon": "Port",
"config": {
"type": "WEBHOOK",
"url": "https://api.port.io/v1/blueprints/service/entities/{{ .outputs[\"manual_trigger\"].service }}",
"method": "GET",
"onTimeout": "fail",
"onFailure": "terminate"
},
"variables": {
"svc_title": "{{ .result.response.data.entity.title }}",
"svc_repo_url": "{{ .result.response.data.entity.properties.repo_url }}",
"svc_identifier": "{{ .result.response.data.entity.identifier }}"
},
"verbose": false
},
{
"identifier": "create_run_target",
"title": "Create fix run",
"icon": "Branch",
"config": {
"type": "UPSERT_ENTITY",
"blueprintIdentifier": "run_target",
"mapping": {
"identifier": "rt-{{ .outputs[\"manual_trigger\"].service }}-{{ .workflowRun.identifier }}",
"title": "{{ .outputs[\"fetch_service\"].svc_title }} (manual fix)",
"properties": {
"status": "Running",
"target_url": "{{ .outputs[\"fetch_service\"].svc_repo_url }}",
"target_title": "{{ .outputs[\"fetch_service\"].svc_title }}",
"target_identifier": "{{ .outputs[\"fetch_service\"].svc_identifier }}"
}
},
"onFailure": "terminate"
},
"variables": {
"rt_id": "rt-{{ .outputs[\"manual_trigger\"].service }}-{{ .workflowRun.identifier }}"
},
"verbose": false
},
{
"identifier": "trigger",
"config": {
"type": "EVENT_TRIGGER",
"event": {
"type": "ENTITY_UPDATED",
"blueprintIdentifier": "run_target"
},
"condition": {
"type": "JQ",
"combinator": "and",
"expressions": [
".diff.after.properties.status == \"Running\"",
".diff.before.properties.status == \"Pending\""
]
}
},
"variables": {},
"verbose": false
},
{
"identifier": "fetch_agentic_initiative",
"title": "Fetch initiative details",
"icon": "Port",
"config": {
"type": "WEBHOOK",
"url": "https://api.port.io/v1/blueprints/agentic_initiative/entities/{{ .outputs[\"trigger\"].diff.after.relations.agentic_initiative }}",
"method": "GET",
"onTimeout": "fail",
"onFailure": "terminate"
},
"variables": {
"prompt": "{{ .result.response.data.entity.properties.prompt }}",
"initiative_id": "{{ .result.response.data.entity.identifier }}"
},
"verbose": false
},
{
"identifier": "cursor_agent",
"title": "Run Cursor agent on the repo",
"icon": "Cursor",
"config": {
"type": "CURSOR_AGENT",
"apiKey": "{{ .secrets[\"CURSOR_API_KEY\"] }}",
"prompt": {
"text": "{{ (.outputs[\"fetch_agentic_initiative\"].prompt // .outputs[\"manual_trigger\"].prompt) }}\n\nTarget service: {{ (.outputs[\"trigger\"].diff.after.properties.target_title // .outputs[\"fetch_service\"].svc_title) }}\nRepository: {{ (.outputs[\"trigger\"].diff.after.properties.target_url // .outputs[\"fetch_service\"].svc_repo_url) }}\n\nKeep changes minimal and focused on the task. Open a pull request with your changes."
},
"source": {
"repository": "{{ (.outputs[\"trigger\"].diff.after.properties.target_url // .outputs[\"fetch_service\"].svc_repo_url) }}",
"ref": "main"
},
"target": {
"autoCreatePr": true,
"branchName": "port/agentic/{{ (.outputs[\"fetch_agentic_initiative\"].initiative_id // \"manual\") }}/{{ (.outputs[\"trigger\"].diff.after.properties.target_identifier // .outputs[\"fetch_service\"].svc_identifier) }}"
}
},
"variables": {},
"verbose": false
},
{
"identifier": "check_success",
"title": "Agent succeeded?",
"icon": "Condition",
"config": {
"type": "CONDITION",
"outlets": [
{ "identifier": "success", "title": "Success", "expression": ".outputs[\"cursor_agent\"].status == \"FINISHED\"" },
{ "identifier": "failed", "title": "Failed", "expression": ".outputs[\"cursor_agent\"].status != \"FINISHED\"" }
]
},
"variables": {},
"verbose": false
},
{
"identifier": "update_target_failed",
"title": "Set target: Failed",
"icon": "Alert",
"config": {
"type": "UPSERT_ENTITY",
"blueprintIdentifier": "run_target",
"mapping": {
"identifier": "{{ (.outputs[\"trigger\"].diff.after.identifier // .outputs[\"create_run_target\"].rt_id) }}",
"properties": {
"status": "Failed",
"error_message": "Cursor agent did not finish successfully. Agent status: {{ .outputs[\"cursor_agent\"].status }}"
}
},
"onFailure": "continue"
},
"variables": {},
"verbose": false
},
{
"identifier": "update_target_pr",
"title": "Set target: PR opened",
"icon": "GitPullRequest",
"config": {
"type": "UPSERT_ENTITY",
"blueprintIdentifier": "run_target",
"mapping": {
"identifier": "{{ (.outputs[\"trigger\"].diff.after.identifier // .outputs[\"create_run_target\"].rt_id) }}",
"properties": {
"pr_url": "{{ .outputs[\"cursor_agent\"].target.prUrl }}",
"status": "PR Opened",
"agent_log_url": "{{ .outputs[\"cursor_agent\"].agentId }}"
}
},
"onFailure": "continue"
},
"variables": {},
"verbose": false
},
{
"identifier": "link_pr_entity",
"title": "Link PR entity",
"icon": "GitPullRequest",
"config": {
"type": "AI",
"userPrompt": "Use list_entities on blueprint 'githubPullRequest'. Filter where property 'link' equals '{{ .outputs[\"cursor_agent\"].target.prUrl }}'.\n\nIf a matching entity is found, call upsert_entity on blueprint 'run_target' with identifier '{{ (.outputs[\"trigger\"].diff.after.identifier // .outputs[\"create_run_target\"].rt_id) }}' and set the relation 'pull_request' to the identifier of the matching githubPullRequest entity.\n\nIf no matching entity is found yet (the PR may not be ingested yet), do nothing and return 'not_found'.",
"systemPrompt": "You are a Port catalog agent. Execute entity lookups and updates precisely.",
"tools": ["list_entities", "upsert_entity"]
},
"variables": {},
"verbose": false
}
],
"connections": [
{ "sourceIdentifier": "trigger", "targetIdentifier": "fetch_agentic_initiative" },
{ "sourceIdentifier": "manual_trigger", "targetIdentifier": "fetch_service" },
{ "sourceIdentifier": "fetch_service", "targetIdentifier": "create_run_target" },
{ "sourceIdentifier": "create_run_target", "targetIdentifier": "cursor_agent" },
{ "sourceIdentifier": "fetch_agentic_initiative", "targetIdentifier": "cursor_agent" },
{ "sourceIdentifier": "cursor_agent", "targetIdentifier": "check_success" },
{ "sourceIdentifier": "check_success", "targetIdentifier": "update_target_pr", "sourceOutletIdentifier": "success" },
{ "sourceIdentifier": "update_target_pr", "targetIdentifier": "link_pr_entity" },
{ "sourceIdentifier": "check_success", "targetIdentifier": "update_target_failed", "sourceOutletIdentifier": "failed" }
]
}
- Click Apply changes.
Add the secret
- Go to Settings > Secrets in Port.
- Create a secret named
CURSOR_API_KEYwith your Cursor API key.
Two entry points feed this workflow: the run_target entities the fan-out creates in bulk, and the manual self-service trigger on service entities (from the bolt menu) for a one-off fix outside an initiative.
This workflow is the human approval gate. It appears in the bolt menu on Draft initiatives and flips the entity to Running, which kicks off the fan-out workflow from step 2.
- Go to the workflows page in Port.
- Click + Workflow.
- Fill out the Create new workflow form, then click Confirm.
- Click the
{...}button to open the JSON editor and paste the following JSON:
Begin Agentic Initiative workflow JSON (click to expand)
{
"identifier": "begin_agentic_initiative",
"title": "Begin Agentic Initiative",
"icon": "Play",
"description": "Review the target list and kick off execution. Cursor agents will run against each target repo.",
"nodes": [
{
"identifier": "trigger",
"config": {
"type": "SELF_SERVE_TRIGGER",
"contexts": [
{
"on": "ENTITY",
"userInput": "initiative"
}
],
"userInputs": {
"properties": {
"initiative": {
"type": "string",
"format": "entity",
"blueprint": "agentic_initiative",
"title": "Initiative",
"dataset": {
"combinator": "and",
"rules": [
{
"property": "status",
"operator": "=",
"value": "Draft"
}
]
}
}
},
"required": ["initiative"]
}
},
"variables": {},
"verbose": false
},
{
"identifier": "start_initiative",
"title": "Start initiative",
"icon": "Play",
"config": {
"type": "UPSERT_ENTITY",
"blueprintIdentifier": "agentic_initiative",
"mapping": {
"identifier": "{{ .outputs.trigger.initiative }}",
"properties": {
"status": "Running"
}
},
"onFailure": "terminate"
},
"variables": {},
"verbose": false
}
],
"connections": [
{ "sourceIdentifier": "trigger", "targetIdentifier": "start_initiative" }
]
}
- Click Apply changes.
The dataset filter on the initiative input means Begin Agentic Initiative only appears in the bolt menu while the initiative is in Draft. Once a platform engineer runs it, the entity's status flips to Running, which is exactly the transition the workflow from step 2 is listening for.
The approval_policy property on agentic_initiative doesn't gate this workflow, it's read by whatever merges the resulting pull requests. Wire your branch protection or a follow-up workflow to check it, and to check the target service's criticality, so a low-risk service can auto-merge while a tier-1 service still waits on a human.
The targets_merged aggregation on agentic_initiative counts run_target entities with status Merged. The executor workflow in step 3 writes PR Opened when an agent finishes, but it does not track whether that pull request later merges.
Port's GitHub integration already ingests merge status into each githubPullRequest entity's status property, and step 3's link_pr_entity node connects the PR to the run_target via the pull_request relation. This workflow closes the loop.
- Go to the workflows page in Port.
- Click + Workflow.
- Fill out the Create new workflow form, then click Confirm.
- Click the
{...}button to open the JSON editor and paste the following JSON:
PR merge sync workflow JSON (click to expand)
{
"identifier": "sync_run_target_pr_merged",
"title": "Sync PR merge status to run targets",
"icon": "GitPullRequest",
"description": "When a linked GitHub pull request merges, updates the related run_target status to Merged.",
"nodes": [
{
"identifier": "trigger",
"config": {
"type": "EVENT_TRIGGER",
"event": {
"type": "ENTITY_UPDATED",
"blueprintIdentifier": "githubPullRequest"
},
"condition": {
"type": "JQ",
"combinator": "and",
"expressions": [
".diff.after.properties.status == \"merged\"",
".diff.before.properties.status != \"merged\""
]
}
},
"variables": {},
"verbose": false
},
{
"identifier": "find_run_target",
"title": "Find related run target",
"icon": "Search",
"config": {
"type": "WEBHOOK",
"url": "https://api.port.io/v1/blueprints/run_target/entities/search",
"method": "POST",
"body": {
"query": {
"combinator": "or",
"rules": [
{
"operator": "relatedTo",
"blueprint": "githubPullRequest",
"value": "{{ .outputs.trigger.diff.after.identifier }}"
},
{
"property": "pr_url",
"operator": "=",
"value": "{{ .outputs.trigger.diff.after.properties.link }}"
}
]
}
},
"onTimeout": "fail",
"onFailure": "continue"
},
"variables": {
"run_target_id": "{{ .result.response.data.entities[0].identifier }}"
},
"verbose": false
},
{
"identifier": "mark_merged",
"title": "Mark run target merged",
"icon": "GitPullRequest",
"config": {
"type": "UPSERT_ENTITY",
"blueprintIdentifier": "run_target",
"mapping": {
"identifier": "{{ .outputs.find_run_target.run_target_id }}",
"properties": {
"status": "Merged"
}
},
"onFailure": "continue"
},
"variables": {},
"verbose": false
}
],
"connections": [
{ "sourceIdentifier": "trigger", "targetIdentifier": "find_run_target" },
{ "sourceIdentifier": "find_run_target", "targetIdentifier": "mark_merged" }
]
}
- Click Apply changes.
This workflow first looks for a run_target linked through the pull_request relation. If link_pr_entity in step 3 hasn't set that relation yet (the PR may not have been ingested when it ran), the search falls back to matching on pr_url instead, so a merge still gets picked up.
An initiative's target_entities list is meant to be populated before a human ever sees it, typically by Port AI reading the scorecard's failing-rule results and drafting the initiative through the same catalog tools it already has (search_entities, upsert_entity). To test the workflows you just built without wiring that step up yet, create the Draft entity directly.
- Go to the Agentic Initiative catalog page.
- Create a new entity with:
status:Draftprompt: a short instruction, for example "Add a CODEOWNERS file assigning the owning team from the catalog."approval_policy:owner-approvaltarget_entities: a JSON array with one object per service currently failing your rule, each shaped as{"serviceIdentifier": "...", "serviceTitle": "...", "repoUrl": "..."}.
- Open the entity and run Begin Agentic Initiative from the ⚡ bolt menu.
- Watch the Run Target catalog page. One entity should appear per object in
target_entities, moving from Pending to Running, then toward PR Opened as each Cursor agent finishes. - Open the initiative entity again.
total_targets,targets_pr_opened, and the other aggregation counts should reflect what you see on the individual targets. - Merge one of the opened pull requests. After Port's GitHub integration syncs the merge and the workflow from step 5 runs, that target's status should move to Merged and
targets_mergedon the initiative should increment. - Open one of the opened pull requests and confirm it matches the
promptyou set on the initiative.
Use the workflow test tab to run agentic_initiative_executor directly against a Draft entity you create with one or two target_entities, instead of waiting for a real scorecard failure.
Track progress
The initiative's aggregation properties are the tracking view: total_targets, targets_pending, targets_running, targets_pr_opened, targets_merged, and targets_failed are counted live from the related run_target entities, so the entity page for any initiative already shows where it stands. The targets_merged count updates only after the workflow from step 5 propagates GitHub merge events onto linked run_target entities.
For a portfolio view across every initiative, add a table widget to a dashboard page scoped to the agentic_initiative blueprint, with the aggregation properties as columns. Add a second table scoped to run_target, grouped by status, when you want to see every pull request an initiative opened in one place.
What you built
A platform engineer approves a Draft initiative. Port bulk-creates one run_target per service in its target list, starts each target, launches a Cursor agent against each repository, and writes the resulting pull request, CI status, and outcome back to that target. When pull requests merge, a follow-up workflow updates each linked target to Merged. The initiative entity rolls every target's status up into a single set of counts, so there's one place to check whether a fleet-wide fix is done, in progress, or stuck.
What's next
- Route approval by criticality - extend step 4 so
approval_policyand the target service's tier decide whether a PR needs a human before merge, not just whether the initiative can start. - Populate
target_entitiesautomatically - connect Port AI to your scorecard's failing-rule results so a Draft initiative appears on its own, instead of being created by hand. - Notify the owning team - use the
team_notifiedproperty onrun_targetto drive a Slack message when a PR opens on a service that team owns. - Add code review enrichment - the PR Enricher agent can surface catalog context on every PR this pattern opens.
For the full picture of where this pattern fits, see Agentic initiatives and the ATR solution overview.