Metadata-Version: 2.1
Name: dynos-warehouse-domain
Version: 0.1.4
Summary: Public warehouse-bot domain for DYNOS
License: Apache-2.0
Requires-Python: >=3.8
Description-Content-Type: text/markdown
Requires-Dist: dynos-client>=0.1.3

# dynos-warehouse-domain

A small, deliberately-toy domain and a bit of a tutorial. This represents a
robot that moves between rooms, picks up boxes, and puts them down. It is the
simplest interesting domain that exercises every part of the symbolic planning
stack (preconditions, effects, typed objects, goals) without any of the
AUV-specific machinery the Sentry domain layers on top.

If you have never used a task planner before, this is the package to read
first. This is sort of a sibling package to `dynos-sentry-domain`. They both
depend on `dynos-client`, but neither depends on the other; pick whichever fits
your problem. You probably want to learn the concepts here and then move to
`dynos-sentry-domain`.

## Install

```bash
pip install dynos-warehouse-domain
```

Pulls in `dynos-client` and `dynos-core` transitively.

---

## Symbolic planning in five minutes

DYNOS is a **task planner**. You hand it a description of the world and a goal;
it hands you back a sequence of actions that, executed in order, will make the
goal true. There are three pieces.

### 1. The world as a set of facts

DYNOS describes the world as a set of named, typed facts called fluents. A
fluent is just a labeled question with a true/false answer:

- `robot_at(room_one)` -> "is the robot in room one?"
- `holding(box_a)` -> "is the robot holding box A?"
- `hands_free()` -> "are the robot's hands empty?"

The set of fluents that are currently true is the world state. Anything not in
that set is false. This is called a closed-world model, and it's why you only
have to list what's true. It's also an approximation, but we work around it
pretty effectively by constantly re-planning and re-assessing what goes on in
the world.

```
World right now:
  { robot_at(room_robot_home), hands_free(), box_at(room_one, box_a) }
```

That's the entire description of the situation: robot is at home, hands are
empty, box A sits in room one. No coordinates, no images, no continuous sensor
data, just a set of true facts. Symbolic planning works on these boolean facts.
DYNOS does extend this by having a separate channel for continuous values like
coordinates and altitudes; that's `ValueField` on object types that will get
explored more later.

### 2. Actions described as before/after

A transition describes what an action looks like *symbolically*. It does
not run any code. It declares:

- preconditions: facts that must already be true for the action to be legal
- adds: facts that become true after the action runs
- removes: facts that stop being true

The `pick` transition in this package, for example, says: "to pick up a box,
your hands must be free, you must be in the room with the box, and the box must
be in that room. Afterwards you're holding the box, the box is no longer in the
room, and your hands are no longer free."

We represent this in python, which aids with introspection and debug:

```python
pick.define(
    preconditions=[hands_free(),
                   robot_at(pick.params.room()),
                   box_at(pick.params.room(), pick.params.box())],
    adds=[holding(pick.params.box())],
    removes=[box_at(pick.params.room(), pick.params.box()), hands_free()],
)
```

The DYNOS backend reads the data when it plans, and there's a separate piece of
code (an `@Action` method, not in this package) that actually moves the robot.
The strict separation is what lets the planner reason about long sequences
without knowing anything about hardware.

### 3. Goals are desired states

A goal is a set of facts you want to be true. You don't tell the planner what
to do; you tell it what should be true at the end. This way, the planner can
figure out the optimization and missing steps for you, and can re-plan to
re-solve when things change. For example:

```
Goal:  { box_at(room_two, box_a) }
```

"I want box A to end up in room two." The planner searches over transitions to
find a sequence whose combined effect changes the current state into one that
satisfies the goal. For the world above, against this goal, the planner
returns:

```
1. move(start=room_robot_home, end=room_one)
2. pick(room=room_one, box=box_a)
3. move(start=room_one, end=room_two)
4. place(room=room_two, box=box_a)
```

You did not write that sequence, the planner derived it from the preconditions
and effects you wrote. If the situation changes (e.g., an obstacle blocks
`room_one`) you describe the change as a fact the planner sees, set the same
goal, and you get a new plan. This is the `dynos call goal "..."` + `dynos call
execute` workflow you'll see in the cross-package walkthrough, or that you can
trigger from the orchestrator (as the mission executive will do during
operation).

That's it. Everything else in DYNOS, like coverage surveys, lifecycle phases,
multi-stage missions, and replanning on failure, is built on these three
principles.

---

## The warehouse domain concretely

This package ships everything you need to give the planner the example above.

### Object types

```python
class Box(ObjectType):
    weight = ValueField(float)

class Room(ObjectType):
    capacity = ValueField(int)
```

`Box` and `Room` are typed entities. Fluent argument types are checked against
them: `box_at(room=Room, box=Box)` will reject `box_at(room_one, room_two)`
because the second argument has the wrong type. The `weight` and `capacity`
`ValueField` descriptors hang continuous values off each instance; useful if
you want to later add a "robot can only carry boxes lighter than X kg"
constraint.

### Pre-built objects

The package creates eight specific instances at import time, ready for goals:

- Boxes: `box_a`, `box_b`, `box_c`
- Rooms: `room_robot_home`, `room_loading_bay`, `room_one`, `room_two`, `room_three`

This saves you from writing `make_new_object(Box)` for the toy examples; the
names are just stable handles into the numeric database.

### Fluents

| Fluent              | Meaning                                                                                                                                       |
|---------------------|-----------------------------------------------------------------------------------------------------------------------------------------------|
| `hands_free()`      | True iff the robot is not currently holding anything. (Defaults to `True`, so the initial world has it true unless you explicitly remove it.) |
| `robot_at(room)`    | The robot is in `room`.                                                                                                                       |
| `box_at(room, box)` | `box` is sitting in `room`.                                                                                                                   |
| `holding(box)`      | The robot is holding `box`.                                                                                                                   |

### Transitions (the robot's verbs)

| Transition         | Parameters        | Meaning                                                                                                                              |
|--------------------|-------------------|--------------------------------------------------------------------------------------------------------------------------------------|
| `pick(room, box)`  | `PickPlaceParams` | Pick up a box that's in the same room as the robot.                                                                                  |
| `place(room, box)` | `PickPlaceParams` | Put down a held box in the room the robot is in.                                                                                     |
| `move(start, end)` | `MoveParams`      | Move from `start` to `end`. (No connectivity graph: every room is reachable from every other room. This is intentional for the toy.) |

Read `warehouse.py` for the full preconditions / adds / removes.

---

## Use it

### Against a running backend

Same flow as the Sentry domain (see `dynos-client`'s README for the session/login basics). Once you have a session:

```python
from dynos_client import RemoteOrchestrator
from dynos_warehouse.warehouse import box_a, room_one, room_robot_home, room_two, box_at, robot_at, hands_free

orch = RemoteOrchestrator.from_config()

# Tell the backend the initial world
orch.set_start([
    robot_at(room_robot_home),
    hands_free(),
    box_at(room_one, box_a),
])

# Set the goal
orch.set_goal([box_at(room_two, box_a)])

# Inspect the plan before running it
print(orch.plan())

# Run it
orch.execute_plan()
```

The CLI equivalent uses string-form goals:

```bash
dynos call goal "box_at(room_two, box_a)"
dynos call plan
dynos call execute
```

### Offline (no backend at all)

Because `Transition` is inert data in `dynos-core`, you can introspect it without any backend or network:

```python
from dynos_warehouse.warehouse import pick

for p in pick.preconditions:
    print(p)
# Output:
#   hands_free()
#   robot_at(room)
#   box_at(room, box)
```

This is the fastest way to confirm you've understood a domain: print the
preconditions and effects of every transition and check that the resulting
before/after picture matches your mental model.

## Public API

| Symbol                                                                                                 | Kind                | Purpose                        |
|--------------------------------------------------------------------------------------------------------|---------------------|--------------------------------|
| `Box`, `Room`                                                                                          | object type         | Typed entities.                |
| `box_a`, `box_b`, `box_c`, `room_robot_home`, `room_loading_bay`, `room_one`, `room_two`, `room_three` | object              | Pre-built instances.           |
| `hands_free`, `robot_at`, `box_at`, `holding`                                                          | fluent              | Boolean facts about the world. |
| `PickPlaceParams`, `MoveParams`                                                                        | parameter dataclass | Transition argument types.     |
| `pick`, `place`, `move`                                                                                | transition          | The robot's verbs.             |

## What's NOT here

- No executable behaviour. The transitions are symbolic specs; nothing in
  this package moves a real or simulated robot. Pair it with a backend that has
  registered `@Action` methods bound to `pick` / `place` / `move`.
- No room connectivity graph. `move(start, end)` is unconditional. If you want
  a topology where some rooms are only reachable from others, add a
  `connected(a, b)` fluent and put it in `move`'s preconditions.
- No payload constraints. `pick` doesn't check `Box.weight` against
  `Room.capacity`. Add a numeric precondition to `pick.define(...)` if you want
  one; that's exactly the kind of extension this domain is meant for.

## Next

- `dynos-core`: the data-layer types (`Transition`, `ObjectType`,
  `ValueField`, `make_new_fluent`) you'll reach for when extending or replacing
  this domain.
- `dynos-client`: the HTTP client and `dynos` CLI for talking to a backend.
- `dynos-sentry-domain`: the same machinery applied to a real underwater
  vehicle, with surveys and lifecycle phases.
- `user_guide.md`: install + auth + first mission, end-to-end.
