Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
89 commits
Select commit Hold shift + click to select a range
96ced59
feat: define ACP primary event rollout policy
plotarmordev Jul 31, 2026
d30993d
Add ACP event projection layer
plotarmordev Jul 31, 2026
39c6218
Add durable structured agent event journal
plotarmordev Jul 31, 2026
ecd03dc
Add strict ACP v1 subprocess transport
plotarmordev Jul 31, 2026
7f62dff
test: expect ACP event journal schema version
plotarmordev Jul 31, 2026
7a5b099
Add guarded ACP event ingestion
plotarmordev Jul 31, 2026
e19c8c0
Add supervised ACP session runtime
plotarmordev Jul 31, 2026
b26f7d1
docs: define rebasing-free ACP adapter upgrades
plotarmordev Jul 31, 2026
ac5c9c8
Harden ACP event journal invariants
plotarmordev Jul 31, 2026
d3634f1
test: expect hardened ACP journal schema
plotarmordev Jul 31, 2026
d881ca3
Integrate ACP runtime into daemon lifecycle
plotarmordev Jul 31, 2026
3147941
Make ACP event ingestion binding-atomic
plotarmordev Jul 31, 2026
8b39b89
Harden ACP transport lifecycle
plotarmordev Jul 31, 2026
d6daf65
Harden ACP event projection boundaries
plotarmordev Jul 31, 2026
7a15fbd
Harden ACP runtime lifecycle and finality
plotarmordev Jul 31, 2026
23ebe2b
Add black-box ACP adapter probe
plotarmordev Jul 31, 2026
1df7bb1
test: expect ACP journal schema
plotarmordev Jul 31, 2026
820fa4d
Harden ACP v1 transport conformance
plotarmordev Jul 31, 2026
2eda9db
fix: keep ACP migration fail closed
plotarmordev Jul 31, 2026
eebab98
fix(acp): make projection ingestion transactional
plotarmordev Jul 31, 2026
d5bc05b
fix: fail ACP runtime closed on stale bindings
plotarmordev Jul 31, 2026
e1d943a
fix: bind new ACP sessions in two phases
plotarmordev Jul 31, 2026
155de16
fix(acp): atomically journal projected events
plotarmordev Jul 31, 2026
b4ee6b5
fix: bound ACP event retention automatically
plotarmordev Jul 31, 2026
a9c0448
fix(acp): harden v1 projection decoding
plotarmordev Jul 31, 2026
2639bc5
fix(acp): preserve ordered prompt lifecycle
plotarmordev Jul 31, 2026
d2b59b6
fix(acp): lease new session bindings
plotarmordev Jul 31, 2026
5dd2413
fix(acp): make private event policy fail closed
plotarmordev Jul 31, 2026
ef150e5
fix(acp): multiplex typed session events
plotarmordev Jul 31, 2026
8f3d921
fix(acp): fence replay projection authority
plotarmordev Jul 31, 2026
6543163
fix(acp): close remaining runtime protocol gaps
plotarmordev Jul 31, 2026
71665af
test: avoid ACP probe scheduling flake
plotarmordev Jul 31, 2026
33c556a
test: expect ACP hardened schema
plotarmordev Jul 31, 2026
cb9b948
test: stabilize final ACP release gate
plotarmordev Jul 31, 2026
91dba77
feat(acp): wire Herdr-owned production runtimes
plotarmordev Jul 31, 2026
c8de818
fix(acp): fence production runtime lifecycle
plotarmordev Aug 1, 2026
fbbf923
feat(acp): bridge durable permission decisions
plotarmordev Aug 1, 2026
b1eaebe
fix(acp): bound permission shutdown cleanup
plotarmordev Aug 1, 2026
2692e2d
docs(acp): update production rollout gates
plotarmordev Aug 1, 2026
d1c9f52
fix(acp): make shadow ownership fail closed
plotarmordev Aug 1, 2026
e7bd6d2
test: stabilize descriptor leak assertion
plotarmordev Aug 1, 2026
826adde
Preserve opaque revisions across connector transport
plotarmordev Aug 1, 2026
e0f8866
feat: bridge ACP sessions into visible panes
plotarmordev Aug 1, 2026
8a0ade6
fix: make ACP pane bridge crash and frame safe
plotarmordev Aug 1, 2026
4bf6dc1
fix: fence ACP prompts on visible console loss
plotarmordev Aug 1, 2026
cec3936
fix: preserve ACP ownership across fallback hazards
plotarmordev Aug 1, 2026
6c21629
fix: retain published ACP ownership through remint failure
plotarmordev Aug 1, 2026
19fe4e3
fix(acp): fail closed on Herdr readiness timeout
plotarmordev Aug 1, 2026
e7fc86f
fix(herdr): separate initial reconcile timeout
plotarmordev Aug 2, 2026
51e0fe7
fix(herdr): isolate turn replay sockets
plotarmordev Aug 2, 2026
f6cd447
fix(herdr): survive health persistence contention
plotarmordev Aug 2, 2026
6d0d9a0
fix(acp): close failed turns and replay completion races
plotarmordev Aug 2, 2026
51e39bf
fix(acp): recover console cursor across session remints
plotarmordev Aug 2, 2026
011126a
fix(acp): cache optional legacy worker probes
plotarmordev Aug 2, 2026
2d2f810
fix(acp): probe optional workers without minting
plotarmordev Aug 2, 2026
e8eb9f4
perf(acp): reduce idle console control traffic
plotarmordev Aug 2, 2026
cf8f750
fix(acp): fence prompt generation before receipt
plotarmordev Aug 2, 2026
47bbc24
fix(acp): persist receipt at transport boundary
plotarmordev Aug 2, 2026
8f7c0fa
fix(acp): mark send start only when writable
plotarmordev Aug 2, 2026
d8f801f
feat(acp): steer live messages into active turns
plotarmordev Aug 3, 2026
85b7f43
fix(acp): preserve bindings across observer refresh
plotarmordev Aug 3, 2026
5e2b70b
fix(acp): scope synthetic events to transport
plotarmordev Aug 3, 2026
0a66883
feat(acp): support live-only console recovery
plotarmordev Aug 3, 2026
c46aa08
fix(acp): trust live steering capability
plotarmordev Aug 3, 2026
d2ce261
fix(acp): retry definite steering misses once
plotarmordev Aug 3, 2026
db7d9c4
fix(commands): prompt resolved Herdr pane targets
plotarmordev Aug 3, 2026
2556e18
Expose linked submissions in turn deltas
plotarmordev Aug 3, 2026
6e73c9f
Fail exhausted zero-job final plans
plotarmordev Aug 3, 2026
f28503d
Expose final delivery source age
plotarmordev Aug 3, 2026
3ea8686
fix(acp): finalize structured backend errors
plotarmordev Aug 3, 2026
91891bf
refactor(acp): require structured agent transport
plotarmordev Aug 4, 2026
b88023f
docs: record Tendwire reduction baseline
plotarmordev Aug 4, 2026
bd1e483
merge wave 0 Tendwire baseline
plotarmordev Aug 4, 2026
bf84e54
delete dead command paths and v0 receipt replay
plotarmordev Aug 4, 2026
32e6feb
merge wave 1 dead command and v0 removal
plotarmordev Aug 4, 2026
67c4606
refactor: make Herdr socket lifecycle-only
plotarmordev Aug 4, 2026
f8126a5
merge wave 2 socket-only lifecycle
plotarmordev Aug 4, 2026
f393dd1
fix: align socket lifecycle and daemon startup
plotarmordev Aug 4, 2026
a5cf8d8
Merge socket lifecycle corrections
plotarmordev Aug 4, 2026
261ed8a
store: replace migrations with explicit current schema
plotarmordev Aug 4, 2026
778cb2f
tests: align socket integration with direct store API
plotarmordev Aug 4, 2026
4dd9ead
store: reset legacy v28 databases at schema 29
plotarmordev Aug 4, 2026
f07a0a6
tests: restore current store behavior coverage
plotarmordev Aug 4, 2026
af51027
tests: follow current store schema version
plotarmordev Aug 4, 2026
8bc8d8b
Merge direct current store excision
plotarmordev Aug 4, 2026
406b1d7
docs: freeze connector RPC contract v2
plotarmordev Aug 4, 2026
59300ad
test: harden connector recovery contract
plotarmordev Aug 4, 2026
9ab5597
Merge connector RPC contract v2
plotarmordev Aug 4, 2026
738220b
docs: add wave 4 store design
plotarmordev Aug 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 15 additions & 9 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -80,18 +80,24 @@ TENDWIRE_STORE_MAINTENANCE_CADENCE_SECONDS=3600
# Optional stable host label for the local machine.
# TENDWIRE_HOST_ID=my-host

# Optional Herdr binary and timeout overrides. Turn-adapter reads use the same
# timeout.
# Optional Herdr binary and timeout overrides. Herdr supplies worker/pane
# lifecycle and owns ACP endpoints; it is not an agent transcript transport.
# Initial socket reconciliation has a separate whole-startup budget so a
# multi-call inventory can exceed one RPC without weakening normal deadlines.
# TENDWIRE_HERDR_BIN=herdr
TENDWIRE_HERDR_TIMEOUT_SECONDS=1.0
TENDWIRE_HERDR_INITIAL_RECONCILE_TIMEOUT_SECONDS=120.0

# Daemon-owned turn-ingestion cadence and dedicated worker pool. Defaults are
# 2.0 seconds and 4 workers; the worker count cannot exceed
# TENDWIRE_MAX_WORKERS. The internal refresh queue is fixed at 64.
TENDWIRE_TURN_REFRESH_INTERVAL_SECONDS=2.0
TENDWIRE_TURN_REFRESH_WORKERS=4
# Compatibility flag: legacy|dual|shadow|observed all use the observed model.
TENDWIRE_TURN_MODEL=observed
# ACP is required for every supported agent. The daemon fails closed when a
# worker lacks a healthy Herdr-owned ACP endpoint; there is no transcript,
# shadow, preferred, or fallback mode. Stable ACP v1 does not distinguish raw
# reasoning from summaries. The safe
# default discards thought chunks. `private_summary` is an explicit trusted-
# adapter convention; `private_all` retains all thoughts for local diagnostics.
TENDWIRE_ACP_THOUGHT_POLICY=disabled
TENDWIRE_ACP_REQUEST_TIMEOUT_SECONDS=30
TENDWIRE_ACP_SHUTDOWN_TIMEOUT_SECONDS=5
TENDWIRE_ACP_MAX_FRAME_BYTES=8388608

# Process-local bounded LRU for repeated public-text sanitization. Set to 0 to
# disable; values above 65536 are capped. The default covers typical retained
Expand Down
55 changes: 43 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,12 @@ background daemon; use [INSTALL.md](INSTALL.md) for persistent service setup.

## Relationship to Herdr, Herdres, and connectors

Herdr is the only concrete runtime backend documented here. Tendwire can observe
Herdr through the conservative CLI one-shot path or, when explicitly enabled,
through the Herdr socket/event backend. Both paths normalize Herdr state into
neutral Tendwire spaces, workers, attention, turns, pending interactions,
command results, connector jobs, and backend health.
Herdr supplies workspace, pane, and worker lifecycle plus ownership of private
ACP endpoints. Tendwire can observe that lifecycle through the conservative CLI
one-shot path or the Herdr socket/event backend. ACP is required for supported
agent semantics and commands; Herdr is not used to read transcripts or inject
pane input. The authority and privacy split is defined in
[docs/acp-migration.md](docs/acp-migration.md).

Herdres can use Tendwire as its source/control plane while Herdres remains the
Telegram connector. Tendwire owns Herdr observation, private bindings,
Expand Down Expand Up @@ -175,6 +176,11 @@ not expose raw backend argv. The Herdr binary path, data directory, and database
path expand `~`; each Herdr probe uses `TENDWIRE_HERDR_TIMEOUT_SECONDS` or
`--herdr-timeout` when set, defaulting to 5.0 seconds.

Socket-daemon startup waits up to
`TENDWIRE_HERDR_INITIAL_RECONCILE_TIMEOUT_SECONDS` (default `120.0`) for the
entire initial reconciliation. This startup-only budget does not change the
normal per-RPC `TENDWIRE_HERDR_TIMEOUT_SECONDS` deadline.

When Herdr 0.7.0 is present, the adapter first tries the no-flag JSON envelopes
(`herdr workspace list`, `herdr agent list`) that wrap records under
`result.workspaces` and `result.agents`, then keeps `--json` list variants as a
Expand Down Expand Up @@ -545,9 +551,10 @@ variables:
| `snapshot_retention_count` | `TENDWIRE_SNAPSHOT_RETENTION_COUNT` | `4096` | positive integer; includes each host's latest row |
| `snapshot_maintenance_batch_size` | `TENDWIRE_SNAPSHOT_MAINTENANCE_BATCH_SIZE` | `100` | integer from 1 through 1000 |
| `store_maintenance_cadence_seconds` | `TENDWIRE_STORE_MAINTENANCE_CADENCE_SECONDS` | `3600` | positive integer |
| `turn_refresh_interval_seconds` | `TENDWIRE_TURN_REFRESH_INTERVAL_SECONDS` | `2.0` | finite positive float |
| `turn_refresh_workers` | `TENDWIRE_TURN_REFRESH_WORKERS` | `4` | integer from 1 through 32 and no greater than `max_workers` |
| `turn_model` | `TENDWIRE_TURN_MODEL` | `observed` | `observed`; `legacy`, `dual`, and `shadow` are deprecated aliases with identical observed behavior |
| `acp_thought_policy` | `TENDWIRE_ACP_THOUGHT_POLICY` | `disabled` | `disabled`, `private_summary`, or `private_all`; never a public-delivery grant |
| `acp_request_timeout_seconds` | `TENDWIRE_ACP_REQUEST_TIMEOUT_SECONDS` | `30.0` | finite positive float |
| `acp_shutdown_timeout_seconds` | `TENDWIRE_ACP_SHUTDOWN_TIMEOUT_SECONDS` | `5.0` | finite positive float |
| `acp_max_frame_bytes` | `TENDWIRE_ACP_MAX_FRAME_BYTES` | `8388608` | integer from 1 through 67108864 |

The socket/event backend uses `event_debounce_seconds` for event batching and
`reconcile_interval_seconds` for bounded periodic full reconciles. Set
Expand All @@ -560,6 +567,34 @@ snapshot/projections instead of publishing a truncated authoritative snapshot.
Incremental events that would add workers over the cap are ignored with the
same public-safe degraded evidence.

The stock daemon requires ACP for supported agents. It uses Herdr's private
`agent.acp_endpoint` contract and accepts only workers explicitly marked
`acp_owned_ready`; ordinary PTY workers are never attached as sidecars. A
missing or unhealthy ACP endpoint degrades daemon health and fails command
submission closed. There is no transcript-reader scheduler, shadow/preferred
mode, or PTY fallback.
Production ACP uses a bounded per-worker permission broker. It publishes only
the sanitized tool title and numeric choices (option label and kind) through
the durable pending-decision surface; ACP option IDs, arguments, session IDs,
and adapter metadata remain private. `answer_decision` is fenced to the exact
worker binding, ACP session, and Herdr generation. A command is accepted only
after the complete JSON-RPC permission-response frame is written. Missing or
retired ACP authority fails closed without falling back to PTY input, and
concurrent answers can produce at most one response. None of this makes agent
thoughts public: thought events remain private diagnostic data unless a
separate, explicit sanitized projection is introduced.

Store maintenance retires expired structured agent-event payloads in bounded
batches using `event_retention_days`. Compact identity tombstones remain so a
replayed source event cannot be reinserted or silently change content after its
private payload has expired. Due automatic daemon maintenance performs the same
host-scoped retirement as explicit `store cleanup`, using a metadata-only scan
that does not load private payloads. Tombstones intentionally retain bounded
per-event hashes and opaque identity only; they are not a recoverable copy of the
removed payload. Their count is permanent and grows with distinct source-event
identities. SQLite secure deletion is best-effort page hygiene, not a promise of
immediate physical erasure across WAL/checkpoints, snapshots, or backups.

Snapshot history defaults are sized for a five-minute observation rhythm:
$14 \times 24 \times 12 = 4032$ observations, while the 4096-row count
ceiling (including the latest row) leaves 64 rows of headroom. This is a
Expand Down Expand Up @@ -1466,10 +1501,6 @@ metadata and does not change observation-derived turn identity, turn-list order,
Goal 10 delivery, or Herdres consumption. Lazy and periodic settlement cover
both submission-first and observation-first arrival order.

`TENDWIRE_TURN_MODEL` remains accepted for rollout compatibility. `legacy`,
`dual`, and `shadow` emit a warning and use the same observation-authoritative
behavior as `observed`.

`disposition`, not `status` alone, is the receipt-authority and finality
contract:

Expand Down
67 changes: 67 additions & 0 deletions docs/acp-adapter-probe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Black-box ACP adapter initialization probe

Tendwire treats an ACP adapter as a separately installed executable. The
adapter is not vendored, imported, rebased, or coupled to a repository layout.
Run the initialization probe after installing or upgrading any adapter and
before promoting that executable into the Tendwire runtime:

```console
python -m tendwire.backends.acp_probe -- /absolute/path/to/adapter adapter-arg
```

The command starts the supplied argv directly without a shell, negotiates ACP
v1 capabilities on a fresh process, closes the process, prints one bounded JSON
object, and exits zero only when negotiation and shutdown succeed. Use
`--timeout`, `--close-timeout`, and an absolute `--cwd` before the `--` marker
when needed. Probe timeouts have hard upper bounds.

The probe scope is deliberately limited to `initialize`. It does not create a
session, send a prompt, spend model tokens, or claim that the adapter actually
implements ACP's mandatory baseline session methods. The output contains fixed
booleans for optional capabilities advertised by the agent, bounded counts for
schema-valid authentication methods and `_meta` capability-extension
namespaces, a process-reaped flag, and a fixed failure category. It never
contains:

- adapter argv or executable paths;
- working directories or environment values;
- stderr, exception text, or raw JSON-RPC payloads;
- agent-provided names, versions, extension names, or extension values;
- session IDs, messages, thoughts, tool data, plans, or authentication values.

Example successful shape:

```json
{
"authentication": {"method_count": 0, "method_count_capped": false},
"advertised_capabilities": {
"additional_directories": true,
"auth_logout": false,
"mcp_http": false,
"mcp_sse": false,
"prompt_audio": false,
"prompt_embedded_context": false,
"prompt_image": false,
"session_close": true,
"session_delete": true,
"session_list": true,
"session_load": true,
"session_resume": true
},
"initialization_compatible": true,
"extensions": {"capability_count": 0, "capability_count_capped": false},
"failure": null,
"probe_scope": "initialize",
"process_reaped": true,
"protocol_version": 1,
"schema_version": 2
}
```

An initialization-incompatible result exits with status 1 and reports only one
of these stable categories: `invalid_configuration`, `launch_failed`, `timeout`,
`protocol_version`, `protocol_error`, `transport_error`, `shutdown_failed`, or
`internal_error`. Keep the previously proven executable available for rollback;
the probe validates initialization and advertised capability parsing, not
account authentication, mandatory baseline method behavior, or a stateful agent
session.
106 changes: 106 additions & 0 deletions docs/acp-migration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# ACP-required architecture

ACP is Tendwire's required semantic and command protocol for every supported
agent. This is a release boundary, not a runtime rollout switch: rollback means
deploying an earlier release. There are no legacy, shadow, preferred, dual, or
fallback modes in the daemon.

## Authority split

| Concern | Authority |
| --- | --- |
| Workspace, pane, worker lifecycle, and ACP endpoint ownership | Herdr |
| Public stable worker identity | Tendwire's authenticated Herdr projection |
| Agent session, messages, tools, plans, usage, and permissions | ACP |
| Durable journal, public turn projection, privacy, and finality | Tendwire |
| Command idempotency and uncertain outcomes | Tendwire receipts |
| Connector retry and delivery state | Tendwire outbox |
| Telegram presentation | Herdres |

Herdr is not a transcript or command-input transport. Tendwire consumes its
worker/pane lifecycle events, verifies the worker generation, and asks it to
mint a one-shot private ACP endpoint. A supported worker without an explicitly
Herdr-owned healthy ACP endpoint is unavailable and degrades ACP health.

An ACP `sessionId`, endpoint ticket, adapter command, terminal ID, and pane ID
are private. Tendwire binds the session to the current private worker binding
and rejects updates or commands after that generation moves, expires, or is
replaced.

## Runtime and protocol boundary

The daemon supervises one ACP worker session for each eligible Herdr worker.
The supervisor owns reconciliation, generation fencing, reconnect, console
exchange, and command routing. The worker session owns initialize, session
open/load/resume, update draining, permission handling, cancellation, and
bounded shutdown. The bounded connection owns subprocess I/O, JSON-RPC request
correlation, framing limits, stderr limits, and backpressure.

Tendwire uses the official `agent-client-protocol` Python package for generated
ACP schemas and validation. Tendwire retains its bounded stdio connection
because its hard frame-size, queue, write-deadline, shutdown, and privacy
requirements are stricter than the upstream convenience transport. Adapter
executables remain separately installed and replaceable; no adapter source tree
is imported or vendored.

Before every prompt and during reconciliation, Tendwire verifies Herdr's
non-mutating ACP status for the exact worker generation. Reconnect always asks
Herdr for a fresh one-shot endpoint. A missing route fails before receipt
reservation; a failure after the durable `send_started` boundary is terminally
uncertain and is never retried through another transport.

## Durable projection and privacy

The structured journal accepts user and agent messages, thoughts, tool calls,
tool updates, plans, usage, session information, and private extension/control
updates. Producer identity and raw ACP payloads are retained only on the
private side. The public turn projection remains deliberately narrower and
continues to drive finality and connector eligibility.

`TENDWIRE_ACP_THOUGHT_POLICY` controls private thought retention:

- `disabled` discards thoughts before persistence.
- `private_summary` retains only updates carrying Tendwire's exact trusted
summary marker.
- `private_all` retains raw thought chunks for explicit local diagnostics.

No thought policy grants public or outbox delivery. Raw reasoning, tool input,
tool output, session IDs, paths, terminal data, and adapter metadata must remain
outside every public API and connector payload.

ACP permission requests use the durable pending-decision projection. Only a
sanitized title and numbered public choices are exposed. The private option ID,
tool call, arguments, ACP session, and metadata remain behind the boundary.
`answer_decision` is fenced to the exact worker binding and generation and is
accepted only after the full JSON-RPC response frame is written.

## Retention and replay

`event_retention_days` bounds private structured-event payloads. Cleanup
replaces expired payloads with compact identity tombstones so exact replays
remain idempotent and conflicting producer-identity reuse fails closed.
Tombstones preserve only bounded replay evidence and authority time; they are
not recoverable event content.

Tendwire continues to own durable command receipts and its neutral connector
outbox. ACP adapter restarts therefore do not erase accepted-command evidence,
turn finality, acknowledgement state, or delivery retries.

## Release and conformance gates

An ACP-required release must pass:

- initialization and official-schema validation against each supported
adapter;
- new/load/resume, prompt, steering, cancellation, and permission flows;
- generation fencing and reconnect with freshly minted Herdr endpoints;
- no missing or duplicated user/final messages across adapter restarts;
- deterministic replay deduplication and exactly-once final projection;
- complete tool and plan lifecycle after cancellation or permission denial;
- absence of raw thoughts, tool payloads, session IDs, and terminal data from
public APIs and the outbox;
- durable receipt behavior at every pre-send and post-send failure boundary;
- exact worker continuity across Herdr pane moves and agent recreation.

If a release fails these gates, roll back the release. Do not reintroduce a
runtime fallback mode.
Loading
Loading