Skip to main content

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:

  1. Use Python with a longer timeout. RemoteOrchestrator.from_config(timeout_s=3600).
  2. 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

dynos_client-0.1.2.tar.gz (39.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

dynos_client-0.1.2-py3-none-any.whl (42.3 kB view details)

Uploaded Python 3

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

Hashes for dynos_client-0.1.2.tar.gz
Algorithm Hash digest
SHA256 1527f3ae593e45b0b1c47e70a3893747a8f33abc6f5a915a963e3aa25fb21b09
MD5 67e01f7163bf5bb11f7691dff215e277
BLAKE2b-256 2e9216674c7fb1b0fe4f593e6a47f016066185c6a897cf9eb016bb8e72cb3378

See more details on using hashes here.

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

Hashes for dynos_client-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 8b765cbf1f557ecdcb7a9cf27e0ec0ac1413fed4f46e0d30b7d8be22cf2c0d58
MD5 a989dc7b903bfd42c0a57f5786d943bc
BLAKE2b-256 4c7dbfd54867c03bfafa9a6af651137d880d37a19c0bd4fe63737cf2908709bb

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page