Metadata-Version: 2.4
Name: crystalia-tower
Version: 0.0.1
Summary: Agent messaging tower - an ATC-style triage tower for agent and human callsigns
Author-email: Vlad Korolev <vlad@v-lad.org>
License-Expression: MIT
License-File: LICENSE
Keywords: agent,callsign,cli,coordination,inbox,messaging,multi-agent,tower
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Operating System :: MacOS
Classifier: Operating System :: POSIX
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Communications
Classifier: Topic :: Utilities
Requires-Python: >=3.13
Requires-Dist: rich>=13.0
Requires-Dist: sentry-sdk>=1.40.0
Requires-Dist: textual>=0.80
Requires-Dist: typer>=0.12
Provides-Extra: dev
Requires-Dist: coverage>=7.0; extra == 'dev'
Requires-Dist: mypy>=1.13; extra == 'dev'
Requires-Dist: pytest-xdist>=3.6; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.8; extra == 'dev'
Description-Content-Type: text/markdown

# crystalia-tower

An ATC-style triage tower for agent and human callsigns.

Work performed by an agent session is invisible until a human relays it, which
makes the human the transport wire between concurrently-running sessions.
`crystalia-tower` replaces that wire: every participant -- agent or human --
is a node with a callsign, an inbox, and presence. Messages are addressed, typed
from a fixed eight-verb vocabulary, and carry refs rather than bulk.

The thing it is for: an agent queues "need your ruling", the human answers, the
agent unblocks -- with no live coordinator session and no copy-paste.

## Install

```bash
pip install crystalia-tower
```

Python 3.13 or newer. Installs three console scripts: `ctower`, `ctower-cab`
and `ctower-launch`.

## Quickstart

A tower is a directory. `init` is the only verb that will not guess where it is;
the rest read `AGENT_CONTROL_TOWER_HOME`, or take `--tower`.

```bash
ctower init --tower ~/towers/demo
export AGENT_CONTROL_TOWER_HOME=~/towers/demo
export AGENT_CONTROL_TOWER_CALLSIGN=vlad

# hand work to an agent callsign that need not exist yet
ctower send ASSIGN gatto-1 unit=DEMO note='look at the failing test'

# the agent's side: what is waiting, and what it owes a readback
ctower check --callsign gatto-1
ctower ack <msg-id> --callsign gatto-1

# the tower, both axes, every strip
ctower status
ctower roll
```

`check --wait 300` blocks until traffic lands instead of polling, and exits 0
with `interrupted: true` in the envelope if a signal cuts the wait short --
a killed wait found *nothing out*, and that is not the same as a quiet tower.

## The eight verbs

`ASSIGN` `REPORT` `WILCO` `ROGER` `STANDBY` `UNABLE` `SAY-AGAIN` `MAYDAY`

The set is closed. A ninth token is a validation failure, not an extension
point. A message carries `unit=`, `status=`, `reason=`, `ref=` and `note=`;
`note=` bytes are stored byte-identically, never parsed, never interpolated
into a shell, and never sent anywhere off the machine.

## The three commands

| Command | What it is |
|---------|------------|
| `ctower` | the CLI: `send`, `inbox`, `check`, `ack`, `status`, `init`, `roll` |
| `ctower-cab` | a terminal UI over your own inbox and the strip tower |
| `ctower-launch` | start an agent process with its queued traffic handed over as its brief |

## Machine-readable output

Every verb takes `--json` and emits a versioned envelope. The seven schemas that
pin it ship in the source distribution under `docs/json-schema/v1/`, one per
verb, identified as `urn:crystalia-tower:json-schema:v1:<verb>`. The
envelope is a public API: it is pinned by test, not described by prose.

Exit codes are frozen: `0` success, `2` usage, `3` validation, `4` unknown
callsign, `5` unknown msg-id, `6` permission, `7` tower unavailable, `8` launch
collision. `ctower-launch` adds `1` for a failure to start the child -- the
tower committed its work and what failed was the exec -- and otherwise exits
with the child's own code, reporting a signalled child as 128+N. Three
non-happy outcomes are exit `0` by design: a `--wait` deadline expiring, a
message to a callsign never seen before, and a `launch` over an empty queue.

## What is built, and what is not

**Single-host works.** A tower is a directory on one machine, and every
participant reaches it through the filesystem.

**Multi-host is NOT built.** There is no server, no network listener, no
signing and no authentication in this release: `--tower` takes a path, not a
URL. Remote towers are Phase 2 of the TOWER-01 multi-host implementation plan,
which lives in the development workspace and is not shipped with this
distribution. Nothing in this package should be read as promising it yet.

**Traffic is pull-only.** No notifier, no cron, no hook, no watcher, no daemon
sits on the message path -- you learn you have mail by asking. The single
narrow exception is crash telemetry, which is on by default and switched off
with `AGENT_CONTROL_TOWER_SENTRY_DISABLED=1`. It never carries `note=` bytes.

## Working on the source

The source distribution carries the tests and the schemas. The TOWER-01 design
documents are not shipped; they live in the development workspace.

```bash
uv sync --extra dev
uv run pytest
uv run ruff check src tests
uv run mypy src/crystalia_tower
```

Always use `uv run`; never `source .venv/bin/activate`.

Two invariants are easy to break and are enforced by lint rather than by
review: `sqlite3` may be imported only under
`src/crystalia_tower/store/sqlite/`, and `subprocess` only by
`src/crystalia_tower/launch.py`. Nothing is swallowed either -- a caught
exception is re-raised, translated to a named error type, or surfaced. There is
no fourth option.

## Licence

MIT. See `LICENSE`.
