Composition
Composition is ref resolution, not service launching.
A tactic can be called:
- in process,
- through an HTTP service,
- through a
psi://...ref resolved from local config.
from lllm import TacticResolver
resolver = TacticResolver()
resolver.register("psi://demo/echo/tactics/echo", EchoTactic())
result = resolver.run("psi://demo/echo/tactics/echo", {"text": "hello"})
Local config can bind the same ref to a service:
[refs."psi://demo/echo/tactics/echo"]
url = "http://127.0.0.1:8000/tactics/echo"
TacticResolver.from_config() can read a shared .psi/config.toml that also
contains non-tactic refs from PsiHub or SSSN. It loads tactic URL bindings and
ignores refs owned by other layers, such as services, channels, snapshots,
docs, examples, and assets. Malformed tactic refs still fail validation.
[refs."psi://demo/echo/tactics/echo"]
url = "http://127.0.0.1:8000/tactics/echo"
[refs."psi://demo/echo/services/api"]
url = "http://127.0.0.1:8000"
[refs."psi://demo/echo/channels/events"]
store = ".sssn"
PsiHub validates and documents refs. It does not launch services.