Skip to content

feat(node): custom domain skill#125

Merged
marc0olo merged 4 commits intomainfrom
andrew/custom-domain-skills
Mar 31, 2026
Merged

feat(node): custom domain skill#125
marc0olo merged 4 commits intomainfrom
andrew/custom-domain-skills

Conversation

@andrewbattat
Copy link
Copy Markdown
Contributor

@andrewbattat andrewbattat self-assigned this Mar 25, 2026
@andrewbattat andrewbattat requested review from a team and JoshDFN as code owners March 25, 2026 21:09
@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 25, 2026

Skill Validation Report

Project Checks


✓ Project checks passed for 2 skills (0 warnings)

Copy link
Copy Markdown
Member

@marc0olo marc0olo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the skill! Custom domains definitely deserves its own dedicated skill. A few changes to request before we merge:

1. Make the skill canister-agnostic

Custom domains work at the boundary node level — they map a domain to any canister ID via DNS, not just asset canisters. The skill should acknowledge this works with any HTTP-serving canister (asset canisters, Juno, custom canisters implementing http_request).

Specifically, mention in "What This Is" that this works with any canister that can serve /.well-known/ic-domains over HTTP.

2. Update the asset-canister skill

Since this is now a dedicated skill, please also update skills/asset-canister/SKILL.md:

  • Trim the "Custom Domain Setup" section (lines 123–158) down to a short cross-reference, something like: "For custom domain setup (DNS, TLS certificates, domain registration), see the custom-domains skill. The only asset-canister-specific detail: your .well-known/ic-domains file must be in your dir directory so it gets deployed."
  • Remove "configuring custom domains" and "custom domains" from the description field in the frontmatter (line 3) so agents don't trigger asset-canister for domain questions

3. Clean trigger separation between the two skills

The two skills need clear trigger boundaries so agents route queries to the right one:

In evaluations/custom-domains.json — add to should_not_trigger:

  • "How do I deploy a frontend on the IC?"
  • "Configure .ic-assets.json5 for my project"
  • "How do I upload files to an asset canister programmatically?"

In evaluations/asset-canister.json — add to should_not_trigger:

  • "How do I set up a custom domain on the IC?"
  • "Configure DNS for my IC canister"

…aration

- custom-domains skill now acknowledges it works with any HTTP-serving
  canister, not just asset canisters
- asset-canister description and body trimmed to cross-reference
  custom-domains instead of duplicating setup instructions
- added should_not_trigger entries to both eval files for clean routing
@marc0olo
Copy link
Copy Markdown
Member

marc0olo commented Mar 31, 2026

asset-canister

Trigger Evals
% node scripts/evaluate-skills.js asset-canister --triggers-only
━━━ Trigger Evals ━━━

  Running trigger evaluation...

  Should trigger: 6/6 correct
    ✅ "How do I deploy a frontend on the IC?"
    ✅ "Set up an asset canister for my React app"
    ✅ "My SPA routing doesn't work on the IC"
    ✅ "How do I upload files to an asset canister programmatically?"
    ✅ "Configure .ic-assets.json5 for my project"
    ✅ "How does the asset canister handle content encoding?"

  Should NOT trigger: 8/8 correct
    ✅ "Add Internet Identity login"
    ✅ "How do I make inter-canister calls?"
    ✅ "Set up stable memory in Motoko"
    ✅ "Connect a wallet to my dapp"
    ✅ "How do I create an ICRC-1 token?"
    ✅ "Deploy a Rust backend canister"
    ✅ "How do I set up a custom domain on the IC?"
    ✅ "Configure DNS for my IC canister"

━━━ Summary ━━━

  Trigger evals: should-trigger 6/6 | should-not-trigger 8/8

custom-domain

Full evaluation
% node scripts/evaluate-skills.js custom-domains

Evaluating skill: custom-domains
Output cases: DNS record configuration, Domain registration API, ic-domains file setup, Cloudflare SSL pitfall, Apex domain limitation, HttpAgent host configuration, Domain update flow, Domain removal flow

━━━ DNS record configuration ━━━

  Running WITH skill...
  Running WITHOUT skill...
  Judging WITH skill...
  Judging WITHOUT skill...

  WITH skill: 4/4 passed
    ✅ Shows a CNAME record for app.example.com pointing to app.example.com.icp1.io
    ✅ Shows a TXT record for _canister-id.app.example.com with the canister ID ryjl3-tyaaa-aaaaa-aaaba-cai
    ✅ Shows a CNAME record for _acme-challenge.app.example.com pointing to _acme-challenge.app.example.com.icp2.io
    ✅ Does NOT hallucinate incorrect subdomains or record types

  WITHOUT skill: 1/4 passed
    ❌ Shows a CNAME record for app.example.com pointing to app.example.com.icp1.io
       → The CNAME value is listed as 'icp1.io' instead of the correct 'app.example.com.icp1.io'.
    ✅ Shows a TXT record for _canister-id.app.example.com with the canister ID ryjl3-tyaaa-aaaaa-aaaba-cai
    ❌ Shows a CNAME record for _acme-challenge.app.example.com pointing to _acme-challenge.app.example.com.icp2.io
       → The output contains no ACME challenge CNAME record for TLS certificate provisioning.
    ❌ Does NOT hallucinate incorrect subdomains or record types
       → The CNAME value 'icp1.io' is incorrect (should be 'app.example.com.icp1.io'), which constitutes a hallucinated/incorrect value.

━━━ Domain registration API ━━━

  Running WITH skill...
  Running WITHOUT skill...
  Judging WITH skill...
  Judging WITHOUT skill...

  WITH skill: 4/4 passed
    ✅ Uses the validate endpoint: GET https://icp0.io/custom-domains/v1/app.example.com/validate
    ✅ Uses the registration endpoint: POST https://icp0.io/custom-domains/v1/app.example.com
    ✅ Uses the status check endpoint: GET https://icp0.io/custom-domains/v1/app.example.com
    ✅ Does NOT invent non-existent API endpoints or parameters

  WITHOUT skill: 0/4 passed
    ❌ Uses the validate endpoint: GET https://icp0.io/custom-domains/v1/app.example.com/validate
       → The output does not include any validate endpoint call; it goes straight to registration.
    ❌ Uses the registration endpoint: POST https://icp0.io/custom-domains/v1/app.example.com
       → The output uses POST https://icp0.io/registrations with a JSON body instead of the correct endpoint POST https://icp0.io/custom-domains/v1/app.example.com.
    ❌ Uses the status check endpoint: GET https://icp0.io/custom-domains/v1/app.example.com
       → The output uses GET https://icp0.io/registrations/<REQUEST_ID> instead of the correct endpoint GET https://icp0.io/custom-domains/v1/app.example.com.
    ❌ Does NOT invent non-existent API endpoints or parameters
       → The output invents non-existent endpoints (https://icp0.io/registrations) and a non-existent JSON body parameter ({"name":"app.example.com"}) that do not match the actual ICP custom domain API.

━━━ ic-domains file setup ━━━

  Running WITH skill...
  Running WITHOUT skill...
  Judging WITH skill...
  Judging WITHOUT skill...

  WITH skill: 4/4 passed
    ✅ Identifies the file as .well-known/ic-domains
    ✅ States the canister must serve /.well-known/ic-domains
    ✅ Lists the domain in the file (mydomain.com)
    ✅ Does NOT assume a specific canister type or framework

  WITHOUT skill: 4/4 passed
    ✅ Identifies the file as .well-known/ic-domains
    ✅ States the canister must serve /.well-known/ic-domains
    ✅ Lists the domain in the file (mydomain.com)
    ✅ Does NOT assume a specific canister type or framework

━━━ Cloudflare SSL pitfall ━━━

  Running WITH skill...
  Running WITHOUT skill...
  Judging WITH skill...
  Judging WITHOUT skill...

  WITH skill: 3/3 passed
    ✅ Identifies Cloudflare's Universal SSL as the likely culprit
    ✅ Advises disabling Cloudflare's SSL/TLS certificate features
    ✅ Explains that provider SSL interferes with the IC's ACME challenge

  WITHOUT skill: 0/3 passed
    ❌ Identifies Cloudflare's Universal SSL as the likely culprit
       → The assistant identified Cloudflare's proxy (orange cloud) as the culprit, not Universal SSL specifically.
    ❌ Advises disabling Cloudflare's SSL/TLS certificate features
       → The assistant advised switching to DNS-only mode (disabling the proxy), not disabling SSL/TLS certificate features.
    ❌ Explains that provider SSL interferes with the IC's ACME challenge
       → The assistant mentioned TLS termination breaking when proxied but never referenced the ACME challenge or certificate issuance process being disrupted by Cloudflare SSL.

━━━ Apex domain limitation ━━━

  Running WITH skill...
  Running WITHOUT skill...
  Judging WITH skill...
  Judging WITHOUT skill...

  WITH skill: 3/3 passed
    ✅ Provides solutions for the CNAME-on-apex limitation
    ✅ Suggests using ANAME or ALIAS record types (CNAME flattening)
    ✅ Suggests using a subdomain like www.example.com as an alternative

  WITHOUT skill: 3/3 passed
    ✅ Provides solutions for the CNAME-on-apex limitation
    ✅ Suggests using ANAME or ALIAS record types (CNAME flattening)
    ✅ Suggests using a subdomain like www.example.com as an alternative

━━━ HttpAgent host configuration ━━━

  Running WITH skill...
  Running WITHOUT skill...
  Judging WITH skill...
  Judging WITHOUT skill...

  WITH skill: 3/3 passed
    ✅ Identifies that HttpAgent cannot auto-detect the IC API host on custom domains
    ✅ Shows setting host to https://icp-api.io for mainnet
    ✅ Shows HttpAgent.create({ host }) or equivalent configuration

  WITHOUT skill: 2/3 passed
    ✅ Identifies that HttpAgent cannot auto-detect the IC API host on custom domains
    ❌ Shows setting host to https://icp-api.io for mainnet
       → The assistant uses 'https://icp0.io' as the host value, not the correct mainnet API endpoint 'https://icp-api.io'.
    ✅ Shows HttpAgent.create({ host }) or equivalent configuration

━━━ Domain update flow ━━━

  Running WITH skill...
  Running WITHOUT skill...
  Judging WITH skill...
  Judging WITHOUT skill...

  WITH skill: 3/3 passed
    ✅ Updates the _canister-id TXT record to the new canister ID
    ✅ Uses PATCH https://icp0.io/custom-domains/v1/DOMAIN to notify the service
    ✅ Does NOT say to delete and re-register the domain

  WITHOUT skill: 0/3 passed
    ❌ Updates the _canister-id TXT record to the new canister ID
       → The assistant mentions updating the CNAME record but never mentions updating a _canister-id TXT record to the new canister ID.
    ❌ Uses PATCH https://icp0.io/custom-domains/v1/DOMAIN to notify the service
       → The assistant uses POST to https://icp0.io/registrations instead of PATCH to https://icp0.io/custom-domains/v1/DOMAIN.
    ❌ Does NOT say to delete and re-register the domain
       → The assistant explicitly suggests canceling/removing the existing registration before re-registering with the new canister ID, which is exactly the pattern this behavior prohibits.

━━━ Domain removal flow ━━━

  Running WITH skill...
  Running WITHOUT skill...
  Judging WITH skill...
  Judging WITHOUT skill...

  WITH skill: 3/3 passed
    ✅ Removes the _canister-id TXT and _acme-challenge CNAME DNS records
    ✅ Uses DELETE https://icp0.io/custom-domains/v1/DOMAIN to notify the service
    ✅ Does NOT suggest only removing DNS records without calling the API

  WITHOUT skill: 1/3 passed
    ❌ Removes the _canister-id TXT and _acme-challenge CNAME DNS records
       → The output only mentions removing the _canister-id TXT record; the _acme-challenge CNAME DNS record is not mentioned at all.
    ❌ Uses DELETE https://icp0.io/custom-domains/v1/DOMAIN to notify the service
       → The output uses DELETE https://icp0.io/registrations/<registration-id>, which is a different endpoint path than the expected https://icp0.io/custom-domains/v1/DOMAIN.
    ✅ Does NOT suggest only removing DNS records without calling the API

━━━ Trigger Evals ━━━

  Running trigger evaluation...

  Should trigger: 7/7 correct
    ✅ "How do I set up a custom domain for my IC canister?"
    ✅ "My custom domain registration is failing"
    ✅ "What DNS records do I need for an IC custom domain?"
    ✅ "How do I update my custom domain to point to a different canister?"
    ✅ "How do I remove a custom domain from the IC?"
    ✅ "The ACME challenge for my IC domain keeps failing"
    ✅ "How do I validate my custom domain configuration?"

  Should NOT trigger: 8/8 correct
    ✅ "How do I deploy a frontend on the IC?"
    ✅ "Set up Internet Identity for my app"
    ✅ "How do I make inter-canister calls?"
    ✅ "Configure .ic-assets.json5 for SPA routing"
    ✅ "How do I transfer ICP tokens?"
    ✅ "Deploy a Rust backend canister"
    ✅ "Configure .ic-assets.json5 for my project"
    ✅ "How do I upload files to an asset canister programmatically?"

━━━ Summary ━━━

  Output evals:
    DNS record configuration: WITH 4/4 | WITHOUT 1/4
    Domain registration API: WITH 4/4 | WITHOUT 0/4
    ic-domains file setup: WITH 4/4 | WITHOUT 4/4
    Cloudflare SSL pitfall: WITH 3/3 | WITHOUT 0/3
    Apex domain limitation: WITH 3/3 | WITHOUT 3/3
    HttpAgent host configuration: WITH 3/3 | WITHOUT 2/3
    Domain update flow: WITH 3/3 | WITHOUT 0/3
    Domain removal flow: WITH 3/3 | WITHOUT 1/3
  Trigger evals: should-trigger 7/7 | should-not-trigger 8/8

- Remove asset-canister-specific content (pitfall #6, .ic-assets.json5
  step, Vite directory layouts) from custom-domains skill
- Replace with canister-agnostic instructions and scoped notes for
  asset canister vs custom http_request canisters
- Generalize deploy steps to not prescribe specific commands
- Rewrite ic-domains eval to be canister-agnostic
- Adjust expected behaviors to test for actionable guidance rather
  than keyword matching
@marc0olo marc0olo self-requested a review March 31, 2026 08:57
Copy link
Copy Markdown
Member

@marc0olo marc0olo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@andrewbattat I applied some changes and will merge now.

@marc0olo marc0olo merged commit 4782c6f into main Mar 31, 2026
6 checks passed
@marc0olo marc0olo deleted the andrew/custom-domain-skills branch March 31, 2026 08:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants