Skip to content

iksir nestles at hum, drops the agent runtime - #4

Merged
adiled merged 7 commits into
mainfrom
iksir-on-hum
Aug 24, 2026
Merged

adiled merged 7 commits into
mainfrom
iksir-on-hum

Conversation

@adiled

@adiled adiled commented Aug 24, 2026

Copy link
Copy Markdown
Owner

Iksīr no longer drives OpenCode. It connects to a humd as a forager bee over the thrum socket and prompts whichever hive al-Kimyawi has kindled. The choice of what burns in the furnace was never Iksīr's to make.

The ~70% that is genuinely Iksīr — Linear, GitHub, istihal branch surgery, mumayyiz triage, quiet hours, code-intel — never moved.

The substrate

New src/hum/:

file role
thrum.ts NDJSON unix-socket client — hello, reconnect, sid dispatch
identity.ts stable fbee_<hex> hid from a persisted ed25519 seed
client.ts AmilHum — the same surface the khuddām always called
thrum.test.ts 5 tests against a fake humd

The tongue, translated once and nowhere else:

khalaqaJalsa      -> mint a sid locally (jalasat were always ours)
sendPrompt        -> chi:"prompt" -> chunk* -> finish
abortSession      -> chi:"cancel"
mahaqaJalsa       -> chi:"cleanup"
summarizeSession  -> chi:"curate"   (no provider, no hardcoded model)
replyToQuestion   -> chi:"release-permit"
question.asked    <- chi:"permission-ask"

Two laws, taken from humd's source

Both load-bearing, both tested:

  1. Manifests are volatile. humd clears them on restart and prunes on disconnect, so the hello rides every connection, not just the first. A silent reconnect without it leaves Iksīr nestled but unrouteable.
  2. Iksīr must never declare bee:["worker"]. humd re-broadcasts the output tones of any bee whose manifest carries "worker" onto the sid sigil — and Iksīr is the bee that claimed that sigil. It would hear its own voice returned.

Instruments

src/mcp/ became src/alat/Ālāt al-Iksīr (آلات الإكسير). There was an MCP server there because OpenCode needed telling what Iksīr could do; nothing needs telling now. The 23 instruments ride in the hello, humd merges every forager's into the foragerTools it hands each worker, and a nida returns by name.

Gone with it: the JSON-RPC dispatch, http-transport.ts, serve.ts, the iksir-mcp systemd unit and the Requires= chaining the daemon behind it. The instrument bodies are unchanged and each still inscribes its hadath into the ahdath table, so Munaffidh keeps draining a durable journal. The sijill is the record; the thrum is the road.

Persistence

Restoring state asked the amil whether each persisted jalsa still existed — sensible against OpenCode, which owned sessions. Not now: AmilHum answers from a map that is empty at boot, so every row was skipped and katib restored nothing on any restart. The source of truth moved and the permission check stayed behind.

hammalaHala now restores unconditionally and hands each vessel back via istaadaJalsa. nestId rides in the existing hala_mufassala JSON, so a resumed murshid carries its resume token — no schema migration. Two sibling checks went with it, including a title-search recovery path that rebuilt branch names from scratch rather than the persisted ones.

Raqib no longer announces a compaction it cannot confirm. It records the count when it asks, checks later whether the vessel actually shrank, and says so once when it did not.

Verified against a live humd

0.31.18, thrum 0.7.0:

thrum.hello                     proto_version=0.7.0
bee.hid.registered              hid=fbee_86af5280f681
forager.capabilities.registered provides=["session"]
forager.tools.registered        count=23
manifest.evict.disconnect

bee.hid.registered rather than bee.hid.invalid is the one thing a fake socket cannot tell you — a malformed hid breaks reconnect dedup silently and leaks a manifest every time.

144 tests pass.

Known gaps

  • Not exercised beyond the handshake. No prompting, tool-call routing, or murshid work against a real nest. The manifest handling was read from 0.32.0 source; this ran against 0.31.18.
  • chi:"curate" is a no-op in humd today — it sits in the thrum.recv.todo arm. Raqib's compaction request is accepted and dropped, which is why raqib now measures instead of trusting. Upstream: feat(curate): wire chi:"curate" end to end hum#54.
  • chi:"release-permit" likewise, and nothing emits permission-ask, so Saail is unreachable — its permission-ask body shape is inferred from the chi contract rather than observed.
  • A worker that curates itself raises no tone Iksīr hears, so only Raqib-initiated curations are noticed.
  • Three pre-existing cli.ts JalsaSijill type errors, untouched — they predate this branch.
  • Two test files carried uncommitted edits from before this work; the rename had to reach them, so they are folded into the first commit.

Config: opencode.serverhum.{miqbas,namudhaj}, both optional. The socket is discovered (HUM_THRUM_SOCKHUM_SOCKET → humd's runtime.json → state dir) and the model is chosen by kindling a hive.

What Iksīr asks of al-Kimyawi is one line, and it belongs in the README as a prerequisite: a nestled worker, plus something in the nest providing fs. A murshid without fs can contemplate the runūz but never inscribe them.

adiled added 7 commits March 30, 2026 22:44
- types.ts: Add Rasul (full messenger), RisalaDakhila (inbound events),
  KhiyarTafauli (interactive options). Rename telegramMessageId →
  huwiyyatRisalaMuqaddim for provider-agnosticism.

- messenger.ts: TelegramMessenger now implements full Rasul interface
  with baddaa()/awqaf()/indaRisala() lifecycle. Inbound Telegram routing
  moved here from main.ts — emits normalized RisalaDakhila events.
  arsalaSualBiKhiyarat() renders KhiyarTafauli[] as inline keyboards.

- saail.ts: banaMafatihSatriyya() → banaKhiyarat() returns abstract
  KhiyarTafauli[] instead of Telegram-specific keyboard markup.

- main.ts: SiyaqKhadim.telegram removed, only rasul: Rasul remains.
  addaMualijatTelegram() deleted — replaced by rabatRisalaDakhila()
  which routes RisalaDakhila events to business logic. All ctx.telegram
  calls converted to ctx.rasul.send/arsalaSualBiKhiyarat.
Iksir no longer drives OpenCode. It connects to a humd as a forager bee
over the thrum socket and prompts whichever hive al-Kimyawi has kindled.
The choice of what burns in the furnace was never Iksir's to make.

Substrate, replaced:

  src/hum/thrum.ts     NDJSON unix-socket client, hello + reconnect
  src/hum/identity.ts  stable fbee_<hex> hid from a persisted ed25519 seed
  src/hum/client.ts    AmilHum — the same surface the khuddam always used

  khalaqaJalsa      -> mint a sid locally (jalasat were always ours)
  sendPrompt        -> chi:"prompt" -> chunk* -> finish
  abortSession      -> chi:"cancel"
  mahaqaJalsa       -> chi:"cleanup"
  summarizeSession  -> chi:"curate"   (no provider, no hardcoded model)
  replyToQuestion   -> chi:"release-permit"
  question.asked    <- chi:"permission-ask"

Two laws taken from humd's source, both load-bearing:

  I.  Manifests are volatile — humd clears them on restart and prunes on
      disconnect. The hello therefore rides every connection, not just the
      first, or Iksir stays nestled but unrouteable.
  II. Iksir must never declare bee:["worker"]. humd re-broadcasts worker
      output onto the sid sigil, and Iksir is the bee that claimed it.

Tool calls come off the wire now instead of a poll. humd routes chi:"tool-call"
by name to whichever manifest declares it; the 24 mun_* bodies are unchanged
and still inscribe their hadath into the sijill, so Munaffidh keeps draining a
durable table. The journal stays the record; the thrum is only the road.

Deleted: src/opencode/client.ts, plugins/iksir.ts, the @opencode-ai/sdk
dependency, the iksir-agent systemd unit, and the sync of prompts and plugins
into OpenCode's config dirs. The ruqan are Iksir's own — read from prompts/
and sent as systemPrompt, since nothing attaches "agents" by name any more.

config: opencode.server -> hum.{miqbas,namudhaj}, both optional. The socket is
discovered (HUM_THRUM_SOCK -> HUM_SOCKET -> humd's runtime.json -> state dir),
and the model is chosen by kindling a hive, not by configuring Iksir.

Note: two test files carried uncommitted edits from before this work and are
folded in here, since the rename had to reach them for the build to pass.

143 tests pass. Adds Orchfile so `hum hive <path> install` can supervise Iksir.
src/mcp/iksir-mcp.ts -> src/alat/alat-al-iksir.ts

There was an MCP server there because OpenCode needed to be told what Iksir
could do. Nothing needs telling any more: the adawat ride in the hello, humd
merges every forager's tools into the foragerTools it hands each worker, and
a nida comes back by name. What was left was a JSON-RPC costume over a tool
registry, so the costume comes off.

  MunadiMunMcpServer -> AlatAlIksir
  aalijTalab(tools/call) -> naffidh(name, args): Promise<string>
  TaarifAlatMcp -> TaarifAla
  MuaallijAlatMcp -> MuaallijAla

Deleted: the JSON-RPC envelope types, the tahyia/tools-list/tools-call
dispatch, src/mcp/http-transport.ts, src/mcp/serve.ts, the iksir-mcp systemd
unit and the Requires= that chained the daemon behind it, and IKSIR_MCP_PORT.

The instruments themselves — all 24 of them — are untouched, and each still
inscribes its hadath into the ahdath table for Munaffidh to drain.

Also collapses the CLI's three-service machinery to one. The MCP server and
the agent runtime were both Iksir's to supervise once; the nest is
al-Kimyawi's concern now, and the instruments ride the daemon's own process.
`iksir start mcp` and `iksir start agent` are gone with the services.

Net -303 lines. 143 tests pass.
A deletion leaves no trace, so a comment explaining an absence only puzzles
whoever reads next. Git holds the why.

Rewrites the headers in alat-al-iksir.ts, hum/client.ts, cli.ts and main.ts
to state the mechanism rather than narrate the change away from it.

Two comments in katib.ts were not merely stale but wrong, and the second
describes a real gap rather than a wording one:

  - aalajaDamj was documented as belt-and-suspenders behind a compaction
    plugin that injected diary entries into the summary itself. With no
    plugin, the follow-up message is the only thing carrying a murshid's
    decisions across a curation.
  - It claimed to catch nest-triggered compactions too. It does not. A
    worker that curates itself on token overflow raises no tone Iksir
    hears, so only curations Raqib asks for are caught.

143 tests pass.
The tree showed only the Khuddām, then dropped to the Buwtaqa, so Iksīr read
as servants with nothing being guided. The Murshidun appeared in the prose
but never in the diagram.

They are not Khuddām. The split is lifetime: the Khuddām are eternal, one of
each, and are Iksīr itself; the Murshidun are summoned per wasfa, are many,
and perish when the work is done. A Sani hangs below each.

The distinction matters more now that a Murshid is a cell burning in someone
else's nest rather than a session Iksīr owns.

Also updates ARABICIZED_TYPES.md for the renamed types.
Restoring state asked the amil whether each persisted jalsa still existed.
That check made sense against OpenCode, which owned the sessions. It does
not now: AmilHum answers from an in-memory map that is empty at boot, so
every row was skipped as "no longer exists" and katib restored nothing on
any restart. The rows survived, orphaned and unreachable, and the next
mention of a ticket minted a fresh vessel on a fresh branch.

The source of truth moved and the permission check stayed behind. This
removes it — hammalaHala restores every row unconditionally, and hands
each back to the amil via istaadaJalsa, which until now had no caller.

nestId rides in the existing hala_mufassala JSON beside activePRs, so a
resumed murshid carries its resume token and wakes remembering its work.
No schema migration needed.

Two more checks of the same kind go with it:

  - wajadaAwKhalaqa asked the amil to confirm a session katib itself
    tracked, and deleted its own entry when the amil said no.
  - the title-search recovery path scanned the amil's map for sessions
    katib had already registered, and rebuilt the branch name from scratch
    rather than the persisted one. Dead, and wrong if it ever fired.

Raqib no longer claims a compaction it cannot confirm. summarizeSession
is fire-and-forget over the wire and always returns true, so "Auto-
compacted session X" was announced whether or not anything happened.
It now records the count when it asks, checks on a later heartbeat
whether the vessel actually shrank, and tells al-Kimyawi once when it
did not — which is also the honest outcome when a curation legitimately
trims nothing.

Adds a restart regression test: a vessel lit under one amil is restored,
with its nestId, by a second amil that has never heard of it. Verified to
fail with the old existence check in place.

144 tests pass.
Verified against a live humd (0.31.18, thrum 0.7.0): the hello is accepted,
`bee.hid.registered` rather than `bee.hid.invalid`, and it counts

  forager.tools.registered count=23

Twenty-three, not twenty-four. The extra name in the old MCP file was the
server's own, and that server is gone.

Also withdraws an unfair note about WIRE.md. It documents an XDG_RUNTIME_DIR
socket and I called it stale against the 0.32 source — but the running 0.31
daemon binds exactly where WIRE.md says. The default moved in 0.32; both are
right for their version, which is precisely why the client asks runtime.json
before falling back.
@adiled
adiled merged commit 955ba23 into main Aug 24, 2026
@adiled
adiled deleted the iksir-on-hum branch August 24, 2026 17:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant