reliant-labs/forge/services
Claude Code and agent skill by reliant-labs/forge
- community
Checking that the SKILL.md file is still in the repository.
Scaffold and wire new services, packages, and frontends in a Forge project.
Install
npx vskill@latest install reliant-labs/forge/servicesSKILL.md
Adding New Components to a Forge Project
Use this skill whenever you need to introduce a new network-facing service, internal package, or frontend into a Forge mono-repo. Every scaffolded service comes with production middleware baked in — structured logging, distributed tracing, auth, rate limiting, and graceful shutdown — so you focus on business logic from day one.
Choosing the Right Command
| I need… | Command | What it creates | |
|---|---|---|---|
| A new network-facing API (Connect RPC) | forge scaffold service <name> | Proto definition, generated stubs, Go service skeleton | |
| A background worker | forge scaffold worker <name> | Worker with Start/Stop lifecycle | |
| A cron-scheduled worker | forge scaffold worker <name> --kind cron --schedule "..." | Worker with cron scheduler | |
| A one-shot step that must finish before something else starts | declare a kind = "job" workload in deploy/kcl/workloads.k and bind it per env (see below) | Ordering primitive — no scaffold needed | |
| An internal Go package with interface contract | forge package new <name> | Package directory with contract interface and default implementation | |
| A Next.js web frontend | forge scaffold frontend <name> | Next.js app wired into the project | |
| A React Native mobile frontend | forge scaffold frontend <name> --kind mobile | Expo app with Connect-web transport |
The fast path: scaffold everything the protos imply
The per-component commands above are the granular path. When you author the proto directly, there is a one-command batch alternative: mark each entity message with a leading // forge:entity comment, add your custom RPCs, then run `forge scaffold`. It births every marked entity (missing CRUD quintet injected + owned create-table migration) and runs generate, which emits a pb-through handler stub for every custom RPC — in one visible, phased run (--dry-run plans; a re-run with nothing missing is a clean no-op). In dev the app boots alive: forge env up auto-seeds a fresh DB from the applied schema. The entity/seed halves live in the db skill, the pb-through RPC handlers in api, and the standalone domain package (when you extract one) in service-layer / contracts.
One-shot steps: the job workload kind
When something must run once to completion before something else starts — register an OAuth client against the IdP, provision a bucket, load a fixture — that is a job. Every other workload kind is long-running: service / worker / operator run forever, cron runs on a schedule forever, and a tool is never scheduled at all. job is the one that ends.
Declare it ONCE in deploy/kcl/workloads.k, like every workload, and bind it in each env that runs it. Ordering (before) is part of the declaration; WHERE it runs is the env's binding:
before is the ordering declaration and it is the point — a one-shot with no ordering is just a cron that fires once. Each runtime honours it:
| runtime | lowering | |
|---|---|---|
cluster (forge.OnCluster) | an init container on each workload named in before, rendered by pkg/deploy.RenderWorkloads. It is the only ordering Kubernetes enforces itself, so it holds under any applier. A gating job renders no standalone Job — that would run the command a second time, unordered. | |
cluster, before empty | a standalone batch/v1 Job, pre-rollout by default (forge env deploy waits for it before applying Deployments); deployPhase = "post-rollout" defers it. | |
hosted (forge.OnHosted) | the control plane runs it the same way, before the workloads it gates. | |
host (forge.OnHost) | forge env up runs go run ./cmd/<p> <args>, waits for exit 0, and only then launches the dependents. Fail-closed: a job that exits non-zero stops the up. | |
compose (forge.OnCompose) | the compose file owns it: restart: "no" plus depends_on: {<job>: {condition: service_completed_successfully}} on each dependent. |
The job's command must be idempotent. The k8s lowering fans it out to one init container per dependent, and init containers re-run on every pod start, every replica and every restart. That is the same contract the deploy-time migration step already keeps (migratekit takes a postgres advisory lock), and a one-shot that is not safe to repeat is unsafe under any retrying orchestrator.
Things forge rejects at load time rather than at 3am: a before naming a component that does not exist, a job with no command, a job carrying a schedule (use kind = "cron"), a workload an env lists without binding a runtime, and — on the host path — a cycle in the job graph.
Wiring Cycle
Follow this sequence every time you scaffold a new component:
- Scaffold — run the appropriate
forge scaffoldorforge package newcommand. - Define the contract — edit the
.protofile (services) or the interface (packages). - Generate — run
forge generateto produce Go code from protos and contracts (handler stubs, mocks, Connect clients). - Implement — write the business logic on the
*Servicemethods ininternal/handlers/<svc>/usings.deps(a custom RPC is a pb-through method on*Service; CRUD delegates viahandlers_crud.go). For a reusable, transport-free domain layer, scaffold a standalone package (forge scaffold package <name>) and call it from the handler. - Compose it into a binary — a binary serves a service because
NewComponentsconstructs it and the serve path mounts it. Add the constructor call to the composition (see below); selection is code, not a string table.
Deploying it: declared once, bound per env
forge scaffold operator|binary appends the workload to deploy/kcl/workloads.k — build, the component's subcommand in args. A service or worker on the composition graph gets NO workload of its own: the binary has no command per service or worker (see "Commands are roots" below), so a stanza with args = ["<service>"] would crash-loop on "unknown command", and forge lint warns about one (component-drift/no-such-command). Every env — dev (a host process under air, hot reload) and staging/prod (hosted on Reliant as scaffolded) — runs every service and worker in ONE workload, _api: the binary's server, every service on one Connect mux, so the browser reaches them all at one origin. A new one needs no binding line (_api is bound if it was not). An operator binds _on_k3d in dev and _on_cluster in deployed envs, since hosting refuses it; see deploy/hosting. Probes need nothing: a service forge builds gets /readyz + /healthz on its http port on every runtime. See the deploy skill for the runtimes and how to rebind one workload (to a cluster you operate, say).
A project that runs its components in processes it composes itself — one binary whose own subcommands (public-api, admin-api, workers) each mount several, so most components of a kind have no workload of their own — gets no workload for a new one: the scaffold prints which processes it found and the stanza, and leaves the choice to you. Mount it in the subcommand that should run it and add it to that workload's serves, or paste the stanza to give it its own. forge lint's component-drift reads the file the same way.
Port Assignment
In a container every service owns :8080 (the http port). In the dev loop each host process gets its own port from plugin.resolve_port in deploy/kcl/dev/main.k. Do not hard-code port numbers elsewhere.
Rules
- Always use `forge scaffold` or `forge package new` — never copy-paste an existing service or package directory.
- One service per proto package — keep proto definitions focused on a single domain.
- Run `forge generate` after any proto or contract change — generated code must stay in sync.
- Service names canonicalize the same way worker names do: lowercase snake_case (hyphens → underscores, PascalCase boundaries split).
forge scaffold service admin-serverkeepsadmin-serveras theforge.yamlname:display key, but the on-disk leaf, Go package decl, andforge.yamlpath:leaf are alladmin_server(internal/handlers/admin_server/,package admin_server,path: internal/handlers/admin_server). See theworkersskill Naming section for the full rule and the migration gotcha; seearchitecturefor the cross-ecosystem naming-conventions table. - Service code lives under `internal/handlers/<svc>/` —
service.go(Deps/New), the rpc methods that hold its logic, and the generated CRUD ops co-located in ONE directory, with nocontract.go. Thehandlers/role subtree is underinternal/, not top-level; a service is app-internal, imported by nobody external.
Serving a service = composing it (the composition root)
What a binary serves is the set of constructors it calls — not a string row in a registry. The explicit composition is split across two files under internal/app/, both yours: providers.go (Infra + OpenInfra) and compose.go (Components + NewComponents(infra *Infra) (*Components, error)). A binary serves a service because NewComponents constructs it and the serve path mounts its handler:
The serve path (cmd/<bin>/cmd/serve.go) then applies the typed Mount<Svc> method values onto a serverkit.Server and calls srv.RequireMounted(...).
- Per-service handler options are a value, passed explicitly. Every service shares the serve path's interceptor chain. To give one service more (an extra interceptor, its own read limit, its own HTTP stack), build the generated
app.MountOptions— one[]mountkit.Optionfield per service — in owned code and name one of its methods as the mount:ServeSpec{Mount: app.MountOptions{Worktree: []mountkit.Option{mountkit.WithHandlerOptions(connect.WithInterceptors(wake))}}.MountAll, ...}inserver.go, or.MountWorktreein that service's subcommand.mountkit.WithHandlerOptionscomes after the shared chain, so the shared interceptors wrap it.
This is the start of the file. Read the full SKILL.md on GitHub.
Security
- Security scanPassed, 95 of 100Pattern checks on the skill's files.
- Model reviewPassed
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryCheckinginternal/templates/project/skills/forge/services/SKILL.md
Popularity
- Repository stars
- 4
- Repository forks
- 1
- 7-day trend score
- 8
Stars and forks are for the whole GitHub repository, not this skill alone. Refreshed 29 Apr 2026, 6 months ago.
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/reliant-labs/forge/services)