Files
tai/taicpp/docs/cap.md
T
retoorandClaude Sonnet 5.5 ec9c471780 orchestration: sub agents render in live panels on the multiplexer screen
Feed panes in the multiplexer (vpane_* control commands, main-vertical layout, header row),
src/orch (board, panel, formatter, transport, CAP adapter), Agent observer and cancel flag,
supervision (idle warn/cancel, user close cancels, soft then hard stop), CAP answers collected
through the spill path. Research archived under background-work-limits.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-25 18:58:22 +01:00

103 lines
7.9 KiB
Markdown

# cap (taicpp)
Port of the CAP section of `../tai.py` (`CAP_VERSION` ... `TAI_CAP_MANIFEST`),
normative text in `../cap.md`. Namespace `detz::cap`, sources in `src/cap/`,
one class per `.h`/`.cpp` pair. The C++ names are the Python names in
camelCase; behavior is verified by running both programs (see Verification).
## Spec delta (this milestone)
- Real `TAI_CAP_MANIFEST` replaces the one-line placeholder
(`CapAgentManifest::TAI_CAP_MANIFEST`, `--cap-manifest` prints it plus
the `print()` newline; byte-identical to `tai.py --cap-manifest`).
- All Cap classes ported: events, errors, bindings, manifest, registry,
session config, stream-json, speak session, the four drivers, answerer,
session, fleet, CLI. The old `Orchestrator` grant/check placeholder is gone.
- Layer `wire_*` (namespace `detz::wire`) supplies what Python's stdlib gave
tai.py: `Json` (json), `WireToml` (tomllib), `WireRegex` (re), `WireProc` /
`WirePty` (subprocess, PtyProc), `WireHttp` (urllib, http.client, http.server),
`WireArgParse` (argparse), `WireShlex`, `WireDifflib`, `WireHash`, `WireText`,
`WireOs`.
## Parity table (Python -> C++)
| Python | C++ |
|---|---|
| `CAP_VERSION` `CAP_MANIFEST_SCHEMA` `CAP_PROFILE_CODING` `CAP_MANIFEST_REVISION` `CAP_PROBE_TIMEOUT` `CAP_ERROR_CODES` `CAP_ERROR_STRINGS` `CAP_SESSION_CONFIG_ENV` `CAP_SPEAK_MAX_TURNS` `CAP_SPEAK_DIFF_MAX` `CAP_PTY_SETTLE_MS` `CAP_PTY_IDLE_MS` `CAP_PTY_SUBMIT_GAP_MS` `CAP_PTY_RESUBMITS_MAX` `CAP_PTY_SCROLLBACK` `CAP_PTY_INIT_TIMEOUT` `CAP_STREAM_LINE_MAX` `CAP_STREAM_STDERR_KEEP` `CAP_SESSION_POLL` `CAP_TURN_TIMEOUT` `CAP_WORKTREE_DIR` `CAP_ROUTE_QUEUE_MAX` `CAP_FLEET_DIFF_MAX` `CAP_MENTION_RE` `CAP_ACP_VERSION` `CAP_ACP_TERMINALS_MAX` `CAP_ACP_TERM_BUF` `CAP_ACP_TERM_WAIT_MAX` `CAP_ACP_FS_MAX` `CAP_A2A_TIMEOUT` `CAP_ANSWER_TIMEOUT` `CAP_ANSWER_PARALLEL` `CAP_ANSWER_MARKER` `CAP_ANSWER_RISKS` | `cap_constants.h` (same names) |
| `CAP_ERROR_SYMBOLS` | `capErrorNumeric()` in `cap_error.h` |
| `TAI_CAP_MANIFEST` | `CapAgentManifest::TAI_CAP_MANIFEST()` |
| `CapError` | `cap_error.h` |
| `CapEvent` | `CapEvent` (`KINDS`, `DIRECTIONS`, `REQUIRED`, `ENUMS`, `make`, `validate`, `direction`) |
| `CapBinding` | `CapBinding` (`ORDER`, `UNSUPPORTED`, `declared`, `select`) |
| `CapManifest` | `CapManifest` |
| `CapRegistry` | `CapRegistry` |
| `CapSessionConfig` | `CapSessionConfig` |
| `CapStreamJson` | `CapStreamJson` |
| `CapSpeakSession`, `cap_interrupt`, `cap_speak_main` | `CapSpeakSession`, `CapInterrupt`, `CapSpeakMain` |
| `CapDriver` | `CapDriver` (`CapDriver::create` in `cap_driver_factory.cpp`) |
| `CapPtyDriver` | `CapPtyDriver` over `WirePty` and `CapVtScreen` (port of `TuiVtScreen`, `CapScreen` seam) |
| `CapPipeReader`, `cap_proc_kill`, `cap_proc_reap`, `cap_stderr_drain` | `CapPipeReader`, `CapProcUtil`, `CapStderrDrain` |
| `CapStreamDriver` `CapAcpDriver` `CapA2aDriver` | same names |
| `CapAnswerer` | `CapAnswerer` |
| `CapTurnResult` `CapSession` `CapFleet` | same names |
| `cap_cli_main` `cap_cli_help` `cap_resolve_main` `cap_chat_main` `cap_run_main` `cap_fleet_plan` `cap_print_result` `cap_build_answerer` `cap_answer_flags` | `CapCli` static methods |
## Event kinds
`cap.session.ready`, `cap.text_chunk`, `cap.tool_call.start`,
`cap.tool_call.delta`, `cap.tool_call.end`, `cap.plan`, `cap.thought`,
`cap.ask_user`, `cap.permission.request`, `cap.usage`, `cap.error`,
`cap.artifact`, `cap.fs.change`, `cap.pty.raw_bytes`,
`cap.user_input.inject`, `cap.cancel`, `cap.session.config`,
`cap.ask_user.answer`, `cap.permission.response`.
## Errors
Symbols: `cap_manifest_missing`, `cap_session_config_missing`,
`cap_unsupported_capability`, `cap_budget_exceeded`, `cap_invalid_answer`,
`cap_unknown_permission`, `cap_queued_input_unsupported`,
`cap_cancel_unsupported`, `cap_binding_unavailable`,
`cap_profile_unsupported`, `cap_scope_violation`, `cap_path_not_found`,
`cap_io_error`, `cap_terminal_unavailable`, `cap_terminal_killed`.
String codes: `rate_limit`, `auth_error`, `tool_timeout`, `context_overflow`,
`model_unavailable`, `pty_died`, `parse_failed`, `git_dirty`, `test_failed`,
`merge_conflict`, `language_server_down`.
## Seams (host-provided)
`CapAgentPort` / `CapSpeakHost` (the agent for `--cap-speak`), `WireLog`
(debug log sink), `WireApp::setVersion` (VERSION on the wire),
`CapPtyDriver::setScreenFactory` (swap the VT screen). Details and the exact
hooks: `docs/wiring/capmcp.md`.
## Differences from Python (all declared)
- `CapAnswerer` runs `[binary, --auto, prompt]` directly; Python ran
`[sys.executable, tai.py, ...]`. Default binary is `/proc/self/exe`.
- `CapVtScreen` parses SGR but does not store colors/attributes (text-only
consumer) and keeps no scrollback history.
- Regex: ECMAScript engine with Python syntax translation. Python-only syntax
beyond named groups, `\A`, `\Z`, `$`, bare `{`/`}`, leading `(?i)` is not
translated; compile error texts differ from `re.error`.
- argparse help is plain (no 3.14 color).
- Dates in TOML come back as strings.
- Terminal `terminal/output` keeps the `read(65536)` blocking-chunk behavior.
- Signals: SIGINT/SIGTERM set a flag polled by waits (`CapInterrupt`).
## Model catalog and `@agent` mentions
`CapModelCatalog` (`src/cap/cap_model_catalog.*`) lists agents from the `*.toml` manifests in the registry user and system dirs (cap `CAP_AGENT_MAX` 64) and checks each: manifest parses, binary on PATH. Status: `ready`, `no_binary`, `bad_manifest`, `no_manifest`. Entries are cached `CAP_CATALOG_TTL` (60) seconds; entries load in parallel.
Model protocol (tai-specific extension, absent from the Python app): `[models] list = [...]` and `default` in the manifest, plus an optional probe, `[models] probe = ["bin", "--cap-models"]` or `capabilities.models = true` for `<binary> --cap-models`. Probe output: JSON array of ids or `{id|name|model}` objects, `{"models": [...], "default": "..."}`, or one id per line. Bounds: `CAP_MODEL_PROBE_TIMEOUT` 5 s, `CAP_MODEL_PROBE_MAX_BYTES` 1 MiB, `CAP_MODEL_MAX` 500 per agent. A failed probe is recorded on the entry, never thrown. Tai's own manifest is unchanged and declares no models.
Mention resolution (after bots, providers, backends, known model ids, `@auto`, exact agent, and file paths; so a real file is never shadowed): the typed text is fuzzy-matched with `FuzzyMatcher` ([fuzzy.md](fuzzy.md)). Order: exact agent name or model id; `head:tail` splits at the first `:`, head fuzzy to a ready agent, tail fuzzy to that agent's models (no floor, the agent already disambiguates); otherwise the whole text is ranked against every ready agent name and every model. Cross-namespace picks need a score of `CAP_FUZZY_FLOOR` (0.3); the best score wins and equal scores resolve shorter id then list order, never an error. Unusable agents (no binary, bad manifest) are considered only when no ready candidate reaches the floor and then report why. An agent without a model list passes the model through with a note. When the pick differs from the typed text a note line shows the resolution and score. With a prompt the turn runs on that agent (`CapMention`, one cached `CapSession` per agent/model/cwd, model sent via `startup.model_arg`), in its own panel on the multiplexer screen, supervised (turn timeout, idle watchdog, closing the panel cancels it) and collected through the spill path ([orchestration.md](orchestration.md)); without a prompt the agent is pinned (`Agent::agent_pin`) until `@auto`. A miss prints the closest candidate with its score and the full per-agent report (status, binary, manifest, binding, models, default, probe error); an unknown tag appends the same report.
Gaps: no TUI completion for agents yet; `tai --cap-models` is not implemented on tai's own side.
## Verification
`tests/capmcp_diff.py` (JSON, TOML, str(), float repr, regex vs CPython),
`tests/capmcp_cli_diff.py` (`--cap-manifest`, `--cap resolve|chat|run` incl.
stream-json, PTY, ACP and A2A fake agents, argparse errors at four widths vs
`tai.py`), `tests/capmcp_unit.cpp` (tables and drift check).