wasmloop

package
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 12, 2026 License: MIT Imports: 26 Imported by: 0

Documentation

Overview

Package wasmloop is the extension tier: third-party loops as signed WASM modules instead of Go compiled into the daemon.

What it replaces: `karmax loops install` ran `go get`, rewrote a generated file, rebuilt the binary and swapped it. That needs a Go toolchain and a source checkout on every user's machine, and it compiles unreviewed third-party Go into a process holding WhatsApp sessions, Google tokens and a GitLoom API key. There is no boundary in that design to enforce.

Install here is a TRANSACTION — fetch, verify, record, activate — and raw bytes are never executed on arrival.

Index

Constants

View Source
const (
	FnLog         = "log"
	FnRecall      = "recall"
	FnRemember    = "remember"
	FnNotify      = "notify"
	FnHTTP        = "http"
	FnTrigger     = "trigger"
	FnAsk         = "ask"
	FnConfig      = "config"
	FnHostTool    = "hosttool"
	FnHarness     = "harness"
	FnGateway     = "gateway"
	FnSummarize   = "summarize"
	FnPropose     = "propose"
	FnRemind      = "remind"
	FnTool        = "tool"
	FnShortSet    = "short_set"
	FnShortGet    = "short_get"
	FnShortAll    = "short_all"
	FnChatGet     = "chat_summary_get"
	FnChatSave    = "chat_summary_save"
	FnRunLoop     = "run_loop"
	FnShortForget = "short_forget"
	FnOperators   = "operator_chats"
)

Host function names. A closed set: an unknown name is refused, not ignored, so a module built against a newer KARMAX fails visibly here.

View Source
const AllocExport = "karmax_alloc"

AllocExport is the guest's allocator, used to hand it a request.

View Source
const DefaultRegistry = "https://raw.githubusercontent.com/MelloB1989/karmax-loops/main"

DefaultRegistry is where loops are fetched from.

View Source
const IndexFile = "/index.json"

IndexFile is the index's path within a registry.

View Source
const ToolExport = "tool"

ToolExport is the function a module exports to serve its provided tools.

Variables

This section is empty.

Functions

func Describe

func Describe(m Manifest) []string

Describe renders a manifest in the plain English an operator approves.

It takes the whole manifest rather than the fields it happens to use today, so a new kind of reach cannot be added to the format and quietly stay out of the list the operator is shown.

func Dir

func Dir() string

Dir is where artifacts and the lockfile live.

func LockPath

func LockPath(dir string) string

LockPath is where the lockfile lives.

func Pack

func Pack(m Manifest, module []byte, sigs []Signature) ([]byte, error)

Pack builds an artifact file: magic, header length, header, module.

func SaveTrust

func SaveTrust(dir string, registries, revoked []string, allowCommunity bool) error

SaveTrust writes the configuration, without the environment's additions — what the operator set is what is stored.

func StoredTrust

func StoredTrust(dir string) (registries, revoked []string, allowCommunity bool)

StoredTrust reads only the file, for a command that is editing it.

func TrustPath

func TrustPath(dir string) string

TrustPath is where the configuration lives.

func UnknownHostFunctions

func UnknownHostFunctions(declared []string) []string

UnknownHostFunctions reports which of a manifest's declared host functions this KARMAX does not have.

An artifact's signature proves it is intact, not that it still fits the host. A loop built against an older ABI verifies, installs and runs — and is refused on its first real call, which looks like a loop that decided to do nothing. This is what lets that be reported instead of discovered.

func VerifyBytes

func VerifyBytes(data []byte, t Trust) (*Artifact, *Verdict, error)

VerifyBytes is Unpack plus Verify, which is the only correct order and so is the only one offered as a single call.

Types

type Artifact

type Artifact struct {
	Manifest   Manifest
	Signatures []Signature
	Module     []byte
}

Artifact is a parsed loop package.

func Unpack

func Unpack(data []byte) (*Artifact, error)

Unpack parses an artifact without verifying it. Verify does that, and nothing should execute a module that has only been unpacked.

func (*Artifact) Digest

func (a *Artifact) Digest() string

Digest is the hex sha256 of the module bytes.

type CapabilityDiff

type CapabilityDiff struct {
	Added   []string
	Removed []string
	Same    bool
}

CapabilityDiff describes how an upgrade changes what a loop may do.

This is the screen that matters at upgrade time: a loop that gained "http:*" between versions is a loop the operator must be shown, not one that quietly inherits its old approval.

func DiffCapabilities

func DiffCapabilities(old Entry, next Manifest) CapabilityDiff

DiffCapabilities compares an installed entry with an incoming manifest.

type ChatSummary

type ChatSummary struct {
	ChatJID         string `json:"jid"`
	ChatName        string `json:"name"`
	IsGroup         bool   `json:"is_group"`
	Summary         string `json:"summary"`
	MessageCount    int    `json:"message_count"`
	OwnMessageCount int    `json:"own_count"`
	// Unix seconds, because a guest and a host do not share a time.Time.
	LastMessageAt int64  `json:"last_message_at"`
	SummarizedAt  int64  `json:"summarized_at"`
	Status        string `json:"status"`
}

ChatSummary is the stored cold-memory record for one chat.

type Client

type Client struct {
	BaseURL string
	HTTP    *http.Client
}

Client fetches from a registry.

func NewClient

func NewClient() *Client

NewClient builds a registry client for the configured registry.

func (*Client) Fetch

func (c *Client) Fetch(ctx context.Context, e RegistryEntry) ([]byte, error)

Fetch downloads an entry's artifact and checks it against the index.

The digest is checked HERE, before the bytes go anywhere near an installer. For a workflow the signature would catch a swap anyway; for a recipe there is no signature, so this is the only thing standing between a compromised CDN and an automation running on the operator's behalf.

func (*Client) Index

func (c *Client) Index(ctx context.Context) (*Index, error)

Index fetches and parses the catalogue.

type Entry

type Entry struct {
	Name         string    `json:"name"`
	Version      string    `json:"version"`
	SHA256       string    `json:"sha256"`
	Publisher    string    `json:"publisher"`
	Registry     string    `json:"registry,omitempty"`
	Tier         Tier      `json:"tier"`
	Host         []string  `json:"host"`
	Capabilities []string  `json:"capabilities"`
	Source       string    `json:"source,omitempty"`
	File         string    `json:"file"`
	InstalledBy  string    `json:"installed_by"`
	InstalledAt  time.Time `json:"installed_at"`
	Enabled      bool      `json:"enabled"`
}

Entry is one installed loop.

type Index

type Index struct {
	Version int             `json:"version"`
	Entries []RegistryEntry `json:"entries"`
	// RegistryKey is the countersigning key this registry uses. Published here
	// so `karmax loops trust-registry` can offer it — an operator still has to
	// accept it, since a key that vouches for itself vouches for nothing.
	RegistryKey string `json:"registry_key,omitempty"`
}

Index is a registry's catalogue.

func (*Index) Find

func (idx *Index) Find(name string) (RegistryEntry, bool)

Find returns one entry by name.

func (*Index) ShipWith

func (idx *Index) ShipWith() []RegistryEntry

ShipWith returns the entries a fresh install starts with.

type Installer

type Installer struct {
	Dir    string // ~/.karmax/loops
	Broker Store
	Trust  Trust
	// Actor is recorded in the lockfile, so "who installed this" has an answer.
	Actor string
}

Installer performs installs against a directory.

func (*Installer) Inspect

func (in *Installer) Inspect(data []byte) (*Preview, error)

Inspect verifies an artifact and reports what installing it would mean, without writing anything.

func (*Installer) Install

func (in *Installer) Install(data []byte) (*Preview, error)

Install writes the artifact, records it, and grants exactly what its manifest declared.

Order matters: the file is written before the lockfile so a lockfile entry never names a file that is not there, and grants are replaced wholesale so an upgrade that dropped a capability actually loses it.

func (*Installer) Installed

func (in *Installer) Installed() ([]Entry, error)

Installed lists what is in the lockfile.

func (*Installer) Load

func (in *Installer) Load(name string) (*Artifact, error)

Load reads an installed loop back, re-verifying it.

The digest is checked against the LOCKFILE, not just against the manifest inside the file — otherwise someone who replaced the artifact wholesale, with a matching self-consistent manifest, would pass. What was approved is what is recorded, and that is what has to still be true.

func (*Installer) Remove

func (in *Installer) Remove(name string) error

Remove uninstalls a loop and takes its capabilities with it.

type Kind

type Kind string

Kind distinguishes the two tiers.

const (
	// KindRecipe is a declarative YAML automation. No signature, no sandbox,
	// no install: it is read as data by KARMAX's own code.
	KindRecipe Kind = "recipe"
	// KindWorkflow is a signed WASM module.
	KindWorkflow Kind = "workflow"
)

type Kit

type Kit interface {
	Recall(query string, limit int) ([]string, error)
	Remember(fact string) error
	Notify(title, body string) error
	Ask(ctx context.Context, prompt string) (string, error)
	HTTP(ctx context.Context, method, url string, headers map[string]string, body string) (string, int, error)

	Config(key string) string
	HostTool(name string) string
	Harness(ctx context.Context, prompt string) (string, error)
	Gateway(ctx context.Context, prompt string, lend ...string) (string, error)
	Summarize(ctx context.Context, prompt string) (string, error)
	Propose(title, summary, action string) error
	Remind(title, due, notes string) error
	// Tool calls one of KARMAX's tools by name. Integrations reach a loop
	// through here and nowhere else, so adding one costs no ABI.
	Tool(ctx context.Context, name string, input map[string]any) (string, error)
	ShortSet(group, key, value string, ttlSeconds int) error
	ShortGet(group, key string) (string, bool, error)
	ShortAll(group string) ([]ShortMemory, error)
	ChatSummary(jid string) (*ChatSummary, error)
	SaveChatSummary(ChatSummary) error
	RunLoop(name string) error
	ShortForget(group, key string) error
	OperatorChats() []string
}

Kit is what the host does on the guest's behalf. Injected so the runner has no opinion about where memory or HTTP actually live.

type Lock

type Lock struct {
	Version int     `json:"version"`
	Entries []Entry `json:"loops"`
}

Lock is the whole file.

func LoadLock

func LoadLock(dir string) (*Lock, error)

LoadLock reads the lockfile. A missing file is an empty lock, not an error.

func (*Lock) Get

func (l *Lock) Get(name string) (Entry, bool)

Get returns an entry by name.

func (*Lock) Put

func (l *Lock) Put(e Entry)

Put adds or replaces an entry.

func (*Lock) Remove

func (l *Lock) Remove(name string) bool

Remove drops an entry, reporting whether it was there.

func (*Lock) Save

func (l *Lock) Save(dir string) error

Save writes the lockfile, sorted so a diff shows what changed rather than what moved.

type Manifest

type Manifest struct {
	Name        string `json:"name" yaml:"name"`
	Version     string `json:"version" yaml:"version"`
	Description string `json:"description" yaml:"description"`
	// Publisher is the Ed25519 key that signed this artifact.
	Publisher string `json:"publisher" yaml:"publisher"`

	// Host names the host functions the module may call. Anything not listed
	// is refused at runtime, not logged.
	Host []string `json:"host" yaml:"host"`
	// Tools names the KARMAX tools the module may call through the `tool` host
	// function. Every integration reaches a loop this way, so this list is the
	// whole of what it can touch outside its own sandbox.
	Tools []string `json:"tools,omitempty" yaml:"tools"`
	// Provides are tools this module IMPLEMENTS and lends to the agent. They
	// exist only while the agent is working on this loop's behalf.
	Provides []ToolSpec `json:"provides,omitempty" yaml:"provides"`
	// Capabilities are Broker grants in class:value form — "http:api.github.com",
	// "memory:nexus:write", "tool:comms.send".
	Capabilities []string `json:"capabilities" yaml:"capabilities"`

	// Triggers.
	Schedule string   `json:"schedule,omitempty" yaml:"schedule"`
	Events   []string `json:"events,omitempty" yaml:"events"`
	Webhook  string   `json:"webhook,omitempty" yaml:"webhook"`

	// Limits the operator can see before installing.
	MemoryMB int `json:"memory_mb,omitempty" yaml:"memory_mb"`

	// SHA256 is the hex digest of the module bytes, inside the signature so the
	// code cannot be swapped for different code under the same manifest.
	SHA256 string `json:"sha256" yaml:"sha256"`

	// Provenance.
	SourceURL string `json:"source_url,omitempty" yaml:"source_url"`
	BuiltAt   int64  `json:"built_at,omitempty" yaml:"built_at"`
}

Manifest is what the artifact declares about itself. It is inside the signature, so none of it can be changed after publishing.

The yaml tags are explicit because the manifest is also hand-written as loop.yaml, and yaml.v3 does not read json tags — it lowercases field names instead. Without them `memory_mb: 32` parsed as nothing at all and every loop silently took the default limit.

func (*Manifest) BuiltAtTime

func (m *Manifest) BuiltAtTime() time.Time

BuiltAtTime renders the build timestamp.

type Options

type Options struct {
	Namespace string
	Kit       Kit
	Grants    *broker.Handle
	Log       *zap.Logger
	// CacheDir persists compiled modules. Compilation is the expensive step —
	// 650ms cold against 26ms cached in the spike — so it is paid at install.
	CacheDir string
}

Options configure a Runner.

type Preview

type Preview struct {
	Manifest Manifest
	Verdict  *Verdict
	// Grants is the plain-English list of what it will be allowed to do.
	Grants []string
	// Diff is set when this replaces an installed version.
	Diff *CapabilityDiff
	// Upgrade is the version being replaced, if any.
	Upgrade string
}

Preview is what the operator is shown before anything is written.

type RegistryEntry

type RegistryEntry struct {
	Name        string   `json:"name"`
	Kind        Kind     `json:"kind"`
	Version     string   `json:"version"`
	Description string   `json:"description"`
	Author      string   `json:"author,omitempty"`
	Tags        []string `json:"tags,omitempty"`
	// Source is the path in the registry repo, for anyone who wants to read it
	// before installing — which for an unsigned tier is the only review there is.
	Source string `json:"source,omitempty"`
	// Artifact is where the installable bytes are. Relative paths resolve
	// against the registry base; a workflow usually points at a release.
	Artifact string `json:"artifact"`
	// SHA256 pins the artifact. For a workflow this is belt and braces over the
	// signature; for a recipe it is the only integrity check there is, which
	// makes it the more important of the two.
	SHA256 string `json:"sha256,omitempty"`
	// ShipWithKARMAX marks what a fresh install starts with.
	ShipWithKARMAX bool `json:"ship_with_karmax,omitempty"`
	// Requires names tools the entry expects to exist, so "this needs WhatsApp
	// set up" is answerable before installing rather than after it fails.
	Requires []string `json:"requires,omitempty"`
}

RegistryEntry is one installable thing in a registry, distinct from the lockfile Entry that records what is actually installed here.

type Role

type Role string

Role says which signature this is.

const (
	RolePublisher Role = "publisher"
	RoleRegistry  Role = "registry"
)

type Runner

type Runner struct {
	// contains filtered or unexported fields
}

Runner executes one loop's module.

func NewRunner

func NewRunner(ctx context.Context, a *Artifact, opts Options) (*Runner, error)

NewRunner compiles a verified artifact.

It takes an Artifact rather than bytes, because an Artifact is what Verify produces, and there is no way to reach this with something unverified except by constructing one deliberately.

func (*Runner) Close

func (r *Runner) Close(ctx context.Context) error

Close releases the runtime.

func (*Runner) InvokeTool

func (r *Runner) InvokeTool(ctx context.Context, name string, input map[string]any, timeout time.Duration) (string, error)

InvokeTool runs one of the workflow's provided tools.

It takes toolMu, NOT the mutex Run holds for the whole of a run. A workflow whose `ask` triggers its own provided tool would otherwise deadlock against itself on the very first call — the run holding the lock, waiting on an agent turn, waiting on this.

func (*Runner) ProvidedTools

func (r *Runner) ProvidedTools() []ToolSpec

ProvidedTools are the tools this workflow implements, from its signed manifest. Empty for a workflow that provides none, which is most of them.

func (*Runner) Run

func (r *Runner) Run(ctx context.Context, timeout time.Duration) error

Run instantiates the module and calls its entry point.

A fresh instance per run: the spike put that at 3.4ms, which is a good price for a loop that cannot carry state or corruption from the last one into the next. Run executes the module for one trigger.

func (*Runner) RunTriggered

func (r *Runner) RunTriggered(ctx context.Context, timeout time.Duration, kind string, payload map[string]any) error

RunTriggered is Run with the trigger the guest can read back.

type ShortMemory

type ShortMemory struct {
	Key   string `json:"key"`
	Value string `json:"value"`
}

ShortMemory is one short-term working note.

type Signature

type Signature struct {
	Role Role   `json:"role"`
	Key  string `json:"key"`
	Sig  string `json:"sig"`
}

Signature is one attestation over the manifest.

func Sign

func Sign(m *Manifest, role Role, priv ed25519.PrivateKey) Signature

Sign produces a signature over a manifest.

type Store

type Store interface {
	SaveGrant(store.Grant) error
	RevokeSubject(string) (int64, error)
}

Store is what install needs to record grants. An interface so the steps can be tested without a database.

type Tier

type Tier string

Tier is how much an artifact was vouched for.

const (
	// TierRegistry is countersigned by a registry key the operator trusts,
	// which means it went through review.
	TierRegistry Tier = "registry"
	// TierCommunity carries only a publisher signature. It runs, loudly, with
	// reduced defaults.
	TierCommunity Tier = "community"
	// TierUntrusted carries no valid signature at all. Nothing is known about
	// where it came from — only that the bytes match the manifest describing
	// them. It exists so a developer can run what they just built without
	// generating a key first, and it is never reached by accident.
	TierUntrusted Tier = "untrusted"
)

type ToolSpec

type ToolSpec struct {
	Name        string          `json:"name" yaml:"name"`
	Description string          `json:"description" yaml:"description"`
	Parameters  json.RawMessage `json:"parameters,omitempty" yaml:"-"`
}

ToolSpec is a tool a module implements, in the shape the agent needs to call it. Inside the signature, so a module cannot widen its own description after the operator approved it.

func (*ToolSpec) UnmarshalYAML

func (t *ToolSpec) UnmarshalYAML(n *yaml.Node) error

UnmarshalYAML lets a manifest write the JSON Schema as ordinary YAML. The canonical form stays JSON — that is what is signed and what the model is shown — so the schema is converted here rather than carried in two shapes.

type Trust

type Trust struct {
	// Registries are the countersigning keys this instance trusts. Empty means
	// no artifact can reach registry tier.
	Registries []string
	// Revoked lists publisher or registry keys that must be refused, whatever
	// else is true of the artifact.
	Revoked []string
	// AllowCommunity permits publisher-only artifacts. Off by default: running
	// unreviewed code from a stranger should be a decision, not a default.
	AllowCommunity bool
	// AllowUntrusted accepts an artifact with no valid signature.
	//
	// Deliberately NOT a setting. It is set for ONE install, by an operator who
	// typed the loop's name to confirm, and it is not written to trust.json —
	// which is the difference between "I am running the thing I just built" and
	// "this machine now accepts anything". The global equivalent would be a
	// switch nobody remembers flipping.
	AllowUntrusted bool
}

Trust is the operator's configuration for what to accept.

func LoadTrust

func LoadTrust(dir string) Trust

LoadTrust reads the trust configuration, merging the environment on top.

type Verdict

type Verdict struct {
	Tier Tier
	// Publisher is the key that signed it.
	Publisher string
	// Registry is the countersigning key, when there is one.
	Registry string
}

Verdict is the result of verifying an artifact.

func Verify

func Verify(a *Artifact, t Trust) (*Verdict, error)

Verify checks an artifact end to end and reports what it may be trusted as.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL