Quickstart
Build with an agent
Paste the instruction into your coding harness, or follow the steps below.
Read the instruction
Build a small Aster Python service and client in the current workspace.
Sources:
- https://sdk.getaster.now/docs/quickstart/python
- https://sdk.getaster.now/docs/bindings/python
- https://github.com/aster-rpc/aster-rpc
Read the linked Python quickstart before writing code. Use a published package
version compatible with that guide, and record the exact Aster and Python
versions you install. If the published package and guide disagree, identify the
mismatch and stop rather than inventing APIs or using private dependencies.
Create an isolated environment and a project containing request and response
types, a HelloService with a say_hello method, a producer, and a consumer. The
producer should print its Aster address. The consumer should take that address
as configuration, call the service with the name World, and print Hello, World!
Use the guide's development configuration for this local demonstration. Do not
describe it as a production authorization setup. Do not open public firewall
ports, commit credentials, or change machine-wide networking settings.
Start the producer locally, capture its actual address, and run the consumer
against it. Confirm the expected response using the real Aster connection. Add
a short README with installation, startup, consumer, and shutdown commands.
Record dependency versions so the project can be reproduced. Stop the processes
you started after verification unless I ask you to keep them running.
Report the files created, installed versions, commands run, observed response,
and any remaining problems. If network access, native package installation, or
execution is unavailable, report that limit; do not claim the example passed.
This prompt is a draft for the documentation design preview and has not yet been
validated in a coding harness.
This instruction has not yet been tested in a harness. It is included here to review the tutorial format.
Write a service on one machine, call it from another – even if they’re on different networks, behind firewalls, with no shared infrastructure between them. In under two minutes.
Prerequisites
Section titled “Prerequisites”- Python 3.9 – 3.13 (3.14+ is not yet supported)
- pip, uv, or any PEP 517 installer
- Node.js 20+ or Bun 1.0+
- TypeScript 5.0+ (decorators require
experimentalDecorators)
Install
Section titled “Install”pip install aster-rpcOr with uv:
uv pip install aster-rpc# bun (recommended)bun add @aster-rpc/aster
# or npmnpm install @aster-rpc/asterDefine a service
Section titled “Define a service”A service is a class that exposes one or more methods callable by remote consumers. You write it like a regular class; Aster handles the wire format, transport, and remote dispatch.
Create hello_service.py:
from dataclasses import dataclassfrom aster import service, rpc
@dataclassclass HelloRequest: name: str = ""
@dataclassclass HelloResponse: message: str = ""
@serviceclass HelloService: @rpc async def say_hello(self, req: HelloRequest) -> HelloResponse: return HelloResponse(message=f"Hello, {req.name}!")Three decorators: @dataclass (standard Python), @service (marks the class as an Aster service), @rpc (marks a method as a callable endpoint). No schema files, no code generation, no base classes. Wire tags are auto-derived from the class names; for production cross-language services you’ll add explicit @wire_type("ns/Type") decorators – see Defining Services and Types.
Create service.ts:
import { Service, Rpc } from '@aster-rpc/aster';
class HelloRequest { name = ""; constructor(init?: Partial<HelloRequest>) { if (init) Object.assign(this, init); }}
class HelloResponse { message = ""; constructor(init?: Partial<HelloResponse>) { if (init) Object.assign(this, init); }}
@Service({ name: "HelloService", version: 1 })export class HelloService { @Rpc() async sayHello(req: HelloRequest): Promise<HelloResponse> { return new HelloResponse({ message: `Hello, ${req.name}!`, }); }}Two decorators: @Service (marks the class as an Aster service), @Rpc (marks a method as a callable endpoint). No schema files, no base classes. Wire tags are auto-derived from the class names; for production cross-language services you’ll add explicit @WireType("ns/Type") decorators – see Defining Services and Types.
Before running, generate type metadata with npx aster-gen — this reads your TypeScript source and emits aster-rpc.generated.ts, which AsterServer auto-imports on startup. See TypeScript Build Setup for details and bundler plugin options.
Run a producer
Section titled “Run a producer”A producer is the node that hosts the service.
Create producer.py:
import asynciofrom hello_service import HelloServicefrom aster import AsterServer
async def main(): async with AsterServer(services=[HelloService()]) as srv: print("Producer ready at:", srv.address) await srv.serve()
asyncio.run(main())python producer.pyGenerate type metadata, then create producer.ts:
npx aster-genimport { AsterServer } from '@aster-rpc/aster';import { HelloService } from './service.js';
const server = new AsterServer({ services: [new HelloService()],});await server.start();console.log("Producer ready at:", server.address);await server.serve();node producer.tsIn dev mode (no ASTER_* environment variables set), the server:
- Generates an ephemeral root key and node identity (no files needed).
- Opens the consumer gate so consumers can connect without credentials.
- Serves RPC, blobs, docs, and gossip on a single endpoint.
- Prints an
aster1...address for consumers to connect to.
Run a consumer
Section titled “Run a consumer”A consumer is the node that calls into a producer’s services. Consumers don’t need access to the service’s source code – they discover methods from the producer’s published contract at runtime, then call them by name.
The producer and consumer don’t have to live on the same machine, or even the same network. Your producer might be in a cloud environment behind a corporate firewall; your consumer might be on your laptop on coffee shop Wi-Fi, or on a Raspberry Pi behind your home router, or on a phone tethered to a 5G connection. No VPN to set up, no firewall rule to add, no port to forward, no DNS entry to register. The address the consumer uses is the producer’s public key – Aster figures out the network path between them, direct if it can and relayed if it has to.
Create consumer.py:
import asynciofrom aster import AsterClient
async def main(): async with AsterClient() as c: # The dynamic proxy speaks JSON and needs no local type definitions -- # methods are discovered from the producer's published contract. hello = c.proxy("HelloService") resp = await hello.say_hello({"name": "World"}) print(resp["message"]) # Hello, World!
asyncio.run(main())Run it, passing the producer’s address:
# macOS / LinuxASTER_ENDPOINT_ADDR=<paste from producer output> python consumer.py# Windows (PowerShell)$env:ASTER_ENDPOINT_ADDR="<paste from producer output>"; python consumer.pyAsterClient reads ASTER_ENDPOINT_ADDR from the environment and connects to the producer. c.proxy("HelloService") returns a dynamic stub – call methods on it like regular async functions. If you’d rather have typed classes (for IDE autocomplete and static checking), share the service definition module between producer and consumer and use c.client(HelloService) instead – see Defining Services and Types.
Create consumer.ts:
import { AsterClientWrapper } from '@aster-rpc/aster';
const client = new AsterClientWrapper({ address: process.env.ASTER_ENDPOINT_ADDR!,});await client.connect();
// The dynamic proxy speaks JSON and needs no local type definitions --// methods are discovered from the producer's published contract.const hello = client.proxy("HelloService");const resp = await hello.sayHello({ name: "World" });console.log(resp.message); // Hello, World!
await client.close();Run it, passing the producer’s address:
# macOS / LinuxASTER_ENDPOINT_ADDR=<paste from producer output> node consumer.ts# Windows (PowerShell)$env:ASTER_ENDPOINT_ADDR="<paste from producer output>"; node consumer.tsWhat just happened
Section titled “What just happened”Three steps, three things to notice:
-
The producer is a server process holding
HelloService. When it starts, it prints anaster1...address. That address is the node’s public key, not a hostname or an IP – it’s stable, globally unique, and can’t be impersonated. -
The consumer connects by that public key. No DNS lookup, no port forwarding, no certificate to trust. The consumer doesn’t even need a copy of the service source code – it asks the producer what methods exist and builds a stub at runtime.
-
The producer and consumer don’t have to be on the same machine, or even the same network. In this quickstart they’re both on your laptop because that’s the simplest demo. But the address is a public key, so you can run the producer on your homelab, your VPS, or a Raspberry Pi behind your home router, and call it from your laptop on coffee shop Wi-Fi without changing a single line of code. Aster handles NAT traversal and chooses the best network path – direct if it can, relayed if it has to.
To try this for real, run producer.py on one machine and consumer.py on another. Copy the aster1... address from the producer’s output, paste it into the consumer’s ASTER_ENDPOINT_ADDR, and the call will arrive over whatever network path actually exists between the two machines. No tunnels, no proxies, no shared secrets.
What’s next
Section titled “What’s next”Build a real service end-to-end with the Mission Control walkthrough – 30 minutes, six chapters, four streaming patterns, capability-scoped auth, cross-language interop, and a working MCP agent demo at the end.