Agents reacting to resource creation
The passive mams-summer and mams-pong examples demonstrate how resource events can trigger work and further HTTP interactions. The HTTP acknowledgement of a new item is separate from the work the agent performs after discovering it.
Calculate a sum
Download the sum project, extract it and run mvn clean compile astra:deploy.
The service publishes /main/sums. Each child has two input fields and a result field:
public class Sum {
public int left;
public int right;
public int total;
}
The agent reacts to the belief added when the passive library discovers and focuses on a new child:
agent Main extends mams.javatypes.Passive {
module ObjectAccess oa;
constant string TYPE = "Sum";
constant string LIST_NAME = "sums";
plan +!main(list args) {
MAMSAgent::!setup();
MAMSAgent::!created("base");
Passive::!listResource(LIST_NAME, TYPE);
}
plan +itemResource(string artifact_name, TYPE)
: artifact(artifact_name, artifact_name, cartago.ArtifactId id) {
!getObject(artifact_name, Sum sum);
int total = oa.getInt(sum, "left") + oa.getInt(sum, "right");
oa.set(sum, "total", total);
!updateObject(artifact_name, sum);
}
}
getObject reconstructs the child’s Java object. ObjectAccess reads left and right and sets total. updateObject sends the modified object back to the artifact. Editing the local Java object alone would leave the published resource unchanged.
Submit a calculation:
curl -i -X POST \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-H 'Slug: sum1' \
-d '{"left":4,"right":5}' \
http://localhost:9000/main/sums
Expect 201 Created with the child URI in Location. Read it:
curl -i -H 'Accept: application/json' \
http://localhost:9000/main/sums/sum1
After the agent has processed the creation, the representation includes:
{"left":4,"right":5,"total":9}
An immediate read can occur before the calculation finishes, so read again if total is still its initial value. This example computes on creation; changing an existing child’s input fields does not trigger another calculation. A service that supports recalculation must also handle updates and define how clients distinguish pending work from completed results.
Ping-pong through resources
Download the ping-pong project, extract it and run mvn clean compile astra:deploy. It hosts two exposed agents in one application:
Agent |
Collection |
Reaction to a new child |
|---|---|---|
Pinger |
|
POST a reply to the callback URI carried by the child |
Ponger |
|
POST a reply until its ten-round limit is reached |
Each message is a resource whose uri field identifies the sender’s collection, so the receiver knows where to POST its reply:
public class Message {
public String uri;
}
The main agent starts the shared web server once, creates both agents, and starts Pinger. Pinger tells Main when its resource body is ready; Main then starts Ponger with Pinger’s base URI:
agent Main extends mams.MAMSAgent {
types startup {
formula ready(string);
}
plan +!main(list args) {
mams.MAMSAgent::!setup();
S.createAgent("pinger", "Pinger");
S.createAgent("ponger", "Ponger");
S.setMainGoal("pinger", []);
}
plan @message(inform, "pinger", ready(string Uri)) {
S.setMainGoal("ponger", [Uri]);
}
}
The startup message is local ASTRA coordination. The ping-pong exchange itself uses HTTP POST and passive resource creation events:
import com.fasterxml.jackson.databind.JsonNode;
import mams.web.HttpResponse;
agent Pinger extends mams.javatypes.Passive {
module mams.JSONBuilder builder;
module Strings strings;
constant string TYPE = "Message";
constant string LIST_NAME = "pings";
constant string URI = "uri";
types startup {
formula ready(string);
}
plan +!main(list args) {
MAMSAgent::!init();
MAMSAgent::!created("base");
Passive::!listResource(LIST_NAME, TYPE);
!agentUri(string Uri);
string LocalUri = strings.replaceAll(Uri,
"^https?://[^/]+", "http://localhost:9000");
send(inform, "main", ready(LocalUri));
}
plan +itemResource(string artifact_name, TYPE) {
!itemProperty(artifact_name, URI, funct value);
!pinged(F.valueAsString(value, 0));
}
plan +!pinged(string pongerUri) :
artifact(LIST_NAME, string artifact_name, cartago.ArtifactId id) &
uri(artifact_name, string pinger_uri) {
JsonNode node = builder.createObject();
string LocalUri = strings.replaceAll(pinger_uri,
"^https?://[^/]+", "http://localhost:9000");
builder.addProperty(node, URI, LocalUri);
mams.MAMSAgent::!post(pongerUri,
builder.toJsonString(node),
HttpResponse response
);
if (httpUtils.hasCode(response, 201)) {
string uri = httpUtils.header(response, "Location");
} else {
S.fail();
}
}
}
import com.fasterxml.jackson.databind.JsonNode;
import mams.web.HttpResponse;
agent Ponger extends mams.javatypes.Passive {
module mams.JSONBuilder builder;
module Strings strings;
constant string TYPE = "Message";
constant string LIST_NAME = "pongs";
constant string URI = "uri";
types ponger {
formula pongs(int);
}
initial pongs(0);
plan +!main([string pingerUri]) {
MAMSAgent::!init();
MAMSAgent::!created("base");
Passive::!listResource(LIST_NAME, TYPE);
!ponged(pingerUri+"/pings");
}
plan +itemResource(string artifact_name, TYPE) : pongs(10) {
cartago.println("10 PONGS!!!");
}
plan +itemResource(string artifact_name, TYPE) : pongs(int X) {
-+pongs(X+1);
!itemProperty(artifact_name, URI, funct value);
!ponged(F.valueAsString(value, 0));
}
plan +!ponged(string pingerUri) :
artifact(LIST_NAME, string artifact_name, cartago.ArtifactId id) &
uri(artifact_name, string ponger_uri) {
JsonNode node = builder.createObject();
string LocalUri = strings.replaceAll(ponger_uri,
"^https?://[^/]+", "http://localhost:9000");
builder.addProperty(node, URI, LocalUri);
mams.MAMSAgent::!post(pingerUri,
builder.toJsonString(node),
HttpResponse response
);
if (httpUtils.hasCode(response, 201)) {
string uri = httpUtils.header(response, "Location");
} else {
S.fail();
}
}
}
Because both workers and the web server share this local application, callback URIs preserve their resource paths while using http://localhost:9000. This avoids relying on the JVM-resolved host address being reachable. Distributed deployments need reachable service addresses instead.
Both workers use init() to find the already-created server and REST client. Each creates its own base and collection. Ponger initiates the exchange by POSTing its callback URI to Pinger’s pings collection. A new child adds an itemResource belief, triggering the receiving agent to read uri and POST a reply.
The run prints 10 PONGS!!! when Ponger stops replying. The collections remain available for inspection:
curl -H 'Accept: application/json' http://localhost:9000/pinger/pings
curl -H 'Accept: application/json' http://localhost:9000/ponger/pongs
Stop the application with Ctrl+C. The example retains its message resources; a long-running application would need a policy for removing them. Keeping both agents in one service is convenient here, while the use of HTTP illustrates the interaction mechanism available across service boundaries.