AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Speckle Impl Versioning

skill-impertio-studio-speckle-claude-skill-package-speckle-impl-versioning · by Impertio-Studio

>

No reviews yet
0 installs
14 views
0.0% view→install

Install

$ agentstack add skill-impertio-studio-speckle-claude-skill-package-speckle-impl-versioning

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-impertio-studio-speckle-claude-skill-package-speckle-impl-versioning)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
4mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Speckle Impl Versioning? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

speckle-impl-versioning

Quick Reference

Terminology Map

| Current Term | Legacy Term | Description | |-------------|-------------|-------------| | Project | Stream | Top-level container for all data | | Model | Branch | Named series of versions within a project | | Version | Commit | Immutable snapshot of data at a point in time |

ALWAYS use current terminology (project/model/version) in new code. Legacy terms still appear in some SDK parameter names (e.g., stream_id in ServerTransport).

Version Lifecycle

[Send Object] --> [Create Version] --> [Version in History]
                                            |
                                    [Compare Versions]
                                            |
                                    [Rollback = new version
                                     pointing to old object]

Critical Warnings

NEVER attempt to modify an existing version -- versions are IMMUTABLE snapshots. To "update" data, ALWAYS create a new version.

NEVER delete versions from the middle of a history chain without understanding that downstream references (webhooks, automation runs, viewer URLs) will break.

NEVER use referencedObject as a stable identifier across sessions -- it is a content hash that changes when data changes. Use the version.id for stable references.

ALWAYS include a descriptive message when creating versions -- empty messages make history unusable for collaboration.

ALWAYS set sourceApplication when creating versions programmatically -- this enables filtering and audit trails.


Version CRUD Operations

Create a Version

A version links an uploaded object (by its hash ID) to a model within a project.

Step 1: Upload the object

from specklepy.api import operations
from specklepy.transports.server import ServerTransport

transport = ServerTransport(stream_id=project_id, client=client)
object_id = operations.send(base=my_object, transports=[transport])

Step 2: Create the version record

from specklepy.core.api.inputs.version_inputs import CreateVersionInput

version = client.version.create(CreateVersionInput(
    project_id=project_id,
    model_id=model_id,
    object_id=object_id,
    message="Added structural grid layout",
    source_application="MyApp/1.0"
))

GraphQL equivalent:

mutation VersionCreate($input: CreateVersionInput!) {
  versionMutations {
    create(input: $input) {
      id
      referencedObject
      message
      sourceApplication
      createdAt
    }
  }
}

Variables:

{
  "input": {
    "objectId": "",
    "modelId": "",
    "projectId": "",
    "message": "Added structural grid layout",
    "sourceApplication": "MyApp/1.0",
    "parents": [""]
  }
}

The parents field is optional. It records lineage but does NOT enforce ordering.

Get a Version

version = client.version.get(project_id, version_id)
# version.id            -- stable identifier
# version.referenced_object  -- object hash for receive
# version.message       -- human-readable description
# version.created_at    -- timestamp
# version.source_application -- origin app

GraphQL:

query VersionGet($projectId: String!, $versionId: String!) {
  project(id: $projectId) {
    version(id: $versionId) {
      id
      referencedObject
      message
      sourceApplication
      createdAt
      previewUrl
      authorUser { id name avatar }
    }
  }
}

List Versions for a Model

versions = client.version.list(
    project_id=project_id,
    model_id=model_id,
    limit=25
)
for v in versions.items:
    print(f"{v.id}: {v.message} ({v.created_at})")

GraphQL (paginated):

query ModelVersions(
  $projectId: String!
  $modelId: String!
  $limit: Int!
  $cursor: String
) {
  project(id: $projectId) {
    model(id: $modelId) {
      versions(limit: $limit, cursor: $cursor) {
        items {
          id
          referencedObject
          message
          sourceApplication
          createdAt
          authorUser { id name }
        }
        totalCount
        cursor
      }
    }
  }
}

ALWAYS use cursor-based pagination when listing versions. NEVER attempt to load all versions at once on models with extensive history.

Update a Version

Only the message field is updatable on an existing version.

from specklepy.core.api.inputs.version_inputs import UpdateVersionInput

client.version.update(UpdateVersionInput(
    version_id=version_id,
    project_id=project_id,
    message="Corrected structural grid layout v2"
))

GraphQL:

mutation VersionUpdate($input: UpdateVersionInput!) {
  versionMutations {
    update(input: $input) {
      id
      message
    }
  }
}

Delete Versions

from specklepy.core.api.inputs.version_inputs import DeleteVersionsInput

client.version.delete(DeleteVersionsInput(
    version_ids=[version_id],
    project_id=project_id
))

GraphQL:

mutation VersionDelete($input: DeleteVersionsInput!) {
  versionMutations {
    delete(input: $input)
  }
}

Returns Boolean!. NEVER delete versions that are referenced by active Automate runs or shared viewer URLs.

Move Versions Between Models

mutation VersionMoveToModel($input: MoveVersionsInput!) {
  versionMutations {
    moveToModel(input: $input) {
      id
    }
  }
}

This relocates versions from one model to another within the same project. Use this to reorganize misplaced data without re-uploading.


Version Comparison and Diff

Retrieving Data for Comparison

To compare two versions, receive both root objects and traverse their trees:

from specklepy.api import operations
from specklepy.transports.server import ServerTransport

transport = ServerTransport(stream_id=project_id, client=client)

old_version = client.version.get(project_id, old_version_id)
new_version = client.version.get(project_id, new_version_id)

old_data = operations.receive(obj_id=old_version.referenced_object, remote_transport=transport)
new_data = operations.receive(obj_id=new_version.referenced_object, remote_transport=transport)

Object-Level Diff Strategy

Speckle objects have content-addressable IDs (hashes). Two objects with the same hash are identical.

def build_object_map(base_obj, obj_map=None):
    """Recursively build a map of applicationId -> object hash."""
    if obj_map is None:
        obj_map = {}
    if hasattr(base_obj, "applicationId") and base_obj.applicationId:
        obj_map[base_obj.applicationId] = base_obj.get_id()
    for name in base_obj.get_member_names():
        if name.startswith("_"):
            continue
        value = getattr(base_obj, name, None)
        if hasattr(value, "get_member_names"):
            build_object_map(value, obj_map)
        elif isinstance(value, list):
            for item in value:
                if hasattr(item, "get_member_names"):
                    build_object_map(item, obj_map)
    return obj_map

old_map = build_object_map(old_data)
new_map = build_object_map(new_data)

added = {k: v for k, v in new_map.items() if k not in old_map}
removed = {k: v for k, v in old_map.items() if k not in new_map}
modified = {k: new_map[k] for k in old_map if k in new_map and old_map[k] != new_map[k]}
unchanged = {k: new_map[k] for k in old_map if k in new_map and old_map[k] == new_map[k]}

ALWAYS use applicationId as the correlation key for diffing -- it persists across versions for the same native element. Object hashes change whenever any property changes.

DiffExtension in the Viewer

The @speckle/viewer package includes a DiffExtension for visual comparison:

import { Viewer, DiffExtension, SpeckleLoader, UrlHelper } from "@speckle/viewer";

const diff = viewer.createExtension(DiffExtension);

// Load both versions into the viewer
const oldUrl = `https://app.speckle.systems/projects/${projectId}/models/${modelId}@${oldVersionId}`;
const newUrl = `https://app.speckle.systems/projects/${projectId}/models/${modelId}@${newVersionId}`;

// DiffExtension highlights:
// - GREEN: added elements
// - RED: removed elements
// - YELLOW: modified elements
// - GREY: unchanged elements

ALWAYS create the DiffExtension AFTER viewer.init() completes. Creating extensions before init causes silent failures.


Rollback Strategy

Speckle has NO built-in rollback mutation. Rollback is achieved by creating a NEW version that references the same object as a previous version.

Rollback Pattern

# 1. Get the target version to roll back to
target_version = client.version.get(project_id, target_version_id)

# 2. Create a new version pointing to the same object
rollback_version = client.version.create(CreateVersionInput(
    project_id=project_id,
    model_id=model_id,
    object_id=target_version.referenced_object,
    message=f"Rollback to version {target_version_id}: {target_version.message}",
    source_application="rollback-script"
))

This preserves the full history chain. The rollback version is a new entry that points to an existing object -- no data duplication occurs because objects are content-addressed.

NEVER delete intermediate versions to simulate rollback. This destroys audit history and breaks references.


Model (Branch) Management

Create a Model

from specklepy.core.api.inputs.model_inputs import CreateModelInput

model = client.model.create(CreateModelInput(
    project_id=project_id,
    name="structural/foundations",
    description="Foundation structural model"
))

GraphQL:

mutation ModelCreate($input: CreateModelInput!) {
  modelMutations {
    create(input: $input) {
      id
      name
      displayName
      description
    }
  }
}

Model names support / separators for hierarchical organization (e.g., structural/foundations, structural/framing). The viewer and web UI render these as nested folders.

Other Model Operations

from specklepy.core.api.inputs.model_inputs import UpdateModelInput, DeleteModelInput

# List models
models = client.model.get_models(project_id=project_id, limit=50)

# Update
client.model.update(UpdateModelInput(
    project_id=project_id, model_id=model_id,
    name="structural/foundations-v2", description="Updated foundation model"
))

# Delete
client.model.delete(DeleteModelInput(project_id=project_id, id=model_id))

NEVER delete a model that has versions actively used in Automate functions or shared viewer URLs. ALWAYS verify downstream dependencies first. See [references/methods.md](references/methods.md) for full GraphQL signatures and filter options.


Multi-Model Coordination

Loading Multiple Models in the Viewer

Iterate over model URLs, calling UrlHelper.getResourceUrls() and viewer.loadObject(loader, false) for each. ALWAYS pass autoFit=false when loading multiple models, then call camera.setCameraView([], true) once after all models are loaded. See [references/examples.md](references/examples.md) for the full TypeScript implementation.

Cross-Model Version Synchronization

Coordinate versions across models by creating tagged snapshots with a shared timestamp in the message. Iterate over model IDs, fetch each model's latest version, and create a new version with a coordination message prefix (e.g., [Coordinated 2024-01-15T10:00:00] Milestone review). See [references/examples.md](references/examples.md) for full implementation.

Model Naming Conventions

| Pattern | Example | Use Case | |---------|---------|----------| | Discipline prefix | architecture/floor-plans | Multi-discipline projects | | Phase prefix | phase-1/demolition | Phased construction | | Source prefix | revit/central-model | Multi-tool workflows | | Flat naming | structural-analysis | Simple single-discipline projects |

ALWAYS establish a naming convention before creating models. Inconsistent naming makes multi-model coordination difficult.


Version History Traversal

Walking the Version Timeline

def get_full_version_history(client, project_id, model_id):
    """Retrieve complete version history for a model."""
    all_versions = []
    cursor = None

    while True:
        result = client.version.list(
            project_id=project_id,
            model_id=model_id,
            limit=25,
            cursor=cursor
        )
        all_versions.extend(result.items)
        if not result.cursor or len(result.items) < 25:
            break
        cursor = result.cursor

    return all_versions

Mark Version as Received

After successfully receiving and processing a version, mark it:

mutation MarkReceived($input: MarkReceivedVersionInput!) {
  versionMutations {
    markReceived(input: $input)
  }
}

This updates the version's received status, useful for tracking sync state across distributed systems.


Reference Links

  • [references/methods.md](references/methods.md) -- API signatures for version, model, and diff operations
  • [references/examples.md](references/examples.md) -- Complete working examples for version management workflows
  • [references/anti-patterns.md](references/anti-patterns.md) -- Common mistakes in version and model management

Official Sources

  • https://docs.speckle.systems/developers/sdks/python/api-reference/client.md
  • https://docs.speckle.systems/developers/server-api/graphql-api.md
  • https://docs.speckle.systems/developers/viewer/extensions
  • https://speckle.guide/dev/python.html

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.