Skip to content

Adjusting how shelley client cli works when streaming responses #243

Description

@minikomi

Issue

I've been intermittently pottering about with creating a telegram bridge for shelley.
At first I wanted to use the shelley client commands as a base

  • shelley chat [-c conv id] to start/continue a conversation
  • shelley read conv_id -wait to stream the responses

Unfortunately, for 2 reasons, I ended up having to poll sqlite in the background:

Tool use messages are a bit anemic,

eg. a bash call to list home dir appears in the chat export as:

## Assistant

**🛠️ Tool: `bash`**


    {
      "command": "ls -la ~"
    }

    <details><summary>Result</summary>

    total 452
    drwxr-x--- 29 exedev exedev   4096 Jul 16 12:11 .
    drwxr-xr-x  3 root   root     4096 Jan  8  2026 ..
....

...but shows up in the CLI output as as:

{"sequence_id":5,"type":"agent","tool_name":"bash","end_of_turn":false}
{"sequence_id":6,"type":"user","end_of_turn":false}

shelley client read -wait only lists up to the first "end_of_turn":true for conversations with multiple user inputs

First interaction

$ exedev@minikomi ~> shelley client chat -p "echo: FIRST RESPONSE"
{"conversation_id":"cJETWZL"}
$ exedev@minikomi ~> shelley client read -wait cJETWZL
{"sequence_id":1,"type":"system","text":"✂︎ system prompt here ✂︎","end_of_turn":false}
{"sequence_id":2,"type":"user","text":"echo: FIRST RESPONSE","end_of_turn":false}
{"sequence_id":3,"type":"agent","text":"FIRST RESPONSE","end_of_turn":true}

So far so good. Let's try a second interaction on the same conversation.

Second interaction

Sending the continuation:

$ exedev@minikomi ~> shelley client chat -c cJETWZL -p "echo: SECOND RESPONSE"
{"conversation_id":"cJETWZL"}

And the stream:

$ exedev@minikomi ~> shelley client read -wait cJETWZL
{"sequence_id":1,"type":"system","text":"✂︎ system prompt here ✂︎","end_of_turn":false}
{"sequence_id":2,"type":"user","text":"echo: FIRST RESPONSE","end_of_turn":false}
{"sequence_id":3,"type":"agent","text":"FIRST RESPONSE","end_of_turn":true}

Proposal

Sequence ID on send and continue from for read

It would be useful if chat returned a sequence ID identifying the exact user message that was accepted.

First interaction

$ exedev@minikomi ~> shelley client chat -p "echo: FIRST RESPONSE"
{"event":"accepted","conversation_id":"cJETWZL","sequence_id":2}

The bridge could then stream only messages created after sequence 2:

$ exedev@minikomi ~> shelley client read -wait -after 2 cJETWZL
{"event":"message","conversation_id":"cJETWZL","message":{"message_id":"<agent-message-id>","sequence_id":3,"type":"agent","text":"FIRST RESPONSE","end_of_turn":true}}
{"event":"done","conversation_id":"cJETWZL","sequence_id":3}

No system prompt or earlier history needs to be replayed.

Second interaction

$ exedev@minikomi ~> shelley client chat -c cJETWZL -p "echo: SECOND RESPONSE"
{"event":"accepted","conversation_id":"cJETWZL","sequence_id":4}

The new cursor makes the follow-up unambiguous:

$ exedev@minikomi ~> shelley client read -wait -after 4 cJETWZL
{"event":"message","conversation_id":"cJETWZL","message":{"message_id":"<agent-message-id>","sequence_id":5,"type":"agent","text":"SECOND RESPONSE","end_of_turn":true}}
{"event":"done","conversation_id":"cJETWZL","sequence_id":5}

Also if we need to catch up on past messages, we can use the last sequence ID seen and run the same command again without losing the response or replaying the whole conversation.

Waiting on chat itself

On sending a message chat -wait could also provide the common one-command form, so we don't have to send and then stream:

$ exedev@minikomi ~> shelley client chat -wait -c cJETWZL -p "echo: THIRD RESPONSE"
{"event":"accepted","conversation_id":"cJETWZL","message_id":"<user-message-id>","sequence_id":6}
{"event":"message","conversation_id":"cJETWZL","message":{"message_id":"<agent-message-id>","sequence_id":7,"type":"agent","text":"THIRD RESPONSE","end_of_turn":true}}
{"event":"done","conversation_id":"cJETWZL","sequence_id":7}

Tool interaction

Tool calls and results would be awesome to get in the stream too, so we can show them to the user:

$ exedev@minikomi ~> shelley client chat -wait -c cJETWZL -p "bash: ls -la ~"
{"event":"accepted","conversation_id":"cJETWZL","message_id":"<user-message-id>","sequence_id":8}
{"event":"message","conversation_id":"cJETWZL","message":{"message_id":"<agent-message-id>","sequence_id":9,"type":"agent","tool_calls":[{"id":"tool_123","name":"bash","input":{"command":"ls -la ~"}}],"end_of_turn":false}}
{"event":"tool_progress","conversation_id":"cJETWZL","tool_progress":{"tool_use_id":"tool_123","tool_name":"bash","output":"total 452\n"}}
{"event":"message","conversation_id":"cJETWZL","message":{"message_id":"<result-message-id>","sequence_id":10,"type":"user","tool_results":[{"tool_use_id":"tool_123","error":false,"text":"total 452\n...","display":{"workingDir":"/home/exedev"}}],"end_of_turn":false}}
{"event":"message","conversation_id":"cJETWZL","message":{"message_id":"<agent-message-id>","sequence_id":11,"type":"agent","text":"Done.","end_of_turn":true}}
{"event":"done","conversation_id":"cJETWZL","sequence_id":11}

We can use the output to report the tool call, progress, and result using tool_123, while still treating persisted messages as the replayable source of truth.

$ shelley-chatplease run bash: ls -la ~

◆ Shelley

├─ 🛠 bash
│  $ ls -la ~

│  total 452
│  drwxr-x--- 29 exedev exedev 4096 Jul 16 12:11 .
│  drwxr-xr-x  3 root   root   4096 Jan  8  2026 ..
│  ...

└─ ✓ completed

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions