chore: commit work in progress before registrar reconciliation
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

Authorised as part of the CUST-WP-0068 identity cleanup. The registrar requires
a clean worktree, and these changes were already present in the checkout.

Refs CUST-WP-0068

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 2583210@bnt-lap001
Assistant-Session: f2bff2d5-e9b2-4338-92ca-10282a927006
This commit is contained in:
codex 2026-08-25 17:44:39 +02:00
parent c62c580575
commit dca3498237
4 changed files with 555 additions and 2 deletions

View file

@ -19,6 +19,8 @@ coordination-engine captures coordination models, specs, and registry entries fo
## In Scope
- Coordination specs, history, and registry indexes
- Headless coordination runtime, including cross-repository worker coordination
(observation, leases, wake-up, checkpoints, safety gates, and receipts)
- State Hub workplans and agent instructions
- Capability registration when coordination patterns stabilize
@ -26,6 +28,6 @@ coordination-engine captures coordination models, specs, and registry entries fo
## Out of Scope
- Runtime orchestration engine implementation (future repos)
- Replacing the systems coordinated through adapters (issue trackers, chat,
workflow, notification, delivery, or evidence systems)
- Replacing issue trackers or chat systems

View file

@ -10,7 +10,12 @@
| --- | --- | --- | --- | --- |
| workplan | COORDINATION-WP-0001 | finished | — | workplans/COORDINATION-WP-0001-statehub-bootstrap.md |
| workplan | COORDINATION-WP-0002 | finished | — | workplans/COORDINATION-WP-0002-coordination-model-spec.md |
| workplan | COORDINATION-WP-0003 | ready | — | workplans/COORDINATION-WP-0003-worker-coordination-service.md |
| task | COORDINATION-WP-0001-T01 | done | — | workplans/COORDINATION-WP-0001-statehub-bootstrap.md |
| task | COORDINATION-WP-0001-T02 | done | — | workplans/COORDINATION-WP-0001-statehub-bootstrap.md |
| task | COORDINATION-WP-0001-T03 | done | — | workplans/COORDINATION-WP-0001-statehub-bootstrap.md |
| task | COORDINATION-WP-0002-T01 | done | — | workplans/COORDINATION-WP-0002-coordination-model-spec.md |
| task | COORDINATION-WP-0003-T01 | done | — | workplans/COORDINATION-WP-0003-worker-coordination-service.md |
| task | COORDINATION-WP-0003-T02 | wait | — | workplans/COORDINATION-WP-0003-worker-coordination-service.md |
| task | COORDINATION-WP-0003-T03 | wait | — | workplans/COORDINATION-WP-0003-worker-coordination-service.md |
| task | COORDINATION-WP-0003-T04 | wait | — | workplans/COORDINATION-WP-0003-worker-coordination-service.md |

View file

@ -0,0 +1,443 @@
# Worker Coordination Service v0.1
**Status:** Proposed contract
**Owner:** coordination-engine (contract and runtime); State Hub and
`tmux-amq` are external local adapters.
**Request:** net-kingdom, “automatic worker wake-up and dependency coordination”
## 1. Problem and outcome
Several repository terminals run independent worker agents. Today an operator
must repeatedly inspect each repository, relay results, and issue prompts that
are effectively a scheduler. The service defined here turns State Hub changes
into safe, deduplicated wake-ups while keeping repository agents responsible for
publishing workplan/task/message state.
The desired loop is:
```text
State Hub change
-> observe and normalize
-> determine actionable task/message
-> acquire idempotent lease
-> wake owning repo session
-> agent resumes, checkpoints, and updates State Hub
-> dependent work becomes actionable and is evaluated again
```
The service does not execute repository code, invent task state, or replace
State Hub as the source of truth. It does own the observation, lease, policy,
checkpoint, and wake orchestration runtime.
## 2. Inputs and normalized trigger
The observer MUST support polling initially and MAY support a State Hub event
subscription later. It watches:
* workplan status and task status/dependencies;
* unread coordination messages and message read/ack state;
* `needs_human` and intervention notes;
* progress/checkpoint receipts.
Each source change is normalized to a trigger with an idempotency key:
```yaml
WorkerTrigger:
id: string
source: workplan | task | message | checkpoint
source_id: string
source_revision: string
target_repo: string
target_agent: string?
reason: task_actionable | dependency_satisfied | inbox_message | retry_due | checkpoint_resume
observed_at: timestamp
```
The idempotency key is `(source, source_id, source_revision, target_repo)`.
Repeated observations MUST collapse to one pending trigger.
## 3. Actionability and safety gates
A trigger is actionable only when its task/workplan is active or ready, its
dependencies are satisfied (or the message explicitly requests action), and no
terminal safety gate applies. The service MUST stop and leave a durable receipt
when any of these are true:
* `needs_human` is set or an intervention note requests review;
* credentials, secrets, access tokens, or private key material are needed;
* destructive, production, live, financial, or externally visible action is
proposed without an approved policy/authority;
* a policy decision, scope expansion, or ambiguous ownership requires a human;
* the target session cannot be authenticated or its repository identity does not
match the trigger.
Safety-gated work is never silently retried or rerouted to another agent.
Retry behavior is configuration-driven: ordinary triggers use bounded
exponential backoff, while safety-gated retries default to zero and may only be
enabled by an explicit policy. No retry setting may bypass the safety gate.
## 4. Lease and deduplication protocol
When connected, coordination-engine atomically claims a short-lived coordination
lease before waking an agent. Independently, `tmux-amq` maintains its own local
queue lease so messages remain deliverable while coordination-engine is offline.
The two leases are correlated but neither is the other's storage dependency:
Coordination-engine stores coordination leases, deduplication keys, and receipts
in a local SQLite database. State Hub receives sanitized projections when it is
available; it is not required for local queue delivery or lease recovery.
```yaml
WorkerLease:
trigger_id: string
lease_id: string
owner: coordination-engine
target_repo: string
target_agent: string?
state: offered | acknowledged | running | checkpointed | completed | retry_wait | stopped
attempt: integer
acquired_at: timestamp
expires_at: timestamp
last_checkpoint_at: timestamp?
coordination_endpoint_id: string?
local_lease_id: string?
```
Only one non-expired coordination lease may exist for a trigger. A wake request is retried
with the same `lease_id`; the receiver MUST treat duplicate requests as no-ops.
The agent acknowledges the lease before work begins, renews it while active,
and releases it with a checkpoint or terminal result. Expiry moves the lease to
`retry_wait` with bounded exponential backoff and an attempt limit. Exhaustion
produces a stopped receipt for operator review.
## 5. Session wake contract
The runtime integrates with `tmux-amq`, a separate local repository that owns
tmux session lifecycle, durable queueing, local leases, and PTY injection.
`tmux-amq` MUST remain functional when coordination-engine is unavailable.
Only repositories registered in gita may be addressed. A session may be started
with, for example,
`tamq --cmd "codex" net-kingdom railiance-platform`; the bridge opens one
terminal per repository and runs the command. Messages addressed as
`@repo: text` are queued/injected into that repository's terminal as
`#sender-repo: text`. Coordination-engine calls the bridge through a local,
authenticated adapter; it does not manipulate tmux directly.
Each tmux-amq endpoint is identified as `tmux-amq-<PID>`, where `<PID>` is the
tmux server PID. Within one endpoint, a repository slug maps to exactly one
tmux window; duplicate windows for the same repo are rejected or reconciled
before attach. Different endpoint PIDs may independently host the same repo.
Outbound messages typed directly by an agent are detected through a tamq
tmux-control-mode client. The client intercepts input lines beginning with
`@<registered-repo>:` and submits a structured local message using the
authenticated source window/repository. It does not wrap or replace the agent
process. Control-mode parsing MUST preserve ordinary input unchanged and must
not treat message-like text inside an unrelated multiline command as a message
unless the configured parser recognizes a complete address line.
If tmux control-mode interception is unavailable, tamq leaves ordinary terminal
use unchanged, marks interception unavailable in status/receipts, and does not
fall back to pane scraping or unverified injection. Operators may explicitly
use `tamq send @repo: message` as a degraded path.
tmux-amq MUST retain a local message history sufficient to inspect delivery
state and export it to a messages file. It MUST also support replaying a
messages file into a selected endpoint. Replay creates a new batch identifier,
passes each entry through normal gita-target validation, policy checks, and
deduplication, and records the original message ID as provenance. Replay MUST
not bypass safety gates or silently overwrite an existing message.
History files use newline-delimited JSON (JSONL): one complete message envelope
per line, including message ID, sender/target, body, timestamps, state, and
replay provenance. JSONL exports are appendable and replayable as streams.
The repository is `tmux-amq`, but its executable is `tamq` (Tmux Agentic Message
Queueing). It MUST provide conventional Unix CLI behavior, including
`--help`/`-h`, `--version`/`-V`, non-zero exit status on errors, diagnostics on
stderr, and machine-readable output options where commands produce records.
Default filesystem locations follow XDG conventions:
```text
config: ${XDG_CONFIG_HOME:-$HOME/.config}/tamq/config.toml
state: ${XDG_STATE_HOME:-$HOME/.local/state}/tamq/tamq.sqlite3
socket: ${XDG_RUNTIME_DIR:-/tmp}/tamq.sock
```
`TAMQ_CONFIG`, `TAMQ_STATE_DIR`, `TAMQ_SOCKET`, and `TAMQ_POLICY_PROFILE` may
override these defaults. The CLI MUST provide tab completion through
`tamq completion bash|zsh|fish` and expose completion for subcommands,
options, and known gita repository targets.
Version 0.1 supports direct addressing only. Unknown or unregistered targets
are rejected; there is no broadcast or wildcard routing. If a target session is
absent, `tmux-amq` opens it and starts the configured command. The sender label
in the injected form is derived from the authenticated source repository, not
from message text.
Delivery acknowledgment is configurable and defaults to `injected`, meaning the
message is considered delivered once tmux-amq successfully writes it to the
target PTY. An optional `acknowledged` mode retains the message until the target
agent explicitly confirms receipt; retries use the same message ID. This
facility carries coordination messages, not task/workplan state. Substantive
work, dependencies, and completion remain represented by State Hub facilities.
Message bodies are human-readable UTF-8 plain text, limited to 8 KiB and
preserving multiline content. The transport envelope carries structured IDs,
sender/target slugs, timestamps, and policy metadata. Binary payloads and
oversized bodies are rejected. Message text is never interpreted as task or
workplan state.
On attach, `tmux-amq` connects to a coordination-engine Unix domain socket and
performs a handshake, receiving a unique
`coordination_endpoint_id`. That identifier namespaces all messages and leases
originating from the connected endpoint, so local IDs can safely be reused
across different machines or sessions without cross-endpoint collision.
Coordination-engine MUST treat `(coordination_endpoint_id, local_message_id)`
as the message identity and preserve the endpoint ID in receipts.
The socket protocol MUST use structured messages and authenticate the peer by
filesystem ownership/permissions. The default deployment runs both processes
under the same approved local user, uses socket mode `0600`, and validates Unix
peer credentials; bearer tokens in command lines are not required. The initial
handshake includes the gita
registered repository slugs, tmux session name, and agent command; the response
contains the endpoint ID and negotiated lease/receipt capabilities. No TCP
listener is required for the local integration.
coordination-engine MUST refresh the local gita registry before accepting an
attach or addressing a target. It accepts only exact registered repository
slugs, requires the attached session to report the matching slug/path, and
never auto-clones or auto-registers a repository.
The adapter interface is:
```yaml
WakeRequest:
lease_id: string
target_repo: string # must be gita-registered and an active tmux-amq target
target_agent: string?
trigger: WorkerTrigger
checkpoint: Checkpoint?
prompt: string
WakeAck:
lease_id: string
session_id: string
accepted_at: timestamp
capability: resumed | started | unavailable
```
The prompt is generated from the trigger and the standard continuation policy;
it MUST contain no secret values. The target agent remains the authority for
editing files, calling tools, and updating State Hub.
## 6. Checkpoints and standard continuation policy
Agents SHOULD publish a checkpoint before yielding or stopping:
```yaml
Checkpoint:
lease_id: string
task_id: string?
summary: string
files_changed: [string]
next_action: string?
blocked_reason: human | secret | destructive | policy | dependency | unavailable | null
recorded_at: timestamp
```
When a lease is resumed, the service supplies the latest checkpoint and asks
the agent to continue. The following policy templates are recognized, in order:
1. inspect open work and implement the highest-priority actionable item;
2. if no work is open, reconcile `SCOPE.md` with `INTENT.md`, record gaps under
`history/`, and create/register a workplan for the most relevant gap;
3. review pending work, refine it where needed, and implement the next safe item;
4. if no safe work remains, close the session and write a perspective entry to
the configured `hall-of-helix` destination.
Templates are suggestions, not permission to bypass safety gates or invent
scope. A repository may override them through its agent instructions.
### 6.1 Policy profiles
Automatic behavior is selected through named policy profiles rather than code
changes. The default profile is conservative:
```yaml
policy:
profile: default
profiles:
default:
allow: [statehub_read, repo_inspect, repo_edit, local_checks,
state_updates, coordination_messages, tmux_wake, transient_retry]
require_human: [secrets, destructive, live_external, scope_change,
ambiguous_ownership, external_publish, financial, legal]
safety_gated_max_attempts: 0
```
Profiles MUST be selected by configuration, validated at startup, and included
by name/version in every lease and receipt. A profile cannot grant access to
credentials that are unavailable through the configured credential-routing
system, and every profile remains subject to authentication and repository
boundary checks.
## 7. Receipts and observability
Every trigger, lease transition, wake attempt, checkpoint, stop, retry, and
completion MUST emit a sanitized progress/audit receipt containing identifiers,
status, reason, timestamps, and retry metadata. Receipts MUST exclude prompt
secrets, credentials, payload contents, and unrestricted terminal output.
The service SHOULD publish receipts through State Hub progress/events and retain
enough correlation data to explain why a dependent task was (or was not) woken.
## 8. Ownership boundary
Repositories publish workplan/task/message state and consume wake requests. They
MUST NOT add independent polling daemons for this feature. Coordination-engine
owns the service process, observation, lease/deduplication, retry policy, and
wake dispatch. State Hub owns durable task/message state. Terminal/session
control is a pluggable adapter owned by coordination-engine and deployed with
the runtime; its underlying bridge remains an external dependency.
## 9. Operator decisions required before implementation
The following choices cannot be inferred safely from repository documents:
1. Which terminal/session bridge is authorized to wake or resume workers?
2. What local SQLite retention and backup policy should be used for leases and
receipts?
3. Default polling interval, lease duration, renewal interval, and retry limit?
4. Which actions are pre-approved policies versus always human-gated?
5. What service identity and authentication path may write State Hub and invoke
the wake adapter?
6. Which Unix socket path and filesystem ownership should the local deployment
use?
### 9.1 Runtime configuration
The following values MUST be configurable without code changes:
```yaml
coordination:
statehub_poll_interval_seconds: 15
wake_ack_timeout_seconds: 30
lease_renew_interval_seconds: 10
retry_backoff_seconds: [5, 15, 60, 300]
max_attempts: 4
safety_gated_max_attempts: 0
delivery_ack_mode: injected # injected | acknowledged
```
Deployments may choose different values, including up to nine attempts. The
service MUST validate `max_attempts` and `safety_gated_max_attempts` in the
range 0..9 and record the effective configuration version in receipts.
State Hub observation uses the configured polling interval (15 seconds by
default), backs off exponentially while unavailable, and resumes promptly when
connectivity returns. Local tmux-amq messaging is never blocked by State Hub
outages; receipt projections are queued for asynchronous publication.
The State Hub endpoint is configurable through `STATEHUB_API_BASE` or
`statehub.api_base`, defaulting to `http://127.0.0.1:8000`. Credentials MUST be
resolved through the approved credential-routing mechanism and never stored in
tamq configuration, SQLite, JSONL exports, tmux commands, or receipts. Missing
credentials leave local messaging operational while State Hub projections stay
pending.
`tamq status` exposes live endpoint/session, queue, lease, and State Hub
connectivity information. `tamq history` exposes full local delivery history;
State Hub receives sanitized receipts and correlation IDs only. Message bodies
remain local unless explicitly exported to JSONL.
History is never deleted automatically. When invoked without explicit filters,
`tamq purge` uses dynamic defaults equivalent to `--before 365d` and
`--max-size 100MB`; purge requires confirmation and may be preceded by JSONL
export. On startup, if history exceeds 100 MB, tamq prints an advisory such as:
`Size of history is NNN MB since YYMMDD, exceeding 100MB. Consider using 'tamq purge'.`
`tamq purge` performs a dry-run summary by default, reporting the matching
message count, date range, and estimated bytes. Interactive execution requires
explicit confirmation; non-interactive execution requires `--yes`. Combined
filters are allowed, and each purge emits a receipt containing filters and
counts without message bodies.
`tamq start` creates or reuses the tmux server/windows and starts the local
service; `tamq serve` runs the Unix-socket service in the foreground; `tamq stop`
stops the service without killing worker terminals or deleting queues. The
initial repo-opening command MUST ensure the service is alive and start it
automatically when absent. Startup is idempotent and lock-protected so two
concurrent repo opens cannot create duplicate services. `--detach` is required
to daemonize; foreground operation is the default for diagnostics.
If automatic startup fails, tamq reports the error on stderr, returns non-zero,
does not register an endpoint or claim delivery, and leaves existing sessions
and SQLite data untouched. `--no-service` may explicitly open terminals without
messaging, with a visible warning.
## 10. Logging and diagnostics
Foreground `tamq serve` logs to stderr. Detached operation logs to
`${XDG_STATE_HOME:-$HOME/.local/state}/tamq/tamq.log`. The default level is
`info`; `--quiet` suppresses non-errors, `--verbose` enables debug diagnostics,
and `--json` requests structured output where applicable. Message bodies,
credentials, tokens, and unrestricted PTY output are omitted by default.
`--orwell` is an explicitly unsafe, non-production diagnostic mode that logs
otherwise-omitted diagnostic fields, including message bodies and credential
material when available. It MUST be supplied per invocation, emit a prominent
warning, write only to a local mode-`0600` sink, never project to State Hub, and
be rejected by the default production policy profile. Operators are responsible
for purging Orwell logs after diagnosis.
This practice is proposed for canonization as the InfoTechCanon PracticePattern
`practice-pattern/orwell-logging-diagnostics` (`OrwellLoggingDiagnostics`). The
canonical owner is info-tech-canon; tamq is a concrete known use, not the owner
of the general practice.
The first runtime implementation uses Python 3.11+ and should prefer standard
library components (`sqlite3`, `asyncio`, Unix sockets, and CLI primitives) so
local installation remains small and reliable.
The project is packaged through a root `pyproject.toml` with a `tamq` console
entry point. It must support `uv tool install .`, editable development installs,
and package-derived `tamq --version` output.
Verification uses layered tests: pytest unit tests for state/config/protocol
logic; Unix-socket integration tests with fake tmux-amq peers; fake State Hub
and gita registry tests; CLI behavior tests; and opt-in live smoke tests only
when explicitly configured. Tests must not require credentials or mutate live
systems.
The Unix-socket protocol is versioned independently from package versions,
starting at `0.1`. Attach handshakes negotiate protocol version and capabilities;
incompatible major versions are rejected, compatible minor versions may
interoperate, and the negotiated values are retained in endpoint/receipt data.
SQLite stores an explicit schema version. Startup applies forward migrations in
transactions, creates a timestamped backup before altering message/lease
tables, and refuses to run against a newer schema. Message history is never
silently dropped; `tamq db-version` reports the current schema.
SQLite uses WAL mode, foreign-key enforcement, and a configurable busy timeout
(5 seconds by default). Queue and lease transitions are transactional, with a
single coordination-engine writer path and concurrent readers. Transient busy
errors may retry without creating duplicate messages.
On `SIGTERM` or `SIGINT`, the service stops accepting socket requests and new
leases, finishes the current transaction, marks the endpoint disconnected,
closes the socket, preserves queues/leases for recovery, emits a sanitized
shutdown receipt, and exits within a configurable grace period (10 seconds by
default).
Default CI gates are `uv run pytest`, `git diff --check`,
`python -m compileall src`, `tamq --help`, and `tamq --version`; package
install/build and live smoke checks are separate opt-in jobs.
Health is local-only: `tamq status` is human-readable, `tamq status --json` is
machine-readable, and `tamq ping` performs a lightweight Unix-socket liveness
check. Status separates process/socket, SQLite, gita registry, State Hub, queue,
and lease health; no TCP health port is exposed.

View file

@ -0,0 +1,103 @@
---
id: COORDINATION-WP-0003
type: workplan
title: "Worker coordination service contract"
domain: communication
repo: coordination-engine
status: ready
owner: codex
topic_slug: communication
created: "2026-08-23"
updated: "2026-08-23"
---
# Worker coordination service contract
Define and implement the cross-repository worker wake-up and
dependency-coordination capability requested by net-kingdom. Coordination-engine
owns the protocol and runtime. State Hub and the separate `tmux-amq` local tmux
message queue are adapters. Only gita-registered repos are valid worker targets.
## Publish the v0.1 contract
```task
id: COORDINATION-WP-0003-T01
status: done
priority: high
```
Authored `spec/worker-coordination-service-v0.1.md`, covering State Hub
observation, actionability gates, leases, deduplication, wake requests,
checkpoints, retries, safety stops, receipts, and operator decisions.
## Agree adapter and deployment decisions
```task
id: COORDINATION-WP-0003-T02
status: wait
priority: high
```
The local transport is a Unix domain socket. Coordination-engine uses local
SQLite for coordination leases, deduplication, and receipts, projecting
sanitized state to State Hub. Net-kingdom/operator must choose the socket
path (same-user mode `0600` by default), `tmux-amq` handshake fields, SQLite
retention/backup policy, timers/retry defaults, the configured policy profile,
and strict gita registry validation. Policy profiles define the allowed and
human-gated action classes. v0.1 uses direct-only routing; broadcast is deferred.
Delivery acknowledgment is configurable and defaults to `injected`; this layer
does not duplicate State Hub task/workplan state.
Message bodies are UTF-8 plain text with an 8 KiB limit and structured metadata
kept in the transport envelope.
Endpoint identity is `tmux-amq-<tmux-server-PID>`; each endpoint has at most one
tmux window per gita repository.
tmux-amq must support local history inspection, export to a messages file, and
safe replay of that file through normal routing and policy checks.
History is retained by default; explicit `tamq purge` uses `--before 365d` and
`--max-size 100MB` defaults, with a startup advisory when history exceeds 100MB.
The service uses `tamq start`/`serve`/`stop`; first repo opening auto-starts an
absent service with an idempotent lock-protected check.
Logging defaults to safe stderr/file output; `--orwell` is an explicit,
non-production, local-only mode for otherwise-omitted sensitive diagnostics.
## Establish OrwellLoggingDiagnostics practice pattern
```task
id: COORDINATION-WP-0003-T04
status: wait
priority: medium
```
Requested candidate canon artifact `practice-pattern/orwell-logging-diagnostics`
from info-tech-canon, with tamq as the first known use. Await canonical owner
review and registration.
The first runtime implementation is Python 3.11+ with standard-library-first
dependencies.
Package through root `pyproject.toml` with a `tamq` console entry point and uv
install/editable-install support.
Verification uses pytest unit, fake-peer integration, CLI, and opt-in live
smoke tests without production credentials.
Default CI gates: `uv run pytest`, `git diff --check`, compileall, and tamq
help/version checks; package and live smoke checks remain opt-in.
The executable name is `tamq`, with Unix-standard `--help`/`-h` and
`--version`/`-V` flags.
Use XDG config/state/runtime paths with environment overrides and provide
`tamq completion bash|zsh|fish` for tab expansion.
`tmux-amq` independently owns its durable queue and local delivery leases.
Lease/retry timing and attempt counts are configuration parameters; the retry
ceiling is 9, while safety-gated retries default to 0 and require explicit
policy. Runtime ownership is coordination-engine;
tmux/session ownership is `tmux-amq`.
## Implement the coordination runtime
```task
id: COORDINATION-WP-0003-T03
status: wait
priority: high
```
Deferred while `tmux-amq` is bootstrapped as the standalone local queue and
tmux control-mode endpoint. Coordination-engine integration will follow the
stable tamq Unix-socket protocol.