Skip to content
PlystraCore

In development · not yet released

Compose the backend first. Write code only where you differ.

Compose a backend from dependencies. Replace any default with ordinary Go. Built for agents and people.

Dependencies

in go.mod

  • plystra/authn
  • plystra/authz
  • plystra/data
  • acme/platform

Capabilities

from dependency

  • authn.password.login/v1authn
  • authn.session.verify/v1authn
  • authz.check/v1authz
  • email.send/v1platform
  • database.primarydata
  • go.mod
  • plystra.yaml

0lines of
custom Go

A running backend

composed, not written

Inspect composition

Dependencies in go.mod

  • github.com/plystra/authn
  • github.com/plystra/authz
  • github.com/plystra/data
  • github.com/acme/platform

Capability implementations

authn.password.login/v1
plystra/authn/password.New
authn.session.verify/v1
plystra/authn/session.New
authz.check/v1
plystra/authz/rbac.New
email.send/v1
acme/platform/smtp.New
database.primary
plystra/data/postgres

plystra.yaml

composition:
  adopt:
    - module: github.com/acme/platform
      export: backend

A backend needs capabilities such as password login, session verification, authorization checks, email and a database. Each is filled by an implementation that lives in a dependency listed in go.mod. The repository contains only go.mod and plystra.yaml, which adopts a reusable composition, and zero lines of custom Go.

Customize

A replacement is ordinary Go.

No plugin model to learn. Write a package with a constructor, as you would anyway.

ses/sender.go
package sesimport (	"context"	sendv1 "github.com/acme/platform/interfaces/email/send/v1")type Config struct {	Region string `yaml:"region" plystra:"required"`}type Sender struct{ region string }//plystra:implements email.send/v1func New(cfg Config) (*Sender, error) {	return &Sender{region: cfg.Region}, nil}func (s *Sender) Send(ctx context.Context, req sendv1.Request) (sendv1.Response, error) {	// Your behavior: send through Amazon SES.	return sendv1.Response{}, nil}

//plystra:implements is the only line Plystra reads. It is a Go comment.

What you don't write

  • a base class to extend
  • a registration call
  • a plugin manifest
  • container wiring

Replace

Every default can be replaced cleanly.

Add your implementation, then name it in one line. When two could fit, Plystra asks instead of guessing.

The default SMTP implementation of email.send/v1 is selected on its own because it is the only compatible implementation. When you add your own SES implementation there are two, and Plystra does not choose: plystra check stops with PLYSTRA_RESOLVE_MULTIPLE_IMPLEMENTATIONS. One line in plystra.yaml, written by plystra use, selects yours, and the check passes.

Agent-first

The same moment, as an Agent sees it.

Every command returns structured facts and a typed way forward. An Agent finishes the task and verifies it without knowing the framework's internals.

An Agent asked to send email through Amazon SES reads a small context: the Plystra guidance generated by plystra new, plystra.yaml and ses/sender.go. plystra check with JSON output returns status decision_required with a typed recovery of kind choose, listing each option with its exact command. The Agent picks the SES option, runs it, and the next check returns success.

Under the hood

Callers never see the swap.

Everything in front of an implementation is derived from the capability. Replacing it only rewires the adapter behind it.

A request from the browser goes through the JavaScript SDK, the Connect handler and the typed proxy, all derived from the capability, then through the Kernel and an adapter to the implementation. When the default SMTP implementation is replaced by your SES implementation, only the adapter is rewired; the SDK, handler, proxy and Kernel are unchanged, and the same request reaches your implementation. A call from another package in the same process enters at the proxy and takes the same path.

Repository

Your repository holds only what differs.

Generic capabilities stay in dependencies. Nothing is copied in by a scaffold, so nothing has to be merged later.

In a copied scaffold, generic authentication, authorization, database, HTTP and mail code sits in your repository next to your own. With Plystra those parts stay in dependencies. Your repository keeps go.mod, plystra.yaml and the one package where your backend differs.

Under the hood

Composed before the build.

The CLI resolves the whole composition, freezes it and derives everything else. No implementation is chosen at runtime.

Before the build, the CLI reads go.mod, plystra.yaml and your package, then discovers, normalizes, resolves and validates the composition and freezes it. After the freeze nothing can change it. It then generates the proxies, adapters, assembly, Connect handlers and JavaScript SDK into generated/, which is derived and never edited, and verifies the result. The Kernel runs it as one backend.

Official capabilities

Defaults, not requirements.

Data, AuthN and AuthZ are separate modules built the same way. Use them, or put your own in their place.

  • Data

    github.com/plystra/data

    Relational schema, queries and migrations, compiled before the build.

    or yours — any implementation of the same Interfaces

  • AuthN

    github.com/plystra/authn

    Method-specific authentication, such as password login and sessions.

    or yours — any implementation of the same Interfaces

  • AuthZ

    github.com/plystra/authz

    Authorization decisions, called explicitly where they are needed.

    or yours — any implementation of the same Interfaces