feat(doing): let an agent advertise what it is working on - #109
Closed
joy13975 wants to merge 5 commits into
Closed
Conversation
hcom tells you whether an agent is awake, not what it is doing. `status`
is a fixed lifecycle enum (active/listening/blocked/inactive/launching/
error) and there is no field an agent can deliberately set to describe
its current task. So a peer deciding whether to interrupt, hand off, or
wait has to read a transcript to find out.
hcom doing "refactoring the auth layer" # set
hcom doing # show
hcom doing "" # clear
Peers see it in `hcom list` (inline, truncated), `hcom list -v`,
`hcom list <name>`, `--json`, `--format '{doing}'`, and the TUI agent
detail pane.
Stored as a `doing` event rather than a new instances column:
- status_context/status_detail are rewritten by hooks on every tool-use
tick, and set_gate_status explicitly overwrites status_detail unless it
equals the "cmd:listen" sentinel, so agent-authored text there does not
survive. hints is reinjected into the owning agent's own feedback and is
not in list output; notes is baked into the launch prompt once.
- no schema change, so no SCHEMA_VERSION bump and no migration for
existing databases.
- `hcom list --stopped` already derives current state from `life` events,
so latest-event-wins is an established pattern here.
- it composes with the existing event filters, so `hcom events --type
doing` and subscriptions work with no extra code.
Named `doing` and not `intent` because `hcom send --intent
request|inform|ack` already means something different.
An unregistered --name is rejected rather than recorded: activity filed
under a name no listing can resolve would be a silent no-op.
The listing reads it with one grouped query for the whole roster instead
of one lookup per agent. doing_map_from_conn takes a raw Connection so the
CLI and the TUI snapshot loader share a single definition of that query.
Tests: db round-trip, latest-wins including clear, per-instance map
isolation from other event types, plus CLI smoke coverage of set/show/
list --json/--format/update/clear and the unregistered-name rejection.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`--type` is a closed clap enum, so `hcom events --type doing` was rejected with "invalid value 'doing'" even though the rows were in the events table. Being filterable is most of the reason to store this as an event, so add it to the enum shared by events, listen, and events sub, and to the two places that spell the valid types out for humans and agents. Found by running the real bus rather than reading the diff: the query returned nothing while sqlite showed three doing rows. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- validate `doing` free text at the write boundary (cmd_doing) via a helper shared with validate_message: reject C0/C1 control chars incl. ESC 0x1B and cap size at MAX_MESSAGE_SIZE, additionally rejecting tabs/newlines since doing renders on one roster line; empty stays allowed as the clear - extract validate_text_field(text, allow_line_whitespace) as the single write-boundary guard so every sink (roster, -v, list <name>, TUI, relay replication) is safe by construction, closing the unvalidated-free-text class - regression tests: CLI smoke rejects escape/newline injection; unit tests pin the single-line vs multi-line control-char + size invariant Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- validate_text_field: reject DEL (0x7F) and bidi controls (U+202A-202E, U+2066-2069) in both modes, closing the display-control-chars class at the write boundary; ZWJ and other Cf chars stay allowed - size-cap error now reads "bytes" (the check counts UTF-8 bytes) - inline-roster newline/tab spoofing closed by the same write boundary (no consumer patch) - list.rs: move misattributed doc comment onto get_unread_count - filters.rs: reference DOING_EVENT_TYPE const at the --type gate (re-exported from db) so the accept-gate cannot drift from the write/read side; parse test now asserts against the const - doing_map_from_conn: log_error on prepare/query failure instead of silently returning an empty map - start_rebind: reset doing to blank on identity reclaim so a takeover does not inherit the prior occupant's stale activity text Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The round-2 fix commit left two hunks unformatted, which turned CI red on `cargo fmt --all -- --check` in both rust-tests and windows-build. No behavior change; `cargo fmt --all -- --check` and `cargo clippy --all-targets -- -D warnings` are now clean and the cli_smoke suite still passes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
hcom tells you whether an agent is awake, not what it is doing.
statusis a fixed lifecycle enum (active/listening/blocked/inactive/launching/error), and there is no field an agent can deliberately set to describe its current task. A peer deciding whether to interrupt, hand off, or wait has to open a transcript to find out.This adds one verb so an agent can say it, and shows it everywhere the roster is already displayed.
Peers read it in
hcom list(inline, truncated to 50 chars),hcom list -v,hcom list <name>,--json,--format '{doing}', the TUI agent detail pane, andhcom events --type doing.Why an event and not an instances column
status_context/status_detailare rewritten by hooks on every tool-use tick, andset_gate_statusexplicitly overwritesstatus_detailunless it equals the one"cmd:listen"sentinel - so agent-authored text there does not survive.hintsis reinjected into the owning agent's own message feedback and never appears inlistoutput.notesis baked into the launch prompt once and is not a live field.SCHEMA_VERSIONbump, noMIGRATIONSentry, nothing for existing databases to migrate.hcom list --stoppedalready derives current state fromlifeevents, so latest-event-wins is an established pattern here rather than a new one.A column would also have meant touching the positional-index
instance_row_to_json, roughly 20InstanceRow/Agentstruct literals in test fixtures, and the TUI's separate hand-written query - about 30 sites for the same user-visible behavior.The second commit is part of that reasoning rather than an afterthought:
--typeis a closed clap enum, sohcom events --type doingwas rejected withinvalid value 'doing'even though the rows were in the table. Adding the type toEventFilterArgsmakes the filter,listen, andevents suball reach it, and there is a parse test so the event type and the filter enum cannot drift apart again. I found that by running the real bus, not by reading the diff.Naming
doing, notintent:hcom send --intent request|inform|ackalready means something different, and two unrelated senses of "intent" one space apart on the CLI would be a trap.Notes
--nameis rejected rather than recorded. Activity filed under a name no listing can resolve would be a silent no-op.doing_map_from_conntakes a rawConnectionso the CLI and the TUI snapshot loader share one definition of that query instead of two copies of the SQL.UNIVERSALandSUBAGENT_BOOTSTRAP) mention the verb, and it is added toSAFE_HCOM_COMMANDSso agents are not prompted for approval on first use.Testing
Unit tests in
src/db/events.rs: round-trip, latest-wins including the empty-string clear, and that the per-instance map ignores other event types. Parse test insrc/commands/events.rsfor--type doing.CLI smoke test in
tests/cli_smoke.rscovering the full path: nothing-set message, set, read back, visible inlist --jsonand--format, latest write wins, clear, and a second test asserting an unregistered name fails.End to end on a real two-harness bus
Not a CLI-only check: I launched a real headless Claude Code agent and a real headless Codex agent onto one bus at the same time, and had them talk.
Both agents set their own
doingthrough the CLI, and each read the other's conversation viahcom transcript. The messages went both ways (intent=requestfrom the claude agent,intent=ackback from the codex agent). Against the released 0.7.25 binary the sameevents --type doingquery fails withinvalid value 'doing', which is the before-state for the second commit.Also verified in the TUI rather than by inspection - the agent detail pane renders:
cargo fmt --checkandcargo clippy -D warningsare clean viajust ci.Note on the local suite
cargo teston my machine has 17 pre-existing failures inhooks::gemini::tests(all assertingsetup_gemini_hooks(false)). They fail identically on unmodifiedmainat 79ebde1 - I diffed the failing node-id sets in both directions and they are the same set, so they are unrelated to this change. They look environment-specific, and CI here is green. Everything else passes: 2161 passed, and the fullcli_smokesuite is green.