Ir al contenido
Developer Preview — APIs and language features may change before 1.0

Building Action Services

Esta página aún no está disponible en tu idioma.

In mashin, all external interaction is expressed as an intent. The runtime mediates each intent before it becomes an action. Action services are the services that fulfill those intents. The standard library provides common actions 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 action service.

An external action service 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 action, with full governance: permission checks, audit logging, and budget enforcement.

Your machine (.mashin) Your action 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 action 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.

Use the CLI to generate a starting point:

Terminal window
mashin action init python my_analyzer

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

Or start from scratch using one of the SDKs.

Every action 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 SentimentService(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__":
SentimentService().serve(port=8080)

The key design points:

  • Capabilities declare what kind of action 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.

Start the service:

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

Test it:

Terminal window
# Quick test via CLI
mashin action 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.

Tell your computer about the service:

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

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

machine analyze_feedback
accepts
feedback as text, is required
responds with
sentiment as text
confidence as number
behaves
ask result, from: "sentiment"
text: input.feedback
returns
sentiment as text
confidence as number
ensures
permissions
allowed to
call
verifies
test "delegates to the sentiment machine"
assuming result {sentiment: "positive", confidence: 0.92}
given {feedback: "Great product"}
expect {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 is what makes external actions 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 action
class CRMService(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"]}
class ClassifierService(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}
type GPIOService struct{}
func (g GPIOService) 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
}

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

  • Local development: mashin action 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 computer: Run alongside the computer process

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