AI skills

Halyx ships skills for AI coding assistants: packaged instructions and reference material for jobs that are mostly mechanical but easy to get subtly wrong. Creating a portable service is one, migrating a service onto Halyx is another, and checking that a Halyx service really is portable is a third. Each skill is a folder of plain Markdown. Claude Code loads it as a skill, and any other assistant can follow it as instructions.

The skills#

Skill Does Changes code
halyx-ai-create creates a new Spring Boot service on Halyx, with a profile per cloud, tests on each cloud's emulators and, if asked, a Helm chart and IAM yes, after you approve a plan
halyx-ai-migrate migrates a Spring Boot service from a cloud's SDK, or from Spring Cloud AWS, Azure or GCP, to Halyx yes, one area at a time, after you approve a plan
halyx-ai-review reviews a Halyx service for anything that still ties it to one cloud, with a verdict per cloud no: it writes a report

They fit together: create a new service or migrate an existing one, then review it before calling it portable or deploying it to another cloud. halyx-ai-create runs the review's checks itself as its last step.

Installing and running them#

The skills are in the repository under .claude/skills/.

Assistant Install Run
Claude Code, in one service copy the skill's folder into the service's .claude/skills/ ask for the job ("create an orders service on Halyx", "migrate this service to Halyx", "review this service for portability"), or type the slash command: /halyx-ai-create, /halyx-ai-migrate, /halyx-ai-review
Claude Code, for every project copy the folder into ~/.claude/skills/ the same
Another assistant point it at the skill's SKILL.md as its instructions; its references are linked from there ask for the job

Claude Code picks a skill from its description, so asking in your own words works as well as the slash command. Outside this repository, the skills link to the published docs at https://halyx.dev/docs/ and to the portable orders sample on GitHub.

halyx-ai-create#

Creates a new Spring Boot service that runs on AWS, Azure and Google Cloud from one jar, in the shape of the portable orders sample: the cloud is a Spring profile, a local profile runs with no cloud, and the code holds nothing from a cloud SDK.

How it works:

  1. Understanding the service: its operations, its data and how each is read, what happens asynchronously, its settings and secrets, the target clouds, Spring Boot 4 or 3.5, and which deployment pieces are wanted.
  2. Plan: each need mapped to a Halyx resource and to the service on each cloud, the data model with its keys and indexes, why each asynchronous step can run twice, what differs per cloud, and the tests. It stops here for your approval.
  3. Scaffold: the build, application.yml with a document per cloud, then the code, built and started after each step.
  4. Tests: one end-to-end suite over the API, run in memory, on each cloud's emulators, from the packaged jar, and a test of each cloud profile that connects nowhere. Then one deliberate mistake per area, to see the right test fail.
  5. Deployment pieces, if asked: a Helm chart with workload identity per cloud, least-privilege permissions per resource, and Terraform.
  6. Review and report: the portability checks, with blockers and risks fixed, then what was built and what only a real cloud can show.
Reference Holds
building-blocks.md each need mapped to a Halyx resource and the service on each cloud; choosing between neighbours; document keys and indexes; what differs per cloud
project.md the build and the versions it resolves, the layout, bean and resource names, application.yml per cloud, and the checks before calling it done
code.md the sample's patterns: records and repositories, store then announce, listeners that can run twice, events, streams, caches, SQL, blobs and secrets, and function calls
testing.md the suite, the memory and emulator runs and how to provision them, the profile test, the packaged jar, and proving the tests can fail
deploy.md the image, the Helm chart, workload identity, permissions per resource on each cloud, and infrastructure as code

How it was tested: three services were generated by following the skill, each with a different mix:

  • invoices, on Spring Boot 4: blob storage, a document store with an index, messaging, secrets and @Cacheable
  • inventory, on Spring Boot 3.5: SQL with Flyway and transactions, events, Kafka streams, and settings that reload
  • pageviews, on Spring Boot 4 with WebFlux: reactive repositories, cache counters for quotas, and streams

Each passed its suite in memory and on the AWS, Azure and Google Cloud emulators (events are skipped on Azure and Google Cloud, which have no emulator for them), its profile tests and its packaged jar. Each also caught the deliberate mistakes made in it. Every trial found something that is now in the skill. Two examples: a profile test that loads a cloud profile's config import reaches the real cloud with the machine's credentials, and DateTimeFormatter.ISO_INSTANT isn't fixed width, so it can't sort a key. The trials also showed that a standalone Spring Boot 4.1 service resolves Lettuce 7.5 and Netty 4.2, and that Halyx's caches and streams work on them. None of the three has run on a real cloud yet.

A fourth trial, shipping on Spring Boot 4, quotes each shipment through a serverless function. It passed its suite in memory, with the function as a registered handler, and on each cloud's emulators with a stand-in function: Floci's Lambda, the Azure Functions host (its function key read from Key Vault) and Google's Functions Framework. Its profile and packaged-jar tests passed, and it caught a wrong Lambda name, a misnamed key and code that ignored the function's rejection. The trial added to the skill that Lambda functions return no status, so a portable function reports rejections in its payload. It also found that a function key missing from Key Vault showed only at the first call, which led to #26: keys now come from the function's secrets, read on every call, and a missing secret or an unresolved placeholder fails the start.

halyx-ai-migrate#

Moves an existing Spring Boot service onto Halyx. It covers:

  • Spring Cloud AWS, Spring Cloud Azure, Spring Cloud GCP and the raw SDKs
  • S3Template, @SqsListener, DynamoDbTemplate, @ServiceBusListener, CosmosRepository and PubSubTemplate
  • EventBridge, Event Grid and Eventarc publishers
  • Lambda invocations, and HTTP calls to Azure Functions and Cloud Run functions
  • Kafka with IAM login
  • Redis with IAM, behind @Cacheable or RedisTemplate
  • JDBC with IAM login
  • aws-parameterstore:, sm:// and Key Vault property sources

How it works:

  1. Inventory: every cloud use, each with its Halyx equivalent and a risk.
  2. Plan: whether the existing data is readable as stored, and how old and new instances coexist during the rollout. It stops here for your approval.
  3. Migration: one area per change, with the existing tests kept passing.
  4. Optionally, profiles: the cloud becomes a Spring profile, plus a local profile with no cloud.
  5. Report: what changed and what behaves differently.

Migrating to Halyx describes the method, the mappings and the pitfalls in full; it's the same method by hand.

Reference Holds
inventory.md how to find every cloud use: build files, imports, annotations, configuration, and hand-written IAM glue
aws.md, azure.md, gcp.md what each cloud API, Spring Cloud template and property becomes
halyx-spring.md the Halyx side: dependencies, SDK versions, properties, beans, imports
differences.md existing data, behaviour, rollouts, permissions, and running the old and new libraries at once
testing.md the tests that prove a migration: old-state, production-profile and packaged-service tests among them

How it was tested: six Spring Cloud services, two per cloud, were migrated step by step on emulators and containers. Each passed its original tests and started cleanly on what the old version left behind. One per cloud then ran on a throwaway EKS, GKE and AKS cluster with workload identity. Every trial found something that is now in the skill, including one Halyx bug, since fixed. The results are in Migrating to Halyx.

Two more services, migrated on emulators, call functions: loyalty (Spring Boot 3.5) invoked a Lambda alias with LambdaClient, the tenant in its ClientContext, and an EVENT invocation for audit; catalog (Spring Boot 4) called an Azure function with RestClient and a key in the query string. Both passed their original tests on Floci's Lambda and the Functions host. Each changed one assertion, on purpose: Halyx' error messages name the function, so a function's reason passed on to clients reads differently. The trials also found that azure.functions.route is the whole path on the app, api/ included. With the bare route, the "unknown product" test kept passing, since the host's 404 is the same NotFoundException as the function's own, and only the known-product test failed. All three points are now in the skill and in the docs.

halyx-ai-review#

Answers one question: would this service run, and behave correctly, on each cloud by changing only its profile? It's read-only and never calls a cloud. It writes PORTABILITY-REVIEW.md: a verdict per cloud (ready, ready with risks or not ready), then each finding with its file and line, the clouds it affects and the fix. It ends with what was checked, and what only a real-cloud run can show.

Its 30 rules, in references/rules.md:

Group Checks
Code native clients outside one isolated class; SDK exceptions Halyx never throws; features a cloud lacks (delays, deduplication, ordering, nack delays, presigned URLs, stream transactions, function triggers); function payloads, retries and deployments; decimals compared by value; payload sizes; consumers relying on cloud-side filters; cloud names in code; bean-name clashes
Configuration cloud settings in the shared configuration; a profile per cloud and a default; names whose shape differs per cloud; Spring settings Halyx's beans make ineffective; credentials in configuration; a no-cloud profile's limits; cache codecs and key namespaces
Build a provider module per target cloud; leftover cloud libraries; SDK artifacts at test scope; cloud-specific runtime pieces such as the Cloud SQL connector
Deployment permissions Halyx needs beyond the old code's; workload identity per cloud; buildpack direct memory; services with no equivalent on another cloud
Tests tests per cloud; containers wired to Boot rather than Halyx; tests of the production profiles; a start of the packaged service

Each finding is classed by severity:

  • blocker: the service won't start, or fails, on a cloud it should run on
  • risk: it runs, but behaves differently or loses data on some cloud
  • hygiene: it works today, but makes the next cloud harder

The review reads every candidate before reporting it: an unwrap in an isolated, guarded class is fine, and the same call in a controller isn't.

How it was tested: on the portable orders sample, and on a copy of it with twelve problems planted across the rule groups. It found all twelve, and classified them by reading the code. For example, a message delay is a blocker on SNS and Pub/Sub but fine on Service Bus. On the sample itself, its first finding was a false positive, which exposed a wrong rule and a stale row in Halyx's own capability table. Both are fixed, and the sample now reports no blockers or risks. For functions, it ran on the shipping trial service and on a copy with eight function problems planted: an unguarded trigger, a Lambda ARN in the shared configuration, a function with no Google Cloud location, unwrap(LambdaClient.class) in a controller, a literal function key, idempotent on a function that charges, a payload holding a PDF, and response headers read on Lambda. It found all eight (planted and reviewed in the same session, so not blind), and didn't flag the local profile's unwrap(InMemoryFunctions.class). The rule's search didn't match the response headers, which reading the class found; the search now does. As with any review, a clean report means no leaks were found in the code and configuration, not that the service has run on each cloud.

Naming#

Skills are named halyx-ai-<verb>, so they can't be mistaken for Maven modules, which are halyx-<module>. The prefix also keeps them together next to other teams' skills. A second word is added only where the verb alone is ambiguous, as in halyx-ai-add-cloud. A skill's description decides when an assistant uses it, so the name only needs to be short to type. Differences between clouds and frameworks go in a skill's reference files, not in a skill per cloud.

Writing a new skill#

A skill is a folder in .claude/skills/ with a SKILL.md: front matter with its name and a description that says what it does and when to use it, then the method. Detail goes in references/. Before a skill is listed here, it gets trial runs like the ones above, ideally including one where the outcome isn't known in advance. Each trial should end with what it found written back into the skill.

Candidates for later:

  • generating least-privilege infrastructure and IAM from a service's halyx.* configuration
  • adding a cloud to a service
  • adding the trial-proven tests to an existing service
  • throwaway real-cloud runs
  • diagnosing startup and runtime errors