Client and CLI for orchestrating DYNOS backends remotely
Project description
dynos-client
The HTTP client and command-line tool for talking to a DYNOS planning backend. You install this on your laptop or server, point it at a backend, and drive the planner from Python or the shell. The backend does the planning; this package gives you the network interface.
You usually combine it with a domain package (e.g. dynos-sentry-domain)
that gives you the actual nouns and verbs your goals reference. dynos-client
ships only the infrastructure: HTTP, CLI, mission builder, session-gateway
authentication.
dynos-client depends on dynos-core, as do the domain files. The domain
files also depend on this package. Specific robot missions or extensions,
such as the adaptive sampling demo, depend on robot implementations.
Most users start here. pip install dynos-client plus a domain package and you
can talk to a hosted backend immediately. You do not need to install the
backend itself.
Install
pip install dynos-client
Pulls in dynos-core, requests, typer, and bcrypt. Registers a dynos console script.
Sessions and the backend
DYNOS backends run in two modes. Direct mode is a single process with no auth.
This is fine for local development against a mock, or on-vehicle work.
Session-gateway mode is multi-tenant, and most likely how you're be developing:
a TLS endpoint accepts password logins, issues bearer tokens, and routes each
user's traffic to a private subprocess (their session) with its own world
model. The hosted backend at https://api.dynosplan.com is in this mode.
You authenticate once:
dynos login
# optionally, you can set the source. The default is the webpage, but this might be an on-ship server:
# dynos login --to https://api.dynosplan.com
That writes ~/.dynos/config.json (mode 0o600). Every subsequent dynos call,
dynos session, and dynos connect reads the cached URL, token, and default
session, so the --to flag is rarely needed after the first login.
A session is a private process that holds your world model. Create one before issuing goals:
dynos session create
dynos call health # confirms the gateway is reachable
dynos session info # confirms a session is selected
dynos session swap <name> switches the default; dynos session stop ends the
current session. The CLI never silently picks a session for you. If your
default session has expired or been stopped, commands error out with a hint to
run swap or create.
CLI map
| Command | Purpose |
|---|---|
dynos login / logout / whoami |
Authenticate against a session gateway and inspect the cache. |
dynos call <subcmd> |
Talk to a running backend: health, goal, plan, execute, state, objects, trigger, reset, geojson, ... |
dynos session <subcmd> |
Manage gateway sessions: create, list, info, swap, extend, reset, stop. |
dynos connect <module>:<Class> |
Run as a servant — the backend dispatches actions over HTTP and your local code executes them. |
dynos hash-password |
Produce a bcrypt hash for the gateway's admin user table. You can send this to the server admin (me) to change your pw |
Run any of them with --help for full options.
60-second example
from dynos_client import RemoteOrchestrator
orch = RemoteOrchestrator.from_config() # reads ~/.dynos/config.json
print(orch.health())
The shell equivalent:
dynos call health
Both go to the same endpoint. The Python form is the one you reach for when missions get longer than the CLI's 30-second HTTP timeout (see "Long-running plans" below).
Goal vs. trigger
There are two ways to make the backend do something, and they are not interchangeable.
dynos call execute (preferred). You set a goal, which is a desired symbolic
state. The planner derives the prerequisite sequence. If something fails, the
planner replans from the current state. This is what the system is designed
for.
dynos call goal "full_coverage_of(site_alpha)" "phase_ascending()"
dynos call plan # optional: print the action sequence the planner picked
dynos call execute
dynos call trigger (debug only). Fires one transition directly, with no
planner involvement and no precondition check. Convenient for confirming a
custom @Action is wired up; not safe for anything that moves the vehicle. A
careless trigger can leave the symbolic world in a state the planner doesn't
trust (e.g. recording at(wp_end) without descent ever happening).
dynos call trigger "resample_zone(source_zone=site_alpha)" # OK for debug
If you find yourself wanting to chain trigger calls, you actually want a
goal. Let the planner sequence them.
Missions
A Mission chains plan blocks and action blocks. Each block runs to completion
before the next starts; the planner replans at every block boundary using the
state after the previous block.
from dynos_client import RemoteOrchestrator, Mission
from dynos_sentry.sentry import Zone, full_coverage_of
orch = RemoteOrchestrator.from_config(timeout_s=3600)
area_north = Zone("area_north", coordinate_frame="geographic",
vertices=[[-70.67, 41.525], [-70.66, 41.525], [-70.66, 41.53], [-70.67, 41.53]],
altitude=70.0, speed=0.8, coverage_width=170.0, robot_width=0.5)
area_south = Zone("area_south", coordinate_frame="geographic",
vertices=[[-70.67, 41.52], [-70.66, 41.52], [-70.66, 41.525], [-70.67, 41.525]],
altitude=70.0, speed=0.8, coverage_width=170.0, robot_width=0.5)
orch.create_object(area_north)
orch.create_object(area_south)
mission = Mission(name="two_surveys")
mission.add_plan([full_coverage_of(area_north)])
mission.add_plan([full_coverage_of(area_south)])
results = orch.execute_blocks(mission)
Mix action blocks (run a single transition immediately, no planning) with plan blocks (set a goal, planner derives the sequence):
from dynos_sentry.sentry import full_coverage_of
mission = Mission(name="custom_sequence")
mission.add_action(takeover_control) # primitive, no planning
mission.add_plan([full_coverage_of(area_north)]) # planned
mission.add_plan([full_coverage_of(area_south)])
mission.add_plan([phase_ascending()])
results = orch.execute_blocks(mission)
A single plan block can carry multiple goals; the planner finds one plan that achieves all of them. A single action block instructs a specific thing to occur, which circumvents a lot of the replanning possibilities (you strip out the context), but sometimes things need to be hard-coded in a mission so it's available. Prefer @Script for hard sequences, because these can be associated with transitions like actions do (see later).
Servants and dynos connect
When the action's implementation lives on your machine — your ML model, a
sensor handler, a custom controller — you run a servant. The backend keeps
planning; your code executes the assignments it dispatches. See
dynos-adaptive-resampling's README for a full worked example.
dynos connect my_package.runnable:MyNode
A Runnable is a small class with three static methods (register,
get_start_state, get_start_goal); the servant loop polls the backend for
assignments and posts results back. dynos connect reads the same cached login
as dynos call.
Custom actions in a client-only env
@Action and @Script (the decorators that bind a Python method to a
Transition) live in the DYNOS backend, not in dynos-client. If your code
needs to import them but should also run in environments where the backend
isn't installed (CI, tests, a public release), use the conditional-import
recipe:
try:
from dynos.databases.typed_action import Action, Script
except ImportError:
def Action(*a, **kw):
return (lambda f: f) if not (a and callable(a[0])) else a[0]
Script = Action
This degrades the decorators to no-ops in client-only environments; the class still loads, registration is a no-op, and the action never fires (because no backend is there to dispatch it).
Long-running plans
dynos call execute is synchronous: the backend holds the HTTP response open
until every step in the plan has finished. The CLI defaults to a 30-second
client-side timeout, so for any plan that takes longer your terminal will print
Read timed out even though the backend is still happily executing. This times
out the client, not the plan.
Two workarounds:
- Use Python with a longer timeout.
RemoteOrchestrator.from_config(timeout_s=3600). - Don't watch, open a second terminal and poll.
dynos call state,dynos call goal,dynos call trace,dynos call log --wall.
Configuration
~/.dynos/config.json (mode 0o600) stores:
| Key | Source |
|---|---|
backend_url |
dynos login --to ... |
token |
dynos login (gateway-issued bearer) |
username |
dynos login |
role |
dynos login (user, admin) |
default_session |
dynos session create / swap |
Set DYNOS_CONFIG_PATH=/some/other/file.json to override the location (handy for tests; the public CLI honours the env var).
Public API
| Symbol | Purpose |
|---|---|
RemoteOrchestrator |
The HTTP client; mirrors the backend's Orchestrator Python API. |
RemoteAuthExpired |
Raised when the cached token is rejected; catch and reauthenticate. |
Mission |
Builder for compound goal sequences. |
PlanBlock |
Plan-derived block within a Mission (one or more goals → planner). |
ActionBlock / AnyActionBlock |
Single-action block within a Mission (no planning). |
Next
Install dynos-sentry-domain to get Zone, Coordinate, full_coverage_of,
and the rest of the goal vocabulary. The walkthrough at user_guide.md ties
everything together end-to-end. A simple toy domain (warehouse) is also
available.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file dynos_client-0.1.2.tar.gz.
File metadata
- Download URL: dynos_client-0.1.2.tar.gz
- Upload date:
- Size: 39.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.8.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1527f3ae593e45b0b1c47e70a3893747a8f33abc6f5a915a963e3aa25fb21b09
|
|
| MD5 |
67e01f7163bf5bb11f7691dff215e277
|
|
| BLAKE2b-256 |
2e9216674c7fb1b0fe4f593e6a47f016066185c6a897cf9eb016bb8e72cb3378
|
File details
Details for the file dynos_client-0.1.2-py3-none-any.whl.
File metadata
- Download URL: dynos_client-0.1.2-py3-none-any.whl
- Upload date:
- Size: 42.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.8.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8b765cbf1f557ecdcb7a9cf27e0ec0ac1413fed4f46e0d30b7d8be22cf2c0d58
|
|
| MD5 |
a989dc7b903bfd42c0a57f5786d943bc
|
|
| BLAKE2b-256 |
4c7dbfd54867c03bfafa9a6af651137d880d37a19c0bd4fe63737cf2908709bb
|