Skip to content

v0.77.0

Choose a tag to compare

@github-actions github-actions released this 01 Nov 06:35
· 1 commit to main since this release

A live voice call opens from a seeded session: a duplex voice session can speak G.711 at 8 kHz, asks the curator for its seed at the ring and at every renewal, streams the caller's words as throttled transcript deltas, and reports per call what it used and how long it has run. The route stamp's early verdict is fired in parallel with the fast model, which holds its answer until the stamp decides; the stamp reads the conversation, may ask the person one question about how a turn is meant, whose answer comes back on later turns, and names the screen form. The screen takes that verdict from the stamp and builds its window in place behind a building mark. A release cut is checked against the commit the battery measured: anything beyond the version and record places is refused. Before the cut, the battery's findings were fixed: a tool name returned with a rest of its arguments is repaired, refused voice upgrades and failed duplex sessions are logged, t_first_audio_ms is the first audible frame, a delegation is answered once, a failed duplex session on a phone call can play a failure medium and hang up instead of leaving silence, web_fetch writes no URL's credentials anywhere, a colony under host disk load no longer exits by watchdog, a session the provider ends mid-call can be replaced at once by a fresh one of the same call, the greeting of a call is no longer answered as if the person had spoken, and a call's usage and call marks survive the replay of the cell's birth params.

Added

  • A duplex call whose provider ends the session mid-call can be recovered (#1377). A live model may end a session in the middle of a call (seen as reason=content a few seconds after a sentence its moderation flagged), and the caller talked on into a dead line. The new duplex.gpt_live.recover_max (default 0 = as before) lets the cell replace such a session at once with a fresh one of the same call, which asks for its seed with seed_ask reason: recover and reports its start on renewed like a renewal; at most that many times per call. When the session in charge ends after that, duplex_failed goes out once on the error lane, which the freeswitch hive answers with failure_media and a hang-up.
  • The quick voice can start beside the route stamp (#1275). With quick_speculative "1" on collector/assemble and on curator/policy, a turn that waits for its stamp sends the same curate earlier, and the curator holds the quick answer until the stamp decides: kept on simple, dropped on every other way. curator/policy.spec_tools (empty = no tool) names the tool families such an early call may use; a dropped early call is booked like any other call. collector/assemble.delegation_answer_mode ("fact", "context") says how a duplex delegation is answered, collector/assemble.delegation_rule (text, empty = the built-in rule) replaces the rule of the context mode with a set's own, and collector/assemble.delegation_inherits_stamp lets a delegation round take the stamp of the turn it belongs to. All ship off.
  • The route stamp reads the conversation and judges the screen (#1279). With talky/route.judge_context "1" the curator answers the stamp's read with a judge context (the last turns of both roles, the topic, the screen's topics, now and place), and the stamp asks which screen topic and blocks help (screen {topic, confidence, lead, further}) in the same decide call. talky/route.clarify "1" adds the class clarify: the curator answers such a turn with one question about how it is meant (its note is curator/policy.clarify_note) and reports the question and the answer to affinity on the new talky exit propose. The stamp carries screen and screen_form; the new talky exit verdict is the presenter's in_verdict -- the rim copy of a stamp that carries the screen, with judge_context on every rim stamp (so a turn without a topic closes as no_topic), and the second stamp of talky/screen_judge.enabled "1", which leaves only there, judged by the new talky/screen_model. Bind the presenter to verdict, not to stamp. All ship off.
  • A how-question the person answered comes back on their next turns (#1279). affinity/brief serves the new slot hints (asked for, never by default; hint_rows 20): the accepted mx.hints.clarify proposals about the subject -- the clarify pairs the curator proposes -- newest first, each only in a round that is a subset of the one it was made in, on the tool lane also as the body list hints. With the new collector/assemble switch brief_hints "1" every turn asks for it (about the counterpart, else about the round's one person) and carries it on curate as the slot hints; curator/intake keeps it per person and curator/history adds it to the judge context as judge.hints, which talky/route renders in the decide state. Off as shipped, and the default bytes do not move. The quick voice's fixed prompt now also says to ask one short question instead of guessing when a request stays unclear despite the conversation.
  • Two optional curator/history params shape a voice call's seed (#1279): seed_entries_max keeps at most that many session and until notes in the seed, the best ranked (priority, then the youngest), and says the rest on stderr; seed_anchor adds the persona's anchor as the seed's last developer line, after the turns and a held hint and right before a held wind-down. Both are off as shipped (0, ""), and the seed's bytes do not move.
  • A duplex call is seeded and fed by the curator (#1279). New talky lanes in_seed_ask (answered on the new exit seed with the judge context, the session's notes and the inner turns as messages), in_seed_line (one line held for the next seed of a call), in_screen_topics (the presenter's topic table) and in_delta (the voice cell's partial transcript); the assistant declares all eight new talky lanes and carries them through its talkies: the topics to both, the partial, the seed ask and the seed line only to the surface of the round's channel (guarded on context.channel_node, like in_delegation), so one ask is answered with exactly one seed. curator/policy.live_feed "1" asks the call for a renewal at its next turn boundary (new exit live_feed, hop.section from live_feed_section, renew with hop.at_turn_end "1") after a duplex turn that moved what the next seed carries -- the notes, the inner turns, the named topic, the screen topics; curator/policy.delta_speculation "1" reuses the stamp of a partial transcript when the final text is the same. curator/policy.quick_instructions_slot "1" gives the quick voice the slot instructions.quick; the brain never sees it. curator/history and curator/policy take place and timezone (set both pairs alike); curator/history.now_utc fixes the clock of the judge context for tests (empty = the clock). All ship off.
  • The curator tells what a phone call carried (#1369). New lane in_call_end on curator, talky and assistant (the set draws the voice cell's final usage there) answered by exactly one new call_facts: {call_id, session_id, seed_used, facts: [{source, id, text}], delegations: [{turn_id, text}]} -- whether a seed or live feed gave the call facts, which ones (notes, topic, screen topics), and the call's delegations, read out of the curator's ledger, which stays as it was. Without the edge nothing changes.
  • A failed duplex session on a phone call is no longer silence (#1374). The freeswitch hive hands the media half's error with hop.error_code duplex_failed to ./signal as well as to its rim, and the new ./signal param failure_media (empty = nothing happens) plays a recorded announcement into the call the error names and then hangs up, as one uuid_transfer <uuid> playback:<failure_media>,hangup:NORMAL_CLEARING inline at the switch. A value with a space or a comma, or a local_stream:// that never ends, is refused with one stderr line naming the reason, as is a call id outside [0-9A-Za-z-]; a -ERR from the switch on the transfer is said on stderr, not dropped. Ships empty, so the line behaves as before.
  • The screen answers from the turn's stamp and builds the window in place (#1357). With the new presenter/stage param verdict_source: stamp the presenter makes no call of its own: it takes topic, candidates and the screen form from the talky's stamp on the new lane in_verdict, and a sure verdict opens the window under construction at once -- a construction pictogram (display-status with kind: building, no text) and one display-skeleton per chosen block, drawn from the topic set and never from data. Blocks take their skeleton's place as their data come and may be rebuilt until the window is done; done takes the pictogram away and lets the frame settle once over the new param settle_ms (600), and from then on the window is frozen. The screen form maps to a rung through the window's relevance (form_rungs: active -> focus, accompanying/referring -> relevant, none -> no window, journal form_none); a later form lifts or withdraws the window before it is done. The journal adds verdict_source, t_building_ms, t_form_ms and t_done_ms. display-stack takes build and settle_ms; the window under construction is drawn in full contrast and stands still under reduced motion. The default selector emits byte for byte what it emitted before.
  • The presenter can say which topics a screen can show (#1357). With the new presenter/stage switch screen_topics (off by default) the table the decider is asked over -- the presenter's own topics and the apps' accepted ones -- leaves on the new lane screen_topics as {topics: [{name, purpose}]}, purpose being the manifest's describe, once on boot and on every change of the table, never per turn. Its taker is the talky's curator; off, the lane is silent.
  • Template versions (#1357): second digit, display@2.12.0, presenter@1.4.0.
  • A duplex voice call reports what it used and how long it has run (#1362). With emit_usage set, the usage lane carries the call's total seconds over every provider session, renewals included, at each meter reading and once with final true at the end of the call; a session whose close never arrived counts with its last meter reading, and that final says complete: false. With call_marks_s (and optionally call_mark_every_s) set, the call_mark lane carries elapsed_s at each mark; one timer per call, which ends with the call. All default off, so an existing colony emits neither lane.
  • A live voice session can speak G.711 and open with a seed (#1359). duplex.gpt_live.audio_format (pcm16 by default, g711_ulaw, g711_alaw) runs the model at 8 kHz G.711 on the wire (audio/pcmu, audio/pcma) while the client keeps L16 at 8000; the adapter compands each frame and never resamples, and a G.711 block that names sample_rate is refused as audio_format_rate_conflict. With duplex.gpt_live.seed_wait_ms set, a session that opens (at the ring, at the media connection, at a renewal) emits seed_ask with reason start or renew and renewal_n (0 for the first session) and waits that long for in_seed, whose messages become the model's session.input (oldest trimmed over 128 messages or 8,192 estimated tokens). in_seed gives renewal_n back and reaches only the session that asked; a late answer is dropped and logged, one without the number is missing_renewal_n, one for a call nobody holds unknown_session. A call that ends while its session waits for the seed starts no session. Both default off, so an existing colony sends the opening frame it always sent. The freeswitch hive passes in_seed and seed_ask through its media half; the member declares both as ports at ./channels.
  • Template versions (#1359): second digit, freeswitch@3.1.0, member@2.8.0, voice@2.7.0.
  • A live voice session streams the caller's words as a throttled speculative stream and takes two more kinds of advice (#1361). duplex.gpt_live.delta_min_chars / delta_min_ms throttle the partial lane per incoming transcript delta (both 0 by default, one emission per delta as before); the last state always leaves right before the turn, which carries the same turn_id and now t_person_end_ms / t_first_audio_ms on the model's clock. duplex.gpt_live.emit_spoken: false stops the spoken lane alone (unset follows emit_partials). in_advise takes interrupt (an instruction, then a commentary with the same words and delegation) and renew (the call's next provider session opens at once and asks for its seed again; a renewal whose seed is still to come and which has not started absorbs it, one asked after that seed or that start opens the next renewal right after the takeover; the old session keeps the call and its open delegations until the handover), and hop.at_turn_end: "1" on any section holds it until the caller's next turn closes; several held renew at one boundary are one renewal, a held answer keeps its delegation open (the fallback still falls), and what is still held when the call ends, or is dropped past 16 held sections, goes to the colony as a session_ended dead letter. t_person_end_ms is the end of the caller's last words before the answer (backchannels and a barge-in's run excluded), t_first_audio_ms the first audible frame of the answer sent to the client (#1373), never before it.
  • Template versions (#1279): second digit, assistant@3.11.0.
  • Template versions (#1279): second digit, affinity@3.12.0.
  • A release cut can be checked against the commit a battery of real model tests measured (#1358). scripts/release_matches_battery.py <receipt.md> reads the last core_commit of a battery receipt and refuses a release commit that changes anything beyond the version places (Cargo.toml, Cargo.lock, template.json) and the release records (CHANGELOG.md, ROADMAP.md, docs/status.en.md): one foreign: line per file, exit 1.

Fixed

  • A duplex call no longer answers its own greeting (#1378). collector/assemble read a turn with an empty user half -- the voice cell's turn for the model's greeting -- as a question of the person, so the greeting was answered, spoken into the call and came back as the next turn without the caller. Such a turn (no text of the person, no peer turn, no consult, a text of the agent) is now held as the agent's line, a deferred assistant row that reaches the curator with the next round, and opens no round. Turns with the person's words and event turns are unchanged.
  • A voice cell born with emit_usage, call_marks_s or call_mark_every_s now runs with them (#1379). Every life of the cell runs on its birth params replayed through the cell.db params overlay, and the overlay carried none of the three keys, so the replay dropped them: a call never left a usage or a call_mark emission, although the birth config.json asked for both. The overlay now carries all three; like credential_backoff_max_ms they are settled at birth, and a runtime update naming one is refused as immutable.
  • A colony under host disk load no longer exits by watchdog with starved=colony_loop because a log line waited (#1372). The stderr log sink wrote each line on the runtime worker that logged it, under the process-wide stderr lock; to a file on a disk under write-back load one line could take half a second, and the colony task woken onto that worker could not run on another. The stderr sink now writes from its own thread, like log.jsonl, and drops no line. A failed boot template scan now ends through an error instead of std::process::exit, so the buffered lines and the root lease are released.
  • A quick turn whose spec curate went through ./handover gets its quick round again (#1371). The turn's normal curate came while the spec curate was there and stored its call as the fallback of the spec answer; back from the handover, the spec curate took that stored call for a decided turn and parked, so neither the spec call nor the stored call went. A spec curate whose own row is await now makes its call, and its answer settles the turn.
  • The route's screen question no longer grows with the topics (#1371). With the presenter's candidates it asked screen.lead.<topic> and screen.further.<topic> per topic -- 70 questions for 23 topics, which the decisions wire refuses above 64, so every turn was stamped on the fallback without a word. One screen.lead and one screen.further now cover the candidates of all topics as <topic>/<key>; a lead of another topic than the chosen one is none, and a refused decide call says so on stderr.
  • The presenter's screen_topics now carries each topic's candidates as {key, describe} (#1368), the shape the talky's in_screen_topics expects; without them the talky never asked which block should lead.
  • A switch of the live line set as JSON true is on (#1370). talky/route (clarify, judge_context, stamp_out), talky/screen_judge (enabled), curator/policy (quick_speculative, quick_instructions_slot, live_feed, delta_speculation) and collector/assemble (quick_speculative, delegation_inherits_stamp) read only "1", so a set that wrote true ran with the live feed and the delta speculation off and nothing said so. Every one of them now reads JSON true, "1", "true" or "on" as on, the presenter's rule; "1" stays the bytes of before.
  • curator/policy.live_feed_section names the one way that is built (#1370). It offered context, whose section carries no text, so the voice cell answered it bad_section; the contract now names renew alone, and any other value is sent as renew with a line on stderr.
  • The talky README describes the seed and verdict exits as they leave (#1370): seed carries hop.call_id, hop.session_id and hop.degraded store on a refused read, and the screen judge's second stamp leaves on verdict with stamp_out "0" as well. Two chain tests run history's seed into the voice cell and the collector's curate with the member's context into the live feed.
  • A duplex call's usage and call_mark leave the freeswitch hive (#1369). The rim edge out of ./voice let neither through, so both died inside the hive although member declares them as ports at ./channels; they now cross on the edge that carries delegation, and the hive's contract declares them. A delta of a voice turn (context.delta "1") asking curator/history for its turns reads the round's rows whatever their session, like the seed, because it stands under the call's id and the conversation under the generation's. talky/route now asks the wall for a delta as it does for a turn (context_turns, judge_context); before, a delta was decided without the conversation and the final turn could take that stamp as it was.
  • The presenter's stamp way finds the turn its stamp belongs to (#1367). With verdict_source: stamp the turn reaches presenter/stage through the apps fan-out without a turn_id, while the stamp on in_verdict carries the session keeper's; the two never met, so an app-owned topic never got its data request (in_show op: data) and its window was withdrawn at data_wait_ms. A stamp now takes the newest open turn of the same round that came without a turn_id and is younger than budget_ms, and a turn without a turn_id gives its words to the newest open stamp of the same round still waiting for them; both orders, never across rounds, a matching turn_id first. The default selector does not move.
  • The curator drops an early quick answer when the stamp needs a tool family it never called (#1366). curator/policy kept a speculative answer as soon as it called any tool, so a turn stamped simple with needs tool and the family orga kept an answer that had only run weather, and the orga tool never ran. Now every family the stamp names must lie inside spec_tools and be called by the early answer; otherwise it is dropped (mark reason family:<name>) and the normal quick round runs. A stamp without families decides as before.
  • The how-question hints in the route stamp's decide window count against its token budget (#1363). talky/route rendered every hint of the judge context in full, outside context_budget_tokens. Now they share that budget with the turns the wall already put in the window: the newest hints that fit stay whole, older ones are left out whole, and a cut ends the block on a mark with the hints and tokens shown and the total. Without hints, or with all of them inside the budget, the decide state does not move.
  • A tool that failed is never spoken by the quick voice (#1279). A tool result whose header carried an error_code but whose text was plain prose reached the curator as a speakable result. The collector now names the codes of an iteration's tool results in the new curate slot tool_errors ({tool_call_id: error_code}, the turn itself unchanged), and the curator's policy hands such a round to the brain with the note "tool not reachable"; a call that never reached a tool (malformed_tool_call, unknown_tool) is left out, its answer stays the repair aid for the model.
  • Tool arguments encoded one level too deep are unpacked once (#1360). A small model on the quick voice called a folded family with args as a JSON string holding the object; the call left folded, came back an error three times, and the model told the person the connection kept failing. The dispatcher now unpacks such args once before it unfolds the call, and the llm cell does the same for function.arguments that hold the JSON string of an object on the chat wire; both name the repair (args_double_decoded). A string inside that string again stays the error it was.
  • A tool name with a rest of its arguments is repaired to the declared tool (#1373). A fast model on the chat wire returned web_search"} for web_search; the call went out under that name, came back unknown_tool, and the model searched a second time. The llm cell now checks every returned tool name against the menu it sent, on every wire: a name that becomes exactly one declared tool once trailing JSON punctuation and whitespace are cut off is repaired once and named on the log line tool_name_repaired with the call id. Every menu entry with an op enum -- a folded family (web {op, args}), a hint or a flat op tool alike -- declares the names of its ops too (web_search), and the op of such a call is checked against its enum by the same rule, read where the dispatcher reads it; a lab run showed the first version never fired on the folded menu. A recovered harmony call to an op name stays malformed_tool_call. Other names and a request without a menu stay as before.
  • A refused voice upgrade is logged (#1373). The cell answered a bad sample_rate, session, mode or encoding with a 400 before the upgrade and wrote nothing itself; on a telephone bridge seventeen refused calls stood only as "connection error" in the bridge's own log. Both doors, socket and link, now also name the refusal on a WARN line voice: upgrade refused with the door, the status and the detail.
  • A failed duplex session is logged (#1373). A duplex start the provider refused (eight 401s in a battery run) reached only the voice_error body; the colony log had no line. The cell now also writes voice: the duplex session failed with the session id and the error.
  • A duplex turn's t_first_audio_ms is the first audible frame sent to the client (#1373). It came from the answer's transcript, which the provider stamps on its own 200 ms raster at the caller's end of speech, and an overlapping answer was clamped to t_person_end_ms; in a battery run 12 of 15 telephone turns showed 0 ms from end of speech to first word. The cell now stamps the first audible frame of the answer it sends (above the barge-in level, so the silence a live model streams before it does not count), to the millisecond; a turn whose answer is only silence has no t_first_audio_ms.
  • One answer per delegation (#1373). A delegated utterance could be answered twice -- by the delegation round and by the turn round beside it -- and both reached the duplex session. The voice cell now speaks the first answer per delegation_id and drops every later one on a WARN line, an interrupt (which carries a spoken fact) included; the deadline's fallback does not count as an answer.
  • web_fetch no longer writes a URL's credentials anywhere (#1375). The success log line named the requested URL as given, so a password in its userinfo reached the daemon log in clear. Every URL the cell writes out -- the log line, the final_url header, error texts -- now has its userinfo and the values of secret-named parameters in query and fragment replaced by REDACTED, the rest byte-identical. A name is secret by its structure: percent-decoded and split into words at camelCase boundaries and at _, -, ., it holds a strong word such as token, secret, signature or assertion anywhere, or a weak one such as key, sig or session as a whole word (accessToken, sessionId, X-Amz-Signature yes; monkey, signal no; code deliberately not). A hash route (#/cb?token=…) is redacted like a query. The redacted URL is parsed again, and one that still shows a secret, or does not parse, is redacted whole. The request itself is unchanged.
  • Template versions (#1360): third digit, builder-librarian@2.2.98, dispatcher@1.6.2; third digit for the pins that follow, assistant@3.10.9, builder@1.30.11, cogny@5.13.8, figure@2.0.10, meclaw-os@2.3.12, member@2.7.11, memory-hive@3.19.8, org@2.2.11, simple-agent@2.2.8, talky@6.12.2.
  • Template versions (#1275): second digit, collector@5.7.0, curator@1.17.0, talky@6.13.0.