Artifacts

Expose app-owned runtime, object-state, and diagnostic exports through .NET SDK artifact providers, then let paired hosts request them as streamed files.

Artifacts are app-owned diagnostic exports that Ansight can discover and pull from a paired .NET app. Use artifacts when runtime memory, object state, or diagnostic output should be preserved as a named file or binary/text payload.

Concrete examples:

  • current Mapbox camera, viewport, style URI, visible layers, loaded sources, selected feature ids, annotations, and tile/cache counters
  • a 3D player or game scene snapshot with player transform, camera transform, active level, loaded scene graph ids, animation state, physics flags, and selected entity components
  • in-memory view-model or store state that is too large or nested for a small JSON tool result
  • current route/navigation stack, modal state, selected tab, and pending deep-link state
  • local sync state, queued operations, conflict summaries, feature flags, and experiment assignments
  • generated support bundles such as a ZIP containing logs, config, cache summaries, and recent domain snapshots

Artifacts are different from custom tools:

  • custom tools return a JSON ToolResult and are best for small state reads or narrow actions
  • artifact providers return metadata plus an IArtifactPayload that the SDK streams to the host
  • artifact requests are read-scoped remote tools, but they can export sensitive app data

Why Artifacts Matter

Agents understand a session by correlating evidence. Screenshots show what rendered, logs show what the app said, telemetry shows how the device behaved, touch input shows what the user did, and visual trees show the UI hierarchy. Artifacts fill the missing layer: what the app actually knew internally at a meaningful moment.

That matters because many bugs are invisible from pixels and logs alone:

  • a map can look blank because the camera is pointed at the wrong coordinates, the style failed to load, a source has no features, or the selected layer is hidden
  • a 3D scene can render incorrectly because the player transform, active camera, loaded scene graph, animation state, or selected entity state is wrong
  • a screen can look idle while a view model is stuck in a busy state, a command is disabled, a paging cursor is stale, or a store has conflicting in-memory values
  • a sync workflow can fail because queued operations, retry metadata, conflict resolution state, and local cache contents disagree

Artifacts let the app export that hidden state as durable session evidence. Once captured, an agent can line the artifact up with the rest of the session timeline: the touch that triggered the behavior, the screenshot that showed the result, the logs emitted around the same timestamp, and the telemetry or visual-tree state nearby. The agent no longer has to infer everything from symptoms; it can compare symptoms against app-owned ground truth.

Artifacts also keep agent access narrower. Instead of opening broad reflection over live runtime objects, the app can publish explicit, reviewable snapshots such as mapbox-state.json, player-state.json, or sync-state.zip. The provider controls which fields leave the process, how secrets are omitted, and which snapshot shapes are stable enough for repeated investigations.

Use artifacts when the question is not just “what happened on screen?” but “what did the app believe was true when this happened?”

Register Providers

Artifact support lives in Ansight.Core under the Ansight.Artifacts namespace. In an all-in-one setup, register one or more providers from the setup callback:

using Ansight;
using Ansight.Artifacts;

var options = Options.CreateBuilder()
    .WithAnsightSdk(ansight =>
    {
#if MYAPP_ANSIGHT_ARTIFACTS
        ansight.AddArtifactProvider(new CurrentReportArtifactProvider(reports));
#endif
        ansight.WithReadOnlyToolAccess();
    })
    .Build();

Runtime.InitializeAndActivate(options);

Core-only apps can call the same provider APIs directly on Options.CreateBuilder():

using Ansight;
using Ansight.Artifacts;

var options = Options.CreateBuilder()
#if MYAPP_ANSIGHT_ARTIFACTS
    .AddArtifactProvider(new CurrentReportArtifactProvider(reports))
#endif
    .WithReadOnlyToolAccess()
    .Build();

Runtime.InitializeAndActivate(options);

When the builder has at least one artifact provider, the SDK automatically registers the core artifact tools:

Tool idScopePurpose
artifacts.queryReadDiscover registered providers and their currently available artifact definitions.
artifacts.requestReadAsk a provider to create an artifact snapshot and stream its payload to the host.

Because both tools are read-scoped, WithReadOnlyToolAccess() is enough to allow discovery and requests. Treat requestable artifacts as high-sensitivity exports anyway: only register providers in local development builds, keep provider ids stable, and expose the narrowest payloads that support the workflow.

Provider Model

An artifact provider implements IArtifactProvider:

MemberMeaning
DescriptorStable provider metadata, including provider id, name, description, category, tags, and string metadata.
QueryAsync(...)Returns the artifact definitions currently available from that provider.
CreateAsync(...)Creates the requested artifact and returns metadata plus a payload source.

Artifact definitions describe what can be requested. Artifact results describe the concrete snapshot that was created for one request.

using Ansight.Artifacts;
using Ansight.Tools;

internal sealed class CurrentReportArtifactProvider(IReportService reports) : IArtifactProvider
{
    public ArtifactProviderDescriptor Descriptor { get; } = new(
        "app.reports",
        "Reports",
        "Exports app report artifacts.",
        "reports")
    {
        Tags = ["reports", "support"]
    };

    public Task<IReadOnlyList<ArtifactDefinition>> QueryAsync(
        ArtifactQueryContext context,
        CancellationToken cancellationToken = default)
    {
        IReadOnlyList<ArtifactDefinition> artifacts =
        [
            new ArtifactDefinition(
                "current-report",
                "Current Report",
                "Exports the current report as CSV.",
                "report",
                "reports",
                new ArtifactContentDescriptor(["text/csv"])
                {
                    DefaultMimeType = "text/csv",
                    SuggestedFileName = "current-report.csv",
                    SupportsText = true
                },
                ToolSchema.Object(),
                new ToolSecurity(
                    ToolSecurityLevel.High,
                    "Exports current report data.",
                    ToolSecurityImplications.ExportsData))
            {
                Tags = ["csv", "current"]
            }
        ];

        return Task.FromResult(artifacts);
    }

    public async Task<ArtifactResult> CreateAsync(
        ArtifactRequest request,
        CancellationToken cancellationToken = default)
    {
        if (!string.Equals(request.ArtifactId, "current-report", StringComparison.Ordinal))
        {
            throw new InvalidOperationException($"Unknown artifact '{request.ArtifactId}'.");
        }

        var csv = await reports.ExportCurrentCsvAsync(cancellationToken);
        var payload = ArtifactPayload.FromText(csv);

        return new ArtifactResult(
            new ArtifactMetadata(
                request.ArtifactId,
                request.ProviderId,
                "Current Report",
                "report",
                "text/csv",
                "current-report.csv")
            {
                Description = "Current report exported from the running app.",
                SizeBytes = payload.SizeBytes,
                Tags = ["csv", "current"]
            },
            payload);
    }
}

CreateAsync(...) can use request.Arguments for provider-specific filters or export options. The SDK supplies request.Context.ToolRequestId, request.Context.SessionId, and request.Context.RequestedAtUtc so generated output can be correlated with the live tool call and session.

Runtime State Examples

Artifacts are useful when an agent needs a stable snapshot of live app memory rather than a one-line answer. The provider decides how to flatten private runtime state into a safe export shape.

ArtifactUseful contentsWhy use an artifact
mapbox-state.jsonCamera center/zoom/bearing/pitch, viewport size, style URI, loaded source ids, visible layer ids, selected feature ids, annotation ids, tile/cache counters, last map error.Lets an agent compare what the map thinks is visible with screenshots, logs, and touch input.
player-state.jsonPlayer id, position/rotation/velocity, camera pose, active scene/level, loaded entities, selected object id, animation state, physics/debug flags.Captures 3D runtime state that is hard to infer from a screenshot.
view-model-state.jsonCurrent page model, selected item ids, validation errors, command busy flags, paging cursor, dirty fields.Gives a durable object-state snapshot without opening broad reflection access.
navigation-state.jsonNavigation stack, modal stack, selected tab, route parameters, pending deep link, last failed navigation.Makes a replayable UI-flow checkpoint.
sync-state.zipQueue database export, conflict summaries, retry metadata, endpoint config, recent sync logs.Packages related evidence that should stay together.

For example, one provider can expose multiple JSON runtime snapshots:

using System.IO;
using System.Text.Json;
using Ansight.Artifacts;
using Ansight.Tools;

internal sealed class RuntimeStateArtifactProvider(
    IMapDiagnostics map,
    IPlayerDiagnostics player) : IArtifactProvider
{
    private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web)
    {
        WriteIndented = true
    };

    public ArtifactProviderDescriptor Descriptor { get; } = new(
        "app.runtime-state",
        "Runtime State",
        "Exports selected runtime memory and object-state snapshots.",
        "runtime")
    {
        Tags = ["state", "memory", "diagnostics"]
    };

    public Task<IReadOnlyList<ArtifactDefinition>> QueryAsync(
        ArtifactQueryContext context,
        CancellationToken cancellationToken = default)
    {
        IReadOnlyList<ArtifactDefinition> artifacts =
        [
            CreateJsonDefinition(
                "mapbox-state",
                "Mapbox State",
                "Exports camera, style, source, layer, selection, and tile/cache state.",
                "map",
                "mapbox-state.json",
                ["mapbox", "map", "runtime"]),

            CreateJsonDefinition(
                "player-state",
                "3D Player State",
                "Exports player, camera, scene, animation, physics, and selected-entity state.",
                "scene",
                "player-state.json",
                ["3d", "player", "scene", "runtime"])
        ];

        return Task.FromResult(artifacts);
    }

    public Task<ArtifactResult> CreateAsync(
        ArtifactRequest request,
        CancellationToken cancellationToken = default)
    {
        var snapshot = request.ArtifactId switch
        {
            "mapbox-state" => map.CaptureState(),
            "player-state" => player.CaptureState(),
            _ => throw new InvalidOperationException($"Unknown artifact '{request.ArtifactId}'.")
        };

        var fileName = request.ArtifactId switch
        {
            "mapbox-state" => "mapbox-state.json",
            "player-state" => "player-state.json",
            _ => $"{request.ArtifactId}.json"
        };

        var json = JsonSerializer.Serialize(snapshot, JsonOptions);
        var payload = ArtifactPayload.FromText(json);

        return Task.FromResult(new ArtifactResult(
            new ArtifactMetadata(
                request.ArtifactId,
                request.ProviderId,
                Path.GetFileNameWithoutExtension(fileName),
                "runtime-state",
                "application/json",
                fileName)
            {
                Description = "Runtime state captured from the running app.",
                SizeBytes = payload.SizeBytes,
                Tags = ["runtime", "state", "json"]
            },
            payload));
    }

    private static ArtifactDefinition CreateJsonDefinition(
        string id,
        string name,
        string description,
        string category,
        string fileName,
        IReadOnlyList<string> tags) =>
        new(
            id,
            name,
            description,
            "runtime-state",
            category,
            new ArtifactContentDescriptor(["application/json"])
            {
                DefaultMimeType = "application/json",
                SuggestedFileName = fileName,
                SupportsText = true
            },
            ToolSchema.Object(),
            new ToolSecurity(
                ToolSecurityLevel.High,
                "Exports live runtime state from app memory.",
                ToolSecurityImplications.ExportsData,
                ToolSecurityImplications.InspectsRuntimeState))
        {
            Tags = tags
        };
}

The capture services should project runtime objects into DTOs instead of serializing framework objects directly. For a Mapbox-style artifact, include the data that explains the rendered map state: camera, style, layer/source ids, selections, annotations, and relevant counters. For a 3D/player artifact, include transforms, scene ids, selected entity/component summaries, animation names, and debug flags, but avoid full texture buffers, meshes, tokens, or user secrets.

Payload Sources

Use ArtifactPayload factory methods for common payload types:

FactoryUse when
ArtifactPayload.FromText(...)The provider can generate text in memory.
ArtifactPayload.FromBytes(...)The provider already has a byte array.
ArtifactPayload.FromStream(...)The provider can open a fresh readable stream on demand.
ArtifactPayload.FromFile(...)The artifact already exists as an app-local file that the SDK can read.

Each request must return metadata whose ProviderId and ArtifactId match the request. The metadata also needs a non-empty Name, Kind, MimeType, and FileName.

Discovery and Request Flow

  1. A paired host calls artifacts.query, optionally filtering by providerId, category, kind, or tag.
  2. The SDK calls each matching provider’s QueryAsync(...) and returns provider metadata plus artifact definitions.
  3. The host calls artifacts.request with providerId, artifactId, optional downloadId, optional chunkBytes, and provider-specific arguments.
  4. The provider creates an ArtifactResult.
  5. The SDK queues a WebSocket binary transfer and returns artifact metadata, transfer id, delivery mode, wire protocol, chunk size, and queued status.
  6. Ansight Studio stores the received payload as a session artifact snapshot so it can be reviewed alongside logs, screenshots, visual trees, telemetry, touches, and annotations.

Artifact requests require an initialized runtime and an active pairing session. Without a live transfer channel, artifacts.request returns an artifact_transfer_unavailable failure.

Builder APIs

APIPurpose
WithArtifactProviders(IEnumerable<IArtifactProvider>)Replace all registered artifact providers.
AddArtifactProvider(IArtifactProvider)Add one provider.
AddArtifactProviders(IEnumerable<IArtifactProvider>)Add several providers.
ContainsArtifactProvider(string)Check whether a provider id is already registered.

Provider ids are case-insensitive and must be unique. Duplicate ids fail validation when the registry is created.

Choosing Artifacts

Use artifacts for outputs that should leave the app as files or durable session evidence:

  • export runtime memory snapshots such as Mapbox state, 3D player state, view-model state, navigation state, or in-memory store state
  • export the current report, trace, local cache summary, or sync diagnostics
  • package a group of related files into one archive
  • expose a domain-specific snapshot too large for a ToolResult
  • preserve a support bundle in the captured session timeline

Use Custom Tools instead when the operation returns small JSON, changes live state, or does not need host-side file materialization. Use Studio evidence and artifact tools to inspect artifacts after Studio has captured them into a session.