Akka Specify builds any kind of AI system from requirements you already have. You describe the outcome, and Akka generates, governs, and runs the system for you.
Data scientists, product managers, citizen developers, and engineers can all take part, supplying their requirements as text, spreadsheets, graphics, or diagrams.
$ claude
> /akka:setup
✓ Akka CLI · Java · Maven · governance floor
> /akka:specify greeter agent — greet each user
in a new language every turn, per session
Inferring definition of done… 17 conditions
NEEDS_INPUT → 3 decisions to close
> /akka:specify keep 80%, p95 < 5 ms, API-only
Definition of done locked. Building…
plan → tasks → implement → verify
✓ compiles ✓ 6/6 tests ✓ p95 4.1 ms
> /akka:status
READY_TO_SHIP — completion policy met
The component client is how one component calls another. You get a type-safe handle to an entity, workflow, view, agent, or timer and invoke it directly.
Calls are location transparent, so the target can run on any node in the cluster and the runtime routes to it. You choose synchronous or asynchronous invocation, and the components it calls provide durability and recovery.
public class CounterEndpoint {
private final ComponentClient componentClient;
public CounterEndpoint(ComponentClient componentClient) {
this.componentClient = componentClient;
}
@Get("/{id}")
public Integer get(String id) {
return componentClient.forEventSourcedEntity(id)
.method(CounterEntity::get).invoke();
}
}
An agent interacts with an AI model to perform a specific task. It keeps contextual history in session memory, which can be shared with other agents working toward the same goal.
It can expose function tools and call them when the model asks for them.
@Component(id = "my-agent")
public class MyAgent extends Agent {
public Effect<String> query(String question) {
return effects()
.systemMessage("You are a helpful assistant.")
.userMessage(question)
.thenReply();
}
}
A model-driven component that runs as a durable process. It works on typed tasks, each with its own instructions and result schema, and the runtime drives the model through a decision loop until the task is done. Agent and task state persist as it goes, so work survives crashes and restarts.
An agent can delegate to specialists, hand off to peers, or lead a team sharing a task list, so multi-agent systems are assembled from focused agents without writing orchestration code.
@Component(id = "consulting-coordinator",
description = "Routes each problem to the right expertise")
public class ConsultingCoordinator extends AutonomousAgent {
@Override
public AgentDefinition definition() {
return define()
.tools(new ConsultingTools())
.capability(TaskAcceptance.of(ENGAGEMENT)
.canHandoffTo(SeniorConsultant.class))
.capability(Delegation.to(Researcher.class)
.maxParallelWorkers(2))
.capability(Delegation.to(FactCheckAgent.class));
}
}
Workflows run long-running, multi-step business processes while you write only the domain logic. They provide durability and consistency, and can call other components and services.
A business transaction lives in one place, and the workflow either keeps it moving or rolls it back when a step fails.
@Component(id = "transfer")
public class TransferWorkflow extends Workflow<TransferState> {
public Effect<Done> startTransfer(Transfer transfer) {
return effects().updateState(new TransferState(transfer))
.transitionTo(TransferWorkflow::withdrawStep)
.thenReply(done());
}
}
Instead of storing the current state, these persist every event that led to it, written to a journal with ACID semantics.
State is rebuilt by replaying those events, which scales horizontally and isolates failures, and gives you a complete history of how the state came to be.
@Component(id = "shopping-cart")
public class ShoppingCartEntity
extends EventSourcedEntity<Cart, Event> {
public Effect<Done> addItem(LineItem item) {
return effects()
.persist(new Event.ItemAdded(item))
.thenReply(state -> Done.getInstance());
}
}
These persist state as a single current value, keyed by id.
Akka guarantees exactly one instance of each entity across the whole cluster, so commands are handled one at a time with no concurrency to reason about. Active state is held in memory and recovered from durable storage after a restart or rebalance.
@Component(id = "counter")
public class CounterEntity extends KeyValueEntity<Counter> {
public Effect<Counter> plusOne() {
Counter c = currentState().increment(1);
return effects().updateState(c).thenReply(c);
}
}
Views let you read across many entities, or find an entity by something other than its id.
Each view is built for a specific access pattern and updates as the underlying entity state changes.
@Component(id = "customers-by-email")
public class CustomersByEmail extends View {
@Consume.FromKeyValueEntity(CustomerEntity.class)
public static class Updater extends TableUpdater<Customer> {}
@Query("SELECT * FROM customers WHERE email = :email")
public QueryEffect<Customer> getCustomer(String email) {
return queryResult();
}
}
An endpoint is how a service is exposed to the outside world. HTTP endpoints accept and return JSON by default.
Lower-level APIs are available when you need full control over what data is accepted and returned.
@HttpEndpoint("/example")
public class ExampleEndpoint extends AbstractHttpEndpoint {
@Get("/hello/{name}")
public String hello(String name) {
return "Hello " + name;
}
}
gRPC endpoints expose a service through protobuf contracts defined in .proto files, so the service contract is explicit rather than implied.
Binary serialization and protobuf's forward and backward compatibility make service-to-service calls fast and safe to evolve without breaking existing clients.
@GrpcEndpoint
public class CustomerGrpcEndpointImpl
implements CustomerGrpcEndpoint {
public Customer getCustomer(GetCustomerRequest in) {
return Customer.newBuilder().setName("Alice").build();
}
}
MCP endpoints expose a service to MCP clients, including LLM desktop applications and agents running on other services.
What you expose becomes tools the model can call on its own behalf.
@McpEndpoint(serverName = "sample", serverVersion = "0.0.1")
public class ExampleMcpEndpoint {
@McpTool(name = "add", description = "Adds two numbers")
public String add(int n1, int n2) {
return Integer.toString(n1 + n2);
}
}
Timers schedule a call to run later, which is useful for checking whether something completed after the fact.
They are stored by the runtime and guaranteed to run at least once: if the call fails, the timer reschedules itself until it succeeds.
@Component(id = "order-timed-action")
public class OrderTimedAction extends TimedAction {
public Effect expireOrder(String orderId) {
// cancel the order if it is still pending
return effects().done();
}
}
Consumers read a stream of events, from an entity's journal, from key value state changes, or from a message broker topic.
They also produce events outward, which is how an Akka service interacts with systems beyond it.
@Component(id = "counter-events")
@Consume.FromEventSourcedEntity(CounterEntity.class)
public class CounterEventsConsumer extends Consumer {
public Effect onEvent(CounterEvent event) {
return switch (event) {
case ValueIncreased v -> effects().done();
default -> effects().ignore();
};
}
}


