From 44ff6a0172f71b65d69f81b4b3a183bdb87a4f86 Mon Sep 17 00:00:00 2001 From: Nathanial Acosta Date: Fri, 24 Jul 2026 11:47:59 -0400 Subject: [PATCH 1/2] fix: adjust logic to skip on failure and delete/reimport on collision --- .github/scripts/deploy.py | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/.github/scripts/deploy.py b/.github/scripts/deploy.py index db5af0a..d333db5 100644 --- a/.github/scripts/deploy.py +++ b/.github/scripts/deploy.py @@ -166,7 +166,7 @@ async def deploy_projects(self, client: Any, project_files: list[Path]) -> None: print(f"👤 Added {member.type} {member_identifier} as {member.role}") except Exception as e: print(f"❌ Failed to import project {project_name}: {e}") - raise + print(f"âš ī¸ Skipping {project_name} and continuing deployment") # async def deploy_agent_projects( # self, client: Any, bundle_files: list[Path] @@ -189,7 +189,7 @@ async def deploy_projects(self, client: Any, project_files: list[Path]) -> None: # print(f"✅ Successfully imported Agent project: {bundle_name}") # except Exception as e: # print(f"❌ Failed to import Agent project {bundle_name}: {e}") - # raise + # print(f"âš ī¸ Skipping {bundle_name} and continuing deployment") async def deploy_automations( self, client: Any, automation_files: list[Path] @@ -217,8 +217,9 @@ async def deploy_automations( ) if existing_automation: - print(f"â„šī¸ Automation already exists, skipping: {automation_name}") - continue + print(f"📝 Automation exists, deleting existing version: {automation_name}") + await automations_resource.delete(automation_name) + print(f"đŸ—‘ī¸ Deleted existing automation: {automation_name}") print(f"đŸ“Ĩ Importing automation: {automation_name}") result = await automations_resource.importer(automation_data) @@ -253,15 +254,16 @@ async def deploy_lifecycle_manager_resources( try: existing_resource = await lm_resource.get_resource_by_name(resource_name) if existing_resource: - print(f"â„šī¸ Resource model already exists, skipping: {resource_name}") - continue + print(f"📝 LCM Resource exists, deleting existing version: {resource_name}") + await lm_resource.delete(resource_name) + print(f"đŸ—‘ī¸ Deleted existing LCM resource: {resource_name}") print(f"đŸ“Ĩ Importing resource model: {resource_name}") result = await lm_resource.importer(lifecyle_manager_resource_payload) print(f"✅ Successfully imported resource model: {result['data']['name']}") except Exception as e: print(f"❌ Failed to import resource model {resource_name}: {e}") - raise + print(f"âš ī¸ Skipping {resource_name} and continuing deployment") async def deploy_configurations( self, client: Any, config_files: list[Path] @@ -278,22 +280,25 @@ async def deploy_configurations( cm_resource = client.resource("configuration_manager") + for config_file in config_files: with open(config_file, "r") as f: config_data = json.load(f) - config_name = config_data.get("data", [{}])[0].get("name", config_file.stem) - + config_name = config_data.get("name",config_file.stem) try: - if await cm_resource.check_if_golden_config_exists(config_name): - print(f"â„šī¸ Golden config already exists, skipping: {config_name}") - continue + # Check if golden config already exists and delete before re-importing + existing_config = await cm_resource.check_if_golden_config_exists(config_name) + if existing_config: + print(f"📝 Golden config exists, deleting existing version: {config_name}") + await cm_resource.delete(config_name) + print(f"đŸ—‘ī¸ Deleted existing golden config: {config_name}") print(f"đŸ“Ĩ Importing golden config: {config_name}") - result = await cm_resource.import_golden_config([config_data]) - print(f"✅ {result.get('message', f'Successfully imported golden config: {config_name}')}") + result = await cm_resource.importer([config_data]) + print(f"✅ Successfully imported golden config: {config_name}") except Exception as e: print(f"❌ Failed to import golden config {config_name}: {e}") - raise + print(f"âš ī¸ Skipping {config_name} and continuing deployment") async def deploy(self) -> None: """Execute the deployment process.""" From e416aed658a5759392faf72cde26703d9f3cb02d Mon Sep 17 00:00:00 2001 From: Nathanial Acosta Date: Mon, 27 Jul 2026 09:46:03 -0400 Subject: [PATCH 2/2] feat(agent-projects): add Agent Projects support to deploy pipeline Wires up deploy_agent_projects() in the deploy script (asset discovery, import call, and the call in deploy()), and documents Agent Projects as a supported asset type in README.md, STANDARDS.md, and CONTRIBUTING.md. --- .github/scripts/deploy.py | 63 +++++++++++++++++++++++++-------------- CONTRIBUTING.md | 4 +-- README.md | 7 +++++ STANDARDS.md | 5 ++++ 4 files changed, 54 insertions(+), 25 deletions(-) diff --git a/.github/scripts/deploy.py b/.github/scripts/deploy.py index d333db5..d648a80 100644 --- a/.github/scripts/deploy.py +++ b/.github/scripts/deploy.py @@ -7,6 +7,7 @@ Currently supports: - Studio projects + - Agent projects - Operations Manager automations - Lifecycle Manager resources - Configuration Manager golden configs @@ -58,6 +59,7 @@ def __init__(self, environment: str, members: list[dict[str, str]] | None = None # bucket key, emoji, and label used when reporting found files. _ASSET_DIR_MAP = { "Studio Projects": ("projects", "đŸ“Ļ", "Studio project"), + "Agent Projects": ("agent_projects", "🧠", "agent project"), "Automations": ("automations", "🤖", "automation"), "LCM Resource Models": ("lifecycle_manager_resources", "🔧", "LCM resource model"), "Golden Configs": ("configurations", "âš™ī¸ ", "golden config"), @@ -78,6 +80,7 @@ def find_asset_files(self) -> dict[str, list[Path]]: assets: dict[str, list[Path]] = { "projects": [], + "agent_projects": [], "automations": [], "lifecycle_manager_resources": [], "configurations": [], @@ -168,28 +171,42 @@ async def deploy_projects(self, client: Any, project_files: list[Path]) -> None: print(f"❌ Failed to import project {project_name}: {e}") print(f"âš ī¸ Skipping {project_name} and continuing deployment") - # async def deploy_agent_projects( - # self, client: Any, bundle_files: list[Path] - # ) -> None: - # agent_projects_resource = client.resource("agent_projects") - # for bundle_file in bundle_files: - # with open(bundle_file, "r") as f: - # bundle_data = json.load(f) - # bundle_name = bundle_data.get("name", bundle_file.stem) - # try: - # members = [] - # for member in self.members: - # member_data = {"type": member["type"], "role": member["role"]} - # if member["type"] == "account": - # member_data["username"] = member["username"] - # else: - # member_data["name"] = member["name"] - # members.append(ProjectMember(**member_data)) - # await agent_projects_resource.importer(bundle_data, members=members) - # print(f"✅ Successfully imported Agent project: {bundle_name}") - # except Exception as e: - # print(f"❌ Failed to import Agent project {bundle_name}: {e}") - # print(f"âš ī¸ Skipping {bundle_name} and continuing deployment") + async def deploy_agent_projects( + self, client: Any, bundle_files: list[Path] + ) -> None: + """Deploy Agent projects to the platform. + + Args: + client: Asyncplatform client instance + bundle_files: List of agent project bundle file paths + """ + if not bundle_files: + print("â„šī¸ No Agent projects to deploy") + return + + agent_projects_resource = client.resource("agent_projects") + for bundle_file in bundle_files: + with open(bundle_file, "r") as f: + bundle_data = json.load(f) + bundle_name = bundle_data.get("name", bundle_file.stem) + try: + members = [] + for member in self.members: + member_data = {"type": member["type"], "role": member["role"]} + if member["type"] == "account": + member_data["username"] = member["username"] + else: + member_data["name"] = member["name"] + members.append(ProjectMember(**member_data)) + + print(f"đŸ“Ĩ Importing agent project: {bundle_name}") + await agent_projects_resource.importer( + bundle_data, members=members, overwrite=True + ) + print(f"✅ Successfully imported Agent project: {bundle_name}") + except Exception as e: + print(f"❌ Failed to import Agent project {bundle_name}: {e}") + print(f"âš ī¸ Skipping {bundle_name} and continuing deployment") async def deploy_automations( self, client: Any, automation_files: list[Path] @@ -324,7 +341,7 @@ async def deploy(self) -> None: await self.deploy_projects(client, assets["projects"]) - # await self.deploy_agent_projects(client, assets["agent_projects"]) + await self.deploy_agent_projects(client, assets["agent_projects"]) await self.deploy_lifecycle_manager_resources( client, assets["lifecycle_manager_resources"] diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d9d305f..138ec1e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -29,7 +29,7 @@ Using an AI coding tool to contribute? Start with [`AGENTS.md`](./AGENTS.md) — ## Contribution Standards -Every asset type in this repo — Studio Projects, Automations, Golden Configurations, OpenAPIs, LCM Resource Models — has its own naming, versioning, and structural rules, plus a set of requirements that apply repo-wide (branding, README structure, no sensitive data, etc.). These all live in [`STANDARDS.md`](./STANDARDS.md). +Every asset type in this repo — Studio Projects, Agent Projects, Automations, Golden Configurations, OpenAPIs, LCM Resource Models — has its own naming, versioning, and structural rules, plus a set of requirements that apply repo-wide (branding, README structure, no sensitive data, etc.). These all live in [`STANDARDS.md`](./STANDARDS.md). **Read it before opening a PR** — most review feedback traces back to one of these rules. @@ -51,7 +51,7 @@ Title your PR (and its commits) as `(): `. | Type | Use for | |---|---| - | `feat` | A new asset: a new vendor, product, or asset (OpenAPI spec, Studio Project, Automation, Golden Configuration) | + | `feat` | A new asset: a new vendor, product, or asset (OpenAPI spec, Studio Project, Agent Project, Automation, Golden Configuration) | | `fix` | A correction to an existing asset (bad workflow task, wrong variable reference, invalid operationId, broken spec field, etc.) | | `chore` | Non-functional maintenance (renaming, removing duplicates, reorganizing folders, metadata-only edits) | | `docs` | Changes to `README.md`, `CONTRIBUTING.md`, `STANDARDS.md`, or a product's own `README.md` only | diff --git a/README.md b/README.md index feb2b43..2383bc0 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ Assets are organized by vendor and product. Each folder may contain one or more | Asset Type | Description | |---|---| | **Studio Projects** | Bundles of related automation assets (workflows, forms, templates, and transformations) for a specific use case | +| **Agent Projects** | Bundles of FlowAI agents (instructions, input schema, and tools) for a specific use case | | **OpenAPIs** | JSON-based definitions imported as Integration Models that specify how Itential Platform connects to external APIs, databases, and systems | | **Golden Configurations** | Config Manager compliance trees for auditing device configuration drift | | **device-drivers** | Netmiko-based drivers for connecting Itential Gateway to physical and virtual devices | @@ -76,6 +77,7 @@ Assets are organized by vendor and product. Each folder may contain one or more ``` Vendor/ └── Product/ + ├── Agent Projects/ ├── Automations/ ├── Configuration Parsers/ ├── device-drivers/ @@ -100,6 +102,11 @@ See [Create and manage projects](https://docs.itential.com/itential-platform/stu 2. Click the **Import** button on the Projects homepage. 3. Upload the `.json` file by drag-and-drop or browse the file system. +### Import an Agent Project +1. In Itential Platform, go to **Agent Projects**. +2. Click the **Import** button on the Agent Projects homepage. +3. Upload the `.agent_project.json` file by drag-and-drop or browse the file system. + ### Import an OpenAPI spec/Integration Model See [Integration models](https://docs.itential.com/itential-platform/6/admin-essentials/integration-models) for full details. 1. In Itential Platform, go to **Admin Essentials**. diff --git a/STANDARDS.md b/STANDARDS.md index b5013f8..44fe50d 100644 --- a/STANDARDS.md +++ b/STANDARDS.md @@ -8,6 +8,7 @@ All asset types below live at `{Vendor}/[{Product}/]{AssetType}/`. The `{Product - [General Principles](#general-principles) - [Studio Projects](#studio-projects-vendorproductstudio-projects) +- [Agent Projects](#agent-projects-vendorproductagent-projects) - [Automations](#automations-vendorproductautomations) - [Golden Configurations](#golden-configurations-vendorproductgolden-configurations) - [OpenAPIs](#openapis-vendorproductopenapis) @@ -29,6 +30,10 @@ All asset types below live at `{Vendor}/[{Product}/]{AssetType}/`. The `{Product - Files are exported Studio projects in `.project.json` format. - **Target the `-latest` Integration Model**: when building or updating a project against an OpenAPI-backed integration, wire its tasks to the `-latest` spec's Integration Model, not a pinned dated version — so projects automatically pick up the curated, actively-maintained spec rather than drifting to a version that will eventually be superseded. +## Agent Projects (`{Vendor}/[{Product}/]Agent Projects/`) +- Bundles of FlowAI agents (instructions, input schema, and tools) for a specific use case. +- Files are exported Agent Project bundles in `.agent_project.json` format. + ## Automations (`{Vendor}/[{Product}/]Automations/`) - Exported automation definitions that correspond to a Studio Project submission. - Should be paired with a Trigger where applicable.