.agents/skills/project-writing-go-modules-framework-v2/SKILL.md
Use with project-writing-collectors. Keep this skill loaded for style; read
source files for evidence.
src/go/plugin/framework/collectorapi/collector.gosrc/go/plugin/framework/docs/changing-framework-code.mdsrc/go/plugin/go.d/docs/how-to-write-a-collector.mdsrc/go/plugin/go.d/docs/helper-packages.mdsrc/go/plugin/go.d/docs/migrate-v1-to-v2.mdsrc/go/plugin/framework/chartengine/README.mdsrc/go/plugin/framework/charttpl/README.md.agents/skills/project-writing-go-modules-framework-v2/go-v2-host-scope.mdsrc/go/plugin/go.d/collector/cato_networks/.
Use focused pieces from it, not the whole collector shape.src/go/plugin/framework/docs/changing-framework-code.md before writing code.collectorapi.CollectorV2 from
src/go/plugin/framework/collectorapi/collector.go and register via
CreateV2.New() SHOULD own defaults, metrix.NewCollectorStore(), typed metric
instruments, and test seams.metrix.CollectorStore during
Collect() and provide chart template YAML through ChartTemplateYAML();
embedded charts.yaml is RECOMMENDED.Collect(ctx) MUST return error and write metrics to metrix; it MUST NOT
return a V1 map[string]int64.collectorapi.CollectorV2Runner. Run(ctx) MUST return
promptly after cancellation. Do not start operational polling from Init() or
Check(), because DynCfg test and autodetection use those methods without
starting the runtime job.Cleanup(ctx) MUST be idempotent. The framework may call it more
than once, including after partial Init / Check setup.collector.go, setup
helpers in init.go when needed, orchestration in collect.go, distinct
upstream operations in collect_<operation>.go, metrics in metrix.go /
write_metrics.go, focused tests.src/go/plugin/go.d/docs/helper-packages.md and
reuse an existing helper when it fits.<name>func/ subpackage with a narrow
Deps interface declared there. The Function package MUST NOT import the
collector package or hold *Collector.Creator.SharedFunctions, its
MethodHandler(job) receives the running canonical runtime job and the public
Function shape has no __job parameter. Use job.Collector() to bind the
Function handler to collector-owned state; do not add a package-global
registry to bridge Function dispatch. The Function is still job-backed:
publication waits for the canonical job to be running and available, and
dispatch rejects unavailable jobs before calling MethodHandler.collectorapi.FunctionAvailability;
keep FunctionAvailable(functionID) cheap and non-blocking.
funcapi.FunctionConfig.Available applies to AgentFunctions, not
job-backed SharedFunctions or InstanceFunctions.collectorapi.Creator.InstancePolicy defaults to
InstancePolicyPerJob. Use InstancePolicySingle only for collectors that
are intentionally one canonical job per agent. Single-instance configs MUST
use name == module after defaults are applied. DynCfg exposes opted-in
single-instance configs as single objects with the module-level collector
config ID, no collector template, and no add/remove; updates target that
single object.InstancePolicySingle, decide how
its initial single object appears in DynCfg. The framework exposes a single
object only after a config exists; it does not publish a template placeholder,
and plain stock enable failures can remove the stock object.store.Write().SnapshotMeter("") for normal metrics.Vec(...) for labels, Gauge for current values,
Counter.ObserveTotal() for source counters, and StateSet for fixed
one-active-state values.charts.yaml.charts.yaml, use version: v1, context_namespace, instances.by_labels,
instances.optional_by_labels, and label_promotion where their operator-facing behavior is needed.algorithm for normal type-driven behavior. At runtime,
chartengine maps metrix counters to incremental dimensions and gauges or
other kinds to absolute, including dynamically built charttpl.Chart
values. Metric names and suffixes do not determine the algorithm.algorithm to intentionally override runtime kind for every
dimension in that chart. This is also REQUIRED when differently typed series
are deliberately aggregated into the same rendered dimension; otherwise,
contributors to one rendered dimension MUST have the same runtime kind.
Chartengine does not diagnose violations at runtime, so real-path collector
tests MUST enforce this authoring rule.metrix.ReadFlatten()
and chartengine forces them to heatmap. Bucket dimensions are named by the
bare le upper-bound value and ordered numerically with +Inf last. Do NOT
add collector-local cumulative-bucket workaround metrics or a bucket-mode
option for V2 charts.instances.by_labels does not select them or an
instances.optional_by_labels key is absent—multiple source series can map to one rendered dimension. The effective
aggregation MUST match the metric meaning. Set it on the chart; it applies to every dimension. Absence means
sum; available reducers are sum, min, max, and unweighted avg
(avg forces floating-point emission). Use separate charts when metrics need
different reducers. Metric kind is not enough to infer this policy: gauges
may be additive stocks, states, timestamps, limits, or averages.sum. Summary
quantiles are not globally mergeable with these reducers. Non-sum reduction
of cumulative counters happens before Netdata calculates rates and can be
misleading when source membership changes.metrix keeps ONE descriptor per metric NAME, resolved atomically at commit and
BOUNDED: a name idle past its retention window (expireAfterSuccessCycles + descriptorGraceCycles, both configurable on NewCollectorStore(...)) is evicted
and can then re-register with a changed contract. Within that window the
descriptor is authoritative — re-registering a TRULY-LIVE name with a changed
kind / summary quantiles / histogram bounds fails the commit (loud), an idle name
is superseded, and Init-time (out-of-cycle) registration still panics
synchronously on conflict.metrix.DescriptorRetention accessor (DescriptorRetentionWindow(),
SuccessfulCommits()), or a stale handle drift-skips a changed-contract name
forever after metrix evicts the descriptor. The prometheus writer
(collector/prometheus/writer.go) is the reference; see
src/go/pkg/metrix/README.md ("Descriptor Lifecycle and Retention").context_namespace (the
fixed prefix, or prefix.<app> per job) so autogen rebuilds prefix.<metric> /
prefix.<app>.<metric> without hand-built chart IDs.charts.yaml):
charttpl.Spec.MarshalTemplate() (runs Validate() only, then
marshals with yaml.v2, the decoder's library). Do NOT hand-roll Validate() +
yaml.Marshal, and do NOT marshal with yaml.v3.charttpl.Group borrowed from a shared profile/catalog, deep-copy
it first with Group.Clone() so per-job edits cannot corrupt the shared template.
A Group you decoded yourself per job is already owned and needs no clone.metrix's Vec handle cache is
unbounded; cache per-series instruments yourself and evict handles unseen for N
cycles to stay bounded. Prefer a framework fix if the need is general
(Decision Discipline).src/go/plugin/go.d/docs/migrate-v1-to-v2.md.A collector using V1 chart Vars is blocked until framework support, an
approved equivalent design, or explicit breaking-alert approval exists.
collecttest.AssertChartCoverage is not chart-identity parity; it cannot
prove old chart IDs, family, priority, lifecycle, labels, or alert variables.
A finished migration MUST pass an import/runtime-path audit proving no V1
collection path or V1 map-to-metrix bridge remains reachable from normal
execution.
Temporary V1-to-V2 parity bridges MAY be used during development, but the
finished collector MUST NOT keep a runtime V1 map-to-metrix bridge.
For migrations, first create a compatibility manifest covering chart IDs, contexts, dimension IDs/names, labels, config keys, DynCfg schema keys, stock config, alerts, docs, and lifecycle behavior.
Migrations MUST preserve existing public contracts unless the SOW records an explicit breaking decision.
Migrations MUST keep old YAML/JSON field names. Add new config as opt-in when cardinality, cost, or user-visible identity could surprise existing users.
Collector integration artifacts MUST follow
.agents/skills/integrations-lifecycle/consistency.md; do not preserve a
partial local artifact checklist in V2 collector work.
MUST NOT log raw secrets, DSNs, bearer tokens, or URLs with embedded credentials.
Warningf/Errorf every collection cycle for a
recoverable partial failure. Use the built-in logger limiter:
c.Limit("collector:stable-operation-key", 1, time.Hour).Warningf(...).Once() is reset by JobV2.runOnce(), so it is useful inside one cycle only;
it is not cross-cycle spam protection.instances.by_labels, a present nonblank instances.optional_by_labels key, or a dimension
name_from_label define chart or dimension identity. Changing one creates a new chart or dimension; collectors MUST
NOT use identity churn merely to refresh metadata.instances.optional_by_labels only when a source conditionally exposes a bounded, sufficiently stable,
operator-useful identity axis. Missing and blank values are omitted; present values create refined instances. Authors
MUST assess value count and churn, and MUST NOT create a duplicate aggregate chart when NIDL/query aggregation already
provides that view.pid="1234" becomes _pid_1234); missing or blank optional identities contribute nothing.label_promotion defines non-identity chart metadata. Chartengine reconciles
its effective intersection across every routed contributor, including an
empty source-label set, and emits a complete replacement only when it changes.ScopeKey and GUID MUST be deterministic._vnode_type=<source>.At minimum, V2 work MUST include these tests, or the PR/SOW MUST justify why a specific item does not apply:
Init, Check, Collect, and Cleanup lifecycle coverage;BeginCycle, success commit, and abort
on expected collection errors;map[string]int64 collection path or V1 map-to-metrix bridge.