8 Commits
Author SHA1 Message Date
zeekayandHanzo Dev c6cf84472e go 1.26.8
One toolchain across the estate. The tree carried 1.26.0, .1, .3, .4 and .5
at once, so which compiler built a binary depended on which repo it came
from.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-08 23:57:24 -07:00
zeekayandHanzo Dev f0d0f44ee8 fix: call the pipeline at the address it actually lives
hanzoai/ci keeps the reusable at .github/workflows/build.yml — the one path
both planes resolve, since github.com reads a reusable only from there and the
forge reads it too. The v1 tag moved with it, so a caller still naming
.hanzo/workflows/build.yml asks for a blob that is not in the tree:

  InsertRun: read hanzoai/ci@v1:.hanzo/workflows/build.yml: object does not exist

The caller itself stays at .hanzo/workflows/cicd.yml on purpose: github.com has
no runner for these labels, so a caller placed there could never be scheduled.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-08 01:14:29 -07:00
zeekayandHanzo Dev df9b1cf0b8 Ignore the built binary
`go build ./...` drops `attendant` in the tree, where it shows up as
uncommitted work forever and is never what anyone means to commit.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-20 20:08:06 -07:00
zeekayandHanzo Dev fa64283508 Let the attendant act on what the meeting decided
A meeting that decides something leaves the decision in the transcript, and
there it stays until a person copies it somewhere — and the copying is the part
that does not happen. The attendant now offers the model the tools the caller's
org already has, runs the ones it asks for, and then says what it did.

The tools are not named here. api.hanzo.ai reports what an org offers and runs
them, so an org holding todo gets todo_create and one holding more gets more,
and this binary never learns which. Only dispatchable and activated tools are
offered: the registry reports everything an org COULD have, and offering one it
has not turned on earns a refusal the room hears as the attendant being broken.

Two things the tests caught rather than the author:

`arguments` arrives as a JSON-encoded STRING, not an object — the model writes
the call's input as text and the transport carries it as text. Reading it as an
object failed before the call was ever made, so the item was never filed and
nothing said so. An object is still accepted; one shape must not be the only one
that works.

The loop was unbounded. Withholding tools on the last round is not the same as
stopping, and a model that keeps calling them is a model not answering while the
room listens to silence. It ends in words now, always.

A catalog it cannot read is not fatal: an attendant that only talks is what it
was before this, and losing the follow-up beats losing the meeting.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-20 20:07:54 -07:00
zeekayandHanzo Dev a7f31e9e15 name the series: an image build that cannot name a version is a broken build
Co-authored-by: Hanzo Dev <[email protected]>
2026-08-13 19:32:10 -07:00
zeekayandHanzo Dev 3032e96f35 answer the room in the gap the floor leaves
The library had both directions and no service: attend.go ended in an empty
main. This closes the loop — a turn is transcribed, our model answers it, and
hanzoai/speech gives that answer a voice the room hears.

The floor decides when. It frees itself `rest` after the holder stops, which is
the pause a person waits out before speaking, and it fills again within `notice`
of anyone starting — so an answer still playing when somebody talks is cut off
there and the track comes down. Nothing here holds a second notion of turns.

Say takes a context and the bytes rather than a path: it returns when the room
has heard the audio or the moment it is cancelled, and unpublishes either way. A
track left up is a microphone nobody takes down, and every answer added another.

A session is now addressed where the open says it lives. A growing transcript is
a window in one process's memory and speech runs two replicas behind a Service
that round-robins, so pushing to the Service address is a 404 for about half of
every turn.

The attendant holds no LiveKit signing key: it asks meet for one room's token
the way a browser does, carrying one IAM identity to meet, the model and the
voice alike.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-13 19:27:06 -07:00
zeekayandHanzo Dev 4a4ea8c555 a turn opens on its first word, not a second into it
Three things the live room found that the replay could not.

A backchannel takes the floor when the wait is one observer interval: a 1.5s
"mhm, yeah" cut a speaker's sentence in half and "reporting green" went with it,
because a mutter holds a steady level while a speaker between words does not, so
the room ranks the quieter one first. The wait is two seconds now.

The wait runs from when a rival STARTED making sound rather than from the report
that named them, or they pay for our latency twice. cleo began while ben was
finishing; by the time the room stopped ranking him she was a second in, and her
notes opened "owe you the capacity numbers".

Held audio goes to a turn in one piece. It is 2.5s arriving at once, which is no
rate a room delivers at, and a turn that queues audio so the room never waits
dropped the overflow — the front of the turn, which is what holding it was for.

Also: talk-over that its speaker then wins the floor with is not counted as lost,
audio reaches one turn and never two, and the scribe's chunking, its rollover at
the service's 600s limit, its ordering and its dropped-frame count are tested
against a server that answers the way hanzoai/speech answers.

Three consecutive runs against the live SFU and hanzoai/speech: 19.4s published,
17.6s transcribed, three turns, no frames lost, every sentence whole and under
the right name.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-09 12:08:18 -07:00
zeekayandHanzo Dev e49f1c492f transcribe whoever holds the floor, so a meeting costs one stream
A meeting has one floor. At any instant one person is talking, so a room of any
size needs one transcript handed from speaker to speaker rather than one per
person. Measured on a real multi-party fixture: twelve in the room cost the same
17.0s as three, against 198.3s for a transcript each.

Two things about the room's speaker signal had to be found by running it against
the live SFU rather than assumed. It is a RANKING, sent only when it changes: a
speaker stayed ranked first for thirteen seconds after her last packet, and five
seconds passed with no report at all while somebody talked straight through. So
the room says who is loudest, the tracks say who is still there, and the floor
reads both on its own clock.

Each speaker's last 1.2s of audio is kept, which is what lets a turn begin before
we knew whose it was and what makes the 500ms a rival waits before taking the
floor cost nothing.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-09 11:42:15 -07:00