Zum Inhalt springen
Developer Preview — APIs and language features may change before 1.0

Building Effect Machines

Dieser Inhalt ist noch nicht in deiner Sprache verfügbar.

In mashin, all external interaction is expressed as an intent. The runtime mediates each intent before it becomes an action. Effect machines are the services that fulfill those intents. The standard library provides common effects like HTTP requests and file operations. But when you need something specific, such as calling a proprietary API, running a Python ML model, or interfacing with a hardware device, you build an external effect machine.

An external effect machine is a small HTTP service that speaks mashin’s three-endpoint contract. You can write it in any language. mashin calls it like any other effect, with full governance: permission checks, audit logging, and budget enforcement.

The mental model

Your machine (.mashin) Your effect service (any language)
┌──────────────────────┐ ┌──────────────────────┐
│ │ HTTP POST │ │
│ ask analyze, from: │ ───────────────► │ POST /execute │
│ "my-ml-service" │ │ Your Python/Go/etc │
│ │ ◄─────────────── │ logic runs here │
│ (governed, audited) │ JSON response │ │
└──────────────────────┘ └──────────────────────┘

Your .mashin file calls the effect with ask ... from:. The runtime checks permissions, calls your service’s /execute endpoint, records the result in the behavioral ledger, and returns the output to the next step.

Step 1: Scaffold the effect service

Use the CLI to generate a starting point:

Terminal window
mashin effect init python my_analyzer

This creates a directory with a working effect service you can edit. Supported languages: python, typescript, go, rust, swift, elixir.

Or start from scratch using one of the SDKs.

Step 2: Implement your logic

Every effect service has two responsibilities:

  1. execute: The actual work. Receives input, returns output.
  2. governance: Declares what the service does, what it needs, and what it returns.

Here is a Python example that wraps a sentiment analysis API:

from mashin_effects import EffectService
class SentimentEffect(EffectService):
name = "sentiment"
version = "1.0.0"
capabilities = ["external_api_call"]
inputs = [
{"name": "text", "type": "string", "required": True},
{"name": "language", "type": "string", "required": False}
]
outputs = [
{"name": "sentiment", "type": "string"},
{"name": "confidence", "type": "number"}
]
def execute(self, input, context):
text = input["text"]
lang = input.get("language", "en")
# Call your internal API, ML model, database, etc.
result = self._analyze(text, lang)
return {
"sentiment": result["label"],
"confidence": result["score"]
}
def _analyze(self, text, lang):
# Your actual logic here
return {"label": "positive", "score": 0.92}
if __name__ == "__main__":
SentimentEffect().serve(port=8080)

The key design points:

  • Capabilities declare what kind of effect this is. Common values: external_api_call, file_system, database, compute, network.
  • Inputs and outputs declare the schema. mashin uses these for validation and documentation.
  • The service runs as a standalone HTTP server. mashin does not embed your code; it calls your service over HTTP.

Step 3: Start and test the service

Start the service:

Terminal window
python my_analyzer/server.py
# Or: mashin effect start my_analyzer/server.py

Test it:

Terminal window
# Quick test via CLI
mashin effect test http://localhost:8080
# Or manually
curl http://localhost:8080/governance
curl -X POST http://localhost:8080/execute \
-H "Content-Type: application/json" \
-d '{"input": {"text": "This is wonderful"}, "context": {}}'

The /governance endpoint should return your declaration. The /execute endpoint should return structured output.

Step 4: Register the effect service

Tell your cell about the service:

Terminal window
mashin effect register http://localhost:8080

This reads the /governance endpoint and registers the service in your cell’s effect registry. Now machines can call it by name.

Step 5: Call it from a machine

machine analyze_feedback
accepts
feedback as text, is required
responds with
sentiment as text
confidence as number
ensures
permissions
allowed to
machine.call
implements
ask result, from: "sentiment"
text: input.feedback
returns
sentiment as text
confidence as number
assuming
sentiment: "positive"
confidence: 0.92

The from: "sentiment" matches the name in your governance declaration. The assuming block provides mock values for testing.

Run it:

Terminal window
# With mock values (no API key needed)
mashin test analyze_feedback.mashin
# Live execution (calls your running service)
mashin run analyze_feedback.mashin --input '{"feedback": "Great product!"}'

The governance declaration

The governance declaration is what makes external effects governable. When your service responds to GET /governance, it tells mashin:

{
"name": "sentiment",
"version": "1.0.0",
"capabilities": ["external_api_call"],
"inputs": [
{"name": "text", "type": "string", "required": true}
],
"outputs": [
{"name": "sentiment", "type": "string"},
{"name": "confidence", "type": "number"}
]
}

mashin uses this to:

  • Check permissions: The calling machine must have machine.call permission and any capability-specific permissions
  • Validate inputs: The runtime checks that the machine sends the right fields
  • Record in the ledger: The behavioral ledger records what was called, with what inputs, and what came back
  • Generate documentation: mashin info and koda use the declaration to describe the effect

Real-world patterns

Wrapping an internal API

class CRMEffect(EffectService):
name = "crm-lookup"
version = "1.0.0"
capabilities = ["external_api_call"]
inputs = [{"name": "customer_id", "type": "string", "required": True}]
outputs = [{"name": "name", "type": "string"}, {"name": "tier", "type": "string"}]
def execute(self, input, context):
resp = requests.get(f"https://internal.crm.company.com/customers/{input['customer_id']}")
data = resp.json()
return {"name": data["full_name"], "tier": data["support_tier"]}

Running a Python ML model

class ClassifierEffect(EffectService):
name = "document-classifier"
version = "2.0.0"
capabilities = ["compute"]
inputs = [{"name": "text", "type": "string", "required": True}]
outputs = [{"name": "category", "type": "string"}, {"name": "probabilities", "type": "map"}]
def __init__(self):
self.model = load_model("./classifier.pkl")
def execute(self, input, context):
prediction = self.model.predict(input["text"])
return {"category": prediction.label, "probabilities": prediction.probs}

Hardware or device integration (Go)

type GPIOEffect struct{}
func (g GPIOEffect) Execute(input, context map[string]any) (map[string]any, error) {
pin := int(input["pin"].(float64))
state := input["state"].(string)
err := gpio.Set(pin, state == "high")
if err != nil {
return nil, err
}
return map[string]any{"pin": pin, "state": state, "ok": true}, nil
}

Deployment

Effect services are standalone processes. Deploy them however you deploy any service:

  • Local development: mashin effect start runs the script directly
  • Docker: Package as a container, register the container URL
  • Cloud: Deploy to any cloud platform, register the public URL
  • Same machine as cell: Run alongside the cell process

The only requirement is that the cell can reach the service over HTTP.

Next steps