Building Action Services
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. 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.
The mental model
Section titled “The mental model”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.
Step 1: Scaffold the action service
Section titled “Step 1: Scaffold the action service”Use the CLI to generate a starting point:
mashin action init python my_analyzerThis 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.
Step 2: Implement your logic
Section titled “Step 2: Implement your logic”Every action service has two responsibilities:
execute: The actual work. Receives input, returns output.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.
Step 3: Start and test the service
Section titled “Step 3: Start and test the service”Start the service:
python my_analyzer/server.py# Or: mashin action start my_analyzer/server.pyTest it:
# Quick test via CLImashin action test http://localhost:8080
# Or manuallycurl http://localhost:8080/governancecurl -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 action service
Section titled “Step 4: Register the action service”Tell your computer about the service:
mashin action register http://localhost:8080This reads the /governance endpoint and registers the service in your computer’s action registry. Now machines can call it by name.
Step 5: Call it from a machine
Section titled “Step 5: Call it from a machine”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:
# 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
Section titled “The governance declaration”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.callpermission 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 infoand koda use the declaration to describe the action
Real-world patterns
Section titled “Real-world patterns”Wrapping an internal API
Section titled “Wrapping an internal API”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"]}Running a Python ML model
Section titled “Running a Python ML model”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}Hardware or device integration (Go)
Section titled “Hardware or device integration (Go)”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}Deployment
Section titled “Deployment”Action services are standalone processes. Deploy them however you deploy any service:
- Local development:
mashin action startruns 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.
Next steps
Section titled “Next steps”- Action SDKs - SDK reference for all six languages
- Actions guide - Calling actions from machines
- CLI Action Commands - Managing action services