Skip to content

Commit 91f4417

Browse files
wow-mileyclaude
andcommitted
AMPR-177 #506: CHI (Computer-Human Interface) Concept Cell
I wrote a new Concept Cell defining CHI as the inverse of HCI — the runtime protocol for computer-initiated human contact. The cell inventories the four uncoordinated paths that implement slices of CHI today (ToolAskHuman, MessageEvent.EscalationRequested, AgentPause, HumanInteractionEvent), names the load-bearing response-pairing invariant, and sketches the target collapse onto EmissionKind.Decision so the Wave 1c unification agent does not have to rediscover each path's wrinkles. Pure documentation — no code changes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent bc2d16d commit 91f4417

2 files changed

Lines changed: 139 additions & 0 deletions

File tree

docs/concepts/_index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,5 +40,6 @@ How the cognitive substrate meets the user, the platform, and the plugin ecosyst
4040
| Concept | Status | One-line summary |
4141
|---------|--------|------------------|
4242
| [AgentSurface](agent-surface.md) | stable | Typed, serializable UI render request (Form, Choice, Confirmation, Card). Plugins emit; platform renderers translate. No platform types in the contract. |
43+
| [ChiProtocol](chi.md) | experimental | Computer-Human Interface: the inverse of HCI. Runtime protocol for computer-initiated human contact. Four uncoordinated paths today; target collapse onto `EmissionKind.Decision`. |
4344
| [PluginPermissions](plugin-permissions.md) | stable | Deterministic gate that runs *before* any plugin tool dispatch. Compares manifest + tool-requested permissions against user grants. |
4445
| [Ampere](ampere.md) | stable | The meta-concept: what makes a framework an AMPERE framework. Glass brain, AniMA agents, electrical metaphor, event-first coordination. |

docs/concepts/chi.md

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
---
2+
concept: ChiProtocol
3+
status: experimental
4+
tracked_sources:
5+
- ampere-core/src/commonMain/kotlin/link/socket/ampere/agents/execution/tools/ToolAskHuman.kt
6+
- ampere-core/src/jvmMain/kotlin/link/socket/ampere/agents/execution/tools/ToolAskHuman.jvm.kt
7+
- ampere-core/src/commonMain/kotlin/link/socket/ampere/agents/events/messages/AgentMessageApi.kt
8+
- ampere-core/src/commonMain/kotlin/link/socket/ampere/agents/events/escalation/EscalationEventHandler.kt
9+
- ampere-core/src/commonMain/kotlin/link/socket/ampere/agents/events/escalation/DefaultEscalationPolicy.kt
10+
- ampere-core/src/commonMain/kotlin/link/socket/ampere/pause/AgentPause.kt
11+
- ampere-core/src/commonMain/kotlin/link/socket/ampere/agents/domain/event/HumanInteractionEvent.kt
12+
related: [Emission, AgentPause, MessageEvent, EventSerialBus]
13+
last_verified: 2026-05-27
14+
---
15+
16+
# CHI (Computer-Human Interface)
17+
18+
## What it is
19+
20+
CHI — **Computer-Human Interface** — is the runtime protocol by which the
21+
*computer* initiates contact back to the human. It is the inverse of HCI:
22+
where HCI is the design discipline for how humans reach into computers, CHI
23+
is the protocol for how the system reaches out to a person. Every
24+
uncertainty escalation, confirmation request, ambient notification,
25+
decision query, or sensor report is an instance of CHI. Today, CHI exists
26+
architecturally but has no coherent implementation; four independent paths
27+
each implement a slice of it. The target state is for all four to collapse
28+
into a single `Emission`-with-affordances primitive carried over the
29+
`EventSerialBus`.
30+
31+
## Why it exists
32+
33+
Without a named umbrella, the four implementations drift further apart with
34+
every feature: each ticket touches the path that is closest to hand, and
35+
the lifecycle assumptions accumulate as silent biases. Naming CHI as the
36+
protocol — and the future `EmissionKind.Decision` as its single carrier —
37+
gives every contributor a shared frame for "the computer is asking a
38+
person something." Concretely:
39+
40+
1. **Routing.** A unified CHI lets the system pick a channel (push, voice,
41+
in-app card, public link, console) from one policy instead of four.
42+
2. **Observability.** Every CHI event would flow over the bus and be
43+
logged with `run_id`, restoring trace continuity that ad-hoc paths
44+
currently break.
45+
3. **Pairing.** A single correlation discipline (request ↔ response) is
46+
the only way a human's reply can be causally attributed to the request
47+
that prompted it — today this is held weakly across three different
48+
identifier fields.
49+
4. **Substitution.** Once CHI is the contract, surfaces (CLI, mobile,
50+
voice) become interchangeable renderers. No surface owns the protocol.
51+
52+
This Concept Cell is descriptive (current four paths) and aspirational
53+
(target collapse). It does **not** unify any code; the Wave 1c unification
54+
agent will do that, and needs this load-bearing context first so it does
55+
not rediscover each path's wrinkles from scratch.
56+
57+
## Where it lives
58+
59+
The four current CHI paths:
60+
61+
- **Path 1 — `ToolAskHuman` (`ask_human` tool).**
62+
- `ampere-core/.../execution/tools/ToolAskHuman.kt``expect` factory + `ASK_HUMAN_TOOL_ID`.
63+
- `ampere-core/.../execution/tools/ToolAskHuman.jvm.kt` — JVM `actual`: prints a console banner, calls `GlobalHumanResponseRegistry.instance.waitForResponse(requestId, 30.minutes)`, returns `ExecutionOutcome.NoChanges.Success` or `.Failure` on timeout.
64+
- `ampere-core/.../execution/tools/human/GlobalHumanResponseRegistry.kt` — process-wide singleton paired by `requestId`.
65+
- **Shape:** blocking suspend, no bus emission, console-only surface, 30-min hard-coded timeout.
66+
67+
- **Path 2 — `MessageEvent.EscalationRequested` (thread escalation).**
68+
- `ampere-core/.../agents/events/messages/AgentMessageApi.kt:151``escalateToHuman(threadId, reason, context)` transitions the thread to `EventStatus.WaitingForHuman` and publishes `EscalationRequested` + `ThreadStatusChanged`.
69+
- `ampere-core/.../agents/domain/event/MessageEvent.kt:86` — the event itself (`threadId`, `reason`, `context`, `urgency`).
70+
- `ampere-core/.../agents/events/escalation/EscalationEventHandler.kt` — subscribes to `EscalationRequested` and calls `humanNotifier.notifyEscalation(...)`.
71+
- `ampere-core/.../agents/events/escalation/DefaultEscalationPolicy.kt` — keyword-based classification into the `Escalation` sealed hierarchy.
72+
- **Shape:** fire-and-forget bus emission, thread-scoped, no response pairing on the publishing side, status transition is the durable record.
73+
74+
- **Path 3 — `AgentPause` (typed pause contract).**
75+
- `ampere-core/.../pause/AgentPause.kt``correlationId: PauseCorrelationId`, `reason`, `urgency: PauseUrgency` (`Routine` | `Important` | `Critical`), `suggestedChannels: List<EscalationChannel>` (ordered fallback), `timeoutMillis`, optional `fallbackUrl`.
76+
- `ampere-core/.../pause/AgentPauseResponse.kt``Approved` / `Rejected` / `TimedOut`.
77+
- `ampere-core/.../pause/EscalationChannel.kt``Push`, `Voice`, `InAppCard`, `PublicLink`.
78+
- **Shape:** type contract only as of W0.3. Bus events deferred. Channel selector is W1.5.
79+
80+
- **Path 4 — `HumanInteractionEvent` (request/response event pair).**
81+
- `ampere-core/.../agents/domain/event/HumanInteractionEvent.kt` — sealed interface with `InputRequested(requestId, agentId, question, context, ticketId?, taskId?)`, `InputProvided(requestId, agentId, response, respondedBy?)`, `RequestTimedOut(requestId, agentId, timeoutMinutes)`.
82+
- **Shape:** event types defined and ready for bus dispatch — but **not currently emitted** by `ToolAskHuman` or `AgentMessageApi.escalateToHuman`. Pairing is by `requestId`.
83+
84+
### Path comparison
85+
86+
| Dimension | `ToolAskHuman` | `MessageEvent.EscalationRequested` | `AgentPause` | `HumanInteractionEvent` |
87+
|--------------------|-----------------------------------|------------------------------------|---------------------------------------|------------------------------------|
88+
| Entry point | Tool dispatch | `AgentMessageApi.escalateToHuman` | (W0.3: contract only) | (defined, no emitter) |
89+
| Payload | `instructions` string | `reason` + `context: Map` | `reason` + `urgency` + channels | `question` + `context: Map` |
90+
| Correlation field | `requestId` (UUID) | `threadId` + `eventId` | `correlationId: PauseCorrelationId` | `requestId` (UUID) |
91+
| Persistence | In-memory registry (singleton) | `EventStore` + thread status row | None yet | `EventStore` (when emitted) |
92+
| Urgency model | None (hard-coded) | `domain.Urgency` (defaults `HIGH`) | `PauseUrgency` (Routine/Important/Critical) | `domain.Urgency` |
93+
| Timeout | `30.minutes` hard-coded | None | `timeoutMillis` per pause | `timeoutMinutes` per event |
94+
| Channel selection | Console only | `humanNotifier` (single sink) | Ordered `suggestedChannels` fallback | None — surface decides |
95+
| Response delivery | `provideResponse(requestId, ...)` | None (out-of-band human action) | `AgentPauseResponse` (target) | `InputProvided` event (paired) |
96+
| Calling-side block | Blocks calling coroutine | Fire-and-forget | Suspends agent (target) | Fire-and-forget (target) |
97+
98+
## Invariants
99+
100+
- **Every CHI request must carry an identifier that uniquely pairs it with its response.** This is the load-bearing CHI invariant: a human's reply must causally link back to the request that prompted it. Today this is held weakly across three fields — `ToolAskHuman.requestId`, `AgentPause.correlationId` (`PauseCorrelationId`), and `HumanInteractionEvent.requestId`. The unification target is one `correlationId` semantics across all four paths.
101+
- **A CHI request must declare its lifecycle.** "Blocks the calling coroutine" (`ToolAskHuman`), "fire-and-forget over the bus" (`MessageEvent.EscalationRequested`), and "suspends the agent until response" (`AgentPause` target) are not interchangeable. Any unification must keep the lifecycle explicit, not paper over it.
102+
- **A CHI request must declare its timeout posture.** A missing timeout is not the same as an infinite timeout; both differ from a fixed 30-minute wall. The current paths span all three.
103+
- **Channel selection is policy, not payload.** `AgentPause.suggestedChannels` is an *ordered preference*; the channel selector (W1.5) decides what actually fires. CHI requests must not assume any specific surface — adding "send a Slack message" inside `ToolAskHuman` is the canonical violation.
104+
- **Thread state transitions on `EscalationRequested` are owned by `AgentMessageApi.escalateToHuman`, not by handlers.** `EscalationEventHandler` only notifies; the `EventStatus.WaitingForHuman` transition is committed inside the API before the event is published. Handlers must not re-transition.
105+
- **`HumanInteractionEvent` is the future bus-side contract; its non-emission today is a gap, not a design choice.** Treating `InputRequested` / `InputProvided` as deprecated would break the target unification before it ships.
106+
107+
## Common operations
108+
109+
Today (use the path that fits the lifecycle, do not mix them):
110+
111+
- **Block an executing tool waiting on a person** — call `ToolAskHuman` from a tool definition with `requiredAgentAutonomy` set; the JVM `actual` will print to console and block on `GlobalHumanResponseRegistry`. Respond out-of-band with `./ampere-cli/ampere respond <requestId> "<text>"`.
112+
- **Escalate a conversational thread**`agentMessageApi.escalateToHuman(threadId, reason, context)`. Status transitions to `WaitingForHuman`, two events publish, and `EscalationEventHandler` fires `humanNotifier.notifyEscalation(...)`.
113+
- **Construct a typed pause descriptor** — build an `AgentPause(correlationId, reason, urgency, suggestedChannels, timeoutMillis, fallbackUrl?)`. The dispatching infrastructure ships in a later wave; for now the contract is consumed by per-Arc override UI and unit tests.
114+
- **Classify an escalation reason**`DefaultEscalationPolicy` maps free-text reasons into the `Escalation` sealed hierarchy (`Discussion`, `Decision`, `Budget`, `Priorities`, `Scope`, `External`) and an `EscalationProcess`.
115+
116+
Target (post-unification, sketch only):
117+
118+
- **Emit a CHI request** — publish `Emission(kind = EmissionKind.Decision, affordances = ..., provenance = EmissionProvenance(...), surface = ...)`. The router decides channels from `affordances` and `provenance.urgency`; the response surfaces as a paired `Emission` (or a typed `EmissionResponse`) carrying the original `correlationId`.
119+
120+
### Mapping current paths onto `EmissionKind.Decision`
121+
122+
Each existing path maps cleanly onto the target Emission, **provided the Emission contract carries the extra metadata each lifecycle currently encodes**. Concrete deltas the Wave 1c unification must preserve:
123+
124+
| Path | Required Emission metadata to preserve fidelity |
125+
|-------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
126+
| `ToolAskHuman` | Calling-coroutine lifecycle (suspend-until-response), default-30-min timeout, "console" as the floor surface when no richer channel is reachable. |
127+
| `MessageEvent.EscalationRequested` | `threadId` link + the `EventStatus.WaitingForHuman` transition (CHI emission must trigger or be triggered by the status change, not race it). |
128+
| `AgentPause` | Ordered `suggestedChannels`, `PauseUrgency` → channel default mapping, optional `fallbackUrl` semantics, and the `Approved`/`Rejected`/`TimedOut` response trichotomy. |
129+
| `HumanInteractionEvent` | Strict `requestId` pairing between request and response Emissions, plus the optional `ticketId` / `taskId` / `respondedBy` attribution fields. |
130+
131+
## Anti-patterns
132+
133+
- **Treating the four paths as interchangeable.** Each was built for a different lifecycle (blocking tool, thread escalation, typed pause, bus event pair). Picking whichever path is closest at hand and "wrapping" the others around it breaks the response-pairing invariant in subtle ways. Pick the path whose lifecycle matches your need; do not bridge them ad hoc.
134+
- **Adding a new fifth CHI path.** If you find yourself writing a new "ask the human" primitive, you are deepening the problem this concept cell exists to flag. Extend one of the four, or wait for the Emission collapse.
135+
- **Calling `ToolAskHuman` from `escalateToHuman` (or vice versa) to "get both".** The console wait and the thread status transition are not composable — you end up blocking a coroutine inside an event handler and dropping the response pairing.
136+
- **Hand-writing a `requestId` and not propagating it.** Every CHI request must carry an identifier that the response can quote back. Generating a UUID and discarding it (or logging it without persistence) breaks causal linkage and the trace projection.
137+
- **Hard-coding a surface inside a CHI emitter.** `ToolAskHuman.jvm.kt` prints to `println` because it predates the channel selector; that is a known wart, not a pattern to copy. New code must declare *intent* (`suggestedChannels`, `PauseUrgency`, affordances) and leave surface choice to the selector.
138+
- **Treating `HumanInteractionEvent` as dead because nothing emits it today.** It is the future bus-side contract for CHI; removing it would force the Wave 1c unification to reinvent the same shape.

0 commit comments

Comments
 (0)