agentskills.codes
NE

new-collection-object

Boilerplate and patterns for creating new Collection-managed runtime objects (Streams, Interfaces, Protocol components). Use when adding a new managed object type.

Install

mkdir -p .claude/skills/new-collection-object && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/14741" && unzip -o skill.zip -d .claude/skills/new-collection-object && rm skill.zip

Installs to .claude/skills/new-collection-object

Activation

This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.

Boilerplate and patterns for creating new Collection-managed runtime objects (Streams, Interfaces, Protocol components). Use when adding a new managed object type.
163 chars✓ has a “when” trigger

About this skill

new-collection-object

Create a new Collection-managed runtime object in the OpenWatt codebase. This skill generates the boilerplate for objects managed by the Collection system (Streams, Interfaces, Protocol components, etc.).

Usage

When the user asks to create a new Collection-managed object, use this skill to guide the implementation. Ask the user for:

  1. Object type: Stream, Interface, Protocol client/server, or other
  2. Module name: e.g., "mqtt", "modbus", "tcp", "serial"
  3. Class name: e.g., "MQTTBroker", "HTTPServer", "CANInterface"
  4. Properties: List of properties with types and descriptions
  5. Base class: Usually Stream, BaseInterface, or BaseObject

Boilerplate Patterns

1. File Structure

All Collection-managed objects follow this structure:

module [layer].[category].[module];

// Standard imports
import urt.lifetime;
import urt.log;
import urt.string;
import urt.time;
// ... other urt imports as needed

import manager;
import manager.base;
import manager.collection;
import manager.console;
import manager.plugin;

// Layer-specific imports
public import [layer].[category];  // e.g., router.stream, router.iface

nothrow @nogc:

// Enums/structs as needed
enum YourEnum : ubyte
{
    Value1,
    Value2,
}

// Main class
class YourObject : BaseClass
{
    __gshared Property[N] Properties = [ Property.create!("prop1", prop1)(),
                                         Property.create!("prop2", prop2)(),
                                         /* ... */ ];
nothrow @nogc:

    alias TypeName = StringLit!"type-name";

    this(String name, ObjectFlags flags = ObjectFlags.None)
    {
        super(collection_type_info!YourObject, name.move, flags);
    }

    // Properties...
    [property implementations]

    // API...
    override bool validate() const pure
    {
        // Validate configuration
        return true;
    }

    override CompletionStatus startup()
    {
        // Initialize resources
        return CompletionStatus.Complete;
    }

    override CompletionStatus shutdown()
    {
        // Clean up resources
        return CompletionStatus.Complete;
    }

    override void update()
    {
        // Per-frame processing
    }

private:
    // Member variables
}

// Module class
class YourModule : Module
{
    mixin DeclareModule!"[layer].[module]";
nothrow @nogc:

    Collection!YourObject objects;

    override void init()
    {
        g_app.console.registerCollection("/[category]/[type]", objects);
    }

    override void update()
    {
        objects.update_all();
    }
}

2. Property Declaration Patterns

The Properties Array (REQUIRED):

__gshared Property[N] Properties = [ Property.create!("prop-name", prop_name)(),
                                     Property.create!("other-prop", other_prop)(),
                                     /* exactly N properties */ ];

Mutually Exclusive Properties:

When properties represent different modes/options where only one should be active, track which is set internally rather than validating mutual exclusion:

void mode_a(TypeA value)
{
    if (_mode_a == value)
        return;
    _mode_a = value;
    _active_mode = Mode.A;  // Track which mode is active
    restart();
}

void mode_b(TypeB value)
{
    if (_mode_b == value)
        return;
    _mode_b = value;
    _active_mode = Mode.B;  // Overwrite previous mode
    restart();
}

// In validate(), check that one mode is set
override bool validate() const
{
    return _active_mode != Mode.None;
}

Property Getter/Setter Patterns:

Simple value type:

// Getter
ushort port() const pure
    => _port;

// Setter
void port(ushort value)
{
    if (_port == value)
        return; // early out is there is an advantage
    _port = value;
    restart();  // Trigger re-initialization if required
}

String/complex type:

// Getter
ref const(String) device() const pure
    => _device;

// Setter with validation (returns StringResult)
StringResult device(String value)
{
    if (!value)
        return StringResult("device cannot be empty");
    if (_device == value)
        return StringResult.success;
    _device = value.move;
    restart();
    return StringResult.success;
}

Enum type:

Parity parity() const pure
    => _parity;

void parity(Parity value)
{
    if (_parity == value)
        return;
    _parity = value;
    restart();
}

Multiple overloads (type conversion):

void port(WellKnownPort value)
    => port(cast(ushort)value);

void port(ushort value)
{
    if (_port == value)
        return;
    _port = value;
    restart();
}

ObjectRef property (reference to another Collection object):

// Getter
inout(Stream) stream() inout pure
    => _stream;

// Setter
StringResult stream(Stream value)
{
    if (!value)
        return StringResult("stream cannot be null");
    if (_stream is value)
        return StringResult.success;
    _stream = value;
    restart();
    return StringResult.success;
}

private:
    ObjectRef!Stream _stream;

3. State Machine Methods

validate() - Configuration Validation

Return true if configuration is valid:

override bool validate() const pure
{
    // Check required properties are set
    if (_device.empty)
        return false;

    // Check valid ranges/combinations
    if (_baud_rate < 300 || _baud_rate > 115200)
        return false;

    // Check dependencies
    if (_stream.get() is null)
        return false;

    return true;
}

startup() - Resource Initialization

Initialize resources. Return:

  • CompletionStatus.Complete - Success
  • CompletionStatus.Continue - Still initializing, call again next frame
  • CompletionStatus.Error - Failed, will retry with exponential backoff
override CompletionStatus startup()
{
    // Async resolution example
    if (_host)
    {
        AddressInfo addrInfo;
        addrInfo.family = AddressFamily.ipv4;
        AddressInfoResolver results;
        get_address_info(_host, null, &addrInfo, results);
        if (!results.next_address(addrInfo))
            return CompletionStatus.Continue;  // Not ready yet
        _address = addrInfo.address;
    }

    // Resource creation with error handling
    Result r = create_resource(_handle);
    if (!r)
    {
        writeError(type, " '", name, "' - failed to create resource: ", r);
        return CompletionStatus.Error;  // Will retry with backoff
    }

    // Initialize state
    _initialized = true;

    writeInfo(type, " '", name, "' started successfully");
    return CompletionStatus.Complete;
}

shutdown() - Resource Cleanup

Clean up all resources. Must not fail. Called repeatedly while in State.Stopping (NOT update()).

Return CompletionStatus.Continue to continue shutdown asynchronously:

override CompletionStatus shutdown()
{
    // For async/cancellable resources, request cancellation and wait
    if (has_pending_operations())
    {
        request_cancellation();
        poll_for_completion();

        if (still_waiting())
            return CompletionStatus.Continue;  // Will be called again
    }

    // Close handles/sockets
    if (_handle != invalid_handle)
    {
        close_handle(_handle);
        _handle = invalid_handle;
    }

    // Clear state
    _initialized = false;

    // Destroy if temporary
    if (_flags & ObjectFlags.Temporary)
        destroy();

    return CompletionStatus.Complete;
}

update() - Per-Frame Processing

Called every frame while Running:

override void update()
{
    // Check connection status
    if (!is_connected())
    {
        restart();
        return;
    }

    // Process incoming data
    ubyte[4096] buffer;
    ptrdiff_t received = read(buffer);
    if (received > 0)
        process_data(buffer[0..received]);

    // Update internal state
    _last_activity = getTime();
}

4. Constructor Pattern

For Streams (with StreamOptions):

this(String name, ObjectFlags flags = ObjectFlags.None, StreamOptions options = StreamOptions.None)
{
    super(collection_type_info!YourStream, name.move, flags, options);
}

For Interfaces (with InterfaceOptions):

this(String name, ObjectFlags flags = ObjectFlags.None, InterfaceOptions options = InterfaceOptions.None)
{
    super(collection_type_info!YourInterface, name.move, flags, options);
}

For other BaseObject subclasses:

this(String name, ObjectFlags flags = ObjectFlags.None)
{
    super(collection_type_info!YourObject, name.move, flags);
}

5. TypeName Requirement

Every Collection-managed class needs:

alias TypeName = StringLit!"type-name";

This determines:

  • Console command path: /stream/type-name, /interface/type-name
  • Default object naming
  • Type identification in logs

Examples:

  • StringLit!"tcp"/stream/tcp-client
  • StringLit!"serial"/stream/serial
  • StringLit!"modbus"/interface/modbus
  • StringLit!"mqtt-broker"/protocol/mqtt/broker

6. Module Class Pattern

class YourModule : Module
{
    mixin DeclareModule!"layer.module";
nothrow @nogc:

    // Collections
    Collection!YourStream streams;
    Collection!YourInterface interfaces;

    // Non-collection objects (if any)
    Map!(const(char)[], YourClient) clients;

    override void init()
    {
        // Register collections with console
        g_app.console.registerCollection("/stream/your-type", streams);
        g_app.console.registerCollection("/interface/your-type", interfaces);
    }

    override void pre_update()
    {
        // Update all collection objects
        streams.update_all();
        interfaces.update_all();
    }

    override void update()
    {
        // Update non-collection objects
        foreach(client; clients.values)
            client.update();
    }

    override void post_update()
    {
        // Post-processing if needed
    }
}

7. Module Registration

Add to `src/ma


Content truncated.

Search skills

Search the agent skills registry