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

7.9 KiB

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). 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); 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).